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 +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
|