rubyless 0.3.5 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/History.txt +31 -16
- data/{README.txt → README.rdoc} +3 -3
- data/Rakefile +47 -36
- data/lib/ruby_less/basic_types.rb +15 -0
- data/lib/ruby_less/error.rb +4 -0
- data/lib/ruby_less/info.rb +3 -0
- data/lib/ruby_less/no_method_error.rb +47 -0
- data/lib/{processor.rb → ruby_less/processor.rb} +62 -23
- data/lib/{safe_class.rb → ruby_less/safe_class.rb} +87 -38
- data/lib/ruby_less/signature_hash.rb +33 -0
- data/lib/ruby_less/syntax_error.rb +4 -0
- data/lib/{typed_string.rb → ruby_less/typed_string.rb} +3 -3
- data/lib/ruby_less.rb +27 -0
- data/lib/rubyless.rb +1 -14
- data/rails/init.rb +1 -0
- data/rubyless.gemspec +56 -13
- data/test/RubyLess/active_record.yml +4 -4
- data/test/RubyLess/basic.yml +56 -18
- data/test/RubyLess/errors.yml +11 -11
- data/test/RubyLess/string.yml +11 -0
- data/test/RubyLess_test.rb +54 -20
- data/test/mock/dummy_class.rb +28 -3
- data/test/mock/dummy_module.rb +8 -0
- data/test/mock/property_column.rb +21 -0
- data/test/safe_class_test.rb +16 -0
- data/test/signature_hash_test.rb +44 -0
- data/test/test_helper.rb +3 -1
- metadata +76 -39
- data/lib/basic_types.rb +0 -15
data/.gitignore
ADDED
data/History.txt
CHANGED
@@ -1,60 +1,75 @@
|
|
1
|
+
== 0.4.0 2010-03-21
|
2
|
+
|
3
|
+
* 4 major enhancement
|
4
|
+
* Parsing inheritance tree to get safe_method_type.
|
5
|
+
* Instance variable (ivar) support (declared as safe_methods).
|
6
|
+
* Added support for prepend variables to tranform methods like link("foo") to link(@node, "foo").
|
7
|
+
* Better handling of sub-types in signature matching.
|
8
|
+
|
9
|
+
* 5 minor enhancements
|
10
|
+
* Moved files into a ruby_less directory to enable file auto-loading.
|
11
|
+
* Raises RubyLess::NoMethodError / RubyLess::SyntaxError instead of generic Error.
|
12
|
+
* Improved error reporting use method signature.
|
13
|
+
* Added 'safe_literal_class' to enable/disable ruby literals.
|
14
|
+
* Added 'RubyLess.translate_string' method.
|
15
|
+
|
1
16
|
== 0.3.5 2009-11-08
|
2
17
|
|
3
18
|
* 1 major enhancement
|
4
|
-
*
|
19
|
+
* Added support for hash in signature: ['img', {'mode' => String, 'class' => String}].
|
5
20
|
|
6
21
|
* 1 minor enhancement
|
7
|
-
*
|
22
|
+
* Added 'disable_safe_read' method.
|
8
23
|
|
9
24
|
== 0.3.4 2009-11-05
|
10
25
|
|
11
26
|
* 1 minor enhancement
|
12
|
-
*
|
27
|
+
* Added 'safe_context' method.
|
13
28
|
|
14
29
|
== 0.3.3 2009-10-26
|
15
30
|
|
16
31
|
* 1 minor enhancement
|
17
|
-
*
|
32
|
+
* Added support for symbols (only used with helper).
|
18
33
|
|
19
34
|
== 0.3.2 2009-10-15
|
20
35
|
|
21
36
|
* 1 minor enhancement
|
22
|
-
*
|
37
|
+
* Removed 'ruby-debug' require.
|
23
38
|
|
24
39
|
== 0.3.1 2009-10-07
|
25
40
|
|
26
41
|
* 3 major enhancements
|
27
|
-
*
|
28
|
-
*
|
29
|
-
*
|
42
|
+
* Method name in signatures should always be a string.
|
43
|
+
* Type[:method] is always set and is always a string.
|
44
|
+
* Fixed how class type is guessed from ActiveRecord column.
|
30
45
|
|
31
46
|
* 1 minor enhancement
|
32
|
-
*
|
47
|
+
* Added 'safe_read' method to objects.
|
33
48
|
|
34
49
|
== 0.3.1 2009-10-03
|
35
50
|
|
36
51
|
* 1 major enhancement
|
37
|
-
* Moved from ParseTree to RubyParser
|
52
|
+
* Moved from ParseTree to RubyParser.
|
38
53
|
|
39
54
|
* 1 minor enhancement:
|
40
|
-
* Using Mr Bones to generate gems
|
55
|
+
* Using Mr Bones to generate gems.
|
41
56
|
|
42
57
|
== 0.2.1 2009-07-01
|
43
58
|
|
44
59
|
* 2 minor enhancements:
|
45
|
-
* :[] method is now treated as in conventional ruby (foo[:bar], not foo.[](:bar))
|
46
|
-
* Better error reporting in case of signature mismatch
|
60
|
+
* :[] method is now treated as in conventional ruby (foo[:bar], not foo.[](:bar)).
|
61
|
+
* Better error reporting in case of signature mismatch.
|
47
62
|
|
48
63
|
== 0.2.0 2009-06-02
|
49
64
|
|
50
65
|
* 1 major enhancement:
|
51
|
-
* Added support for ActiveRecord attributes
|
66
|
+
* Added support for ActiveRecord attributes.
|
52
67
|
|
53
68
|
* 2 minor enhancements:
|
54
69
|
* Better documentation
|
55
|
-
* Removed eval (this means safe methods are globally declared)
|
70
|
+
* Removed eval (this means safe methods are globally declared).
|
56
71
|
|
57
72
|
== 0.1.0 2009-06-02
|
58
73
|
|
59
74
|
* 1 major enhancement:
|
60
|
-
* Initial alpha release
|
75
|
+
* Initial alpha release.
|
data/{README.txt → README.rdoc}
RENAMED
@@ -50,16 +50,16 @@ Or you can group all declarations in a single place with 'safe_method_for':
|
|
50
50
|
You can now parse some ruby code:
|
51
51
|
|
52
52
|
RubyLess.translate("!prev.ancestor?(main) && !node.ancestor?(main)", self)
|
53
|
-
=> "(not previous.ancestor?(@node) and not
|
53
|
+
=> "(not previous.ancestor?(@node) and not node.ancestor?(@node))"
|
54
54
|
|
55
55
|
RubyLess.translate("id > 45 and (3 > -id or 3+3)", self)
|
56
|
-
=> "(
|
56
|
+
=> "(node.zip>45 and ((3>-node.zip) or (3+3)))"
|
57
57
|
|
58
58
|
RubyLess.translate("strftime(now, '%Y')", self)
|
59
59
|
=> "strftime(Time.now, \"%Y\")"
|
60
60
|
|
61
61
|
RubyLess.translate("log_info(spouse, spouse.name)", self)
|
62
|
-
=> "(
|
62
|
+
=> "(node.spouse ? log_info(node.spouse, node.spouse.name) : nil)"
|
63
63
|
|
64
64
|
You can look at the tests for an idea of how to declare things. If you have more questions, ask on zena's mailing list:
|
65
65
|
|
data/Rakefile
CHANGED
@@ -1,48 +1,59 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
3
|
|
4
|
-
|
4
|
+
require(File.join(File.dirname(__FILE__), 'lib/ruby_less/info'))
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
begin
|
7
|
+
require 'jeweler'
|
8
|
+
Jeweler::Tasks.new do |gem|
|
9
|
+
gem.version = RubyLess::VERSION
|
10
|
+
gem.name = "rubyless"
|
11
|
+
gem.summary = %Q{RubyLess is an interpreter for "safe ruby"}
|
12
|
+
gem.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.}
|
13
|
+
gem.email = "gaspard@teti.ch"
|
14
|
+
gem.homepage = "http://zenadmin.org/546"
|
15
|
+
gem.authors = ["Gaspard Bucher"]
|
16
|
+
gem.add_dependency 'ruby_parser', '>= 2.0.4'
|
17
|
+
gem.add_dependency 'sexp_processor', '>= 3.0.1'
|
18
|
+
gem.add_development_dependency "shoulda", ">= 0"
|
19
|
+
gem.add_development_dependency "yamltest", ">= 0.5.3"
|
20
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
21
|
+
end
|
22
|
+
Jeweler::GemcutterTasks.new
|
23
|
+
rescue LoadError
|
24
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
11
25
|
end
|
12
26
|
|
13
|
-
|
14
|
-
|
27
|
+
require 'rake/testtask'
|
28
|
+
Rake::TestTask.new(:test) do |test|
|
29
|
+
test.libs << 'lib' << 'test'
|
30
|
+
test.pattern = 'test/**/*_test.rb'
|
31
|
+
test.verbose = true
|
32
|
+
end
|
15
33
|
|
16
34
|
begin
|
17
|
-
require '
|
18
|
-
|
35
|
+
require 'rcov/rcovtask'
|
36
|
+
Rcov::RcovTask.new do |test|
|
37
|
+
test.libs << 'test'
|
38
|
+
test.pattern = 'test/**/*_test.rb'
|
39
|
+
test.verbose = true
|
40
|
+
end
|
19
41
|
rescue LoadError
|
20
|
-
|
21
|
-
|
22
|
-
rescue LoadError
|
23
|
-
raise RuntimeError, '### please install the "bones" gem ###'
|
42
|
+
task :rcov do
|
43
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
24
44
|
end
|
25
45
|
end
|
26
46
|
|
27
|
-
|
28
|
-
require 'rubyless'
|
29
|
-
|
30
|
-
PROJ.name = 'rubyless'
|
31
|
-
PROJ.authors = 'Gaspard Bucher'
|
32
|
-
PROJ.email = 'gaspard@teti.ch'
|
33
|
-
PROJ.url = 'http://zenadmin.org/546'
|
34
|
-
PROJ.version = RubyLess::VERSION
|
35
|
-
PROJ.rubyforge.name = 'rubyless'
|
36
|
-
|
37
|
-
PROJ.spec.opts << '--color'
|
38
|
-
PROJ.gem.files = (
|
39
|
-
['History.txt', 'README.txt', 'Rakefile', 'rubyless.gemspec'] +
|
40
|
-
['lib', 'test'].map do |d|
|
41
|
-
Dir.glob("#{d}/**/*").reject {|path| File.basename(path) =~ /^\./ }
|
42
|
-
end
|
43
|
-
).flatten
|
47
|
+
task :test => :check_dependencies
|
44
48
|
|
45
|
-
|
46
|
-
PROJ.gem.dependencies << ['sexp_processor', '>= 3.0.1']
|
47
|
-
PROJ.gem.development_dependencies << ['yamltest', '>= 0.5.3']
|
49
|
+
task :default => :test
|
48
50
|
|
51
|
+
require 'rake/rdoctask'
|
52
|
+
Rake::RDocTask.new do |rdoc|
|
53
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
54
|
+
|
55
|
+
rdoc.rdoc_dir = 'rdoc'
|
56
|
+
rdoc.title = "rubyless #{version}"
|
57
|
+
rdoc.rdoc_files.include('README*')
|
58
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
59
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'ruby_less/safe_class'
|
2
|
+
|
3
|
+
|
4
|
+
class Boolean
|
5
|
+
end
|
6
|
+
|
7
|
+
class Number
|
8
|
+
end
|
9
|
+
|
10
|
+
RubyLess::SafeClass.safe_literal_class Fixnum => Number, Float => Number, Symbol => Symbol, Regexp => Regexp
|
11
|
+
RubyLess::SafeClass.safe_method_for( Number,
|
12
|
+
[:==, Number] => Boolean, [:< , Number] => Boolean, [:> , Number] => Boolean,
|
13
|
+
[:<=, Number] => Boolean, [:>=, Number] => Boolean, [:- , Number] => Number,
|
14
|
+
[:+ , Number] => Number, [:* , Number] => Number, [:/ , Number] => Number,
|
15
|
+
[:% , Number] => Number, [:"-@"] => Number )
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module RubyLess
|
2
|
+
class NoMethodError < RubyLess::Error
|
3
|
+
attr_reader :receiver, :klass, :signature
|
4
|
+
|
5
|
+
def initialize(receiver, klass, signature)
|
6
|
+
@receiver = receiver
|
7
|
+
@klass = klass
|
8
|
+
@signature = signature
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
"#{receiver_with_class}: #{error_message} '#{method_with_arguments}'."
|
13
|
+
end
|
14
|
+
|
15
|
+
def error_message
|
16
|
+
if ivar?
|
17
|
+
"unknown instance variable"
|
18
|
+
else
|
19
|
+
"unknown method"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def receiver_with_class
|
24
|
+
@receiver ? "#{@receiver} (#{@klass})" : "(#{@klass.class})"
|
25
|
+
end
|
26
|
+
|
27
|
+
def method_with_arguments
|
28
|
+
method = @signature.first
|
29
|
+
signature = @signature[1..-1]
|
30
|
+
return method if ivar?
|
31
|
+
if signature.size == 0
|
32
|
+
arguments = ''
|
33
|
+
else
|
34
|
+
arguments = signature.map{|s| s.kind_of?(Class) ? s.to_s : s.inspect}.join(', ')
|
35
|
+
if signature.size == 1 && (signature.first.kind_of?(Array) || signature.first.kind_of?(Hash))
|
36
|
+
arguments = arguments[1..-2]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
"#{method}(#{arguments})"
|
40
|
+
end
|
41
|
+
|
42
|
+
def ivar?
|
43
|
+
@signature.first =~ /\A@/
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -2,10 +2,6 @@ require 'rubygems'
|
|
2
2
|
require 'ruby_parser'
|
3
3
|
require 'sexp_processor'
|
4
4
|
|
5
|
-
require 'basic_types'
|
6
|
-
require 'typed_string'
|
7
|
-
require 'safe_class'
|
8
|
-
|
9
5
|
module RubyLess
|
10
6
|
class RubyLessProcessor < SexpProcessor
|
11
7
|
attr_reader :ruby
|
@@ -27,11 +23,18 @@ module RubyLess
|
|
27
23
|
self.expected = TypedString
|
28
24
|
end
|
29
25
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
26
|
+
def process(exp)
|
27
|
+
super
|
28
|
+
rescue UnknownNodeError => err
|
29
|
+
if err.message =~ /Unknown node-type :(.*?) /
|
30
|
+
raise RubyLess::SyntaxError.new("'#{$1}' not available in RubyLess.")
|
31
|
+
else
|
32
|
+
raise RubyLess::SyntaxError.new(err.message)
|
33
|
+
end
|
34
|
+
# return nil if exp.nil?
|
35
|
+
# method = exp.shift
|
36
|
+
# send("process_#{method}", exp)
|
37
|
+
end
|
35
38
|
|
36
39
|
def process_and(exp)
|
37
40
|
t "(#{process(exp.shift)} and #{process(exp.shift)})", Boolean
|
@@ -51,9 +54,9 @@ module RubyLess
|
|
51
54
|
false_res = process(exp.shift)
|
52
55
|
|
53
56
|
if true_res && false_res && true_res.klass != false_res.klass
|
54
|
-
raise "Error in conditional expression: '#{true_res}' and '#{false_res}' do not return results of same type (#{true_res.klass} != #{false_res.klass})."
|
57
|
+
raise RubyLess::SyntaxError.new("Error in conditional expression: '#{true_res}' and '#{false_res}' do not return results of same type (#{true_res.klass} != #{false_res.klass}).")
|
55
58
|
end
|
56
|
-
raise "Error in conditional expression." unless true_res || false_res
|
59
|
+
raise RubyLess::SyntaxError.new("Error in conditional expression.") unless true_res || false_res
|
57
60
|
opts = {}
|
58
61
|
opts[:nil] = true_res.nil? || true_res.could_be_nil? || false_res.nil? || false_res.could_be_nil?
|
59
62
|
opts[:class] = true_res ? true_res.klass : false_res.klass
|
@@ -90,15 +93,18 @@ module RubyLess
|
|
90
93
|
def process_vcall(exp)
|
91
94
|
var_name = exp.shift
|
92
95
|
unless opts = get_method([var_name], @helper, false)
|
93
|
-
raise "Unknown variable or method '#{var_name}'."
|
96
|
+
raise RubyLess::NoMethodError.new("Unknown variable or method '#{var_name}'.")
|
94
97
|
end
|
95
98
|
method = opts[:method]
|
99
|
+
if args = opts[:prepend_args]
|
100
|
+
method = "#{method}(#{args.raw})"
|
101
|
+
end
|
96
102
|
t method, opts
|
97
103
|
end
|
98
104
|
|
99
105
|
def process_lit(exp)
|
100
106
|
lit = exp.shift
|
101
|
-
t lit.inspect, lit.class
|
107
|
+
t lit.inspect, get_lit_class(lit.class)
|
102
108
|
end
|
103
109
|
|
104
110
|
def process_str(exp)
|
@@ -130,13 +136,17 @@ module RubyLess
|
|
130
136
|
klass[key] = rhs.klass
|
131
137
|
else
|
132
138
|
# ERROR: invalid key
|
133
|
-
raise "Invalid key type for hash (should be a literal value, was #{key.first.inspect})"
|
139
|
+
raise RubyLess::SyntaxError.new("Invalid key type for hash (should be a literal value, was #{key.first.inspect})")
|
134
140
|
end
|
135
141
|
end
|
136
142
|
|
137
143
|
t "{#{result.join(', ')}}", :class => klass
|
138
144
|
end
|
139
145
|
|
146
|
+
def process_ivar(exp)
|
147
|
+
method_call(nil, exp)
|
148
|
+
end
|
149
|
+
|
140
150
|
private
|
141
151
|
def t(content, opts = nil)
|
142
152
|
if opts.nil?
|
@@ -174,14 +184,15 @@ module RubyLess
|
|
174
184
|
if arg_sexp
|
175
185
|
args = process(arg_sexp)
|
176
186
|
if args == ''
|
187
|
+
args = nil
|
177
188
|
signature = [method]
|
178
189
|
else
|
179
190
|
signature = [method] + [args.klass].flatten
|
180
191
|
end
|
181
192
|
# execution conditional
|
182
|
-
cond = args.cond || []
|
193
|
+
cond = args ? (args.cond || []) : []
|
183
194
|
else
|
184
|
-
args =
|
195
|
+
args = nil
|
185
196
|
signature = [method]
|
186
197
|
cond = []
|
187
198
|
end
|
@@ -190,7 +201,7 @@ module RubyLess
|
|
190
201
|
if receiver.could_be_nil?
|
191
202
|
cond += receiver.cond
|
192
203
|
end
|
193
|
-
|
204
|
+
opts = get_method(receiver, signature)
|
194
205
|
method = opts[:method]
|
195
206
|
if method == '/'
|
196
207
|
t_if cond, "(#{receiver.raw}#{method}#{args.raw} rescue nil)", opts.merge(:nil => true)
|
@@ -201,13 +212,15 @@ module RubyLess
|
|
201
212
|
elsif method == '[]'
|
202
213
|
t_if cond, "#{receiver.raw}[#{args.raw}]", opts
|
203
214
|
else
|
204
|
-
args =
|
215
|
+
args = args_with_prepend(args, opts)
|
216
|
+
args = "(#{args.raw})" if args
|
205
217
|
t_if cond, "#{receiver.raw}.#{method}#{args}", opts
|
206
218
|
end
|
207
219
|
else
|
208
|
-
|
220
|
+
opts = get_method(nil, signature)
|
209
221
|
method = opts[:method]
|
210
|
-
args =
|
222
|
+
args = args_with_prepend(args, opts)
|
223
|
+
args = "(#{args.raw})" if args
|
211
224
|
t_if cond, "#{method}#{args}", opts
|
212
225
|
end
|
213
226
|
end
|
@@ -232,11 +245,37 @@ module RubyLess
|
|
232
245
|
res
|
233
246
|
end
|
234
247
|
|
235
|
-
def get_method(
|
236
|
-
|
237
|
-
|
248
|
+
def get_method(receiver, signature)
|
249
|
+
klass = receiver ? receiver.klass : @helper
|
250
|
+
|
251
|
+
type = klass.respond_to?(:safe_method_type) ? klass.safe_method_type(signature) : SafeClass.safe_method_type_for(klass, signature)
|
252
|
+
|
253
|
+
if type.nil?
|
254
|
+
# We try to match with the superclass of the arguments
|
255
|
+
end
|
256
|
+
raise RubyLess::NoMethodError.new(receiver, klass, signature) if !type || type[:class].kind_of?(Symbol) # we cannot send: no object.
|
238
257
|
|
239
258
|
type[:class].kind_of?(Proc) ? type[:class].call(@helper, signature) : type
|
240
259
|
end
|
260
|
+
|
261
|
+
def get_lit_class(klass)
|
262
|
+
unless lit_class = RubyLess::SafeClass.literal_class_for(klass)
|
263
|
+
raise RubyLess::SyntaxError.new("#{klass} literal not supported by RubyLess.")
|
264
|
+
end
|
265
|
+
lit_class
|
266
|
+
end
|
267
|
+
|
268
|
+
def args_with_prepend(args, opts)
|
269
|
+
if prepend_args = opts[:prepend_args]
|
270
|
+
if args
|
271
|
+
prepend_args.append_argument(args)
|
272
|
+
prepend_args
|
273
|
+
else
|
274
|
+
prepend_args
|
275
|
+
end
|
276
|
+
else
|
277
|
+
args
|
278
|
+
end
|
279
|
+
end
|
241
280
|
end
|
242
281
|
end
|
@@ -1,52 +1,51 @@
|
|
1
1
|
module RubyLess
|
2
2
|
module SafeClass
|
3
|
-
@@_safe_methods
|
4
|
-
@@
|
3
|
+
@@_safe_methods ||= {} # defined for each class
|
4
|
+
@@_safe_methods_parsed ||= {} # full list with inherited attributes
|
5
|
+
@@_safe_literal_classes ||= {}
|
5
6
|
|
6
7
|
# List of safe methods for a specific class.
|
7
8
|
def self.safe_methods_for(klass)
|
8
|
-
|
9
|
+
# Caching safe_methods_all is bad when modules are dynamically added / removed.
|
10
|
+
@@_safe_methods_parsed[klass] ||= build_safe_methods_list(klass)
|
9
11
|
end
|
10
12
|
|
11
13
|
# Return method type (options) if the given signature is a safe method for the class.
|
12
14
|
def self.safe_method_type_for(klass, signature)
|
13
|
-
|
14
|
-
|
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
|
15
|
+
# Signature might be ['name', {:mode => String, :type => Number}].
|
16
|
+
# build signature arguments
|
35
17
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
return nil unless allowed_args_for_position[k] == v
|
43
|
-
end
|
44
|
-
end
|
45
|
-
type
|
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
|
46
24
|
else
|
47
|
-
nil
|
25
|
+
signature_args << nil
|
26
|
+
s
|
27
|
+
end
|
28
|
+
end
|
29
|
+
# Find safe method in all ancestry
|
30
|
+
klass.ancestors.each do |ancestor|
|
31
|
+
return nil if ancestor == RubyLess::SafeClass
|
32
|
+
if type = safe_method_with_hash_args(ancestor, signature, signature_args)
|
33
|
+
return type
|
48
34
|
end
|
49
35
|
end
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.literal_class_for(klass)
|
40
|
+
@@_safe_literal_classes[klass]
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.safe_literal_class(hash)
|
44
|
+
@@_safe_literal_classes.merge!(hash)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.all_safe_methods
|
48
|
+
@@_safe_methods
|
50
49
|
end
|
51
50
|
|
52
51
|
# Declare a safe method for a given class ( same as #safe_method)
|
@@ -107,6 +106,10 @@ module RubyLess
|
|
107
106
|
safe_method(methods_hash)
|
108
107
|
end
|
109
108
|
|
109
|
+
def self.safe_literal_class(hash)
|
110
|
+
RubyLess::SafeClass.safe_literal_class(hash)
|
111
|
+
end
|
112
|
+
|
110
113
|
# Declare a safe method to access a list of attributes.
|
111
114
|
# This method should only be used when the class is linked with a database table and provides
|
112
115
|
# proper introspection to detect types and the possibility of NULL values.
|
@@ -129,6 +132,30 @@ module RubyLess
|
|
129
132
|
end
|
130
133
|
end
|
131
134
|
|
135
|
+
# Declare a safe method to access a list of properties.
|
136
|
+
# This method should only be used in conjunction with the Property gem.
|
137
|
+
def self.safe_property(*properties)
|
138
|
+
columns = schema.columns
|
139
|
+
properties.each do |att|
|
140
|
+
if col = columns[att.to_s]
|
141
|
+
opts = {}
|
142
|
+
opts[:nil] = col.default.nil?
|
143
|
+
if col.number?
|
144
|
+
opts[:class] = Number
|
145
|
+
elsif col.text?
|
146
|
+
opts[:class] = String
|
147
|
+
else
|
148
|
+
opts[:class] = col.klass
|
149
|
+
end
|
150
|
+
opts[:method] = "prop['#{att.to_s.gsub("'",'')}']"
|
151
|
+
safe_method att.to_sym => opts
|
152
|
+
else
|
153
|
+
puts "Warning: could not declare safe_property '#{att}' (No property column with this name found in class #{self})"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
|
132
159
|
# Declare a safe method for a given class
|
133
160
|
def self.safe_method_for(klass, signature)
|
134
161
|
SafeClass.safe_method_for(klass, signature)
|
@@ -160,7 +187,7 @@ module RubyLess
|
|
160
187
|
|
161
188
|
# Return the type if the given signature corresponds to a safe method for the object's class.
|
162
189
|
def safe_method_type(signature)
|
163
|
-
if type = self.class
|
190
|
+
if type = SafeClass.safe_method_type_for(self.class, signature)
|
164
191
|
type[:class].kind_of?(Symbol) ? self.send(type[:class], signature) : type
|
165
192
|
end
|
166
193
|
end
|
@@ -190,7 +217,7 @@ module RubyLess
|
|
190
217
|
end
|
191
218
|
|
192
219
|
def self.build_safe_methods_list(klass)
|
193
|
-
list =
|
220
|
+
list = SignatureHash.new
|
194
221
|
(@@_safe_methods[klass] || {}).map do |signature, return_value|
|
195
222
|
if return_value.kind_of?(Hash)
|
196
223
|
return_value[:class] = parse_class(return_value[:class])
|
@@ -221,5 +248,27 @@ module RubyLess
|
|
221
248
|
end
|
222
249
|
end
|
223
250
|
end
|
251
|
+
|
252
|
+
def self.safe_method_with_hash_args(klass, signature, hash_args)
|
253
|
+
if type = safe_methods_for(klass)[signature]
|
254
|
+
unless allowed_args = type[:hash_args]
|
255
|
+
# All arguments allowed
|
256
|
+
return type
|
257
|
+
end
|
258
|
+
|
259
|
+
# Verify arguments
|
260
|
+
hash_args.each_with_index do |args, i|
|
261
|
+
next unless args
|
262
|
+
# verify for each position: ({:a => 3}, {:x => :y})
|
263
|
+
return nil unless allowed_args_for_position = allowed_args[i]
|
264
|
+
args.each do |k,v|
|
265
|
+
return nil unless v.ancestors.include?(allowed_args_for_position[k])
|
266
|
+
end
|
267
|
+
end
|
268
|
+
type
|
269
|
+
else
|
270
|
+
nil
|
271
|
+
end
|
272
|
+
end
|
224
273
|
end
|
225
274
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
module RubyLess
|
3
|
+
class SignatureHash < Hash
|
4
|
+
alias get []
|
5
|
+
|
6
|
+
def [](signature)
|
7
|
+
if type = get(signature)
|
8
|
+
# fastest: all keys are equal
|
9
|
+
return type
|
10
|
+
elsif signature.kind_of?(Array)
|
11
|
+
size = signature.size
|
12
|
+
ancestors = signature.map {|k| k.kind_of?(Class) ? k.ancestors : [k]}
|
13
|
+
|
14
|
+
each do |key, type|
|
15
|
+
next unless key.size == size
|
16
|
+
ok = true
|
17
|
+
key.each_with_index do |k, i|
|
18
|
+
if !ancestors[i].include?(k)
|
19
|
+
ok = false
|
20
|
+
break
|
21
|
+
end
|
22
|
+
end
|
23
|
+
if ok
|
24
|
+
# insert in cache
|
25
|
+
self[signature] = type
|
26
|
+
return type
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|