soroban 0.4.0 → 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +1 -1
- data/README.md +2 -2
- data/Soroban.gemspec +4 -2
- data/VERSION +1 -1
- data/lib/soroban/cell.rb +9 -2
- data/lib/soroban/functions.rb +2 -0
- data/lib/soroban/functions/exp.rb +4 -0
- data/lib/soroban/functions/ln.rb +4 -0
- data/lib/soroban/parser/grammar.rb +2 -2
- data/lib/soroban/parser/grammar.treetop +2 -2
- data/lib/soroban/parser/nodes.rb +12 -0
- data/lib/soroban/sheet.rb +29 -0
- data/spec/soroban_spec.rb +50 -0
- metadata +81 -67
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -134,7 +134,7 @@ Soroban implements some Excel functions, but you may find that you need more
|
|
134
134
|
than those. In that case, it's easy to add more.
|
135
135
|
|
136
136
|
```ruby
|
137
|
-
Soroban::functions # => ["
|
137
|
+
Soroban::functions # => ["AVERAGE", "SUM", "VLOOKUP", "IF", "AND", "OR", "NOT", "MAX", "MIN", "LN", "EXP"]
|
138
138
|
|
139
139
|
Soroban::define :FOO => lambda { |lo, hi|
|
140
140
|
raise ArgumentError if lo > hi
|
@@ -160,4 +160,4 @@ Contributing to Soroban
|
|
160
160
|
Copyright
|
161
161
|
---------
|
162
162
|
|
163
|
-
Copyright (c)
|
163
|
+
Copyright (c) 2013 Agworld Pty. Ltd. See LICENSE.txt for further details.
|
data/Soroban.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "soroban"
|
8
|
-
s.version = "0.4
|
8
|
+
s.version = "0.5.4"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Jason Hutchens"]
|
12
|
-
s.date = "2013-01-
|
12
|
+
s.date = "2013-01-18"
|
13
13
|
s.description = "Soroban makes it easy to extract and execute formulas from Excel spreadsheets. It rewrites Excel formulas as Ruby expressions, and allows you to bind named variables to spreadsheet cells to easily manipulate inputs and capture outputs."
|
14
14
|
s.email = "jason.hutchens@agworld.com.au"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -35,7 +35,9 @@ Gem::Specification.new do |s|
|
|
35
35
|
"lib/soroban/functions.rb",
|
36
36
|
"lib/soroban/functions/and.rb",
|
37
37
|
"lib/soroban/functions/average.rb",
|
38
|
+
"lib/soroban/functions/exp.rb",
|
38
39
|
"lib/soroban/functions/if.rb",
|
40
|
+
"lib/soroban/functions/ln.rb",
|
39
41
|
"lib/soroban/functions/max.rb",
|
40
42
|
"lib/soroban/functions/min.rb",
|
41
43
|
"lib/soroban/functions/not.rb",
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4
|
1
|
+
0.5.4
|
data/lib/soroban/cell.rb
CHANGED
@@ -10,26 +10,33 @@ module Soroban
|
|
10
10
|
attr_reader :excel, :ruby, :dependencies
|
11
11
|
|
12
12
|
# Cells are initialised with a binding to allow formulas to be executed
|
13
|
-
# within the context of the sheet which
|
13
|
+
# within the context of the sheet which owns the cell.
|
14
14
|
def initialize(context)
|
15
15
|
@dependencies = []
|
16
16
|
@binding = context
|
17
17
|
@touched = false
|
18
|
+
@value = nil
|
18
19
|
end
|
19
20
|
|
20
21
|
# Set the contents of a cell, and store the executable Ruby version.
|
21
22
|
def set(contents)
|
22
23
|
contents = contents.to_s
|
23
24
|
contents = "'#{contents}'" if Soroban::unknown?(contents)
|
25
|
+
clear
|
24
26
|
@excel, @ruby = contents, _convert(contents)
|
25
27
|
end
|
26
28
|
|
29
|
+
# Clear the cached value of a cell to force it to be recalculated
|
30
|
+
def clear
|
31
|
+
@value = nil
|
32
|
+
end
|
33
|
+
|
27
34
|
# Eval the Ruby version of the string contents within the context of the
|
28
35
|
# owning sheet. Will throw Soroban::RecursionError if recursion is detected.
|
29
36
|
def get
|
30
37
|
raise Soroban::RecursionError, "Loop detected when evaluating '#{@excel}'" if @touched
|
31
38
|
@touched = true
|
32
|
-
eval(@ruby, @binding)
|
39
|
+
@value ||= eval(@ruby, @binding)
|
33
40
|
rescue TypeError, RangeError, ZeroDivisionError
|
34
41
|
nil
|
35
42
|
ensure
|
data/lib/soroban/functions.rb
CHANGED
@@ -1340,7 +1340,7 @@ module Soroban
|
|
1340
1340
|
end
|
1341
1341
|
end
|
1342
1342
|
if s0.last
|
1343
|
-
r0 = instantiate_node(
|
1343
|
+
r0 = instantiate_node(FloatValue,input, i0...index, s0)
|
1344
1344
|
r0.extend(Float0)
|
1345
1345
|
else
|
1346
1346
|
@index = i0
|
@@ -1381,7 +1381,7 @@ module Soroban
|
|
1381
1381
|
@index = i0
|
1382
1382
|
r0 = nil
|
1383
1383
|
else
|
1384
|
-
r0 = instantiate_node(
|
1384
|
+
r0 = instantiate_node(IntegerValue,input, i0...index, s0)
|
1385
1385
|
end
|
1386
1386
|
|
1387
1387
|
node_cache[:integer][start_index] = r0
|
@@ -45,10 +45,10 @@ grammar Soroban
|
|
45
45
|
( float / integer / '-' float / '-' integer )
|
46
46
|
end
|
47
47
|
rule float
|
48
|
-
[0-9]* '.' [0-9]+
|
48
|
+
[0-9]* '.' [0-9]+ <FloatValue>
|
49
49
|
end
|
50
50
|
rule integer
|
51
|
-
[0-9]+
|
51
|
+
[0-9]+ <IntegerValue>
|
52
52
|
end
|
53
53
|
rule identifier
|
54
54
|
[a-zA-Z] [a-zA-Z0-9]* <Identifier>
|
data/lib/soroban/parser/nodes.rb
CHANGED
@@ -15,6 +15,18 @@ module Soroban
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
+
class IntegerValue < Treetop::Runtime::SyntaxNode
|
19
|
+
def rewrite(value)
|
20
|
+
"#{value.to_f}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class FloatValue < Treetop::Runtime::SyntaxNode
|
25
|
+
def rewrite(value)
|
26
|
+
"#{value.to_f}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
18
30
|
class Function < Treetop::Runtime::SyntaxNode
|
19
31
|
def rewrite(value)
|
20
32
|
match = /^([^(]*)(.*)$/.match(value)
|
data/lib/soroban/sheet.rb
CHANGED
@@ -14,6 +14,7 @@ module Soroban
|
|
14
14
|
def initialize(logger=nil)
|
15
15
|
@logger = logger
|
16
16
|
@cells = {}
|
17
|
+
@changes = Hash.new{ |h, k| h[k] = Set.new }
|
17
18
|
@bindings = {}
|
18
19
|
end
|
19
20
|
|
@@ -106,7 +107,21 @@ module Soroban
|
|
106
107
|
@logger.debug "SOROBAN: #{message}"
|
107
108
|
end
|
108
109
|
|
110
|
+
def _link(name, dependencies)
|
111
|
+
dependencies.each { |target| @changes[target] << name if name != target }
|
112
|
+
end
|
113
|
+
|
114
|
+
def _unlink(name, dependencies)
|
115
|
+
dependencies.each { |target| @changes[target].delete(name) }
|
116
|
+
end
|
117
|
+
|
109
118
|
def _add(label, contents)
|
119
|
+
name = @bindings[label.to_sym] || label
|
120
|
+
if cells.has_key?(name)
|
121
|
+
cell = eval("@#{name}", binding)
|
122
|
+
cell.set(contents)
|
123
|
+
return
|
124
|
+
end
|
110
125
|
internal = "@#{label}"
|
111
126
|
_expose(internal, label)
|
112
127
|
cell = Cell.new(binding)
|
@@ -117,8 +132,22 @@ module Soroban
|
|
117
132
|
def _set(label_or_name, cell, contents)
|
118
133
|
label = label_or_name.to_sym
|
119
134
|
name = @bindings[label] || label
|
135
|
+
_unlink(name, cell.dependencies)
|
120
136
|
cell.set(contents)
|
121
137
|
@cells[name] = cell.dependencies
|
138
|
+
_link(name, cell.dependencies)
|
139
|
+
_clear(name)
|
140
|
+
end
|
141
|
+
|
142
|
+
def _clear(name)
|
143
|
+
@changes[name].each do |target|
|
144
|
+
next unless @cells.has_key?(target)
|
145
|
+
begin
|
146
|
+
eval("@#{target.to_s}.clear")
|
147
|
+
_clear(target)
|
148
|
+
rescue
|
149
|
+
end
|
150
|
+
end
|
122
151
|
end
|
123
152
|
|
124
153
|
def _get(label_or_name, cell)
|
data/spec/soroban_spec.rb
CHANGED
@@ -75,6 +75,32 @@ describe "Soroban" do
|
|
75
75
|
sheet.Z3.should eq('foo')
|
76
76
|
end
|
77
77
|
|
78
|
+
it "can retrieve bound variables in multiple ways" do
|
79
|
+
sheet.set(:A1 => 'life')
|
80
|
+
sheet.A1.should eq('life')
|
81
|
+
sheet.get(:A1).should eq('life')
|
82
|
+
sheet.get('A1').should eq('life')
|
83
|
+
sheet.A1 = 'death'
|
84
|
+
sheet.A1.should eq('death')
|
85
|
+
sheet.get(:A1).should eq('death')
|
86
|
+
sheet.get('A1').should eq('death')
|
87
|
+
sheet.bind(:foo => :A1)
|
88
|
+
sheet.set('foo' => 'win')
|
89
|
+
sheet.A1.should eq('win')
|
90
|
+
sheet.get(:A1).should eq('win')
|
91
|
+
sheet.get('A1').should eq('win')
|
92
|
+
sheet.foo.should eq('win')
|
93
|
+
sheet.get(:foo).should eq('win')
|
94
|
+
sheet.get('foo').should eq('win')
|
95
|
+
sheet.foo = 'lose'
|
96
|
+
sheet.A1.should eq('lose')
|
97
|
+
sheet.get(:A1).should eq('lose')
|
98
|
+
sheet.get('A1').should eq('lose')
|
99
|
+
sheet.foo.should eq('lose')
|
100
|
+
sheet.get(:foo).should eq('lose')
|
101
|
+
sheet.get('foo').should eq('lose')
|
102
|
+
end
|
103
|
+
|
78
104
|
it "can define new functions" do
|
79
105
|
Soroban::define :FOO => lambda { |a, b| 2 * a + b / 2 }
|
80
106
|
sheet.A1 = 7
|
@@ -111,4 +137,28 @@ describe "Soroban" do
|
|
111
137
|
sheet.A3.should eq(-20)
|
112
138
|
end
|
113
139
|
|
140
|
+
it "can calculate natural logarithms" do
|
141
|
+
sheet.set(:A1 => "=LN(#{Math::E})")
|
142
|
+
sheet.A1.should be_within(1e-6).of(1.0)
|
143
|
+
end
|
144
|
+
|
145
|
+
it "can calculate exponentials" do
|
146
|
+
sheet.set(:A1 => 1)
|
147
|
+
sheet.set(:A2 => "=EXP(A1)")
|
148
|
+
sheet.A2.should be_within(1e-6).of(Math::E)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "can use special symbols in values" do
|
152
|
+
sheet.set(:A1 => "> Threshold")
|
153
|
+
sheet.bind(:input => :A1)
|
154
|
+
sheet.get(:input).should eq('> Threshold')
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should use floating point numbers" do
|
158
|
+
sheet.set(:A1 => "20")
|
159
|
+
sheet.set(:A2 => "10")
|
160
|
+
sheet.set(:A3 => "=A2/A1")
|
161
|
+
sheet.A3.should be_within(1e-6).of(0.5)
|
162
|
+
end
|
163
|
+
|
114
164
|
end
|
metadata
CHANGED
@@ -1,90 +1,96 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: soroban
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 3
|
5
5
|
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 5
|
9
|
+
- 4
|
10
|
+
version: 0.5.4
|
6
11
|
platform: ruby
|
7
|
-
authors:
|
12
|
+
authors:
|
8
13
|
- Jason Hutchens
|
9
14
|
autorequire:
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
17
|
+
|
18
|
+
date: 2013-01-18 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
15
21
|
name: treetop
|
16
|
-
|
22
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
17
23
|
none: false
|
18
|
-
requirements:
|
24
|
+
requirements:
|
19
25
|
- - ~>
|
20
|
-
- !ruby/object:Gem::Version
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
hash: 19
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 4
|
31
|
+
- 10
|
21
32
|
version: 1.4.10
|
22
33
|
type: :runtime
|
34
|
+
requirement: *id001
|
23
35
|
prerelease: false
|
24
|
-
|
25
|
-
none: false
|
26
|
-
requirements:
|
27
|
-
- - ~>
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: 1.4.10
|
30
|
-
- !ruby/object:Gem::Dependency
|
36
|
+
- !ruby/object:Gem::Dependency
|
31
37
|
name: rubyXL
|
32
|
-
|
38
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
33
39
|
none: false
|
34
|
-
requirements:
|
40
|
+
requirements:
|
35
41
|
- - ~>
|
36
|
-
- !ruby/object:Gem::Version
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 17
|
44
|
+
segments:
|
45
|
+
- 1
|
46
|
+
- 2
|
47
|
+
- 7
|
37
48
|
version: 1.2.7
|
38
49
|
type: :development
|
50
|
+
requirement: *id002
|
39
51
|
prerelease: false
|
40
|
-
|
41
|
-
none: false
|
42
|
-
requirements:
|
43
|
-
- - ~>
|
44
|
-
- !ruby/object:Gem::Version
|
45
|
-
version: 1.2.7
|
46
|
-
- !ruby/object:Gem::Dependency
|
52
|
+
- !ruby/object:Gem::Dependency
|
47
53
|
name: nokogiri
|
48
|
-
|
54
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
49
55
|
none: false
|
50
|
-
requirements:
|
51
|
-
- -
|
52
|
-
- !ruby/object:Gem::Version
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 15
|
60
|
+
segments:
|
61
|
+
- 1
|
62
|
+
- 4
|
63
|
+
- 4
|
53
64
|
version: 1.4.4
|
54
65
|
type: :development
|
66
|
+
requirement: *id003
|
55
67
|
prerelease: false
|
56
|
-
|
57
|
-
none: false
|
58
|
-
requirements:
|
59
|
-
- - ! '>='
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: 1.4.4
|
62
|
-
- !ruby/object:Gem::Dependency
|
68
|
+
- !ruby/object:Gem::Dependency
|
63
69
|
name: rubyzip
|
64
|
-
|
70
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
65
71
|
none: false
|
66
|
-
requirements:
|
67
|
-
- -
|
68
|
-
- !ruby/object:Gem::Version
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 51
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
- 9
|
79
|
+
- 4
|
69
80
|
version: 0.9.4
|
70
81
|
type: :development
|
82
|
+
requirement: *id004
|
71
83
|
prerelease: false
|
72
|
-
|
73
|
-
none: false
|
74
|
-
requirements:
|
75
|
-
- - ! '>='
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
version: 0.9.4
|
78
|
-
description: Soroban makes it easy to extract and execute formulas from Excel spreadsheets.
|
79
|
-
It rewrites Excel formulas as Ruby expressions, and allows you to bind named variables
|
80
|
-
to spreadsheet cells to easily manipulate inputs and capture outputs.
|
84
|
+
description: Soroban makes it easy to extract and execute formulas from Excel spreadsheets. It rewrites Excel formulas as Ruby expressions, and allows you to bind named variables to spreadsheet cells to easily manipulate inputs and capture outputs.
|
81
85
|
email: jason.hutchens@agworld.com.au
|
82
86
|
executables: []
|
87
|
+
|
83
88
|
extensions: []
|
84
|
-
|
89
|
+
|
90
|
+
extra_rdoc_files:
|
85
91
|
- LICENSE.txt
|
86
92
|
- README.md
|
87
|
-
files:
|
93
|
+
files:
|
88
94
|
- .document
|
89
95
|
- .rspec
|
90
96
|
- .travis.yml
|
@@ -103,7 +109,9 @@ files:
|
|
103
109
|
- lib/soroban/functions.rb
|
104
110
|
- lib/soroban/functions/and.rb
|
105
111
|
- lib/soroban/functions/average.rb
|
112
|
+
- lib/soroban/functions/exp.rb
|
106
113
|
- lib/soroban/functions/if.rb
|
114
|
+
- lib/soroban/functions/ln.rb
|
107
115
|
- lib/soroban/functions/max.rb
|
108
116
|
- lib/soroban/functions/min.rb
|
109
117
|
- lib/soroban/functions/not.rb
|
@@ -127,31 +135,37 @@ files:
|
|
127
135
|
- spec/soroban_spec.rb
|
128
136
|
- spec/spec_helper.rb
|
129
137
|
homepage: https://github.com/agworld/soroban
|
130
|
-
licenses:
|
138
|
+
licenses:
|
131
139
|
- MIT
|
132
140
|
post_install_message:
|
133
141
|
rdoc_options: []
|
134
|
-
|
142
|
+
|
143
|
+
require_paths:
|
135
144
|
- lib
|
136
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
137
146
|
none: false
|
138
|
-
requirements:
|
139
|
-
- -
|
140
|
-
- !ruby/object:Gem::Version
|
141
|
-
|
142
|
-
segments:
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
hash: 3
|
151
|
+
segments:
|
143
152
|
- 0
|
144
|
-
|
145
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
|
+
version: "0"
|
154
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
155
|
none: false
|
147
|
-
requirements:
|
148
|
-
- -
|
149
|
-
- !ruby/object:Gem::Version
|
150
|
-
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
hash: 3
|
160
|
+
segments:
|
161
|
+
- 0
|
162
|
+
version: "0"
|
151
163
|
requirements: []
|
164
|
+
|
152
165
|
rubyforge_project:
|
153
166
|
rubygems_version: 1.8.24
|
154
167
|
signing_key:
|
155
168
|
specification_version: 3
|
156
169
|
summary: Soroban is a calculating engine that understands Excel formulas.
|
157
170
|
test_files: []
|
171
|
+
|