rubyless 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|