formula_eval 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +3 -0
- data/VERSION +1 -1
- data/lib/formula_eval/calculating_collection.rb +116 -37
- data/lib/formula_eval/multi_eval.rb +26 -3
- data/lib/formula_eval/wrapper.rb +54 -2
- data/lib/formula_eval.rb +44 -93
- data/spec/calculating_collection_spec.rb +47 -1
- data/spec/formula_eval_spec.rb +47 -8
- data/spec/spec_helper.rb +28 -0
- metadata +39 -3
data/Rakefile
CHANGED
@@ -11,6 +11,9 @@ begin
|
|
11
11
|
gem.homepage = "http://github.com/mharris717/formula_eval"
|
12
12
|
gem.authors = ["Mike Harris"]
|
13
13
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
gem.add_dependency 'mharris_ext'
|
15
|
+
gem.add_dependency 'nested_hash_tricks'
|
16
|
+
gem.add_dependency 'safe_eval'
|
14
17
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
18
|
end
|
16
19
|
Jeweler::GemcutterTasks.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
@@ -1,55 +1,134 @@
|
|
1
|
-
|
1
|
+
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
def each_with_str_key
|
5
|
+
each do |k,v|
|
6
|
+
yield(k.to_s,v)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
def nested_hash_set(*args)
|
10
|
+
args = args.flatten
|
11
|
+
KeyParts.with_parts(args[0..-2],:array => true) do |start,lst,mult|
|
12
|
+
start.each { |k| self[k] ||= {} }
|
13
|
+
self[lst] = args[-1]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class WrappingCollection
|
2
19
|
attr_accessor :coll
|
3
20
|
include FromHash
|
21
|
+
def method_missing(sym,*args,&b)
|
22
|
+
coll.send(sym,*args,&b)
|
23
|
+
end
|
24
|
+
def user_coll
|
25
|
+
coll.user_coll
|
26
|
+
end
|
27
|
+
fattr(:user_coll_last_calc_dt) do
|
28
|
+
user_coll.save! unless user_coll.last_calc_dt
|
29
|
+
user_coll.last_calc_dt
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class CalculatingCollection < WrappingCollection
|
4
34
|
fattr(:column_hash) do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
35
|
+
{}
|
36
|
+
end
|
37
|
+
fattr(:constants_hash) do
|
38
|
+
{}
|
39
|
+
end
|
40
|
+
def add_constant_column(name,formula)
|
41
|
+
arr = eval(formula).to_a
|
42
|
+
constants_hash[name] = arr
|
43
|
+
end
|
44
|
+
def add_column(name,blk)
|
45
|
+
blk = FormulaEval.new(:formula => blk, :coll => coll) if blk.kind_of?(String)
|
46
|
+
self.column_hash[name.to_s] = blk
|
47
|
+
end
|
48
|
+
def cleaned_doc(doc)
|
49
|
+
column_hash.keys.each do |k|
|
50
|
+
doc.dot_set(k,nil)
|
51
|
+
end
|
52
|
+
doc
|
14
53
|
end
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
|
54
|
+
def constants_enriched_doc(doc,ops)
|
55
|
+
constants_hash.each do |col,vals|
|
56
|
+
vals = vals.to_a
|
57
|
+
KeyParts.with_parts(col) do |start,lst,mult|
|
58
|
+
if mult
|
59
|
+
doc[start] ||= []
|
60
|
+
vals.each_with_index do |val,i|
|
61
|
+
field = "#{start}.#{i}.#{lst}"
|
62
|
+
doc.dot_set(field,val)
|
63
|
+
end
|
64
|
+
elsif ops[:row_index]
|
65
|
+
doc[col] = vals[ops[:row_index]]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
doc
|
70
|
+
end
|
71
|
+
def calc_enriched_doc(doc,ops)
|
72
|
+
doc = cleaned_doc(doc)
|
73
|
+
column_hash.each_with_str_key do |col,blk|
|
74
|
+
if KeyParts.single?(col)
|
19
75
|
val = blk.call(doc)
|
20
|
-
|
21
|
-
doc.dot_set(col.to_s,val)
|
76
|
+
doc.dot_set(col,val)
|
22
77
|
else
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
doc.dot_set(col.to_s,val)
|
78
|
+
doc.dot_set(col) do |obj|
|
79
|
+
multi = MultiEval.new(:objs => [obj,doc])
|
80
|
+
blk.call(multi)
|
81
|
+
end
|
28
82
|
end
|
29
83
|
end
|
30
84
|
doc.to_unwrapped
|
31
85
|
end
|
32
|
-
def
|
33
|
-
|
34
|
-
|
86
|
+
def enriched_doc(doc,other_ops={})
|
87
|
+
doc = constants_enriched_doc(doc,other_ops)
|
88
|
+
doc = calc_enriched_doc(doc,other_ops)
|
89
|
+
end
|
90
|
+
def keys
|
91
|
+
ks = column_hash.keys.map { |x| x.split('.').first }.uniq
|
92
|
+
coll.keys.make_last(ks)
|
93
|
+
end
|
94
|
+
def save(doc,ops={},other_ops={})
|
95
|
+
doc = enriched_doc(doc,other_ops)
|
96
|
+
doc.nested_hash_set '_admin','last_calc_dt',Time.now
|
97
|
+
|
98
|
+
res = coll.save(doc,ops)
|
99
|
+
doc[:_id] = res
|
100
|
+
doc
|
35
101
|
end
|
36
102
|
def update_calcs!
|
37
|
-
|
38
|
-
|
103
|
+
end
|
104
|
+
def update_calcs_real!
|
105
|
+
constants_max = constants_hash.values.map { |x| x.to_a.size }.max || 0
|
106
|
+
find({},{:limit => 9999}).each_with_index do |row,i|
|
107
|
+
save(row,{},:row_index => i)
|
108
|
+
end
|
109
|
+
if constants_max > count
|
110
|
+
(count...constants_max).each do |i|
|
111
|
+
save({},{},:row_index => i)
|
112
|
+
end
|
39
113
|
end
|
114
|
+
rescue => exp
|
115
|
+
mylog 'update_calcs', :trace => exp.backtrace.join("\n"), :message => exp.message
|
116
|
+
raise exp
|
40
117
|
end
|
41
118
|
def update_row(row_id,fields)
|
42
|
-
row = (row_id
|
43
|
-
|
44
|
-
|
45
|
-
row.dot_set(k,mongo_value(v))
|
46
|
-
row.delete(k) if v.blank?
|
47
|
-
end
|
48
|
-
row = enriched_doc(row)
|
49
|
-
save(row)
|
50
|
-
row
|
119
|
+
row = coll.update_row(row_id,fields)
|
120
|
+
mylog 'calc_update_row', :row => row, :coll => coll.class
|
121
|
+
row = save(row)
|
51
122
|
end
|
52
|
-
def
|
53
|
-
|
123
|
+
def needs_calc?(doc)
|
124
|
+
doc_dt = doc['_admin'].andand['last_calc_dt'] || Time.local(1970,1,1)
|
125
|
+
user_coll_last_calc_dt > doc_dt
|
126
|
+
end
|
127
|
+
def find(selector={},ops={})
|
128
|
+
res = coll.find(selector,ops)
|
129
|
+
if res.respond_to?(:each)
|
130
|
+
res.select { |doc| needs_calc?(doc) }.each { |doc| save(doc) }
|
131
|
+
end
|
132
|
+
res
|
54
133
|
end
|
55
134
|
end
|
@@ -27,9 +27,10 @@ class MultiEval
|
|
27
27
|
end
|
28
28
|
def method_missing(sym,*args,&b)
|
29
29
|
objs.each do |obj|
|
30
|
-
if obj.
|
30
|
+
if obj.smart_respond_to?(sym,args)
|
31
31
|
res = obj.send(sym,*args,&b)
|
32
|
-
return MultiEval.new(:objs => [res]+without_obj(obj)) if res
|
32
|
+
#return MultiEval.new(:objs => [res]+without_obj(obj)) #if res
|
33
|
+
return res if res
|
33
34
|
end
|
34
35
|
end
|
35
36
|
raise 'none respond'
|
@@ -48,9 +49,31 @@ class MultiEval
|
|
48
49
|
objs.first * x
|
49
50
|
end
|
50
51
|
def self.get_nested(obj,method_str)
|
51
|
-
|
52
|
+
method_str = fix_str(method_str)
|
53
|
+
other = obj.to_wrapped.safe_instance_eval(method_str)
|
52
54
|
mylog 'get_nested', :obj => obj, :method_str => method_str, :other => other
|
53
55
|
arr = [other,obj]
|
54
56
|
new(:objs => arr)
|
55
57
|
end
|
58
|
+
def to_unwrapped
|
59
|
+
objs.first.to_unwrapped
|
60
|
+
end
|
61
|
+
def self.fix_str(str)
|
62
|
+
str.gsub(/\.(\d+)\./) { "._arrayindex_#{$1}." }.gsub(/\.(\d+)$/) { "._arrayindex_#{$1}" }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Object
|
67
|
+
def smart_respond_to?(k,args)
|
68
|
+
sup = respond_to?(k)
|
69
|
+
if k.to_s == '[]'
|
70
|
+
return false if args.first.kind_of?(Numeric) && kind_of?(Hash)
|
71
|
+
return false if !args.first.kind_of?(Numeric) && kind_of?(Array)
|
72
|
+
return false if !args.first.kind_of?(Numeric) && kind_of?(Numeric)
|
73
|
+
return false if !args.first.kind_of?(Numeric) && kind_of?(String)
|
74
|
+
sup
|
75
|
+
else
|
76
|
+
sup
|
77
|
+
end
|
78
|
+
end
|
56
79
|
end
|
data/lib/formula_eval/wrapper.rb
CHANGED
@@ -33,14 +33,52 @@ class HashWrapper
|
|
33
33
|
obj[k.to_s] = v
|
34
34
|
end
|
35
35
|
def obj; hash; end
|
36
|
+
def to_wrapped
|
37
|
+
self
|
38
|
+
end
|
36
39
|
def to_unwrapped
|
37
40
|
hash.to_unwrapped
|
38
41
|
end
|
39
42
|
def respond_to?(sym)
|
40
43
|
obj.keys.include?(sym.to_s)
|
41
44
|
end
|
45
|
+
def delete(k)
|
46
|
+
obj.delete(k)
|
47
|
+
end
|
48
|
+
def kind_of?(x)
|
49
|
+
return true if x == Hash
|
50
|
+
super
|
51
|
+
end
|
52
|
+
def keys
|
53
|
+
obj.keys
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Array
|
58
|
+
def contains_all_hashes?
|
59
|
+
all? { |x| x.kind_of?(Hash) || x.kind_of?(OrderedHash) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Array
|
64
|
+
def method_missing(sym,*args,&b)
|
65
|
+
if sym.to_s =~ /_arrayindex_(\d+)/
|
66
|
+
self[$1.to_i].to_wrapped
|
67
|
+
else
|
68
|
+
super
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
module ArrayMod
|
74
|
+
def [](i)
|
75
|
+
raise "tried to pass string #{i} to array [] #{inspect}" unless i.kind_of?(Fixnum)
|
76
|
+
super
|
77
|
+
end
|
42
78
|
end
|
43
79
|
|
80
|
+
Array.send(:include,ArrayMod)
|
81
|
+
|
44
82
|
class ArrayWrapper
|
45
83
|
attr_accessor :obj
|
46
84
|
include FromHash
|
@@ -48,20 +86,34 @@ class ArrayWrapper
|
|
48
86
|
map { |h| h[sym.to_s] }.select { |x| x }.flatten.to_wrapped
|
49
87
|
end
|
50
88
|
def method_missing(sym,*args,&b)
|
51
|
-
if obj.respond_to?(sym)
|
89
|
+
res = if obj.respond_to?(sym)
|
52
90
|
obj.send(sym,*args,&b).to_wrapped
|
53
|
-
elsif
|
91
|
+
elsif sym.to_s =~ /_arrayindex_(\d+)/
|
92
|
+
self[$1.to_i]
|
93
|
+
elsif obj.contains_all_hashes?
|
54
94
|
hash_mm(sym)
|
55
95
|
else
|
56
96
|
obj.send(sym,*args,&b).to_wrapped
|
57
97
|
end
|
98
|
+
res
|
99
|
+
end
|
100
|
+
def [](i)
|
101
|
+
raise "tried to pass string #{i} to array [] #{inspect}" unless i.kind_of?(Fixnum)
|
102
|
+
obj[i].to_wrapped
|
58
103
|
end
|
59
104
|
def *(arg)
|
60
105
|
map { |x| x * arg }
|
61
106
|
end
|
107
|
+
def to_wrapped
|
108
|
+
self
|
109
|
+
end
|
62
110
|
def to_unwrapped
|
63
111
|
obj.to_unwrapped
|
64
112
|
end
|
113
|
+
def kind_of?(x)
|
114
|
+
return true if x == Array
|
115
|
+
super
|
116
|
+
end
|
65
117
|
end
|
66
118
|
|
67
119
|
class Array
|
data/lib/formula_eval.rb
CHANGED
@@ -1,15 +1,26 @@
|
|
1
1
|
require 'mharris_ext'
|
2
|
-
require '
|
2
|
+
require 'safe_eval'
|
3
3
|
|
4
4
|
class Object
|
5
5
|
attr_accessor :calculating_formula
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
class WrappingProxy
|
9
|
+
attr_accessor :obj
|
10
|
+
include FromHash
|
11
|
+
def method_missing(sym,*args,&b)
|
12
|
+
WrappingProxy.new(:obj => obj.send(sym,*args,&b).to_wrapped)
|
13
|
+
end
|
14
|
+
def respond_to?(x)
|
15
|
+
obj.respond_to?(x)
|
16
|
+
end
|
17
|
+
def kind_of?(x)
|
18
|
+
obj.kind_of?(x)
|
19
|
+
end
|
20
|
+
end
|
10
21
|
|
11
22
|
class FormulaEval
|
12
|
-
attr_accessor :row, :formula, :row_index, :rows
|
23
|
+
attr_accessor :row, :formula, :row_index, :rows, :coll
|
13
24
|
include FromHash
|
14
25
|
def current_user
|
15
26
|
$current_user
|
@@ -24,21 +35,25 @@ class FormulaEval
|
|
24
35
|
elsif row.respond_to?('[]')
|
25
36
|
wrapped_row[sym.to_s]
|
26
37
|
else
|
38
|
+
puts "mm #{sym} #{args.inspect}"
|
27
39
|
super
|
28
40
|
end
|
29
41
|
ensure
|
30
42
|
# puts "Formula mm #{sym} #{args.inspect} #{res.inspect}"
|
31
43
|
end
|
44
|
+
def fixed_formula
|
45
|
+
MultiEval.fix_str(formula)
|
46
|
+
end
|
32
47
|
def safe_eval_result
|
33
|
-
|
34
|
-
# rescue => exp
|
35
|
-
# t = exp.backtrace.join("\n").gsub("/Users/mharris/.rvm/gems/ruby-1.9.1-p378/gems","gems").gsub("/Users/mharris/Code/smartlist/vendor/mongo_ui","mongo_ui")
|
36
|
-
# t = t.gsub("/Users/mharris/Code/smartlist","smartlist")
|
37
|
-
# mylog "formula_eval", :formula => formula, :row => row, :message => exp.message, :trace => t
|
38
|
-
# "Error #{exp.message}"
|
48
|
+
safe_instance_eval(fixed_formula).to_unwrapped
|
39
49
|
rescue => exp
|
50
|
+
t = exp.backtrace.join("\n").gsub("/Users/mharris/.rvm/gems/ruby-1.9.1-p378/gems","gems").gsub("/Users/mharris/Code/smartlist/vendor/mongo_ui","mongo_ui")
|
51
|
+
t = t.gsub("/Users/mharris/Code/smartlist","smartlist")
|
52
|
+
mylog "formula_eval", :formula => formula, :row => row, :message => exp.message, :trace => t
|
53
|
+
"Error #{exp.message}"
|
54
|
+
#rescue => exp
|
40
55
|
# puts "error evaling #{formula} against #{row.inspect}, #{exp.message}"
|
41
|
-
raise exp
|
56
|
+
#raise exp
|
42
57
|
end
|
43
58
|
def result
|
44
59
|
self.formula = formula[1..-1] if formula[0..0] == '='
|
@@ -66,15 +81,27 @@ class FormulaEval
|
|
66
81
|
end
|
67
82
|
end
|
68
83
|
|
69
|
-
load File.dirname(__FILE__) + "/formula_eval/wrapper.rb"
|
70
|
-
load File.dirname(__FILE__) + "/formula_eval/calculating_collection.rb"
|
71
|
-
load File.dirname(__FILE__) + "/formula_eval/multi_eval.rb"
|
72
84
|
|
73
|
-
|
74
|
-
|
75
|
-
|
85
|
+
|
86
|
+
class FormulaEval
|
87
|
+
def self.load_subfiles!
|
88
|
+
load File.dirname(__FILE__) + "/formula_eval/wrapper.rb"
|
89
|
+
load File.dirname(__FILE__) + "/formula_eval/calculating_collection.rb"
|
90
|
+
load File.dirname(__FILE__) + "/formula_eval/multi_eval.rb"
|
91
|
+
end
|
92
|
+
def self.load_self!
|
93
|
+
load File.dirname(__FILE__) + "/formula_eval.rb"
|
94
|
+
end
|
95
|
+
def self.load_files!
|
96
|
+
load_subfiles!
|
97
|
+
load_self!
|
98
|
+
end
|
76
99
|
end
|
77
100
|
|
101
|
+
FormulaEval.load_subfiles!
|
102
|
+
require 'nested_hash_tricks'
|
103
|
+
|
104
|
+
|
78
105
|
class Object
|
79
106
|
def klass
|
80
107
|
self.class
|
@@ -90,79 +117,3 @@ class Object
|
|
90
117
|
end
|
91
118
|
end
|
92
119
|
|
93
|
-
class Object
|
94
|
-
def dot_get(str)
|
95
|
-
str = str.split(".") if str.is_a?(String)
|
96
|
-
res = self
|
97
|
-
last_f = last_res = nil
|
98
|
-
str.each do |f|
|
99
|
-
if f.num? && !res.kind_of?(Array)
|
100
|
-
last_res[last_f] = res = []
|
101
|
-
end
|
102
|
-
last_res = res
|
103
|
-
if res.kind_of?(Array)
|
104
|
-
temp = res[f.safe_to_i]
|
105
|
-
if !temp
|
106
|
-
res << {}
|
107
|
-
temp = res.last
|
108
|
-
raise "can only add new row at end" unless res.size-1 == f.safe_to_i
|
109
|
-
end
|
110
|
-
res = temp
|
111
|
-
else
|
112
|
-
res = res[f]
|
113
|
-
end
|
114
|
-
last_f = f
|
115
|
-
end
|
116
|
-
res
|
117
|
-
end
|
118
|
-
def dot_set(str,val)
|
119
|
-
mylog 'dot_set', :str => str, :val => val, :self => self do
|
120
|
-
return self[str] = val if str.split(".").size == 1
|
121
|
-
strs = str.split(".")[0..-2]
|
122
|
-
lst = str.split(".")[-1]
|
123
|
-
obj = dot_get(strs)
|
124
|
-
return obj unless obj
|
125
|
-
#puts "dot_set, obj is #{obj.inspect}, str is #{str}, val is #{val}, lst is #{lst}"
|
126
|
-
obj.nested_set(lst,val)
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
class Object
|
132
|
-
def nested_set(k,v)
|
133
|
-
self[k] = v
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
class Array
|
138
|
-
def nested_set(k,v)
|
139
|
-
mylog 'dot_set', :context => 'nested', :klass => klass, :self => self, :k => k, :v => v do
|
140
|
-
each { |x| x.nested_set(k,v) }
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
class String
|
146
|
-
def num?
|
147
|
-
size > 0 && self =~ /^[\d\.]*$/
|
148
|
-
end
|
149
|
-
def date?
|
150
|
-
matches = (self =~ /\/\d+\//) || (self =~ /-\d+-/)
|
151
|
-
matches2 = self =~ /^[ \d\-\/:]+$/
|
152
|
-
!!(matches && matches2 && Time.parse(self))
|
153
|
-
rescue
|
154
|
-
return false
|
155
|
-
end
|
156
|
-
def to_time
|
157
|
-
Time.parse(self)
|
158
|
-
end
|
159
|
-
def tmo
|
160
|
-
if num?
|
161
|
-
to_f.tmo
|
162
|
-
elsif blank?
|
163
|
-
nil
|
164
|
-
else
|
165
|
-
self
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
@@ -6,8 +6,11 @@ describe "CalculatingCollection" do
|
|
6
6
|
fattr(:formula) { FormulaEval.new(:row => row) }
|
7
7
|
fattr(:column) { 'pension.perc_plus_salary' }
|
8
8
|
fattr(:str) { '=perc*salary' }
|
9
|
+
fattr(:column_hash) { {column => FormulaEval.new(:formula => str)} }
|
10
|
+
fattr(:constants_hash) { {} }
|
9
11
|
fattr(:calc) do
|
10
|
-
CalculatingCollection.new(:column_hash =>
|
12
|
+
CalculatingCollection.new(:column_hash => column_hash, :constants_hash => constants_hash,
|
13
|
+
:user_coll_last_calc_dt => Time.now, :coll => SilentMM.new)
|
11
14
|
end
|
12
15
|
fattr(:enriched) do
|
13
16
|
calc.enriched_doc(wrapped_row)
|
@@ -23,4 +26,47 @@ describe "CalculatingCollection" do
|
|
23
26
|
self.str = '=year+1'
|
24
27
|
enriched['pension']['perc_plus_salary'].should == 2026
|
25
28
|
end
|
29
|
+
it 'sub array' do
|
30
|
+
self.row['pension'] = [{'year' => 2025, 'perc' => 0.65},{'year' => 2026, 'perc' => 0.7}]
|
31
|
+
enriched['pension'][0]['perc_plus_salary'].should == 42000.0*0.65
|
32
|
+
end
|
33
|
+
it 'reverse uniq' do
|
34
|
+
a = [1,2,3,4,5,2,7]
|
35
|
+
a.reverse.uniq.reverse.should == [1,3,4,5,2,7]
|
36
|
+
end
|
37
|
+
it 'saving' do
|
38
|
+
calc.save(row)
|
39
|
+
row['pension']['perc_plus_salary'].should == 42000*0.65
|
40
|
+
end
|
41
|
+
describe 'constants' do
|
42
|
+
before do
|
43
|
+
self.row['pension'] = [{'year' => 2025, 'perc' => 0.65},{'year' => 2026, 'perc' => 0.7}]
|
44
|
+
end
|
45
|
+
it 'sub level' do
|
46
|
+
self.constants_hash = {'pension.one_five' => [1,2,3,4,5]}
|
47
|
+
calc.save(row)
|
48
|
+
row['pension'][0]['one_five'].should == 1
|
49
|
+
row['pension'].size.should == 5
|
50
|
+
row['pension'][3]['one_five'].should == 4
|
51
|
+
end
|
52
|
+
it 'top level' do
|
53
|
+
self.constants_hash = {'one_five' => [1,2,3,4,5]}
|
54
|
+
calc.save(row,{},:row_index => 2)
|
55
|
+
row['one_five'].should == 3
|
56
|
+
end
|
57
|
+
end
|
58
|
+
describe 'safe_eval' do
|
59
|
+
it 'shellout' do
|
60
|
+
SafeEval.with_level(4) do
|
61
|
+
self.str = "=`rm -r -f /code/fghgfhrthyhthyht`; perc*salary"
|
62
|
+
lambda { enriched }.should raise_error(SecurityError)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
# it 'change' do
|
66
|
+
# self.str = "=self.year = 42; perc*salary"
|
67
|
+
# #lambda { enriched }.should raise_error(SecurityError)
|
68
|
+
# raise enriched.inspect
|
69
|
+
# end
|
70
|
+
end
|
71
|
+
|
26
72
|
end
|
data/spec/formula_eval_spec.rb
CHANGED
@@ -20,11 +20,19 @@ class B
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
23
|
+
class C
|
24
|
+
def c
|
25
|
+
3
|
26
|
+
end
|
27
|
+
def c_array
|
28
|
+
[{'d' => 4, 'e' => 5},{'d' => 6, 'e' => 7}]
|
29
|
+
end
|
30
|
+
def c_hash
|
31
|
+
{'d' => 4, 'e' => 5}
|
32
|
+
end
|
33
|
+
def [](k)
|
34
|
+
send(k)
|
35
|
+
end
|
28
36
|
end
|
29
37
|
|
30
38
|
describe "FormulaEval" do
|
@@ -37,9 +45,6 @@ describe "FormulaEval" do
|
|
37
45
|
it 'double_year' do
|
38
46
|
self.str = '=year*2'
|
39
47
|
formula.result.should == 4020
|
40
|
-
end
|
41
|
-
it 'nested' do
|
42
|
-
|
43
48
|
end
|
44
49
|
it 'double' do
|
45
50
|
self.row = MultiEval.new(:objs => [A.new,B.new])
|
@@ -57,4 +62,38 @@ describe "FormulaEval" do
|
|
57
62
|
self.str = '=year'
|
58
63
|
formula.result.should == 2010
|
59
64
|
end
|
65
|
+
it 'nested with array' do
|
66
|
+
self.row = MultiEval.get_nested(C.new,'c_array._arrayindex_0')
|
67
|
+
row.objs[0].obj.should == {'d' => 4, 'e' => 5}
|
68
|
+
end
|
69
|
+
it 'nested with array 2' do
|
70
|
+
MultiEval.fix_str('c_array.0').should == 'c_array._arrayindex_0'
|
71
|
+
MultiEval.fix_str('c_array.0.d').should == 'c_array._arrayindex_0.d'
|
72
|
+
end
|
73
|
+
it 'nested with array 3' do
|
74
|
+
self.row = MultiEval.get_nested(C.new,'c_array.0')
|
75
|
+
row.objs[0].obj.should == {'d' => 4, 'e' => 5}
|
76
|
+
self.str = '=c+c_array.0.d'
|
77
|
+
formula.result.should == 7
|
78
|
+
end
|
79
|
+
it 'nested with array 4' do
|
80
|
+
self.row = MultiEval.get_nested(C.new,'c_array.0')
|
81
|
+
self.str = '=c+d'
|
82
|
+
formula.result.should == 7
|
83
|
+
end
|
84
|
+
it 'nested with array 5' do
|
85
|
+
self.row = MultiEval.get_nested(C.new,'c_array.1')
|
86
|
+
self.str = '=c+d'
|
87
|
+
formula.result.should == 9
|
88
|
+
end
|
89
|
+
it 'nested with array' do
|
90
|
+
self.row = MultiEval.get_nested(C.new,'c_array._arrayindex_0')
|
91
|
+
#raise row.objs.inspect
|
92
|
+
self.str = '=c+d'
|
93
|
+
formula.result.should == 7
|
94
|
+
end
|
95
|
+
# it 'double 3' do
|
96
|
+
# hash = {'year' => 2010, 'pension' => [{'start_year' => 2025, 'perc' => 0.65},{'start_year' => 2026, 'perc' => 0.7}]}
|
97
|
+
# self.row = MultiEval.get_nested(Wrapper.new(hash),'pension')
|
98
|
+
# end
|
60
99
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -7,3 +7,31 @@ require 'spec/autorun'
|
|
7
7
|
Spec::Runner.configure do |config|
|
8
8
|
|
9
9
|
end
|
10
|
+
|
11
|
+
def mylog(*args)
|
12
|
+
yield if block_given?
|
13
|
+
#puts args.inspect if args.first == 'enriched_doc' or args.first == 'dot_set'
|
14
|
+
end
|
15
|
+
|
16
|
+
def bt
|
17
|
+
raise 'foo'
|
18
|
+
rescue => exp
|
19
|
+
puts exp.message
|
20
|
+
puts exp.backtrace.join("\n")
|
21
|
+
end
|
22
|
+
|
23
|
+
class SilentMM
|
24
|
+
def method_missing(*args)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class String
|
29
|
+
def safe_to_i
|
30
|
+
num? ? to_i : (raise 'not num')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def debug_log(*args)
|
35
|
+
end
|
36
|
+
|
37
|
+
DEFAULT_SAFE_LEVEL = 0
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
- 0
|
8
7
|
- 1
|
9
|
-
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Mike Harris
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-06-01 00:00:00 -04:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -31,6 +31,42 @@ dependencies:
|
|
31
31
|
version: 1.2.9
|
32
32
|
type: :development
|
33
33
|
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: mharris_ext
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
version: "0"
|
44
|
+
type: :runtime
|
45
|
+
version_requirements: *id002
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: nested_hash_tricks
|
48
|
+
prerelease: false
|
49
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
segments:
|
54
|
+
- 0
|
55
|
+
version: "0"
|
56
|
+
type: :runtime
|
57
|
+
version_requirements: *id003
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: safe_eval
|
60
|
+
prerelease: false
|
61
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
type: :runtime
|
69
|
+
version_requirements: *id004
|
34
70
|
description: formula_eval
|
35
71
|
email: mharris717@gmail.com
|
36
72
|
executables: []
|