formula_eval 0.0.1 → 0.1.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/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: []
|