rubyless 0.3.4 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,11 @@
1
+ == 0.3.5 2009-11-08
2
+
3
+ * 1 major enhancement
4
+ * added support for hash in signature: ['img', {'mode' => String, 'class' => String}]
5
+
6
+ * 1 minor enhancement
7
+ * added 'disable_safe_read' method
8
+
1
9
  == 0.3.4 2009-11-05
2
10
 
3
11
  * 1 minor enhancement
data/lib/processor.rb CHANGED
@@ -113,6 +113,30 @@ module RubyLess
113
113
  exp.empty? ? t('', String) : process(exp.shift)
114
114
  end
115
115
 
116
+ def process_hash(exp)
117
+ result = []
118
+ klass = {}
119
+ until exp.empty?
120
+ key = exp.shift
121
+ if [:lit, :str].include?(key.first)
122
+ key = key[1]
123
+
124
+ rhs = exp.shift
125
+ type = rhs.first
126
+ rhs = process rhs
127
+ #rhs = "(#{rhs})" unless [:lit, :str].include? type # TODO: verify better!
128
+
129
+ result << "#{key.inspect} => #{rhs}"
130
+ klass[key] = rhs.klass
131
+ else
132
+ # ERROR: invalid key
133
+ raise "Invalid key type for hash (should be a literal value, was #{key.first.inspect})"
134
+ end
135
+ end
136
+
137
+ t "{#{result.join(', ')}}", :class => klass
138
+ end
139
+
116
140
  private
117
141
  def t(content, opts = nil)
118
142
  if opts.nil?
@@ -209,9 +233,10 @@ module RubyLess
209
233
  end
210
234
 
211
235
  def get_method(signature, receiver, is_method = true)
212
- res = receiver.respond_to?(:safe_method_type) ? receiver.safe_method_type(signature) : SafeClass.safe_method_type_for(receiver, signature)
213
- res = res.call(@helper, signature) if res.kind_of?(Proc)
214
- res.kind_of?(Symbol) ? nil : res # Symbols not allowed here (should be resolved in receiver.safe_method_type)
236
+ type = receiver.respond_to?(:safe_method_type) ? receiver.safe_method_type(signature) : SafeClass.safe_method_type_for(receiver, signature)
237
+ return nil if !type || type[:class].kind_of?(Symbol) # we cannot send: no object.
238
+
239
+ type[:class].kind_of?(Proc) ? type[:class].call(@helper, signature) : type
215
240
  end
216
241
  end
217
242
  end
data/lib/rubyless.rb CHANGED
@@ -6,7 +6,7 @@ require 'processor'
6
6
  =begin rdoc
7
7
  =end
8
8
  module RubyLess
9
- VERSION = '0.3.4'
9
+ VERSION = '0.3.5'
10
10
 
11
11
  def self.translate(string, helper)
12
12
  RubyLessProcessor.translate(string, helper)
data/lib/safe_class.rb CHANGED
@@ -10,17 +10,56 @@ module RubyLess
10
10
 
11
11
  # Return method type (options) if the given signature is a safe method for the class.
12
12
  def self.safe_method_type_for(klass, signature)
13
- safe_methods_for(klass)[signature]
13
+ if type = safe_methods_for(klass)[signature]
14
+ type
15
+ else
16
+ # Signature might be ['name', {:mode => String, :type => Number}].
17
+
18
+ # Replace all hashes in signature by Hash class and check for arguments
19
+ signature_args = []
20
+ signature = signature.map do |s|
21
+ if s.kind_of?(Hash)
22
+ signature_args << s
23
+ Hash
24
+ else
25
+ signature_args << nil
26
+ s
27
+ end
28
+ end
29
+
30
+ if type = safe_methods_for(klass)[signature]
31
+ unless allowed_args = type[:hash_args]
32
+ # All arguments allowed
33
+ return type
34
+ end
35
+
36
+ # Verify arguments
37
+ signature_args.each_with_index do |args, i|
38
+ next unless args
39
+ # verify for each position: ({:a => 3}, {:x => :y})
40
+ return nil unless allowed_args_for_position = allowed_args[i]
41
+ args.each do |k,v|
42
+ return nil unless allowed_args_for_position[k] == v
43
+ end
44
+ end
45
+ type
46
+ else
47
+ nil
48
+ end
49
+ end
14
50
  end
15
51
 
16
- # Declare a safe method for a given class
17
- def self.safe_method_for(klass, hash)
52
+ # Declare a safe method for a given class ( same as #safe_method)
53
+ def self.safe_method_for(klass, methods_hash)
54
+ defaults = methods_hash.delete(:defaults) || {}
55
+
18
56
  list = (@@_safe_methods[klass] ||= {})
19
- hash.each do |k,v|
20
- k = [k] unless k.kind_of?(Array)
21
- k[0] = k[0].to_s
22
- v = {:class => v} unless v.kind_of?(Hash) || v.kind_of?(Proc)
57
+ methods_hash.each do |k,v|
58
+ k, hash_args = build_signature(k)
59
+ v = {:class => v} unless v.kind_of?(Hash)
60
+ v = defaults.merge(v)
23
61
  v[:method] = v[:method] ? v[:method].to_s : k.first.to_s
62
+ v[:hash_args] = hash_args if hash_args
24
63
  list[k] = v
25
64
  end
26
65
  end
@@ -42,6 +81,11 @@ module RubyLess
42
81
  # The signature can be either a single symbol or an array containing the method name and type arguments like:
43
82
  # [:strftime, Time, String]
44
83
  #
84
+ # If your method accepts variable arguments through a Hash, you should declare it with:
85
+ # [:img, String, {:mode => String, :max_size => Number}]
86
+ #
87
+ # Make sure your literal values are of the right type: +:mode+ and +'mode'+ are not the same here.
88
+ #
45
89
  # If the signature is :defaults, the options defined are used as defaults for the other elements defined in the
46
90
  # same call.
47
91
  #
@@ -51,22 +95,7 @@ module RubyLess
51
95
  # :class the return type (class name)
52
96
  # :nil set this to true if the method could return nil
53
97
  def self.safe_method(methods_hash)
54
- defaults = methods_hash.delete(:defaults) || {}
55
-
56
- list = (@@_safe_methods[self] ||= {})
57
- methods_hash.each do |k,v|
58
- k = [k] unless k.kind_of?(Array)
59
- k[0] = k[0].to_s
60
- if v.kind_of?(Hash)
61
- v = defaults.merge(v)
62
- v[:method] = v[:method] ? v[:method].to_s : k.first.to_s
63
- elsif v.kind_of?(Proc) || v.kind_of?(Symbol)
64
- # cannot merge defaults
65
- else
66
- v = defaults.merge(:class => v, :method => k.first.to_s)
67
- end
68
- list[k] = v
69
- end
98
+ RubyLess::SafeClass.safe_method_for(self, methods_hash)
70
99
  end
71
100
 
72
101
  # A safe context is simply a safe method that can return nil in some situations. The rest of the
@@ -114,6 +143,17 @@ module RubyLess
114
143
  def self.safe_method_type(signature)
115
144
  SafeClass.safe_method_type_for(self, signature)
116
145
  end
146
+
147
+ # Return true if the class is safe (we can call safe_read on its instances)
148
+ def self.safe_class?
149
+ true
150
+ end
151
+
152
+ # Use this if you want to disable 'safe_read'. This is useful if you provided
153
+ # a mock as return type signature.
154
+ def self.disable_safe_read
155
+ undef_method(:safe_read)
156
+ end
117
157
  end # base.class_eval
118
158
  end # included
119
159
 
@@ -121,7 +161,7 @@ module RubyLess
121
161
  # Return the type if the given signature corresponds to a safe method for the object's class.
122
162
  def safe_method_type(signature)
123
163
  if type = self.class.safe_method_type(signature)
124
- type.kind_of?(Symbol) ? self.send(type, signature) : type
164
+ type[:class].kind_of?(Symbol) ? self.send(type[:class], signature) : type
125
165
  end
126
166
  end
127
167
 
@@ -133,6 +173,22 @@ module RubyLess
133
173
  end
134
174
 
135
175
  private
176
+ def self.build_signature(key)
177
+ keys = key.kind_of?(Array) ? key : [key]
178
+ keys[0] = keys[0].to_s
179
+ hash_args = []
180
+ signature = keys.map do |k|
181
+ if k.kind_of?(Hash)
182
+ hash_args << k
183
+ Hash
184
+ else
185
+ hash_args << nil
186
+ k
187
+ end
188
+ end
189
+ [signature, (hash_args.compact.empty? ? nil : hash_args)]
190
+ end
191
+
136
192
  def self.build_safe_methods_list(klass)
137
193
  list = klass.superclass.respond_to?(:safe_methods) ? klass.superclass.safe_methods : {}
138
194
  (@@_safe_methods[klass] || {}).map do |signature, return_value|
data/rubyless.gemspec CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{rubyless}
5
- s.version = "0.3.4"
5
+ s.version = "0.3.5"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Gaspard Bucher"]
9
- s.date = %q{2009-11-05}
9
+ s.date = %q{2009-11-08}
10
10
  s.description = %q{RubyLess is an interpreter for "safe ruby". The idea is to transform some "unsafe" ruby code into safe, type checked
11
11
  ruby, eventually rewriting some variables or methods.}
12
12
  s.email = %q{gaspard@teti.ch}
@@ -91,3 +91,13 @@ literal_argument_for_method:
91
91
  safe_method_defined_as_symbol:
92
92
  src: "foo"
93
93
  tem: "contextual_foo"
94
+
95
+ optional_arguments:
96
+ src: "width(:mode => 'pv')"
97
+ tem: "var1.width({:mode => \"pv\"})"
98
+ res: "mode: pv, type: none"
99
+
100
+ optional_arguments_string:
101
+ src: "width('nice' => 1 == 1)"
102
+ tem: "var1.width({\"nice\" => (1==1)})"
103
+ res: "nice!"
@@ -33,4 +33,17 @@ string_argument:
33
33
 
34
34
  symbol_type_not_used_out_of_helper:
35
35
  src: "node.foo"
36
- res: "'var1' does not respond to 'foo()'."
36
+ tem: "'var1' does not respond to 'foo()'."
37
+
38
+ optional_arguments_with_dynamic_string:
39
+ src: "spouse.width(\"nice#{spouse.name}\" => 'pv')"
40
+ tem: "Invalid key type for hash (should be a literal value, was :dstr)"
41
+
42
+
43
+ optional_arguments_bad_type:
44
+ src: "width(:mode => 12)"
45
+ res: "Unknown method 'width({:mode => 12})'."
46
+
47
+ optional_arguments_bad_argument:
48
+ src: "width(:xyz => 'pv')"
49
+ res: "Unknown method 'width({:xyz => \"pv\"})'."
@@ -4,9 +4,10 @@ require File.dirname(__FILE__) + '/test_helper.rb'
4
4
  class StringDictionary
5
5
  include RubyLess::SafeClass
6
6
  safe_method ['[]', Symbol] => {:class => String, :nil => true}
7
+ disable_safe_read
7
8
  end
8
9
 
9
- class SimpleHelper < Test::Unit::TestCase
10
+ class RubyLessTest < Test::Unit::TestCase
10
11
  attr_reader :context
11
12
  yamltest :src_from_title => false
12
13
  include RubyLess::SafeClass
@@ -56,6 +57,17 @@ class SimpleHelper < Test::Unit::TestCase
56
57
  assert_equal "'rm' not readable", Dummy.new.safe_read('rm')
57
58
  end
58
59
 
60
+ def test_disable_safe_read
61
+ assert Dummy.instance_methods.include?('safe_read')
62
+ assert !StringDictionary.instance_methods.include?('safe_read')
63
+ end
64
+
65
+ def test_safe_method_type
66
+ type = Dummy.safe_method_type(['husband'])
67
+ type_should_be = {:class => Dummy, :method => 'husband', :nil => true, :context => {:clever => 'no'}}
68
+ assert_equal type_should_be, type
69
+ end
70
+
59
71
  def yt_do_test(file, test, context = yt_get('context',file,test))
60
72
  @@test_strings[file][test].keys.each do |key|
61
73
  next if ['src', 'context'].include?(key)
@@ -78,9 +90,9 @@ class SimpleHelper < Test::Unit::TestCase
78
90
  "Unknown key '#{key}'. Should be 'tem' or 'res'."
79
91
  end
80
92
  rescue => err
81
- # puts "\n\n#{err.message}"
82
- # puts err.backtrace
83
- err.message
93
+ #puts "\n\n#{err.message}"
94
+ #puts err.backtrace
95
+ err.message
84
96
  end
85
97
 
86
98
  yt_make
@@ -8,11 +8,13 @@ class Dummy < RubyLess::ActiveRecordMock
8
8
  safe_method :parent => {:class => 'Dummy', :special_option => 'foobar'},
9
9
  :children => ['Dummy'],
10
10
  :project => 'Dummy',
11
+ :image => 'Dummy',
11
12
  :id => {:class => Number, :method => :zip},
12
13
  :name => String,
13
- :foo => :bar
14
+ :foo => :bar,
15
+ [:width, {:mode => String, :type => String, 'nice' => Boolean}] => String
14
16
  safe_context :spouse => 'Dummy',
15
- :husband => {:class => 'Dummy'}
17
+ :husband => {:class => 'Dummy', :context => {:clever => 'no'}}
16
18
 
17
19
  safe_attribute :age, :friend_id, :log_at, :format
18
20
 
@@ -20,11 +22,20 @@ class Dummy < RubyLess::ActiveRecordMock
20
22
  @name = name
21
23
  end
22
24
 
25
+ def width(opts = {})
26
+ return 'nice!' if opts['nice']
27
+ "mode: #{(opts[:mode] || 'none')}, type: #{(opts[:type] || 'none')}"
28
+ end
29
+
23
30
  # This method returns pseudo-nil and does not need to be declared with :nil => true
24
31
  def project
25
32
  Dummy.new('project')
26
33
  end
27
34
 
35
+ def image
36
+ Dummy.new('image')
37
+ end
38
+
28
39
  # This method can return nil and must be declared with :nil => true
29
40
  def spouse
30
41
  nil
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyless
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gaspard Bucher
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-05 00:00:00 +01:00
12
+ date: 2009-11-08 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency