rubyless 0.3.4 → 0.3.5
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 +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
|