rubyless 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History.txt CHANGED
@@ -1,3 +1,12 @@
1
+ == 0.8.1 2011-01-15
2
+
3
+ * Major enhancements
4
+ * Added support for Array method on list types.
5
+ * Changed API for Proc types (we now send 'helper, this, args').
6
+ * Passing receiver on pre_processor resolution.
7
+ * Enable receiver rewrite.
8
+ * Added support for pre-processing on elements that can be nil.
9
+
1
10
  == 0.8.0 2010-11-15
2
11
 
3
12
  * Major enhancements
data/lib/ruby_less.rb CHANGED
@@ -28,6 +28,8 @@ module RubyLess
28
28
  if err.kind_of?(RubyLess::Error)
29
29
  raise err
30
30
  else
31
+ #puts err
32
+ #puts err.backtrace
31
33
  raise RubyLess::Error.new("Error parsing \"#{string}\": #{err.message.strip}")
32
34
  end
33
35
  end
@@ -35,3 +35,7 @@ RubyLess::SafeClass.safe_method_for( NilClass,
35
35
  [:==, String] => Boolean,
36
36
  [:==, Number] => Boolean
37
37
  )
38
+
39
+ RubyLess::SafeClass.safe_method_for( Array,
40
+ :size => Number
41
+ )
@@ -1,3 +1,3 @@
1
1
  module RubyLess
2
- VERSION = '0.8.0'
2
+ VERSION = '0.8.1'
3
3
  end
@@ -119,7 +119,7 @@ module RubyLess
119
119
  end
120
120
 
121
121
  res.opts[:class] = Array
122
- res.opts[:array_content_class] = content_class
122
+ res.opts[:elem] = content_class
123
123
  t "[#{list * ','}]", res.opts.merge(:literal => nil)
124
124
  end
125
125
 
@@ -229,17 +229,26 @@ module RubyLess
229
229
  cond = []
230
230
  end
231
231
 
232
+ opts = get_method(receiver, signature)
233
+
234
+ # method type can rewrite receiver
235
+ if opts[:receiver]
236
+ if receiver
237
+ receiver = "#{receiver}.#{opts[:receiver]}"
238
+ else
239
+ receiver = opts[:receiver]
240
+ end
241
+ end
242
+
232
243
  if receiver
233
- opts = get_method(receiver, signature)
234
244
  method_call_with_receiver(receiver, args, opts, cond, signature)
235
245
  else
236
- opts = get_method(nil, signature)
237
246
  method = opts[:method]
238
247
  args = args_with_prepend(args, opts)
239
248
 
240
249
  if (proc = opts[:pre_processor]) && !args.list.detect {|a| !a.literal}
241
250
  if proc.kind_of?(Proc)
242
- res = proc.call(*args.list.map(&:literal))
251
+ res = proc.call(*([receiver] + args.list.map(&:literal)))
243
252
  else
244
253
  res = @helper.send(proc, *args.list.map(&:literal))
245
254
  end
@@ -280,13 +289,27 @@ module RubyLess
280
289
  !(opts == SafeClass.safe_method_type_for(NilClass, signature) && receiver.cond == [receiver])
281
290
  # Do not add a condition if the method applies on nil
282
291
  cond += receiver.cond
283
- elsif receiver.literal && (proc = opts[:pre_processor]) && !arg_list.detect {|a| !a.literal}
292
+ if (proc = opts[:pre_processor]) && !arg_list.detect {|a| !a.literal}
293
+ # pre-processor on element that can be nil
294
+ if proc.kind_of?(Proc)
295
+ res = proc.call([receiver] + arg_list.map(&:literal))
296
+ return t_if cond, res, res.opts
297
+ end
298
+ end
299
+ elsif (proc = opts[:pre_processor]) && !arg_list.detect {|a| !a.literal}
284
300
  if proc.kind_of?(Proc)
285
- res = proc.call([receiver.literal] + arg_list.map(&:literal))
286
- else
301
+ res = proc.call([receiver] + arg_list.map(&:literal))
302
+ elsif receiver.literal
287
303
  res = receiver.literal.send(*([method] + arg_list.map(&:literal)))
288
304
  end
289
- return res.kind_of?(TypedString) ? res : t(res.inspect, :class => String, :literal => res)
305
+ if res
306
+ if res.opts.nil?
307
+ # This can happen if we use native methods on TypedString (like gsub) that return a new
308
+ # typedstring without calling initialize....
309
+ res.instance_variable_set(:@opts, :class => String, :literal => res)
310
+ end
311
+ return res.kind_of?(TypedString) ? res : t(res.inspect, :class => String, :literal => res)
312
+ end
290
313
  end
291
314
 
292
315
  if method == '/'
@@ -334,7 +357,7 @@ module RubyLess
334
357
  end
335
358
  raise RubyLess::NoMethodError.new(receiver, klass, signature) if !type || type[:class].kind_of?(Symbol) # we cannot send: no object.
336
359
 
337
- type[:class].kind_of?(Proc) ? type[:class].call(@helper, signature) : type
360
+ type[:class].kind_of?(Proc) ? type[:class].call(@helper, receiver ? receiver.klass : @helper, signature) : type
338
361
  end
339
362
 
340
363
  def get_lit_class(lit)
@@ -12,6 +12,10 @@ module RubyLess
12
12
 
13
13
  # Return method type (options) if the given signature is a safe method for the class.
14
14
  def self.safe_method_type_for(klass, signature)
15
+ if klass.kind_of?(Array)
16
+ return safe_method_type_for(Array, signature)
17
+ end
18
+
15
19
  # Signature might be ['name', {:mode => String, :type => Number}].
16
20
  # build signature arguments
17
21
 
@@ -26,7 +30,7 @@ module RubyLess
26
30
  s
27
31
  end
28
32
  end
29
-
33
+
30
34
  # Find safe method in all ancestry
31
35
  klass.ancestors.each do |ancestor|
32
36
  # FIXME: find a way to optimize this search !
@@ -267,7 +271,7 @@ module RubyLess
267
271
  end
268
272
 
269
273
  def self.safe_method_with_hash_args(klass, signature, hash_args)
270
-
274
+
271
275
  if type = safe_methods_for(klass)[signature]
272
276
  unless allowed_args = type[:hash_args]
273
277
  # All arguments allowed
data/rubyless.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{rubyless}
8
- s.version = "0.8.0"
8
+ s.version = "0.8.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Gaspard Bucher"]
12
- s.date = %q{2010-11-15}
12
+ s.date = %q{2011-01-15}
13
13
  s.description = %q{RubyLess is an interpreter for "safe ruby". The idea is to transform some "unsafe" ruby code into safe, type checked ruby, eventually rewriting some variables or methods.}
14
14
  s.email = %q{gaspard@teti.ch}
15
15
  s.extra_rdoc_files = [
@@ -206,3 +206,20 @@ class_map_as_string:
206
206
  proc_to_resolve_class:
207
207
  src: "author"
208
208
  tem: "node.author"
209
+
210
+ method_on_array:
211
+ src: "@foo.genitors.size"
212
+ tem: "node.genitors.size"
213
+
214
+ method_on_array_with_nil:
215
+ src: "@foo.children.size"
216
+ tem: "(node.children ? node.children.size : nil)"
217
+
218
+ map_allowed_method:
219
+ src: "%w{45 52}.map(:to_i)"
220
+ tem: "[\"45\",\"52\"].map(&:to_i).compact"
221
+ res: "4552"
222
+
223
+ map_forbidden_method:
224
+ src: "%w{45 52}.map(:foo)"
225
+ tem: "unknown method 'map(:foo)' for '[\"45\",\"52\"]' of type Array."
@@ -9,9 +9,31 @@ class RubyLessTest < Test::Unit::TestCase
9
9
  attr_reader :context
10
10
  yamltest :src_from_title => false
11
11
  include RubyLess
12
+
13
+ # Dynamic resolution of map
14
+ def self.map_proc
15
+ @@map_proc ||= Proc.new do |receiver, method|
16
+ if elem = receiver.opts[:elem] || receiver.klass.first
17
+ if type = RubyLess::safe_method_type_for(elem, [method.to_s])
18
+ if type[:method] =~ /\A\w+\Z/
19
+ res = "#{receiver.raw}.map(&#{type[:method].to_sym.inspect}).compact"
20
+ else
21
+ res = "#{receiver.raw}.map{|_map_obj| _map_obj.#{type[:method]}}.compact"
22
+ end
23
+ res = RubyLess::TypedString.new(res, :class => [type[:class]])
24
+ else
25
+ raise RubyLess::NoMethodError.new(receiver.raw, receiver.klass, ['map', method])
26
+ end
27
+ else
28
+ # should never happen
29
+ raise RubyLess::NoMethodError.new(receiver.raw, receiver.klass, ['map', method])
30
+ end
31
+ end
32
+ end
33
+
12
34
  safe_method :prev => {:class => Dummy, :method => 'previous'}
13
35
  safe_method :main => {:class => Dummy, :method => '@node'}
14
- safe_method :node => lambda {|h, s| {:class => h.context[:node_class], :method => h.context[:node]}}
36
+ safe_method :node => Proc.new {|h, r, s| {:class => h.context[:node_class], :method => h.context[:node]}}
15
37
  safe_method :now => {:class => Time, :method => "Time.now"}
16
38
  safe_method :birth => {:class => Time, :method => "Date.parse('2009-06-02 18:44')"}
17
39
  safe_method 'dictionary' => {:class => StringDictionary, :method => 'get_dict'}
@@ -21,7 +43,18 @@ class RubyLessTest < Test::Unit::TestCase
21
43
 
22
44
  safe_method_for String, [:==, String] => Boolean
23
45
  safe_method_for String, [:to_s] => String
24
- safe_method_for String, [:gsub, Regexp, String] => {:class => String, :pre_processor => Proc.new {|this, reg, str| this.gsub(reg, str)}}
46
+ safe_method_for String, [:to_i] => {:class => Number, :pre_processor => true}
47
+ safe_method_for String, [:gsub, Regexp, String] => {:class => String, :pre_processor => Proc.new {|this, reg, str|
48
+ # We have to test if 'this' is a literal
49
+ if literal = this.literal
50
+ this.gsub(reg, str)
51
+ else
52
+ # abort pre-processing
53
+ nil
54
+ end}}
55
+
56
+ safe_method_for Array, [:map, Symbol] => {:method => 'nil', :class => nil, :pre_processor => self.map_proc}
57
+
25
58
  safe_method_for String, :upcase => {:class => String, :pre_processor => true}
26
59
 
27
60
  safe_method_for Time, [:strftime, String] => String
@@ -42,7 +75,7 @@ class RubyLessTest < Test::Unit::TestCase
42
75
  safe_method [:hash_args, {'age' => Number, 'name' => String}] => String
43
76
  safe_method [:append_hash, Number, {'foo' => String}] => :make_append_hash
44
77
 
45
- safe_method [:concat, String, String] => {:class => String, :pre_processor => Proc.new{|a,b| a + b }}
78
+ safe_method [:concat, String, String] => {:class => String, :pre_processor => Proc.new{|this,a,b| a + b }}
46
79
  safe_method [:find, String] => {:class => NilClass, :method => 'nil', :pre_processor => :build_finder}
47
80
 
48
81
  # methods on nil
@@ -175,5 +208,12 @@ class RubyLessTest < Test::Unit::TestCase
175
208
  err.message
176
209
  end
177
210
 
211
+ def test_proc
212
+ x = RubyLess.translate(self, 'main.proc_test')
213
+ assert_equal self, x.opts[:h]
214
+ assert_equal Dummy, x.opts[:r]
215
+ assert_equal ['proc_test'], x.opts[:s]
216
+ end
217
+
178
218
  yt_make
179
219
  end
@@ -10,7 +10,8 @@ class Dummy < RubyLess::ActiveRecordMock
10
10
 
11
11
  safe_method [:ancestor?, Dummy] => Boolean
12
12
  safe_method :parent => {:class => 'Dummy', :special_option => 'foobar'},
13
- :children => ['Dummy'],
13
+ :genitors => ['Dummy'],
14
+ :children => {:class => ['Dummy'], :nil => true},
14
15
  :project => 'Dummy',
15
16
  :image => 'Dummy',
16
17
  :id => {:class => Number, :method => :zip},
@@ -19,9 +20,13 @@ class Dummy < RubyLess::ActiveRecordMock
19
20
  [:width, {:mode => String, :type => String, 'nice' => Boolean}] => String,
20
21
  [:kind_of?, Class] => Boolean,
21
22
  [:kind_of?, String] => {:method => 'is_like?', :class => Boolean},
22
- :author => (Proc.new do |h, s| {:method => 'author', :class => Dummy} end)
23
+ # helper, receiver, signature
24
+ :author => (Proc.new do |h, r, s| {:method => 'author', :class => Dummy} end)
23
25
  #:author => {:method => 'author', :class => Dummy}
24
26
 
27
+ # just to test Proc.call
28
+ safe_method :proc_test => Proc.new {|h, r, s| {:class => String, :method => '""', :h => h, :r => r, :s => s}}
29
+
25
30
  safe_context :spouse => 'Dummy',
26
31
  :husband => {:class => 'Dummy', :context => {:clever => 'no'}}
27
32
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyless
3
3
  version: !ruby/object:Gem::Version
4
- hash: 63
4
+ hash: 61
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 8
9
- - 0
10
- version: 0.8.0
9
+ - 1
10
+ version: 0.8.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Gaspard Bucher
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-11-15 00:00:00 +01:00
18
+ date: 2011-01-15 00:00:00 +01:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency