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