rubyless 0.8.0 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +9 -0
- data/lib/ruby_less.rb +2 -0
- data/lib/ruby_less/basic_types.rb +4 -0
- data/lib/ruby_less/info.rb +1 -1
- data/lib/ruby_less/processor.rb +32 -9
- data/lib/ruby_less/safe_class.rb +6 -2
- data/rubyless.gemspec +2 -2
- data/test/RubyLess/basic.yml +17 -0
- data/test/RubyLess_test.rb +43 -3
- data/test/mock/dummy_class.rb +7 -2
- metadata +4 -4
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
data/lib/ruby_less/info.rb
CHANGED
data/lib/ruby_less/processor.rb
CHANGED
@@ -119,7 +119,7 @@ module RubyLess
|
|
119
119
|
end
|
120
120
|
|
121
121
|
res.opts[:class] = Array
|
122
|
-
res.opts[:
|
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
|
-
|
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
|
286
|
-
|
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
|
-
|
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)
|
data/lib/ruby_less/safe_class.rb
CHANGED
@@ -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.
|
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{
|
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 = [
|
data/test/RubyLess/basic.yml
CHANGED
@@ -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."
|
data/test/RubyLess_test.rb
CHANGED
@@ -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 =>
|
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, [:
|
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
|
data/test/mock/dummy_class.rb
CHANGED
@@ -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
|
-
:
|
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
|
-
|
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:
|
4
|
+
hash: 61
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 8
|
9
|
-
-
|
10
|
-
version: 0.8.
|
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:
|
18
|
+
date: 2011-01-15 00:00:00 +01:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|