rubyless 0.3.5 → 0.4.0
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/.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
|