rubyless 0.3.0 → 0.3.1
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 +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
|