rubyless 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,6 +1,20 @@
1
+ == 0.5.0 2010-05-27
2
+
3
+ * Major enhancements
4
+ * Added 'accept_nil' option.
5
+ * Added 'append_hash' options.
6
+ * RubyLess activation is now enabled with 'include RubyLess' (no need for SafeClass).
7
+ * Added support for compile time literal args evaluation.
8
+ * Added support for compile time literal args evaluation on literal objects (strings for example).
9
+ * All method signatures that have an optional hash should accept calls without the last hash.
10
+ * Better error messages for missing methods.
11
+ * Added support for Array with Mixed array detection.
12
+ * Added support for pre_processing with helper method.
13
+ * Added support for method detection on NilClass.
14
+
1
15
  == 0.4.0 2010-03-21
2
16
 
3
- * 4 major enhancement
17
+ * 4 major enhancements
4
18
  * Parsing inheritance tree to get safe_method_type.
5
19
  * Instance variable (ivar) support (declared as safe_methods).
6
20
  * Added support for prepend variables to tranform methods like link("foo") to link(@node, "foo").
data/README.rdoc CHANGED
@@ -24,14 +24,14 @@ return 'nil' instead of the declared output, you need to wrap your final ruby 'e
24
24
 
25
25
  # signature is made of [method, arg_class, arg_class, ...]
26
26
  class Node
27
- include RubyLess::SafeClass
27
+ include RubyLess
28
28
  safe_method [:ancestor?, Node] => Boolean
29
29
  end
30
30
 
31
31
  # methods defined in helper
32
32
 
33
33
  # global methods
34
- include RubyLess::SafeClass
34
+ include RubyLess
35
35
  safe_method :prev => {:class => Dummy, :method => 'previous', :nil => true}
36
36
  safe_method :node => lambda {|h| {:class => h.context[:node_class], :method => h.context[:node]}}
37
37
  safe_method [:strftime, Time, String] => String
data/Rakefile CHANGED
@@ -16,7 +16,7 @@ begin
16
16
  gem.add_dependency 'ruby_parser', '>= 2.0.4'
17
17
  gem.add_dependency 'sexp_processor', '>= 3.0.1'
18
18
  gem.add_development_dependency "shoulda", ">= 0"
19
- gem.add_development_dependency "yamltest", ">= 0.5.3"
19
+ gem.add_development_dependency "yamltest", ">= 0.6.0"
20
20
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
21
21
  end
22
22
  Jeweler::GemcutterTasks.new
data/TODO ADDED
@@ -0,0 +1,8 @@
1
+ 2010-03-28
2
+
3
+ 1. Write shoulda tests for TypedMethod, TypedArray and TypedHash
4
+ 2. Create sub-classes of TypedString: TypedMethod, TypedArray, TypedHash
5
+ A TypedMethod should have a TypedArray as arguments, eventually with a TypedHash as last argument
6
+ 3. Move list code from TypedString into TypedArray
7
+ 4. Move hash code from TypedString into TypedHash
8
+
@@ -1,15 +1,27 @@
1
1
  require 'ruby_less/safe_class'
2
2
 
3
3
 
4
+ # Dummy classes
4
5
  class Boolean
5
6
  end
6
7
 
7
8
  class Number
8
9
  end
9
10
 
11
+ class StringDictionary
12
+ include RubyLess
13
+ safe_method ['[]', Symbol] => {:class => String, :nil => true}
14
+ disable_safe_read # ?
15
+ end
16
+
10
17
  RubyLess::SafeClass.safe_literal_class Fixnum => Number, Float => Number, Symbol => Symbol, Regexp => Regexp
11
18
  RubyLess::SafeClass.safe_method_for( Number,
12
19
  [:==, Number] => Boolean, [:< , Number] => Boolean, [:> , Number] => Boolean,
13
20
  [:<=, Number] => Boolean, [:>=, Number] => Boolean, [:- , Number] => Number,
14
21
  [:+ , Number] => Number, [:* , Number] => Number, [:/ , Number] => Number,
15
- [:% , Number] => Number, [:"-@"] => Number )
22
+ [:% , Number] => Number, [:"-@"] => Number
23
+ )
24
+
25
+ RubyLess::SafeClass.safe_method_for( String,
26
+ [:==, String] => Boolean
27
+ )
@@ -1,3 +1,3 @@
1
1
  module RubyLess
2
- VERSION = '0.4.0'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -9,7 +9,7 @@ module RubyLess
9
9
  end
10
10
 
11
11
  def message
12
- "#{receiver_with_class}: #{error_message} '#{method_with_arguments}'."
12
+ "#{error_message} '#{method_with_arguments}' for #{receiver_with_class}."
13
13
  end
14
14
 
15
15
  def error_message
@@ -21,7 +21,8 @@ module RubyLess
21
21
  end
22
22
 
23
23
  def receiver_with_class
24
- @receiver ? "#{@receiver} (#{@klass})" : "(#{@klass.class})"
24
+ klass = @klass.kind_of?(Class) ? @klass : @klass.class
25
+ @receiver ? "'#{@receiver}' of type #{@klass}" : klass
25
26
  end
26
27
 
27
28
  def method_with_arguments
@@ -12,6 +12,8 @@ module RubyLess
12
12
  def self.translate(string, helper)
13
13
  sexp = RubyParser.new.parse(string)
14
14
  self.new(helper).process(sexp)
15
+ rescue Racc::ParseError => err
16
+ raise RubyLess::SyntaxError.new(err.message)
15
17
  end
16
18
 
17
19
  def initialize(helper)
@@ -86,8 +88,22 @@ module RubyLess
86
88
  end
87
89
 
88
90
  def process_array(exp)
89
- res = process_arglist(exp)
90
- exp.size > 1 ? t("[#{res}]", res.opts) : res
91
+ literal = true
92
+ list = []
93
+ classes = []
94
+ while !exp.empty?
95
+ res = process(exp.shift)
96
+ content_class ||= res.opts[:class]
97
+ unless res.opts[:class] <= content_class
98
+ classes = list.map { content_class.name } + [res.opts[:class].name]
99
+ raise RubyLess::Error.new("Mixed Array not supported ([#{classes * ','}]).")
100
+ end
101
+ list << res
102
+ end
103
+
104
+ res.opts[:class] = Array
105
+ res.opts[:array_content_class] = content_class
106
+ t "[#{list * ','}]", res.opts.merge(:literal => nil)
91
107
  end
92
108
 
93
109
  def process_vcall(exp)
@@ -104,11 +120,12 @@ module RubyLess
104
120
 
105
121
  def process_lit(exp)
106
122
  lit = exp.shift
107
- t lit.inspect, get_lit_class(lit.class)
123
+ t lit.inspect, get_lit_class(lit)
108
124
  end
109
125
 
110
126
  def process_str(exp)
111
- t exp.shift.inspect, String
127
+ lit = exp.shift
128
+ t lit.inspect, :class => String, :literal => lit
112
129
  end
113
130
 
114
131
  def process_dstr(exp)
@@ -120,8 +137,7 @@ module RubyLess
120
137
  end
121
138
 
122
139
  def process_hash(exp)
123
- result = []
124
- klass = {}
140
+ result = t "", String
125
141
  until exp.empty?
126
142
  key = exp.shift
127
143
  if [:lit, :str].include?(key.first)
@@ -131,16 +147,14 @@ module RubyLess
131
147
  type = rhs.first
132
148
  rhs = process rhs
133
149
  #rhs = "(#{rhs})" unless [:lit, :str].include? type # TODO: verify better!
134
-
135
- result << "#{key.inspect} => #{rhs}"
136
- klass[key] = rhs.klass
150
+ result.set_hash(key, rhs)
137
151
  else
138
152
  # ERROR: invalid key
139
153
  raise RubyLess::SyntaxError.new("Invalid key type for hash (should be a literal value, was #{key.first.inspect})")
140
154
  end
141
155
  end
142
-
143
- t "{#{result.join(', ')}}", :class => klass
156
+ result.rebuild_hash
157
+ result
144
158
  end
145
159
 
146
160
  def process_ivar(exp)
@@ -198,30 +212,76 @@ module RubyLess
198
212
  end
199
213
 
200
214
  if receiver
201
- if receiver.could_be_nil?
202
- cond += receiver.cond
203
- end
204
215
  opts = get_method(receiver, signature)
216
+ method_call_with_receiver(receiver, args, opts, cond, signature)
217
+ else
218
+ opts = get_method(nil, signature)
205
219
  method = opts[:method]
206
- if method == '/'
207
- t_if cond, "(#{receiver.raw}#{method}#{args.raw} rescue nil)", opts.merge(:nil => true)
208
- elsif INFIX_OPERATOR.include?(method)
209
- t_if cond, "(#{receiver.raw}#{method}#{args.raw})", opts
210
- elsif PREFIX_OPERATOR.include?(method)
211
- t_if cond, "#{method.to_s[0..0]}#{receiver.raw}", opts
212
- elsif method == '[]'
213
- t_if cond, "#{receiver.raw}[#{args.raw}]", opts
220
+ args = args_with_prepend(args, opts)
221
+
222
+ if (proc = opts[:pre_processor]) && !args.list.detect {|a| !a.literal}
223
+ if proc.kind_of?(Proc)
224
+ res = proc.call(*args.list.map(&:literal))
225
+ else
226
+ res = @helper.send(proc, *args.list.map(&:literal))
227
+ end
228
+
229
+ return res.kind_of?(TypedString) ? res : t(res.inspect, :class => String, :literal => res)
230
+ end
231
+
232
+ if opts[:accept_nil]
233
+ method_call_accepting_nil(method, args, opts)
214
234
  else
215
- args = args_with_prepend(args, opts)
216
235
  args = "(#{args.raw})" if args
217
- t_if cond, "#{receiver.raw}.#{method}#{args}", opts
236
+ t_if cond, "#{method}#{args}", opts
218
237
  end
238
+ end
239
+ end
240
+
241
+ def method_call_accepting_nil(method, args, opts)
242
+ if args
243
+ args = args.list.map do |arg|
244
+ if !arg.could_be_nil? || arg.raw == arg.cond.to_s
245
+ arg.raw
246
+ else
247
+ "(#{arg.cond} ? #{arg.raw} : nil)"
248
+ end
249
+ end.join(', ')
250
+
251
+ t "#{method}(#{args})", opts
252
+ else
253
+ t method, opts
254
+ end
255
+ end
256
+
257
+ def method_call_with_receiver(receiver, args, opts, cond, signature)
258
+ method = opts[:method]
259
+ arg_list = args ? args.list : []
260
+
261
+ if receiver.could_be_nil? && opts != SafeClass.safe_method_type_for(NilClass, signature)
262
+ # Do not add a condition if the method applies on nil
263
+ cond += receiver.cond
264
+ elsif receiver.literal && (proc = opts[:pre_processor]) && !arg_list.detect {|a| !a.literal}
265
+ if proc.kind_of?(Proc)
266
+ res = proc.call([receiver.literal] + arg_list.map(&:literal))
267
+ else
268
+ res = receiver.literal.send(*([method] + arg_list.map(&:literal)))
269
+ end
270
+ return res.kind_of?(TypedString) ? res : t(res.inspect, :class => String, :literal => res)
271
+ end
272
+
273
+ if method == '/'
274
+ t_if cond, "(#{receiver.raw}#{method}#{args.raw} rescue nil)", opts.merge(:nil => true)
275
+ elsif INFIX_OPERATOR.include?(method)
276
+ t_if cond, "(#{receiver.raw}#{method}#{args.raw})", opts
277
+ elsif PREFIX_OPERATOR.include?(method)
278
+ t_if cond, "#{method.to_s[0..0]}#{receiver.raw}", opts
279
+ elsif method == '[]'
280
+ t_if cond, "#{receiver.raw}[#{args.raw}]", opts
219
281
  else
220
- opts = get_method(nil, signature)
221
- method = opts[:method]
222
282
  args = args_with_prepend(args, opts)
223
283
  args = "(#{args.raw})" if args
224
- t_if cond, "#{method}#{args}", opts
284
+ t_if cond, "#{receiver.raw}.#{method}#{args}", opts
225
285
  end
226
286
  end
227
287
 
@@ -258,24 +318,36 @@ module RubyLess
258
318
  type[:class].kind_of?(Proc) ? type[:class].call(@helper, signature) : type
259
319
  end
260
320
 
261
- def get_lit_class(klass)
262
- unless lit_class = RubyLess::SafeClass.literal_class_for(klass)
321
+ def get_lit_class(lit)
322
+ unless lit_class = RubyLess::SafeClass.literal_class_for(lit.class)
263
323
  raise RubyLess::SyntaxError.new("#{klass} literal not supported by RubyLess.")
264
324
  end
265
- lit_class
325
+ {:class => lit_class, :literal => lit}
266
326
  end
267
327
 
268
328
  def args_with_prepend(args, opts)
269
329
  if prepend_args = opts[:prepend_args]
270
330
  if args
271
331
  prepend_args.append_argument(args)
272
- prepend_args
332
+ args = prepend_args
273
333
  else
274
- prepend_args
334
+ args = prepend_args
275
335
  end
276
- else
277
- args
278
336
  end
337
+
338
+ if append_hash = opts[:append_hash]
339
+ last_arg = args.list.last
340
+ unless last_arg.klass.kind_of?(Hash)
341
+ last_arg = t "", String
342
+ args.append_argument(last_arg)
343
+ end
344
+ append_hash.each do |key, value|
345
+ last_arg.set_hash(key, value)
346
+ end
347
+ last_arg.rebuild_hash
348
+ args.rebuild_arguments
349
+ end
350
+ args
279
351
  end
280
352
  end
281
353
  end
@@ -26,9 +26,10 @@ module RubyLess
26
26
  s
27
27
  end
28
28
  end
29
+
29
30
  # Find safe method in all ancestry
30
31
  klass.ancestors.each do |ancestor|
31
- return nil if ancestor == RubyLess::SafeClass
32
+ # FIXME: find a way to optimize this search !
32
33
  if type = safe_method_with_hash_args(ancestor, signature, signature_args)
33
34
  return type
34
35
  end
@@ -53,13 +54,22 @@ module RubyLess
53
54
  defaults = methods_hash.delete(:defaults) || {}
54
55
 
55
56
  list = (@@_safe_methods[klass] ||= {})
56
- methods_hash.each do |k,v|
57
- k, hash_args = build_signature(k)
58
- v = {:class => v} unless v.kind_of?(Hash)
59
- v = defaults.merge(v)
60
- v[:method] = v[:method] ? v[:method].to_s : k.first.to_s
61
- v[:hash_args] = hash_args if hash_args
62
- list[k] = v
57
+ methods_hash.each do |signature, type|
58
+ signature, hash_args = build_signature(signature)
59
+ type = {:class => type} unless type.kind_of?(Hash)
60
+ type = defaults.merge(type)
61
+ type[:method] = type[:method] ? type[:method].to_s : signature.first.to_s
62
+ if hash_args
63
+ type[:hash_args] = hash_args
64
+ list[signature] = type
65
+ if hash_args.last.kind_of?(Hash)
66
+ # Also build signature without last hash. This enables the common idiom
67
+ # method(arg, arg, opts = {})
68
+ list[signature[0..-2]] = type.dup
69
+ end
70
+ else
71
+ list[signature] = type
72
+ end
63
73
  end
64
74
  end
65
75
 
@@ -193,7 +203,7 @@ module RubyLess
193
203
  end
194
204
 
195
205
  # Safe attribute reader used when 'safe_readable?' could not be called because the class
196
- # is not known during compile time.
206
+ # is not known during compile time. FIXME: Is this used anymore ?
197
207
  def safe_read(key)
198
208
  return "'#{key}' not readable" unless type = self.class.safe_method_type([key])
199
209
  self.send(type[:method])
@@ -0,0 +1,50 @@
1
+ module RubyLess
2
+ class TypedMethod
3
+ attr_accessor :name, :args
4
+
5
+ def initialize(*args)
6
+ @name = args.shift
7
+ @args = args
8
+ end
9
+
10
+ def add_argument(arg, klass_or_opts = nil)
11
+ if klass_or_opts
12
+ @args << TypedString.new(arg.to_s, klass_or_opts)
13
+ elsif arg.kind_of?(TypedString)
14
+ @arg << arg
15
+ else
16
+ raise Exception.new("Cannot add #{arg} to TypedMethod '#{name}' (no type and not a TypedString)")
17
+ end
18
+ end
19
+
20
+ def set_hash(key, value, klass_or_opts)
21
+ if last_is_hash?
22
+ hash = @args.last
23
+ else
24
+ hash = TypedString.new
25
+ @args << hash
26
+ end
27
+
28
+ hash.set_hash(key, TypedString.new(value, klass_or_opts))
29
+ end
30
+
31
+ def last_is_hash?
32
+ @args.last.kind_of?(TypedString) && !@args.last.hash.empty?
33
+ end
34
+
35
+ def to_s
36
+ if @args.empty?
37
+ @name
38
+ elsif last_is_hash?
39
+ args = @args[0..-2].map(&:to_s)
40
+ hash = @args.last
41
+ hash.rebuild_hash
42
+ hash = hash.to_s[1..-2]
43
+ args += [hash]
44
+ "#{@name}(#{args.join(', ')})"
45
+ else
46
+ "#{@name}(#{@args.join(', ')})"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,29 +1,29 @@
1
1
  module RubyLess
2
-
2
+
3
3
  # This is a special kind of string containing ruby code that retains some information from the
4
4
  # elements that compose it.
5
5
  class TypedString < String
6
6
  attr_reader :klass, :opts
7
7
 
8
- def initialize(content = "", opts = nil)
9
- opts ||= {:class => String}
8
+ def initialize(content = "", klass_or_opts = nil)
9
+ klass_or_opts ||= {:class => String}
10
10
  replace(content)
11
- @opts = opts.dup
11
+ @opts = klass_or_opts.kind_of?(Hash) ? klass_or_opts.dup : {:class => klass_or_opts}
12
12
  if could_be_nil? && !@opts[:cond]
13
13
  @opts[:cond] = [self.to_s]
14
14
  end
15
15
  end
16
-
16
+
17
17
  # Resulting class of the evaluated ruby code if it is not nil.
18
18
  def klass
19
19
  @opts[:class]
20
20
  end
21
-
21
+
22
22
  # Returns true if the evaluation of the ruby code represented by the string could be 'nil'.
23
23
  def could_be_nil?
24
24
  @opts[:nil]
25
25
  end
26
-
26
+
27
27
  # Condition that could yield a nil result in the whole expression.
28
28
  # For example in the following expression:
29
29
  # node.spouse.name == ''
@@ -31,15 +31,39 @@ module RubyLess
31
31
  def cond
32
32
  @opts[:cond]
33
33
  end
34
-
34
+
35
+ # Return the literal value (string before inspect, number)
36
+ def literal
37
+ @opts[:literal]
38
+ end
39
+
40
+ # List of typed_strings that form the argument list. This is only used
41
+ # to resolve nil when the receiver of the arguments accepts nil values.
42
+ def list
43
+ @list ||= self.empty? ? [] : [self]
44
+ end
45
+
46
+ # Hash arguments. This is only used to resolve parameter insertion with
47
+ # append_hash.
48
+ def hash
49
+ @hash ||= {}
50
+ end
51
+
52
+ # Used to keep hash order (this is useful for testing).
53
+ def keys
54
+ @hash_keys ||= []
55
+ end
56
+
35
57
  # raw result without nil checking:
36
58
  # "node.spouse.name" instead of "(node.spouse ? node.spouse.name : nil)"
37
59
  def raw
38
60
  @opts[:raw] || self.to_s
39
61
  end
40
-
62
+
41
63
  # Append a typed string to build an argument list
42
64
  def append_argument(typed_string)
65
+ self.list << typed_string
66
+
43
67
  append_opts(typed_string)
44
68
  if self.empty?
45
69
  replace(typed_string.raw)
@@ -47,7 +71,28 @@ module RubyLess
47
71
  replace("#{self.raw}, #{typed_string.raw}")
48
72
  end
49
73
  end
50
-
74
+
75
+ def rebuild_arguments
76
+ replace(list.map {|arg| arg.raw}.join(', ')) if @list
77
+ end
78
+
79
+ def set_hash(key, value)
80
+ self.hash[key] = value
81
+ self.keys << key unless self.keys.include?(key)
82
+ @opts[:class] = {} unless self.klass.kind_of?(Hash)
83
+ self.klass[key] = value.klass
84
+ end
85
+
86
+ def rebuild_hash
87
+ if @hash
88
+ result = []
89
+ @hash_keys.each do |k|
90
+ result << "#{k.inspect} => #{@hash[k]}"
91
+ end
92
+ replace "{#{result.join(', ')}}"
93
+ end
94
+ end
95
+
51
96
  private
52
97
  def append_opts(typed_string)
53
98
  if self.empty?
@@ -61,7 +106,7 @@ module RubyLess
61
106
  append_cond(typed_string.cond) if typed_string.could_be_nil?
62
107
  end
63
108
  end
64
-
109
+
65
110
  def append_cond(condition)
66
111
  @opts[:cond] ||= []
67
112
  @opts[:cond] += [condition].flatten
data/lib/ruby_less.rb CHANGED
@@ -1,27 +1,40 @@
1
1
  =begin rdoc
2
2
  =end
3
3
  require 'ruby_less/info'
4
- require 'ruby_less/basic_types'
5
4
  require 'ruby_less/signature_hash'
6
5
  require 'ruby_less/error'
7
6
  require 'ruby_less/no_method_error'
8
7
  require 'ruby_less/syntax_error'
9
8
  require 'ruby_less/typed_string'
9
+ require 'ruby_less/typed_method'
10
10
  require 'ruby_less/safe_class'
11
11
  require 'ruby_less/processor'
12
12
 
13
13
  module RubyLess
14
+ def self.included(base)
15
+ base.class_eval do
16
+ include SafeClass
17
+ end
18
+ end
19
+
20
+ # Return method type (options) if the given signature is a safe method for the class.
21
+ def self.safe_method_type_for(klass, signature)
22
+ SafeClass.safe_method_type_for(klass, signature)
23
+ end
24
+
14
25
  def self.translate(string, helper)
15
26
  RubyLessProcessor.translate(string, helper)
16
27
  end
17
28
 
18
29
  def self.translate_string(string, helper)
19
30
  if string =~ /\#\{/
20
- ::RubyLess.translate("%Q{#{string}}", helper)
31
+ translate("%Q{#{string}}", helper)
21
32
  else
22
- string.inspect
33
+ TypedString.new(string.inspect, :class => String, :literal => string)
23
34
  end
24
35
  rescue => err
25
- raise ::RubyLess::Error.new("Error parsing string \"#{string}\": #{err.message.strip}")
36
+ raise RubyLess::Error.new("Error parsing string \"#{string}\": #{err.message.strip}")
26
37
  end
27
38
  end
39
+
40
+ require 'ruby_less/basic_types'
data/rubyless.gemspec CHANGED
@@ -5,21 +5,23 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{rubyless}
8
- s.version = "0.4.0"
8
+ s.version = "0.5.0"
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{2010-03-21}
12
+ s.date = %q{2010-05-27}
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 = [
16
- "README.rdoc"
16
+ "README.rdoc",
17
+ "TODO"
17
18
  ]
18
19
  s.files = [
19
20
  ".gitignore",
20
21
  "History.txt",
21
22
  "README.rdoc",
22
23
  "Rakefile",
24
+ "TODO",
23
25
  "lib/ruby_less.rb",
24
26
  "lib/ruby_less/basic_types.rb",
25
27
  "lib/ruby_less/error.rb",
@@ -29,6 +31,7 @@ Gem::Specification.new do |s|
29
31
  "lib/ruby_less/safe_class.rb",
30
32
  "lib/ruby_less/signature_hash.rb",
31
33
  "lib/ruby_less/syntax_error.rb",
34
+ "lib/ruby_less/typed_method.rb",
32
35
  "lib/ruby_less/typed_string.rb",
33
36
  "lib/rubyless.rb",
34
37
  "rails/init.rb",
@@ -36,6 +39,7 @@ Gem::Specification.new do |s|
36
39
  "test/RubyLess/active_record.yml",
37
40
  "test/RubyLess/basic.yml",
38
41
  "test/RubyLess/errors.yml",
42
+ "test/RubyLess/hash.yml",
39
43
  "test/RubyLess/string.yml",
40
44
  "test/RubyLess_test.rb",
41
45
  "test/mock/active_record_mock.rb",
@@ -44,7 +48,9 @@ Gem::Specification.new do |s|
44
48
  "test/mock/property_column.rb",
45
49
  "test/safe_class_test.rb",
46
50
  "test/signature_hash_test.rb",
47
- "test/test_helper.rb"
51
+ "test/test_helper.rb",
52
+ "test/typed_method_test.rb",
53
+ "test/typed_string_test.rb"
48
54
  ]
49
55
  s.homepage = %q{http://zenadmin.org/546}
50
56
  s.rdoc_options = ["--charset=UTF-8"]
@@ -59,7 +65,9 @@ Gem::Specification.new do |s|
59
65
  "test/RubyLess_test.rb",
60
66
  "test/safe_class_test.rb",
61
67
  "test/signature_hash_test.rb",
62
- "test/test_helper.rb"
68
+ "test/test_helper.rb",
69
+ "test/typed_method_test.rb",
70
+ "test/typed_string_test.rb"
63
71
  ]
64
72
 
65
73
  if s.respond_to? :specification_version then
@@ -70,18 +78,18 @@ Gem::Specification.new do |s|
70
78
  s.add_runtime_dependency(%q<ruby_parser>, [">= 2.0.4"])
71
79
  s.add_runtime_dependency(%q<sexp_processor>, [">= 3.0.1"])
72
80
  s.add_development_dependency(%q<shoulda>, [">= 0"])
73
- s.add_development_dependency(%q<yamltest>, [">= 0.5.3"])
81
+ s.add_development_dependency(%q<yamltest>, [">= 0.6.0"])
74
82
  else
75
83
  s.add_dependency(%q<ruby_parser>, [">= 2.0.4"])
76
84
  s.add_dependency(%q<sexp_processor>, [">= 3.0.1"])
77
85
  s.add_dependency(%q<shoulda>, [">= 0"])
78
- s.add_dependency(%q<yamltest>, [">= 0.5.3"])
86
+ s.add_dependency(%q<yamltest>, [">= 0.6.0"])
79
87
  end
80
88
  else
81
89
  s.add_dependency(%q<ruby_parser>, [">= 2.0.4"])
82
90
  s.add_dependency(%q<sexp_processor>, [">= 3.0.1"])
83
91
  s.add_dependency(%q<shoulda>, [">= 0"])
84
- s.add_dependency(%q<yamltest>, [">= 0.5.3"])
92
+ s.add_dependency(%q<yamltest>, [">= 0.6.0"])
85
93
  end
86
94
  end
87
95
 
@@ -22,10 +22,6 @@ symbol:
22
22
  src: ":foobar"
23
23
  sxp: 's(:lit, :foobar)'
24
24
 
25
- hash_access:
26
- src: "dictionary[:key]"
27
- tem: "get_dict[:key]"
28
-
29
25
  rewrite_variables:
30
26
  src: "!prev.ancestor?(main) && !node.ancestor?(main)"
31
27
  tem: "(not previous.ancestor?(@node) and not node.ancestor?(@node))"
@@ -138,4 +134,54 @@ match_on_subclass:
138
134
 
139
135
  optional_argument_subclass:
140
136
  src: "width(:mode => str)"
141
- tem: "node.width({:mode => str})"
137
+ tem: "node.width({:mode => str})"
138
+
139
+ simple_accept_nil:
140
+ src: "accept_nil(dictionary[:foo])"
141
+ tem: "accept_nil(get_dict[:foo])"
142
+
143
+ accept_nil_many_arguments:
144
+ src: "accept_nil(dictionary[:foo], dictionary[:bar])"
145
+ tem: "accept_nil(get_dict[:foo], get_dict[:bar])"
146
+ res: "[\"Foo\", nil]"
147
+
148
+ accept_nil_many_arguments_no_all_nil:
149
+ src: "accept_nil('hop', dictionary[:bar])"
150
+ tem: "accept_nil(\"hop\", get_dict[:bar])"
151
+ res: "[\"hop\", nil]"
152
+
153
+ no_nil_in_accept_nil:
154
+ src: "accept_nil(no_nil(dictionary[:foo]))"
155
+ tem: "accept_nil((get_dict[:foo] ? no_nil(get_dict[:foo]) : nil))"
156
+ res: "[\"Foo\", nil]"
157
+
158
+ two_no_nil_in_accept_nil:
159
+ src: "accept_nil(no_nil(dictionary[:foo]), no_nil(dictionary[:bar]))"
160
+ tem: "accept_nil((get_dict[:foo] ? no_nil(get_dict[:foo]) : nil), (get_dict[:bar] ? no_nil(get_dict[:bar]) : nil))"
161
+ res: "[\"Foo\", nil]"
162
+
163
+ accept_nil_in_no_nil:
164
+ src: "no_nil(accept_nil(dictionary[:foo]))"
165
+ tem: "no_nil(accept_nil(get_dict[:foo]))"
166
+
167
+ parse_array:
168
+ src: "[3, 6]"
169
+ tem: '[3,6]'
170
+
171
+ noop_method:
172
+ src: 'no_op("hello")'
173
+ tem: '("hello")'
174
+ res: 'hello'
175
+
176
+ noop_other_signature:
177
+ src: 'no_op(45)'
178
+ tem: 'transform(45)'
179
+
180
+ build_finder:
181
+ # This test shows a way to 'pre_process' literal content with a helper method that should return a TypedString
182
+ src: 'find("one two")'
183
+ tem: 'secure(Node) { Node.find("one two") }'
184
+
185
+ methods_on_nil:
186
+ src: 'dictionary[:foo].blank?'
187
+ tem: 'get_dict[:foo].blank?'
@@ -1,10 +1,10 @@
1
1
  unknown_global_method:
2
2
  src: "system('echo date')"
3
- res: "(RubyLessTest): unknown method 'system(String)'."
3
+ res: "unknown method 'system(String)' for RubyLessTest."
4
4
 
5
5
  bad_argument_types:
6
6
  src: "strftime(34,'ffoo')"
7
- res: "(RubyLessTest): unknown method 'strftime(Number, String)'."
7
+ res: "unknown method 'strftime(Number, String)' for RubyLessTest."
8
8
 
9
9
  zero_div:
10
10
  src: "1/(id-10)"
@@ -17,23 +17,23 @@ looping:
17
17
 
18
18
  add_two_strings:
19
19
  src: "name + 14"
20
- res: "node.name (String): unknown method '+(Number)'."
20
+ res: "unknown method '+(Number)' for 'node.name' of type String."
21
21
 
22
22
  two_arguments_in_hash:
23
23
  src: "dictionary[:one, :two]"
24
- res: "get_dict (StringDictionary): unknown method '[](Symbol, Symbol)'."
24
+ res: "unknown method '[](Symbol, Symbol)' for 'get_dict' of type StringDictionary."
25
25
 
26
26
  number_argument:
27
27
  src: "dictionary[43]"
28
- res: "get_dict (StringDictionary): unknown method '[](Number)'."
28
+ res: "unknown method '[](Number)' for 'get_dict' of type StringDictionary."
29
29
 
30
30
  string_argument:
31
31
  src: "dictionary[spouse.name]"
32
- res: "get_dict (StringDictionary): unknown method '[](String)'."
32
+ res: "unknown method '[](String)' for 'get_dict' of type StringDictionary."
33
33
 
34
34
  symbol_type_not_used_out_of_helper:
35
35
  src: "node.foo"
36
- tem: "node (Dummy): unknown method 'foo()'."
36
+ tem: "unknown method 'foo()' for 'node' of type Dummy."
37
37
 
38
38
  optional_arguments_with_dynamic_string:
39
39
  src: "spouse.width(\"nice#{spouse.name}\" => 'pv')"
@@ -42,8 +42,16 @@ optional_arguments_with_dynamic_string:
42
42
 
43
43
  optional_arguments_bad_type:
44
44
  src: "width(:mode => 12)"
45
- res: "(RubyLessTest): unknown method 'width(:mode=>Number)'."
45
+ res: "unknown method 'width(:mode=>Number)' for RubyLessTest."
46
46
 
47
47
  optional_arguments_bad_argument:
48
48
  src: "width(:xyz => 'pv')"
49
- res: "(RubyLessTest): unknown method 'width(:xyz=>String)'."
49
+ res: "unknown method 'width(:xyz=>String)' for RubyLessTest."
50
+
51
+ hash_arguments_wrong_type:
52
+ src: "hash_args('name' => 45)"
53
+ tem: "unknown method 'hash_args(\"name\"=>Number)' for RubyLessTest."
54
+
55
+ mixed_array:
56
+ src: "[3, '4']"
57
+ tem: 'Mixed Array not supported ([Number,String]).'
@@ -0,0 +1,19 @@
1
+ parse_hash:
2
+ src: "{'one' => 1, 2 => 'two'}"
3
+ tem: "{\"one\" => 1, 2 => \"two\"}"
4
+
5
+ hash_access:
6
+ src: "dictionary[:key]"
7
+ tem: "get_dict[:key]"
8
+
9
+ append_hash:
10
+ src: "append_hash(5, 'foo' => 'Foo')"
11
+ tem: "add(5, {\"foo\" => \"Foo\", :xyz => bar})"
12
+
13
+ append_hash_on_empty:
14
+ src: "append_hash(5)"
15
+ tem: "add(5, {:xyz => bar})"
16
+
17
+ hash_arguments:
18
+ src: "hash_args('name' => str, 'age' => 13)"
19
+ tem: "hash_args({\"name\" => str, \"age\" => 13})"
@@ -9,3 +9,20 @@ text:
9
9
  dynamic_string:
10
10
  str: 'one #{name} two'
11
11
  tem: '"one #{node.name} two"'
12
+
13
+ concat_static:
14
+ src: "concat('hello ', 'World!')"
15
+ tem: '"hello World!"'
16
+
17
+ gsub_static:
18
+ src: "'one.one'.gsub(/\\./,';')"
19
+ tem: '"one;one"'
20
+
21
+ upcase_static:
22
+ src: "'one.one'.gsub(/\\./,';').upcase"
23
+ tem: '"ONE;ONE"'
24
+
25
+ equal:
26
+ src: "str == 'str'"
27
+ tem: '(str=="str")'
28
+ res: 'true'
@@ -1,12 +1,6 @@
1
1
  require 'date'
2
2
  require 'test_helper'
3
3
 
4
- class StringDictionary
5
- include RubyLess::SafeClass
6
- safe_method ['[]', Symbol] => {:class => String, :nil => true}
7
- disable_safe_read
8
- end
9
-
10
4
  # Used to test sub-classes in optional arguments
11
5
  class SubString < String
12
6
  end
@@ -14,7 +8,7 @@ end
14
8
  class RubyLessTest < Test::Unit::TestCase
15
9
  attr_reader :context
16
10
  yamltest :src_from_title => false
17
- include RubyLess::SafeClass
11
+ include RubyLess
18
12
  safe_method :prev => {:class => Dummy, :method => 'previous'}
19
13
  safe_method :main => {:class => Dummy, :method => '@node'}
20
14
  safe_method :node => lambda {|h, s| {:class => h.context[:node_class], :method => h.context[:node]}}
@@ -24,14 +18,34 @@ class RubyLessTest < Test::Unit::TestCase
24
18
  safe_method [:vowel_count, String] => Number
25
19
  safe_method [:log_info, Dummy, String] => String
26
20
  safe_method :foo => :contextual_method, :bar => :contextual_method
21
+
27
22
  safe_method_for String, [:==, String] => Boolean
28
23
  safe_method_for String, [:to_s] => String
29
- safe_method_for String, [:gsub, Regexp, String] => String
24
+ safe_method_for String, [:gsub, Regexp, String] => {:class => String, :pre_processor => Proc.new {|this, reg, str| this.gsub(reg, str)}}
25
+ safe_method_for String, :upcase => {:class => String, :pre_processor => true}
26
+
30
27
  safe_method_for Time, [:strftime, String] => String
28
+
31
29
  safe_method :@foo => {:class => Dummy, :method => "node"}
32
30
  safe_method :sub => SubDummy
33
31
  safe_method :str => SubString
34
32
 
33
+ safe_method [:accept_nil, String] => {:class => String, :accept_nil => true}
34
+ safe_method [:accept_nil, String, String] => {:class => String, :accept_nil => true}
35
+ safe_method [:no_nil, String] => String
36
+
37
+ safe_method [:no_op, String] => {:class => String, :method => ''}
38
+ safe_method [:no_op, Number] => {:class => String, :method => 'transform'}
39
+
40
+ safe_method [:hash_args, {'age' => Number, 'name' => String}] => String
41
+ safe_method [:append_hash, Number, {'foo' => String}] => :make_append_hash
42
+
43
+ safe_method [:concat, String, String] => {:class => String, :pre_processor => Proc.new{|a,b| a + b }}
44
+ safe_method [:find, String] => {:class => NilClass, :method => 'nil', :pre_processor => :build_finder}
45
+
46
+ # methods on nil
47
+ safe_method_for Object, :blank? => Boolean
48
+
35
49
  # Example to dynamically rewrite method calls during compilation
36
50
  def safe_method_type(signature)
37
51
  unless res = super
@@ -46,6 +60,26 @@ class RubyLessTest < Test::Unit::TestCase
46
60
  res
47
61
  end
48
62
 
63
+ def make_append_hash(signature = {})
64
+ {:class => Number, :append_hash => {:xyz => RubyLess::TypedString.new('bar', :class => Dummy)}, :method => 'add'}
65
+ end
66
+
67
+ def build_finder(string)
68
+ TypedString.new("secure(Node) { Node.find(#{string.inspect}) }", :class => Dummy )
69
+ end
70
+
71
+ def accept_nil(foo, bar=nil)
72
+ [foo, bar].inspect
73
+ end
74
+
75
+ def no_nil(foo)
76
+ foo
77
+ end
78
+
79
+ def get_dict
80
+ {:foo => 'Foo'}
81
+ end
82
+
49
83
  def contextual_method(signature)
50
84
  {:method => "contextual_#{signature[0]}", :class => String}
51
85
  end
@@ -4,7 +4,7 @@ require File.dirname(__FILE__) + '/property_column'
4
4
 
5
5
  class Dummy < RubyLess::ActiveRecordMock
6
6
  include DummyModule
7
- include RubyLess::SafeClass
7
+ include RubyLess
8
8
 
9
9
  attr_reader :name
10
10
 
@@ -16,8 +16,7 @@ class Dummy < RubyLess::ActiveRecordMock
16
16
  :id => {:class => Number, :method => :zip},
17
17
  :name => String,
18
18
  :foo => :bar,
19
- [:width, {:mode => String, :type => String, 'nice' => Boolean}] => String,
20
- [:width] => String
19
+ [:width, {:mode => String, :type => String, 'nice' => Boolean}] => String
21
20
  safe_context :spouse => 'Dummy',
22
21
  :husband => {:class => 'Dummy', :context => {:clever => 'no'}}
23
22
 
@@ -1,5 +1,5 @@
1
1
  module DummyModule
2
- include RubyLess::SafeClass
2
+ include RubyLess
3
3
  safe_method :maze => {:class => String, :method => 'mazette'}
4
4
 
5
5
  def mazette
@@ -0,0 +1,47 @@
1
+ require 'test_helper'
2
+
3
+ class TypedMethodTest < Test::Unit::TestCase
4
+ TypedMethod = RubyLess::TypedMethod
5
+
6
+ context 'A method' do
7
+ subject do
8
+ TypedMethod.new('foo')
9
+ end
10
+
11
+ should 'render with name' do
12
+ assert_equal 'foo', subject.to_s
13
+ end
14
+
15
+ should 'accept new arguments with add_argument' do
16
+ assert_nothing_raised { subject.add_argument(1, Number) }
17
+ end
18
+
19
+ should 'accept new hash arguments' do
20
+ subject.set_hash(:foo, 'bar', Dummy)
21
+ assert_equal 'foo(:foo => bar)', subject.to_s
22
+ end
23
+
24
+ context 'with arguments' do
25
+ subject do
26
+ TypedMethod.new('foo', '1', '2')
27
+ end
28
+
29
+ should 'render with arguments' do
30
+ assert_equal 'foo(1, 2)', subject.to_s
31
+ end
32
+ end
33
+
34
+ context 'with a hash as last argument' do
35
+ subject do
36
+ m = TypedMethod.new('foo')
37
+ m.add_argument('1', Number)
38
+ m.set_hash(:foo, 'bar', Dummy)
39
+ m
40
+ end
41
+
42
+ should 'use ruby last hash syntax' do
43
+ assert_equal 'foo(1, :foo => bar)', subject.to_s
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,19 @@
1
+ require 'test_helper'
2
+
3
+ class TypedStringTest < Test::Unit::TestCase
4
+ TypedString = RubyLess::TypedString
5
+
6
+ context 'A typed string' do
7
+ subject do
8
+ TypedString.new('foo', Number)
9
+ end
10
+
11
+ should 'render with name' do
12
+ assert_equal 'foo', subject.to_s
13
+ end
14
+
15
+ should 'return class of content on klass' do
16
+ assert_equal Number, subject.klass
17
+ end
18
+ end
19
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 4
7
+ - 5
8
8
  - 0
9
- version: 0.4.0
9
+ version: 0.5.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Gaspard Bucher
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-21 00:00:00 +01:00
17
+ date: 2010-05-27 00:00:00 +02:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -66,9 +66,9 @@ dependencies:
66
66
  - !ruby/object:Gem::Version
67
67
  segments:
68
68
  - 0
69
- - 5
70
- - 3
71
- version: 0.5.3
69
+ - 6
70
+ - 0
71
+ version: 0.6.0
72
72
  type: :development
73
73
  version_requirements: *id004
74
74
  description: 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.
@@ -79,11 +79,13 @@ extensions: []
79
79
 
80
80
  extra_rdoc_files:
81
81
  - README.rdoc
82
+ - TODO
82
83
  files:
83
84
  - .gitignore
84
85
  - History.txt
85
86
  - README.rdoc
86
87
  - Rakefile
88
+ - TODO
87
89
  - lib/ruby_less.rb
88
90
  - lib/ruby_less/basic_types.rb
89
91
  - lib/ruby_less/error.rb
@@ -93,6 +95,7 @@ files:
93
95
  - lib/ruby_less/safe_class.rb
94
96
  - lib/ruby_less/signature_hash.rb
95
97
  - lib/ruby_less/syntax_error.rb
98
+ - lib/ruby_less/typed_method.rb
96
99
  - lib/ruby_less/typed_string.rb
97
100
  - lib/rubyless.rb
98
101
  - rails/init.rb
@@ -100,6 +103,7 @@ files:
100
103
  - test/RubyLess/active_record.yml
101
104
  - test/RubyLess/basic.yml
102
105
  - test/RubyLess/errors.yml
106
+ - test/RubyLess/hash.yml
103
107
  - test/RubyLess/string.yml
104
108
  - test/RubyLess_test.rb
105
109
  - test/mock/active_record_mock.rb
@@ -109,6 +113,8 @@ files:
109
113
  - test/safe_class_test.rb
110
114
  - test/signature_hash_test.rb
111
115
  - test/test_helper.rb
116
+ - test/typed_method_test.rb
117
+ - test/typed_string_test.rb
112
118
  has_rdoc: true
113
119
  homepage: http://zenadmin.org/546
114
120
  licenses: []
@@ -148,3 +154,5 @@ test_files:
148
154
  - test/safe_class_test.rb
149
155
  - test/signature_hash_test.rb
150
156
  - test/test_helper.rb
157
+ - test/typed_method_test.rb
158
+ - test/typed_string_test.rb