rubyless 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +11 -1
- data/lib/processor.rb +8 -8
- data/lib/rubyless.rb +1 -1
- data/lib/safe_class.rb +30 -20
- data/rubyless.gemspec +1 -1
- data/test/RubyLess_test.rb +8 -2
- data/test/mock/active_record_mock.rb +30 -7
- metadata +2 -2
data/History.txt
CHANGED
@@ -1,4 +1,14 @@
|
|
1
|
-
== 0.3.
|
1
|
+
== 0.3.1 2009-10-07
|
2
|
+
|
3
|
+
* 3 major enhancements
|
4
|
+
* method name in signatures should always be a string
|
5
|
+
* type[:method] is always set and is always a string
|
6
|
+
* fixed how class type is guessed from ActiveRecord column
|
7
|
+
|
8
|
+
* 1 minor enhancement
|
9
|
+
* added 'safe_read' method to objects
|
10
|
+
|
11
|
+
== 0.3.1 2009-10-03
|
2
12
|
|
3
13
|
* 1 major enhancement
|
4
14
|
* Moved from ParseTree to RubyParser
|
data/lib/processor.rb
CHANGED
@@ -12,8 +12,8 @@ module RubyLess
|
|
12
12
|
class RubyLessProcessor < SexpProcessor
|
13
13
|
attr_reader :ruby
|
14
14
|
|
15
|
-
INFIX_OPERATOR = [
|
16
|
-
PREFIX_OPERATOR = [
|
15
|
+
INFIX_OPERATOR = ['<=>', '==', '<', '>', '<=', '>=', '-', '+', '*', '/', '%']
|
16
|
+
PREFIX_OPERATOR = ['-@']
|
17
17
|
|
18
18
|
def self.translate(string, helper)
|
19
19
|
sexp = RubyParser.new.parse(string)
|
@@ -94,7 +94,7 @@ module RubyLess
|
|
94
94
|
unless opts = get_method([var_name], @helper, false)
|
95
95
|
raise "Unknown variable or method '#{var_name}'."
|
96
96
|
end
|
97
|
-
method = opts[:method]
|
97
|
+
method = opts[:method]
|
98
98
|
t method, opts
|
99
99
|
end
|
100
100
|
|
@@ -147,7 +147,7 @@ module RubyLess
|
|
147
147
|
end
|
148
148
|
|
149
149
|
def method_call(receiver, exp)
|
150
|
-
method = exp.shift
|
150
|
+
method = exp.shift.to_s
|
151
151
|
arg_sexp = args = exp.shift # rescue nil
|
152
152
|
if arg_sexp
|
153
153
|
args = process(arg_sexp)
|
@@ -169,14 +169,14 @@ module RubyLess
|
|
169
169
|
cond += receiver.cond
|
170
170
|
end
|
171
171
|
raise "'#{receiver}' does not respond to '#{method}(#{signature[1..-1].join(', ')})'." unless opts = get_method(signature, receiver.klass)
|
172
|
-
method = opts[:method]
|
173
|
-
if method ==
|
172
|
+
method = opts[:method]
|
173
|
+
if method == '/'
|
174
174
|
t_if cond, "(#{receiver.raw}#{method}#{args.raw} rescue nil)", opts.merge(:nil => true)
|
175
175
|
elsif INFIX_OPERATOR.include?(method)
|
176
176
|
t_if cond, "(#{receiver.raw}#{method}#{args.raw})", opts
|
177
177
|
elsif PREFIX_OPERATOR.include?(method)
|
178
178
|
t_if cond, "#{method.to_s[0..0]}#{receiver.raw}", opts
|
179
|
-
elsif method ==
|
179
|
+
elsif method == '[]'
|
180
180
|
t_if cond, "#{receiver.raw}[#{args.raw}]", opts
|
181
181
|
else
|
182
182
|
args = "(#{args.raw})" if args != ''
|
@@ -184,7 +184,7 @@ module RubyLess
|
|
184
184
|
end
|
185
185
|
else
|
186
186
|
raise "Unknown method '#{method}(#{args.raw})'." unless opts = get_method(signature, @helper, false)
|
187
|
-
method = opts[:method]
|
187
|
+
method = opts[:method]
|
188
188
|
args = "(#{args.raw})" if args != ''
|
189
189
|
t_if cond, "#{method}#{args}", opts
|
190
190
|
end
|
data/lib/rubyless.rb
CHANGED
data/lib/safe_class.rb
CHANGED
@@ -2,12 +2,12 @@ module RubyLess
|
|
2
2
|
module SafeClass
|
3
3
|
@@_safe_methods ||= {} # defined for each class
|
4
4
|
@@_safe_methods_all ||= {} # full list with inherited attributes
|
5
|
-
|
5
|
+
|
6
6
|
# List of safe methods for a specific class.
|
7
7
|
def self.safe_methods_for(klass)
|
8
8
|
@@_safe_methods_all[klass] ||= build_safe_methods_list(klass)
|
9
9
|
end
|
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
13
|
if res = safe_methods_for(klass)[signature]
|
@@ -16,20 +16,22 @@ module RubyLess
|
|
16
16
|
nil
|
17
17
|
end
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
# Declare a safe method for a given class
|
21
21
|
def self.safe_method_for(klass, hash)
|
22
22
|
list = (@@_safe_methods[klass] ||= {})
|
23
23
|
hash.each do |k,v|
|
24
24
|
k = [k] unless k.kind_of?(Array)
|
25
|
+
k[0] = k[0].to_s
|
25
26
|
v = {:class => v} unless v.kind_of?(Hash) || v.kind_of?(Proc)
|
27
|
+
v[:method] = v[:method] ? v[:method].to_s : k.first.to_s
|
26
28
|
list[k] = v
|
27
29
|
end
|
28
30
|
end
|
29
|
-
|
31
|
+
|
30
32
|
def self.included(base)
|
31
33
|
base.class_eval do
|
32
|
-
|
34
|
+
|
33
35
|
# Declare safe methods. By providing
|
34
36
|
#
|
35
37
|
# The methods hash has the following format:
|
@@ -55,21 +57,23 @@ module RubyLess
|
|
55
57
|
def self.safe_method(hash)
|
56
58
|
methods_hash = hash
|
57
59
|
defaults = methods_hash.delete(:defaults) || {}
|
58
|
-
|
60
|
+
|
59
61
|
list = (@@_safe_methods[self] ||= {})
|
60
62
|
methods_hash.each do |k,v|
|
61
63
|
k = [k] unless k.kind_of?(Array)
|
64
|
+
k[0] = k[0].to_s
|
62
65
|
if v.kind_of?(Hash)
|
63
66
|
v = defaults.merge(v)
|
67
|
+
v[:method] = v[:method] ? v[:method].to_s : k.first.to_s
|
64
68
|
elsif v.kind_of?(Proc)
|
65
69
|
# cannot merge defaults
|
66
70
|
else
|
67
|
-
v = defaults.merge(:class => v)
|
71
|
+
v = defaults.merge(:class => v, :method => k.first.to_s)
|
68
72
|
end
|
69
73
|
list[k] = v
|
70
74
|
end
|
71
75
|
end
|
72
|
-
|
76
|
+
|
73
77
|
# Declare a safe method to access a list of attributes.
|
74
78
|
# This method should only be used when the class is linked with a database table and provides
|
75
79
|
# proper introspection to detect types and the possibility of NULL values.
|
@@ -82,10 +86,8 @@ module RubyLess
|
|
82
86
|
opts[:class] = Number
|
83
87
|
elsif col.text?
|
84
88
|
opts[:class] = String
|
85
|
-
elsif att.to_s =~ /_at$/
|
86
|
-
opts[:class] = Time
|
87
89
|
else
|
88
|
-
|
90
|
+
opts[:class] = col.klass
|
89
91
|
end
|
90
92
|
safe_method att.to_sym => opts
|
91
93
|
else
|
@@ -93,34 +95,41 @@ module RubyLess
|
|
93
95
|
end
|
94
96
|
end
|
95
97
|
end
|
96
|
-
|
98
|
+
|
97
99
|
# Declare a safe method for a given class
|
98
100
|
def self.safe_method_for(klass, signature)
|
99
101
|
SafeClass.safe_method_for(klass, signature)
|
100
102
|
end
|
101
|
-
|
103
|
+
|
102
104
|
# Hash of all safe methods defined for the class.
|
103
105
|
def self.safe_methods
|
104
106
|
SafeClass.safe_methods_for(self)
|
105
107
|
end
|
106
|
-
|
108
|
+
|
107
109
|
# Return true if the given signature corresponds to a safe method for the class.
|
108
110
|
def self.safe_method_type(signature)
|
109
111
|
if res = SafeClass.safe_method_type_for(self, signature)
|
110
|
-
res.dup
|
112
|
+
res.dup # TODO: replace by freeze
|
111
113
|
else
|
112
114
|
nil
|
113
115
|
end
|
114
116
|
end
|
115
|
-
|
117
|
+
|
116
118
|
# Return the method type (options) if the given signature is a safe method for the class.
|
117
119
|
def safe_method_type(signature)
|
118
120
|
self.class.safe_method_type(signature)
|
119
121
|
end
|
120
122
|
end # base.class_eval
|
121
123
|
end # included
|
122
|
-
|
123
|
-
|
124
|
+
|
125
|
+
# Safe attribute reader used when 'safe_readable?' could not be called because the class
|
126
|
+
# is not known during compile time.
|
127
|
+
def safe_read(key)
|
128
|
+
return "'#{key}' not readable" unless type = self.class.safe_method_type([key])
|
129
|
+
self.send(type[:method])
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
124
133
|
def self.build_safe_methods_list(klass)
|
125
134
|
list = klass.superclass.respond_to?(:safe_methods) ? klass.superclass.safe_methods : {}
|
126
135
|
(@@_safe_methods[klass] || {}).map do |signature, return_value|
|
@@ -129,12 +138,13 @@ module RubyLess
|
|
129
138
|
elsif !return_value.kind_of?(Proc)
|
130
139
|
return_value = {:class => return_value}
|
131
140
|
end
|
132
|
-
signature.
|
141
|
+
method = signature.shift
|
142
|
+
signature = [method] + signature.map {|e| parse_class(e)}
|
133
143
|
list[signature] = return_value
|
134
144
|
end
|
135
145
|
list
|
136
146
|
end
|
137
|
-
|
147
|
+
|
138
148
|
def self.parse_class(klass)
|
139
149
|
if klass.kind_of?(Array)
|
140
150
|
if klass[0].kind_of?(String)
|
data/rubyless.gemspec
CHANGED
data/test/RubyLess_test.rb
CHANGED
@@ -3,7 +3,7 @@ require File.dirname(__FILE__) + '/test_helper.rb'
|
|
3
3
|
|
4
4
|
class StringDictionary
|
5
5
|
include RubyLess::SafeClass
|
6
|
-
safe_method [
|
6
|
+
safe_method ['[]', Symbol] => {:class => String, :nil => true}
|
7
7
|
end
|
8
8
|
|
9
9
|
class SimpleHelper < Test::Unit::TestCase
|
@@ -15,7 +15,7 @@ class SimpleHelper < Test::Unit::TestCase
|
|
15
15
|
safe_method :node => lambda {|h| {:class => h.context[:node_class], :method => h.context[:node]}}
|
16
16
|
safe_method :now => {:class => Time, :method => "Time.now"}
|
17
17
|
safe_method :birth => {:class => Time, :method => "Date.parse('2009-06-02 18:44')"}
|
18
|
-
safe_method
|
18
|
+
safe_method 'dictionary' => {:class => StringDictionary, :method => 'get_dict'}
|
19
19
|
safe_method [:vowel_count, String] => Number
|
20
20
|
safe_method [:log_info, Dummy, String] => String
|
21
21
|
safe_method_for String, [:==, String] => Boolean
|
@@ -46,6 +46,11 @@ class SimpleHelper < Test::Unit::TestCase
|
|
46
46
|
"[#{obj.name}] #{msg}"
|
47
47
|
end
|
48
48
|
|
49
|
+
def test_safe_read
|
50
|
+
assert_equal 10, Dummy.new.safe_read('id')
|
51
|
+
assert_equal "'rm' not readable", Dummy.new.safe_read('rm')
|
52
|
+
end
|
53
|
+
|
49
54
|
def yt_do_test(file, test, context = yt_get('context',file,test))
|
50
55
|
@@test_strings[file][test].keys.each do |key|
|
51
56
|
next if ['src', 'context'].include?(key)
|
@@ -60,6 +65,7 @@ class SimpleHelper < Test::Unit::TestCase
|
|
60
65
|
when 'tem'
|
61
66
|
source ? RubyLess.translate(source, self) : yt_get('tem', file, test)
|
62
67
|
when 'res'
|
68
|
+
res = RubyLess.translate(source, self)
|
63
69
|
eval(source ? RubyLess.translate(source, self) : yt_get('tem', file, test)).to_s
|
64
70
|
when 'sxp'
|
65
71
|
RubyParser.new.parse(source).inspect
|
@@ -8,26 +8,49 @@ module RubyLess
|
|
8
8
|
@opts[:default]
|
9
9
|
end
|
10
10
|
|
11
|
+
def type
|
12
|
+
@opts[:type]
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns +true+ if the column is either of type string or text.
|
11
16
|
def text?
|
12
|
-
|
17
|
+
type == :string || type == :text
|
13
18
|
end
|
14
19
|
|
20
|
+
# Returns +true+ if the column is either of type integer, float or decimal.
|
15
21
|
def number?
|
16
|
-
|
22
|
+
type == :integer || type == :float || type == :decimal
|
17
23
|
end
|
24
|
+
|
25
|
+
# Returns the Ruby class that corresponds to the abstract data type.
|
26
|
+
def klass
|
27
|
+
case type
|
28
|
+
when :integer then Fixnum
|
29
|
+
when :float then Float
|
30
|
+
when :decimal then BigDecimal
|
31
|
+
when :datetime then Time
|
32
|
+
when :date then Date
|
33
|
+
when :timestamp then Time
|
34
|
+
when :time then Time
|
35
|
+
when :text, :string then String
|
36
|
+
when :binary then String
|
37
|
+
when :boolean then Object
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
18
41
|
end
|
19
42
|
|
20
43
|
class ActiveRecordMock
|
21
44
|
COLUMNS = {
|
22
|
-
'format' => ColumnMock.new(:default => '%d.%m.%Y', :
|
23
|
-
'age' => ColumnMock.new(:default => 5, :
|
24
|
-
'friend_id' => ColumnMock.new(:
|
25
|
-
'log_at' => ColumnMock.new,
|
45
|
+
'format' => ColumnMock.new(:default => '%d.%m.%Y', :type => :text),
|
46
|
+
'age' => ColumnMock.new(:default => 5, :type => :float),
|
47
|
+
'friend_id' => ColumnMock.new(:type => :integer),
|
48
|
+
'log_at' => ColumnMock.new(:type => :datetime),
|
26
49
|
}
|
27
50
|
def self.columns_hash
|
28
51
|
COLUMNS
|
29
52
|
end
|
30
|
-
|
53
|
+
|
31
54
|
COLUMNS.each do |k, v|
|
32
55
|
define_method(k) do
|
33
56
|
v.default
|
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
|
+
version: 0.3.1
|
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-10-
|
12
|
+
date: 2009-10-15 00:00:00 +02:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|