rubyless 0.3.4 → 0.3.5
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +8 -0
- data/lib/processor.rb +28 -3
- data/lib/rubyless.rb +1 -1
- data/lib/safe_class.rb +80 -24
- data/rubyless.gemspec +2 -2
- data/test/RubyLess/basic.yml +10 -0
- data/test/RubyLess/errors.yml +14 -1
- data/test/RubyLess_test.rb +16 -4
- data/test/mock/dummy_class.rb +13 -2
- metadata +2 -2
data/History.txt
CHANGED
data/lib/processor.rb
CHANGED
@@ -113,6 +113,30 @@ module RubyLess
|
|
113
113
|
exp.empty? ? t('', String) : process(exp.shift)
|
114
114
|
end
|
115
115
|
|
116
|
+
def process_hash(exp)
|
117
|
+
result = []
|
118
|
+
klass = {}
|
119
|
+
until exp.empty?
|
120
|
+
key = exp.shift
|
121
|
+
if [:lit, :str].include?(key.first)
|
122
|
+
key = key[1]
|
123
|
+
|
124
|
+
rhs = exp.shift
|
125
|
+
type = rhs.first
|
126
|
+
rhs = process rhs
|
127
|
+
#rhs = "(#{rhs})" unless [:lit, :str].include? type # TODO: verify better!
|
128
|
+
|
129
|
+
result << "#{key.inspect} => #{rhs}"
|
130
|
+
klass[key] = rhs.klass
|
131
|
+
else
|
132
|
+
# ERROR: invalid key
|
133
|
+
raise "Invalid key type for hash (should be a literal value, was #{key.first.inspect})"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
t "{#{result.join(', ')}}", :class => klass
|
138
|
+
end
|
139
|
+
|
116
140
|
private
|
117
141
|
def t(content, opts = nil)
|
118
142
|
if opts.nil?
|
@@ -209,9 +233,10 @@ module RubyLess
|
|
209
233
|
end
|
210
234
|
|
211
235
|
def get_method(signature, receiver, is_method = true)
|
212
|
-
|
213
|
-
|
214
|
-
|
236
|
+
type = receiver.respond_to?(:safe_method_type) ? receiver.safe_method_type(signature) : SafeClass.safe_method_type_for(receiver, signature)
|
237
|
+
return nil if !type || type[:class].kind_of?(Symbol) # we cannot send: no object.
|
238
|
+
|
239
|
+
type[:class].kind_of?(Proc) ? type[:class].call(@helper, signature) : type
|
215
240
|
end
|
216
241
|
end
|
217
242
|
end
|
data/lib/rubyless.rb
CHANGED
data/lib/safe_class.rb
CHANGED
@@ -10,17 +10,56 @@ module RubyLess
|
|
10
10
|
|
11
11
|
# Return method type (options) if the given signature is a safe method for the class.
|
12
12
|
def self.safe_method_type_for(klass, signature)
|
13
|
-
safe_methods_for(klass)[signature]
|
13
|
+
if type = safe_methods_for(klass)[signature]
|
14
|
+
type
|
15
|
+
else
|
16
|
+
# Signature might be ['name', {:mode => String, :type => Number}].
|
17
|
+
|
18
|
+
# Replace all hashes in signature by Hash class and check for arguments
|
19
|
+
signature_args = []
|
20
|
+
signature = signature.map do |s|
|
21
|
+
if s.kind_of?(Hash)
|
22
|
+
signature_args << s
|
23
|
+
Hash
|
24
|
+
else
|
25
|
+
signature_args << nil
|
26
|
+
s
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
if type = safe_methods_for(klass)[signature]
|
31
|
+
unless allowed_args = type[:hash_args]
|
32
|
+
# All arguments allowed
|
33
|
+
return type
|
34
|
+
end
|
35
|
+
|
36
|
+
# Verify arguments
|
37
|
+
signature_args.each_with_index do |args, i|
|
38
|
+
next unless args
|
39
|
+
# verify for each position: ({:a => 3}, {:x => :y})
|
40
|
+
return nil unless allowed_args_for_position = allowed_args[i]
|
41
|
+
args.each do |k,v|
|
42
|
+
return nil unless allowed_args_for_position[k] == v
|
43
|
+
end
|
44
|
+
end
|
45
|
+
type
|
46
|
+
else
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
end
|
14
50
|
end
|
15
51
|
|
16
|
-
# Declare a safe method for a given class
|
17
|
-
def self.safe_method_for(klass,
|
52
|
+
# Declare a safe method for a given class ( same as #safe_method)
|
53
|
+
def self.safe_method_for(klass, methods_hash)
|
54
|
+
defaults = methods_hash.delete(:defaults) || {}
|
55
|
+
|
18
56
|
list = (@@_safe_methods[klass] ||= {})
|
19
|
-
|
20
|
-
k =
|
21
|
-
|
22
|
-
v =
|
57
|
+
methods_hash.each do |k,v|
|
58
|
+
k, hash_args = build_signature(k)
|
59
|
+
v = {:class => v} unless v.kind_of?(Hash)
|
60
|
+
v = defaults.merge(v)
|
23
61
|
v[:method] = v[:method] ? v[:method].to_s : k.first.to_s
|
62
|
+
v[:hash_args] = hash_args if hash_args
|
24
63
|
list[k] = v
|
25
64
|
end
|
26
65
|
end
|
@@ -42,6 +81,11 @@ module RubyLess
|
|
42
81
|
# The signature can be either a single symbol or an array containing the method name and type arguments like:
|
43
82
|
# [:strftime, Time, String]
|
44
83
|
#
|
84
|
+
# If your method accepts variable arguments through a Hash, you should declare it with:
|
85
|
+
# [:img, String, {:mode => String, :max_size => Number}]
|
86
|
+
#
|
87
|
+
# Make sure your literal values are of the right type: +:mode+ and +'mode'+ are not the same here.
|
88
|
+
#
|
45
89
|
# If the signature is :defaults, the options defined are used as defaults for the other elements defined in the
|
46
90
|
# same call.
|
47
91
|
#
|
@@ -51,22 +95,7 @@ module RubyLess
|
|
51
95
|
# :class the return type (class name)
|
52
96
|
# :nil set this to true if the method could return nil
|
53
97
|
def self.safe_method(methods_hash)
|
54
|
-
|
55
|
-
|
56
|
-
list = (@@_safe_methods[self] ||= {})
|
57
|
-
methods_hash.each do |k,v|
|
58
|
-
k = [k] unless k.kind_of?(Array)
|
59
|
-
k[0] = k[0].to_s
|
60
|
-
if v.kind_of?(Hash)
|
61
|
-
v = defaults.merge(v)
|
62
|
-
v[:method] = v[:method] ? v[:method].to_s : k.first.to_s
|
63
|
-
elsif v.kind_of?(Proc) || v.kind_of?(Symbol)
|
64
|
-
# cannot merge defaults
|
65
|
-
else
|
66
|
-
v = defaults.merge(:class => v, :method => k.first.to_s)
|
67
|
-
end
|
68
|
-
list[k] = v
|
69
|
-
end
|
98
|
+
RubyLess::SafeClass.safe_method_for(self, methods_hash)
|
70
99
|
end
|
71
100
|
|
72
101
|
# A safe context is simply a safe method that can return nil in some situations. The rest of the
|
@@ -114,6 +143,17 @@ module RubyLess
|
|
114
143
|
def self.safe_method_type(signature)
|
115
144
|
SafeClass.safe_method_type_for(self, signature)
|
116
145
|
end
|
146
|
+
|
147
|
+
# Return true if the class is safe (we can call safe_read on its instances)
|
148
|
+
def self.safe_class?
|
149
|
+
true
|
150
|
+
end
|
151
|
+
|
152
|
+
# Use this if you want to disable 'safe_read'. This is useful if you provided
|
153
|
+
# a mock as return type signature.
|
154
|
+
def self.disable_safe_read
|
155
|
+
undef_method(:safe_read)
|
156
|
+
end
|
117
157
|
end # base.class_eval
|
118
158
|
end # included
|
119
159
|
|
@@ -121,7 +161,7 @@ module RubyLess
|
|
121
161
|
# Return the type if the given signature corresponds to a safe method for the object's class.
|
122
162
|
def safe_method_type(signature)
|
123
163
|
if type = self.class.safe_method_type(signature)
|
124
|
-
type.kind_of?(Symbol) ? self.send(type, signature) : type
|
164
|
+
type[:class].kind_of?(Symbol) ? self.send(type[:class], signature) : type
|
125
165
|
end
|
126
166
|
end
|
127
167
|
|
@@ -133,6 +173,22 @@ module RubyLess
|
|
133
173
|
end
|
134
174
|
|
135
175
|
private
|
176
|
+
def self.build_signature(key)
|
177
|
+
keys = key.kind_of?(Array) ? key : [key]
|
178
|
+
keys[0] = keys[0].to_s
|
179
|
+
hash_args = []
|
180
|
+
signature = keys.map do |k|
|
181
|
+
if k.kind_of?(Hash)
|
182
|
+
hash_args << k
|
183
|
+
Hash
|
184
|
+
else
|
185
|
+
hash_args << nil
|
186
|
+
k
|
187
|
+
end
|
188
|
+
end
|
189
|
+
[signature, (hash_args.compact.empty? ? nil : hash_args)]
|
190
|
+
end
|
191
|
+
|
136
192
|
def self.build_safe_methods_list(klass)
|
137
193
|
list = klass.superclass.respond_to?(:safe_methods) ? klass.superclass.safe_methods : {}
|
138
194
|
(@@_safe_methods[klass] || {}).map do |signature, return_value|
|
data/rubyless.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{rubyless}
|
5
|
-
s.version = "0.3.
|
5
|
+
s.version = "0.3.5"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Gaspard Bucher"]
|
9
|
-
s.date = %q{2009-11-
|
9
|
+
s.date = %q{2009-11-08}
|
10
10
|
s.description = %q{RubyLess is an interpreter for "safe ruby". The idea is to transform some "unsafe" ruby code into safe, type checked
|
11
11
|
ruby, eventually rewriting some variables or methods.}
|
12
12
|
s.email = %q{gaspard@teti.ch}
|
data/test/RubyLess/basic.yml
CHANGED
@@ -91,3 +91,13 @@ literal_argument_for_method:
|
|
91
91
|
safe_method_defined_as_symbol:
|
92
92
|
src: "foo"
|
93
93
|
tem: "contextual_foo"
|
94
|
+
|
95
|
+
optional_arguments:
|
96
|
+
src: "width(:mode => 'pv')"
|
97
|
+
tem: "var1.width({:mode => \"pv\"})"
|
98
|
+
res: "mode: pv, type: none"
|
99
|
+
|
100
|
+
optional_arguments_string:
|
101
|
+
src: "width('nice' => 1 == 1)"
|
102
|
+
tem: "var1.width({\"nice\" => (1==1)})"
|
103
|
+
res: "nice!"
|
data/test/RubyLess/errors.yml
CHANGED
@@ -33,4 +33,17 @@ string_argument:
|
|
33
33
|
|
34
34
|
symbol_type_not_used_out_of_helper:
|
35
35
|
src: "node.foo"
|
36
|
-
|
36
|
+
tem: "'var1' does not respond to 'foo()'."
|
37
|
+
|
38
|
+
optional_arguments_with_dynamic_string:
|
39
|
+
src: "spouse.width(\"nice#{spouse.name}\" => 'pv')"
|
40
|
+
tem: "Invalid key type for hash (should be a literal value, was :dstr)"
|
41
|
+
|
42
|
+
|
43
|
+
optional_arguments_bad_type:
|
44
|
+
src: "width(:mode => 12)"
|
45
|
+
res: "Unknown method 'width({:mode => 12})'."
|
46
|
+
|
47
|
+
optional_arguments_bad_argument:
|
48
|
+
src: "width(:xyz => 'pv')"
|
49
|
+
res: "Unknown method 'width({:xyz => \"pv\"})'."
|
data/test/RubyLess_test.rb
CHANGED
@@ -4,9 +4,10 @@ require File.dirname(__FILE__) + '/test_helper.rb'
|
|
4
4
|
class StringDictionary
|
5
5
|
include RubyLess::SafeClass
|
6
6
|
safe_method ['[]', Symbol] => {:class => String, :nil => true}
|
7
|
+
disable_safe_read
|
7
8
|
end
|
8
9
|
|
9
|
-
class
|
10
|
+
class RubyLessTest < Test::Unit::TestCase
|
10
11
|
attr_reader :context
|
11
12
|
yamltest :src_from_title => false
|
12
13
|
include RubyLess::SafeClass
|
@@ -56,6 +57,17 @@ class SimpleHelper < Test::Unit::TestCase
|
|
56
57
|
assert_equal "'rm' not readable", Dummy.new.safe_read('rm')
|
57
58
|
end
|
58
59
|
|
60
|
+
def test_disable_safe_read
|
61
|
+
assert Dummy.instance_methods.include?('safe_read')
|
62
|
+
assert !StringDictionary.instance_methods.include?('safe_read')
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_safe_method_type
|
66
|
+
type = Dummy.safe_method_type(['husband'])
|
67
|
+
type_should_be = {:class => Dummy, :method => 'husband', :nil => true, :context => {:clever => 'no'}}
|
68
|
+
assert_equal type_should_be, type
|
69
|
+
end
|
70
|
+
|
59
71
|
def yt_do_test(file, test, context = yt_get('context',file,test))
|
60
72
|
@@test_strings[file][test].keys.each do |key|
|
61
73
|
next if ['src', 'context'].include?(key)
|
@@ -78,9 +90,9 @@ class SimpleHelper < Test::Unit::TestCase
|
|
78
90
|
"Unknown key '#{key}'. Should be 'tem' or 'res'."
|
79
91
|
end
|
80
92
|
rescue => err
|
81
|
-
#
|
82
|
-
#
|
83
|
-
|
93
|
+
#puts "\n\n#{err.message}"
|
94
|
+
#puts err.backtrace
|
95
|
+
err.message
|
84
96
|
end
|
85
97
|
|
86
98
|
yt_make
|
data/test/mock/dummy_class.rb
CHANGED
@@ -8,11 +8,13 @@ class Dummy < RubyLess::ActiveRecordMock
|
|
8
8
|
safe_method :parent => {:class => 'Dummy', :special_option => 'foobar'},
|
9
9
|
:children => ['Dummy'],
|
10
10
|
:project => 'Dummy',
|
11
|
+
:image => 'Dummy',
|
11
12
|
:id => {:class => Number, :method => :zip},
|
12
13
|
:name => String,
|
13
|
-
:foo => :bar
|
14
|
+
:foo => :bar,
|
15
|
+
[:width, {:mode => String, :type => String, 'nice' => Boolean}] => String
|
14
16
|
safe_context :spouse => 'Dummy',
|
15
|
-
:husband => {:class => 'Dummy'}
|
17
|
+
:husband => {:class => 'Dummy', :context => {:clever => 'no'}}
|
16
18
|
|
17
19
|
safe_attribute :age, :friend_id, :log_at, :format
|
18
20
|
|
@@ -20,11 +22,20 @@ class Dummy < RubyLess::ActiveRecordMock
|
|
20
22
|
@name = name
|
21
23
|
end
|
22
24
|
|
25
|
+
def width(opts = {})
|
26
|
+
return 'nice!' if opts['nice']
|
27
|
+
"mode: #{(opts[:mode] || 'none')}, type: #{(opts[:type] || 'none')}"
|
28
|
+
end
|
29
|
+
|
23
30
|
# This method returns pseudo-nil and does not need to be declared with :nil => true
|
24
31
|
def project
|
25
32
|
Dummy.new('project')
|
26
33
|
end
|
27
34
|
|
35
|
+
def image
|
36
|
+
Dummy.new('image')
|
37
|
+
end
|
38
|
+
|
28
39
|
# This method can return nil and must be declared with :nil => true
|
29
40
|
def spouse
|
30
41
|
nil
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubyless
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gaspard Bucher
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-11-
|
12
|
+
date: 2009-11-08 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|