rubyless 0.1.0 → 0.2.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 +9 -0
- data/README.rdoc +7 -3
- data/Rakefile +1 -1
- data/lib/basic_types.rb +39 -0
- data/lib/{RubyLess.rb → processor.rb} +6 -108
- data/lib/rubyless.rb +14 -0
- data/lib/safe_class.rb +140 -0
- data/lib/typed_string.rb +71 -0
- data/rubyless.gemspec +2 -2
- data/test/RubyLess/active_record.yml +19 -0
- data/test/RubyLess/basic.yml +6 -6
- data/test/RubyLess/errors.yml +6 -2
- data/test/RubyLess_test.rb +8 -5
- data/test/mock/active_record_mock.rb +37 -0
- data/test/mock/dummy_class.rb +12 -8
- data/test/test_helper.rb +1 -1
- metadata +8 -3
- data/lib/SafeClass.rb +0 -98
data/History.txt
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
== 0.2.0 2009-06-02
|
2
|
+
|
3
|
+
* 1 major enhancement:
|
4
|
+
* Added support for ActiveRecord attributes
|
5
|
+
|
6
|
+
* 2 minor enhancement:
|
7
|
+
* Better documentation
|
8
|
+
* Removed eval (this means safe methods are globally declared)
|
9
|
+
|
1
10
|
== 0.1.0 2009-06-02
|
2
11
|
|
3
12
|
* 1 major enhancement:
|
data/README.rdoc
CHANGED
@@ -35,10 +35,15 @@ return 'nil' instead of the declared output, you need to wrap your final ruby 'e
|
|
35
35
|
safe_method_for String, [:==, String] => RubyLess::Boolean
|
36
36
|
safe_method_for String, [:to_s] => String
|
37
37
|
|
38
|
-
You can also redefine '
|
38
|
+
You can also redefine 'safe_method_type' for any class or for the main helper in order to do some more complicated renaming. Note
|
39
39
|
also that you should add ':nil => true' declaration to any method that could return a nil value so that RubyLess can render
|
40
40
|
code that will not break during runtime (adding nil checking in the form of "foo ? foo.name : nil").
|
41
41
|
|
42
|
+
Or you can group all declarations in a single place with 'safe_method_for':
|
43
|
+
|
44
|
+
RubyLess::SafeClass.safe_method_for Dummy, :prev => {:class => Dummy, :method => 'previous', :nil => true},
|
45
|
+
:node => lambda {|h| {:class => h.context[:node_class], :method => h.context[:node]}}
|
46
|
+
|
42
47
|
You can now parse some ruby code:
|
43
48
|
|
44
49
|
RubyLess.translate("!prev.ancestor?(main) && !node.ancestor?(main)", self)
|
@@ -53,8 +58,7 @@ You can now parse some ruby code:
|
|
53
58
|
RubyLess.translate("log_info(spouse, spouse.name)", self)
|
54
59
|
=> "(var1.spouse ? log_info(var1.spouse, var1.spouse.name) : nil)"
|
55
60
|
|
56
|
-
|
57
|
-
can look at the tests for an idea of how to declare things. If you have more questions, ask on zena's mailing list:
|
61
|
+
You can look at the tests for an idea of how to declare things. If you have more questions, ask on zena's mailing list:
|
58
62
|
|
59
63
|
http://zenadmin.org/community
|
60
64
|
|
data/Rakefile
CHANGED
data/lib/basic_types.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'safe_class'
|
2
|
+
|
3
|
+
module RubyLess
|
4
|
+
|
5
|
+
class Boolean
|
6
|
+
end
|
7
|
+
|
8
|
+
class Number
|
9
|
+
include SafeClass
|
10
|
+
safe_method( [:==, Number] => Boolean, [:< , Number] => Boolean, [:> , Number] => Boolean, [:<=, Number] => Boolean, [:>=, Number] => Boolean,
|
11
|
+
[:- , Number] => Number, [:+ , Number] => Number, [:* , Number] => Number, [:/ , Number] => Number,
|
12
|
+
[:% , Number] => Number, [:"-@"] => Number )
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
class Missing
|
17
|
+
[:==, :< , :> , :<=, :>=, :"?"].each do |sym|
|
18
|
+
define_method(sym) do |arg|
|
19
|
+
false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
''
|
25
|
+
end
|
26
|
+
|
27
|
+
def nil?
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def method_missing(*meth)
|
32
|
+
self
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
Nil = Missing.new
|
37
|
+
|
38
|
+
|
39
|
+
end
|
@@ -1,113 +1,11 @@
|
|
1
|
-
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
-
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
-
|
4
1
|
require 'rubygems'
|
5
2
|
require 'parse_tree'
|
6
|
-
require 'SafeClass'
|
7
|
-
=begin rdoc
|
8
|
-
=end
|
9
|
-
module RubyLess
|
10
|
-
VERSION = '0.1.0'
|
11
|
-
|
12
|
-
def self.translate(string, helper)
|
13
|
-
RubyLessProcessor.translate(string, helper)
|
14
|
-
end
|
15
|
-
|
16
|
-
class Boolean
|
17
|
-
end
|
18
|
-
|
19
|
-
class Number
|
20
|
-
include SafeClass
|
21
|
-
safe_method( [:==, Number] => Boolean, [:< , Number] => Boolean, [:> , Number] => Boolean, [:<=, Number] => Boolean, [:>=, Number] => Boolean,
|
22
|
-
[:- , Number] => Number, [:+ , Number] => Number, [:* , Number] => Number, [:/ , Number] => Number,
|
23
|
-
[:% , Number] => Number, [:"-@"] => Number )
|
24
|
-
end
|
25
|
-
|
26
|
-
|
27
|
-
class Missing
|
28
|
-
[:==, :< , :> , :<=, :>=, :"?"].each do |sym|
|
29
|
-
define_method(sym) do |arg|
|
30
|
-
false
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def to_s
|
35
|
-
''
|
36
|
-
end
|
37
|
-
|
38
|
-
def nil?
|
39
|
-
true
|
40
|
-
end
|
41
|
-
|
42
|
-
def method_missing(*meth)
|
43
|
-
self
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
Nil = Missing.new
|
48
|
-
|
49
|
-
class TypedString < String
|
50
|
-
attr_reader :klass, :opts
|
51
3
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
@opts = opts.dup
|
56
|
-
if could_be_nil? && !@opts[:cond]
|
57
|
-
@opts[:cond] = [self.to_s]
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def klass
|
62
|
-
@opts[:class]
|
63
|
-
end
|
64
|
-
|
65
|
-
def could_be_nil?
|
66
|
-
@opts[:nil]
|
67
|
-
end
|
68
|
-
|
69
|
-
# condition when 'could_be_nil' comes from a different method then the last one:
|
70
|
-
# var1.spouse.name == ''
|
71
|
-
# "var1.spouse" would be the condition that inserted 'could_be_nil?'.
|
72
|
-
def cond
|
73
|
-
@opts[:cond]
|
74
|
-
end
|
75
|
-
|
76
|
-
# raw result without nil checking:
|
77
|
-
# "var1.spouse.name" instead of "(var1.spouse ? var1.spouse.name : nil)"
|
78
|
-
def raw
|
79
|
-
@opts[:raw] || self.to_s
|
80
|
-
end
|
81
|
-
|
82
|
-
def <<(typed_string)
|
83
|
-
append_opts(typed_string)
|
84
|
-
if self.empty?
|
85
|
-
replace(typed_string.raw)
|
86
|
-
else
|
87
|
-
replace("#{self.raw}, #{typed_string.raw}")
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def append_opts(typed_string)
|
92
|
-
if self.empty?
|
93
|
-
@opts = typed_string.opts.dup
|
94
|
-
else
|
95
|
-
if klass.kind_of?(Array)
|
96
|
-
klass << typed_string.klass
|
97
|
-
else
|
98
|
-
@opts[:class] = [klass, typed_string.klass]
|
99
|
-
end
|
100
|
-
append_cond(typed_string.cond) if typed_string.could_be_nil?
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def append_cond(condition)
|
105
|
-
@opts[:cond] ||= []
|
106
|
-
@opts[:cond] += [condition].flatten
|
107
|
-
@opts[:cond].uniq!
|
108
|
-
end
|
109
|
-
end
|
4
|
+
require 'basic_types'
|
5
|
+
require 'typed_string'
|
6
|
+
require 'safe_class'
|
110
7
|
|
8
|
+
module RubyLess
|
111
9
|
class RubyLessProcessor < SexpProcessor
|
112
10
|
attr_reader :ruby
|
113
11
|
|
@@ -172,7 +70,7 @@ module RubyLess
|
|
172
70
|
def process_arglist(exp)
|
173
71
|
code = t("")
|
174
72
|
until exp.empty? do
|
175
|
-
code
|
73
|
+
code.append_argument(process(exp.shift))
|
176
74
|
end
|
177
75
|
code
|
178
76
|
end
|
@@ -296,7 +194,7 @@ module RubyLess
|
|
296
194
|
end
|
297
195
|
|
298
196
|
def get_method(signature, receiver, is_method = true)
|
299
|
-
res = receiver.respond_to?(:
|
197
|
+
res = receiver.respond_to?(:safe_method_type) ? receiver.safe_method_type(signature) : SafeClass.safe_method_type_for(receiver, signature)
|
300
198
|
res = res.call(@helper) if res.kind_of?(Proc)
|
301
199
|
res
|
302
200
|
end
|
data/lib/rubyless.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'processor'
|
5
|
+
|
6
|
+
=begin rdoc
|
7
|
+
=end
|
8
|
+
module RubyLess
|
9
|
+
VERSION = '0.2.0'
|
10
|
+
|
11
|
+
def self.translate(string, helper)
|
12
|
+
RubyLessProcessor.translate(string, helper)
|
13
|
+
end
|
14
|
+
end
|
data/lib/safe_class.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
module RubyLess
|
2
|
+
module SafeClass
|
3
|
+
@@_safe_methods ||= {} # defined for each class
|
4
|
+
@@_safe_methods_all ||= {} # full list with inherited attributes
|
5
|
+
|
6
|
+
# List of safe methods for a specific class.
|
7
|
+
def self.safe_methods_for(klass)
|
8
|
+
@@_safe_methods_all[klass] ||= build_safe_methods_list(klass)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Return method type (options) if the given signature is a safe method for the class.
|
12
|
+
def self.safe_method_type_for(klass, signature)
|
13
|
+
if res = safe_methods_for(klass)[signature]
|
14
|
+
res.dup
|
15
|
+
else
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Declare a safe method for a given class
|
21
|
+
def self.safe_method_for(klass, hash)
|
22
|
+
list = (@@_safe_methods[klass] ||= {})
|
23
|
+
hash.each do |k,v|
|
24
|
+
k = [k] unless k.kind_of?(Array)
|
25
|
+
v = {:class => v} unless v.kind_of?(Hash) || v.kind_of?(Proc)
|
26
|
+
list[k] = v
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.included(base)
|
31
|
+
base.class_eval do
|
32
|
+
|
33
|
+
# Declare a safe method through a hash of either
|
34
|
+
# signature => return type
|
35
|
+
# or
|
36
|
+
# signature => options
|
37
|
+
# or
|
38
|
+
# signature => lambda {|h| ... }
|
39
|
+
#
|
40
|
+
# The lambda expression will be called with @helper as argument during compilation.
|
41
|
+
#
|
42
|
+
# The signature can be either a single symbol or an array containing the method name and type arguments like:
|
43
|
+
# [:strftime, Time, String]
|
44
|
+
#
|
45
|
+
# The return type can be a string with the class name or a class.
|
46
|
+
#
|
47
|
+
# Options are:
|
48
|
+
# :class the return type (class name)
|
49
|
+
# :nil set this to true if the method could return nil
|
50
|
+
def self.safe_method(hash)
|
51
|
+
list = (@@_safe_methods[self] ||= {})
|
52
|
+
hash.each do |k,v|
|
53
|
+
k = [k] unless k.kind_of?(Array)
|
54
|
+
v = {:class => v} unless v.kind_of?(Hash) || v.kind_of?(Proc)
|
55
|
+
list[k] = v
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Declare a safe method to access a list of attributes.
|
60
|
+
# This method should only be used when the class is linked with a database table and provides
|
61
|
+
# proper introspection to detect types and the possibility of NULL values.
|
62
|
+
def self.safe_attribute(*attributes)
|
63
|
+
attributes.each do |att|
|
64
|
+
if col = columns_hash[att.to_s]
|
65
|
+
opts = {}
|
66
|
+
opts[:nil] = col.default.nil?
|
67
|
+
if col.number?
|
68
|
+
opts[:class] = RubyLess::Number
|
69
|
+
elsif col.text?
|
70
|
+
opts[:class] = String
|
71
|
+
elsif att.to_s =~ /_at$/
|
72
|
+
opts[:class] = Time
|
73
|
+
else
|
74
|
+
raise "Could not declare safe_method for '#{att}': could not guess return type"
|
75
|
+
end
|
76
|
+
safe_method att.to_sym => opts
|
77
|
+
else
|
78
|
+
puts "Warning: could not declare safe_attribute '#{att}' (No column with this name found in class #{self})"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Declare a safe method for a given class
|
84
|
+
def self.safe_method_for(klass, signature)
|
85
|
+
SafeClass.safe_method_for(klass, signature)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Hash of all safe methods defined for the class.
|
89
|
+
def self.safe_methods
|
90
|
+
SafeClass.safe_methods_for(self)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Return true if the given signature corresponds to a safe method for the class.
|
94
|
+
def self.safe_method_type(signature)
|
95
|
+
if res = SafeClass.safe_method_type_for(self, signature)
|
96
|
+
res.dup
|
97
|
+
else
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Return the method type (options) if the given signature is a safe method for the class.
|
103
|
+
def safe_method_type(signature)
|
104
|
+
self.class.safe_method_type(signature)
|
105
|
+
end
|
106
|
+
end # base.class_eval
|
107
|
+
end # included
|
108
|
+
|
109
|
+
private
|
110
|
+
def self.build_safe_methods_list(klass)
|
111
|
+
list = klass.superclass.respond_to?(:safe_methods) ? klass.superclass.safe_methods : {}
|
112
|
+
(@@_safe_methods[klass] || {}).map do |signature, return_value|
|
113
|
+
if return_value.kind_of?(Hash)
|
114
|
+
return_value[:class] = parse_class(return_value[:class])
|
115
|
+
elsif !return_value.kind_of?(Proc)
|
116
|
+
return_value = {:class => return_value}
|
117
|
+
end
|
118
|
+
signature.map! {|e| parse_class(e)}
|
119
|
+
list[signature] = return_value
|
120
|
+
end
|
121
|
+
list
|
122
|
+
end
|
123
|
+
|
124
|
+
def self.parse_class(klass)
|
125
|
+
if klass.kind_of?(Array)
|
126
|
+
if klass[0].kind_of?(String)
|
127
|
+
[Module::const_get(klass[0])]
|
128
|
+
else
|
129
|
+
klass
|
130
|
+
end
|
131
|
+
else
|
132
|
+
if klass.kind_of?(String)
|
133
|
+
Module::const_get(klass)
|
134
|
+
else
|
135
|
+
klass
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
data/lib/typed_string.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
module RubyLess
|
2
|
+
|
3
|
+
# This is a special kind of string containing ruby code that retains some information from the
|
4
|
+
# elements that compose it.
|
5
|
+
class TypedString < String
|
6
|
+
attr_reader :klass, :opts
|
7
|
+
|
8
|
+
def initialize(content = "", opts = nil)
|
9
|
+
opts ||= {:class => String}
|
10
|
+
replace(content)
|
11
|
+
@opts = opts.dup
|
12
|
+
if could_be_nil? && !@opts[:cond]
|
13
|
+
@opts[:cond] = [self.to_s]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Resulting class of the evaluated ruby code if it is not nil.
|
18
|
+
def klass
|
19
|
+
@opts[:class]
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns true if the evaluation of the ruby code represented by the string could be 'nil'.
|
23
|
+
def could_be_nil?
|
24
|
+
@opts[:nil]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Condition that could yield a nil result in the whole expression.
|
28
|
+
# For example in the following expression:
|
29
|
+
# var1.spouse.name == ''
|
30
|
+
# "var1.spouse" would be the condition that could yield 'nil'.
|
31
|
+
def cond
|
32
|
+
@opts[:cond]
|
33
|
+
end
|
34
|
+
|
35
|
+
# raw result without nil checking:
|
36
|
+
# "var1.spouse.name" instead of "(var1.spouse ? var1.spouse.name : nil)"
|
37
|
+
def raw
|
38
|
+
@opts[:raw] || self.to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
# Append a typed string to build an argument list
|
42
|
+
def append_argument(typed_string)
|
43
|
+
append_opts(typed_string)
|
44
|
+
if self.empty?
|
45
|
+
replace(typed_string.raw)
|
46
|
+
else
|
47
|
+
replace("#{self.raw}, #{typed_string.raw}")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def append_opts(typed_string)
|
53
|
+
if self.empty?
|
54
|
+
@opts = typed_string.opts.dup
|
55
|
+
else
|
56
|
+
if klass.kind_of?(Array)
|
57
|
+
klass << typed_string.klass
|
58
|
+
else
|
59
|
+
@opts[:class] = [klass, typed_string.klass]
|
60
|
+
end
|
61
|
+
append_cond(typed_string.cond) if typed_string.could_be_nil?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def append_cond(condition)
|
66
|
+
@opts[:cond] ||= []
|
67
|
+
@opts[:cond] += [condition].flatten
|
68
|
+
@opts[:cond].uniq!
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/rubyless.gemspec
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{rubyless}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "0.2.0"
|
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
9
|
s.date = %q{2009-06-02}
|
10
10
|
s.email = %q{gaspard@teti.ch}
|
11
11
|
s.extra_rdoc_files = ["README.rdoc"]
|
12
|
-
s.files = ["History.txt", "Rakefile", "README.rdoc", "rubyless.gemspec", "test/mock", "test/mock/dummy_class.rb", "test/RubyLess", "test/RubyLess/basic.yml", "test/RubyLess/errors.yml", "test/RubyLess_test.rb", "test/test_helper.rb", "lib/
|
12
|
+
s.files = ["History.txt", "Rakefile", "README.rdoc", "rubyless.gemspec", "test/mock", "test/mock/active_record_mock.rb", "test/mock/dummy_class.rb", "test/RubyLess", "test/RubyLess/active_record.yml", "test/RubyLess/basic.yml", "test/RubyLess/errors.yml", "test/RubyLess_test.rb", "test/test_helper.rb", "lib/basic_types.rb", "lib/processor.rb", "lib/rubyless.rb", "lib/safe_class.rb", "lib/typed_string.rb"]
|
13
13
|
s.has_rdoc = true
|
14
14
|
s.homepage = %q{http://zenadmin.org/546}
|
15
15
|
s.rdoc_options = ["--main", "README.rdoc"]
|
@@ -0,0 +1,19 @@
|
|
1
|
+
time_type_from_columns:
|
2
|
+
src: 'log_at.strftime("%Y")'
|
3
|
+
tem: "(var1.log_at ? var1.log_at.strftime(\"%Y\") : nil)"
|
4
|
+
|
5
|
+
text_type_from_columns:
|
6
|
+
src: 'birth.strftime(format)'
|
7
|
+
tem: "Date.parse('2009-06-02 18:44').strftime(var1.format)"
|
8
|
+
res: '02.06.2009'
|
9
|
+
|
10
|
+
number_type_from_columns:
|
11
|
+
src: "age + 15"
|
12
|
+
tem: "(var1.age+15)"
|
13
|
+
res: "20"
|
14
|
+
|
15
|
+
number_type_from_columns_no_default:
|
16
|
+
src: "friend_id + 15"
|
17
|
+
tem: "(var1.friend_id ? (var1.friend_id+15) : nil)"
|
18
|
+
res: ""
|
19
|
+
|
data/test/RubyLess/basic.yml
CHANGED
@@ -7,16 +7,16 @@ numbers:
|
|
7
7
|
tem: "((var1.zip>45) and ((3>-var1.zip) or (3+3)))"
|
8
8
|
|
9
9
|
global_method:
|
10
|
-
src: "strftime(
|
11
|
-
tem: "
|
10
|
+
src: "now.strftime('%Y')"
|
11
|
+
tem: "Time.now.strftime(\"%Y\")"
|
12
12
|
|
13
13
|
dynamic_string:
|
14
|
-
src: "strftime(
|
15
|
-
tem: "
|
14
|
+
src: "now.strftime(\"#{name}\")"
|
15
|
+
tem: "Time.now.strftime(\"#{var1.name}\")"
|
16
16
|
|
17
17
|
dynamic_string_again:
|
18
|
-
src: "strftime(
|
19
|
-
tem: "
|
18
|
+
src: "now.strftime(\"#{name}\")"
|
19
|
+
tem: "Time.now.strftime(\"#{var1.name}\")"
|
20
20
|
|
21
21
|
rewrite_variables:
|
22
22
|
src: "!prev.ancestor?(main) && !node.ancestor?(main)"
|
data/test/RubyLess/errors.yml
CHANGED
@@ -11,6 +11,10 @@ zero_div:
|
|
11
11
|
tem: "(1/(var1.zip-10) rescue nil)"
|
12
12
|
res: ""
|
13
13
|
|
14
|
-
|
14
|
+
looping:
|
15
15
|
src: "while(true) do puts 'flood' end"
|
16
|
-
res: 'Bug! Unknown node-type :while to RubyLess::RubyLessProcessor'
|
16
|
+
res: 'Bug! Unknown node-type :while to RubyLess::RubyLessProcessor'
|
17
|
+
|
18
|
+
add_two_strings:
|
19
|
+
src: "name + 14"
|
20
|
+
res: "'var1.name' does not respond to '+(14)'."
|
data/test/RubyLess_test.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'date'
|
1
2
|
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
3
|
|
3
4
|
class SimpleHelper < Test::Unit::TestCase
|
@@ -7,17 +8,19 @@ class SimpleHelper < Test::Unit::TestCase
|
|
7
8
|
safe_method :prev => {:class => Dummy, :method => 'previous'}
|
8
9
|
safe_method :main => {:class => Dummy, :method => '@node'}
|
9
10
|
safe_method :node => lambda {|h| {:class => h.context[:node_class], :method => h.context[:node]}}
|
10
|
-
safe_method :now
|
11
|
-
safe_method
|
11
|
+
safe_method :now => {:class => Time, :method => "Time.now"}
|
12
|
+
safe_method :birth => {:class => Time, :method => "Date.parse('2009-06-02 18:44')"}
|
12
13
|
safe_method [:vowel_count, String] => RubyLess::Number
|
13
14
|
safe_method [:log_info, Dummy, String] => String
|
14
15
|
safe_method_for String, [:==, String] => RubyLess::Boolean
|
15
16
|
safe_method_for String, [:to_s] => String
|
17
|
+
safe_method_for Time, [:strftime, String] => String
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
+
# Example to dynamically rewrite method calls during compilation
|
20
|
+
def safe_method_type(signature)
|
21
|
+
unless res = self.class.safe_method_type(signature)
|
19
22
|
# try to execute method in the current var "var.method"
|
20
|
-
if res = context[:node_class].
|
23
|
+
if res = context[:node_class].safe_method_type(signature)
|
21
24
|
res = res.call(self) if res.kind_of?(Proc)
|
22
25
|
res[:method] = "#{context[:node]}.#{res[:method] || signature[0]}"
|
23
26
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module RubyLess
|
2
|
+
class ColumnMock
|
3
|
+
def initialize(opts = {})
|
4
|
+
@opts = opts
|
5
|
+
end
|
6
|
+
|
7
|
+
def default
|
8
|
+
@opts[:default]
|
9
|
+
end
|
10
|
+
|
11
|
+
def text?
|
12
|
+
@opts[:text]
|
13
|
+
end
|
14
|
+
|
15
|
+
def number?
|
16
|
+
@opts[:number]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class ActiveRecordMock
|
21
|
+
COLUMNS = {
|
22
|
+
'format' => ColumnMock.new(:default => '%d.%m.%Y', :text => true),
|
23
|
+
'age' => ColumnMock.new(:default => 5, :number => true),
|
24
|
+
'friend_id' => ColumnMock.new(:number => true),
|
25
|
+
'log_at' => ColumnMock.new,
|
26
|
+
}
|
27
|
+
def self.columns_hash
|
28
|
+
COLUMNS
|
29
|
+
end
|
30
|
+
|
31
|
+
COLUMNS.each do |k, v|
|
32
|
+
define_method(k) do
|
33
|
+
v.default
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/test/mock/dummy_class.rb
CHANGED
@@ -1,15 +1,19 @@
|
|
1
|
-
|
1
|
+
require File.dirname(__FILE__) + '/active_record_mock'
|
2
|
+
|
3
|
+
class Dummy < RubyLess::ActiveRecordMock
|
2
4
|
attr_reader :name
|
3
5
|
include RubyLess::SafeClass
|
4
6
|
|
5
7
|
safe_method [:ancestor?, Dummy] => RubyLess::Boolean
|
6
|
-
safe_method :parent => {:class => 'Dummy', :special_option => 'foobar'}
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
safe_method :parent => {:class => 'Dummy', :special_option => 'foobar'},
|
9
|
+
:children => ['Dummy'],
|
10
|
+
:project => 'Dummy',
|
11
|
+
:spouse => {:class => 'Dummy', :nil => true},
|
12
|
+
:husband => {:class => 'Dummy', :nil => true},
|
13
|
+
:id => {:class => RubyLess::Number, :method => :zip},
|
14
|
+
:name => String
|
15
|
+
|
16
|
+
safe_attribute :age, :friend_id, :log_at, :format
|
13
17
|
|
14
18
|
def initialize(name = 'dummy')
|
15
19
|
@name = name
|
data/test/test_helper.rb
CHANGED
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.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gaspard Bucher
|
@@ -27,14 +27,19 @@ files:
|
|
27
27
|
- README.rdoc
|
28
28
|
- rubyless.gemspec
|
29
29
|
- test/mock
|
30
|
+
- test/mock/active_record_mock.rb
|
30
31
|
- test/mock/dummy_class.rb
|
31
32
|
- test/RubyLess
|
33
|
+
- test/RubyLess/active_record.yml
|
32
34
|
- test/RubyLess/basic.yml
|
33
35
|
- test/RubyLess/errors.yml
|
34
36
|
- test/RubyLess_test.rb
|
35
37
|
- test/test_helper.rb
|
36
|
-
- lib/
|
37
|
-
- lib/
|
38
|
+
- lib/basic_types.rb
|
39
|
+
- lib/processor.rb
|
40
|
+
- lib/rubyless.rb
|
41
|
+
- lib/safe_class.rb
|
42
|
+
- lib/typed_string.rb
|
38
43
|
has_rdoc: true
|
39
44
|
homepage: http://zenadmin.org/546
|
40
45
|
post_install_message:
|
data/lib/SafeClass.rb
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
module RubyLess
|
2
|
-
module SafeClass
|
3
|
-
def self.included(base)
|
4
|
-
# add all methods from the module "AddActsAsMethod" to the 'base' module
|
5
|
-
base.class_eval <<-END
|
6
|
-
@@_safe_methods ||= {} # defined for each class
|
7
|
-
@@_safe_methods_all ||= {} # full list with inherited attributes
|
8
|
-
|
9
|
-
def self.safe_method(hash)
|
10
|
-
list = (@@_safe_methods[self] ||= {})
|
11
|
-
hash.each do |k,v|
|
12
|
-
k = [k] unless k.kind_of?(Array)
|
13
|
-
v = {:class => v} unless v.kind_of?(Hash) || v.kind_of?(Proc)
|
14
|
-
list[k] = v
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.safe_methods
|
19
|
-
safe_methods_for(self)
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.safe_methods_for(klass)
|
23
|
-
@@_safe_methods_all[klass] ||= build_safe_methods_list(klass)
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.build_safe_methods_list(klass)
|
27
|
-
list = klass.superclass.respond_to?(:safe_methods) ? klass.superclass.safe_methods : {}
|
28
|
-
(@@_safe_methods[klass] || {}).map do |signature, return_value|
|
29
|
-
if return_value.kind_of?(Hash)
|
30
|
-
return_value[:class] = parse_class(return_value[:class])
|
31
|
-
elsif !return_value.kind_of?(Proc)
|
32
|
-
return_value = {:class => return_value}
|
33
|
-
end
|
34
|
-
signature.map! {|e| parse_class(e)}
|
35
|
-
list[signature] = return_value
|
36
|
-
end
|
37
|
-
list
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.safe_method?(signature)
|
41
|
-
if res = safe_methods[signature]
|
42
|
-
res.dup
|
43
|
-
else
|
44
|
-
nil
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def self.safe_method_for?(klass, signature)
|
49
|
-
if res = safe_methods_for(klass)[signature]
|
50
|
-
res.dup
|
51
|
-
else
|
52
|
-
nil
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def safe_method?(signature)
|
57
|
-
self.class.safe_methods[signature]
|
58
|
-
end
|
59
|
-
|
60
|
-
def self.safe_method_for(klass, hash)
|
61
|
-
list = (@@_safe_methods[klass] ||= {})
|
62
|
-
hash.each do |k,v|
|
63
|
-
k = [k] unless k.kind_of?(Array)
|
64
|
-
v = {:class => v} unless v.kind_of?(Hash) || v.kind_of?(Proc)
|
65
|
-
list[k] = v
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def self.parse_class(klass)
|
70
|
-
if klass.kind_of?(Array)
|
71
|
-
if klass[0].kind_of?(String)
|
72
|
-
[Module::const_get(klass[0])]
|
73
|
-
else
|
74
|
-
klass
|
75
|
-
end
|
76
|
-
else
|
77
|
-
if klass.kind_of?(String)
|
78
|
-
Module::const_get(klass)
|
79
|
-
else
|
80
|
-
klass
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def self.safe_attribute?(sym)
|
86
|
-
column_names.include?(sym) || zafu_readable?(sym) || safe_attribute_list.include?(sym.to_s)
|
87
|
-
end
|
88
|
-
|
89
|
-
def self.zafu_readable?(sym)
|
90
|
-
if sym.to_s =~ /(.*)_zips?$/
|
91
|
-
return true if self.ancestors.include?(Node) && RelationProxy.find_by_role($1.singularize)
|
92
|
-
end
|
93
|
-
self.zafu_readable_attributes.include?(sym.to_s)
|
94
|
-
end
|
95
|
-
END
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|