calco 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.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +26 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +22 -0
- data/README.md +360 -0
- data/Rakefile +1 -0
- data/calco.gemspec +23 -0
- data/examples/ages.ods +0 -0
- data/examples/compute_cells.rb +61 -0
- data/examples/data.csv +8 -0
- data/examples/example.rb +97 -0
- data/examples/multiplication_tables.ods +0 -0
- data/examples/multiplication_tables.rb +73 -0
- data/examples/register_function.rb +44 -0
- data/examples/using_date_functions.rb +42 -0
- data/examples/write_csv.rb +68 -0
- data/examples/write_ods.rb +69 -0
- data/lib/calco.rb +17 -0
- data/lib/calco/core_ext/fixnum.rb +22 -0
- data/lib/calco/core_ext/float.rb +22 -0
- data/lib/calco/core_ext/range.rb +15 -0
- data/lib/calco/core_ext/string.rb +20 -0
- data/lib/calco/date_functions.rb +13 -0
- data/lib/calco/definition_dsl.rb +127 -0
- data/lib/calco/elements/aggregator.rb +17 -0
- data/lib/calco/elements/builtin_function.rb +84 -0
- data/lib/calco/elements/constant.rb +31 -0
- data/lib/calco/elements/current.rb +19 -0
- data/lib/calco/elements/element.rb +31 -0
- data/lib/calco/elements/empty.rb +9 -0
- data/lib/calco/elements/formula.rb +42 -0
- data/lib/calco/elements/if.rb +26 -0
- data/lib/calco/elements/operation.rb +34 -0
- data/lib/calco/elements/operator.rb +17 -0
- data/lib/calco/elements/or.rb +26 -0
- data/lib/calco/elements/value_extractor.rb +42 -0
- data/lib/calco/elements/variable.rb +35 -0
- data/lib/calco/engines/calculator_builtin_functions.rb +32 -0
- data/lib/calco/engines/csv_engine.rb +80 -0
- data/lib/calco/engines/default_engine.rb +140 -0
- data/lib/calco/engines/office_engine.rb +263 -0
- data/lib/calco/engines/simple_calculator_engine.rb +151 -0
- data/lib/calco/math_functions.rb +9 -0
- data/lib/calco/sheet.rb +363 -0
- data/lib/calco/spreadsheet.rb +172 -0
- data/lib/calco/string_functions.rb +9 -0
- data/lib/calco/style.rb +15 -0
- data/lib/calco/time_functions.rb +12 -0
- data/lib/calco/version.rb +3 -0
- data/spec/absolute_references_spec.rb +86 -0
- data/spec/builtin_functions_spec.rb +161 -0
- data/spec/calculator_engine_spec.rb +251 -0
- data/spec/conditions_spec.rb +118 -0
- data/spec/content_change_spec.rb +190 -0
- data/spec/csv_engine_spec.rb +324 -0
- data/spec/default_engine_spec.rb +135 -0
- data/spec/definitions_spec.rb +65 -0
- data/spec/errors_spec.rb +189 -0
- data/spec/functions_spec.rb +251 -0
- data/spec/header_row_spec.rb +63 -0
- data/spec/range_spec.rb +189 -0
- data/spec/sheet_selections_spec.rb +49 -0
- data/spec/sheet_spec.rb +229 -0
- data/spec/smart_types_spec.rb +43 -0
- data/spec/spreadsheet_spec.rb +80 -0
- data/spec/styles_spec.rb +29 -0
- data/spec/variables_spec.rb +41 -0
- metadata +158 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'calco'
|
2
|
+
|
3
|
+
describe 'Header row' do
|
4
|
+
|
5
|
+
it "row[0] is an empty header/title row by default" do
|
6
|
+
|
7
|
+
doc = spreadsheet do
|
8
|
+
|
9
|
+
definitions do
|
10
|
+
|
11
|
+
set price: 14.4
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
sheet do
|
16
|
+
|
17
|
+
column value_of(:price)
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
row = doc.row(0)
|
24
|
+
|
25
|
+
expect(row).to be_empty
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
it "can be set with titles to columns" do
|
30
|
+
|
31
|
+
doc = spreadsheet do
|
32
|
+
|
33
|
+
definitions do
|
34
|
+
|
35
|
+
set price: 14.4
|
36
|
+
set product: 'USB 8Gb'
|
37
|
+
set tax: 13
|
38
|
+
set quantity: 30
|
39
|
+
|
40
|
+
function total: price * quantity * (tax / 100 + 1)
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
sheet do
|
45
|
+
|
46
|
+
column value_of(:product), title: 'Product'
|
47
|
+
column value_of(:price), title: 'Price'
|
48
|
+
column value_of(:quantity), title: 'Qty'
|
49
|
+
column value_of(:tax), title: 'Tax rate'
|
50
|
+
column skip
|
51
|
+
column :total, title: 'Total'
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
row = doc.row(0)
|
58
|
+
|
59
|
+
expect(row).to match_array(['Product', 'Price', 'Qty', 'Tax rate', nil, 'Total'])
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
data/spec/range_spec.rb
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'calco/engines/default_engine'
|
2
|
+
|
3
|
+
describe 'Spreadsheet' do
|
4
|
+
|
5
|
+
it "accepts column range" do
|
6
|
+
|
7
|
+
doc = spreadsheet do
|
8
|
+
|
9
|
+
definitions do
|
10
|
+
|
11
|
+
set quantity: 17
|
12
|
+
function total: sum(quantity[1..-1])
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
sheet do
|
17
|
+
|
18
|
+
column value_of(:quantity)
|
19
|
+
column :total
|
20
|
+
|
21
|
+
total.absolute_row = 1
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
sheet = doc.current
|
28
|
+
|
29
|
+
row = sheet.row(1)
|
30
|
+
expect(row[0]).to eq('17')
|
31
|
+
expect(row[1]).to eq("SUM(A1:A#{Calco::DefaultEngine.row_infinity})")
|
32
|
+
|
33
|
+
row = sheet.row(10)
|
34
|
+
expect(row[0]).to eq('17')
|
35
|
+
expect(row[1]).to be_nil
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
it "accepts range linked to variable" do
|
40
|
+
|
41
|
+
doc = spreadsheet do
|
42
|
+
|
43
|
+
definitions do
|
44
|
+
|
45
|
+
set quantity: 17
|
46
|
+
function total: sum(quantity[10..21])
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
sheet do
|
51
|
+
|
52
|
+
column value_of(:quantity)
|
53
|
+
column :total
|
54
|
+
|
55
|
+
total.absolute_row = 1
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
sheet = doc.current
|
62
|
+
|
63
|
+
row = sheet.row(1)
|
64
|
+
expect(row[0]).to eq('17')
|
65
|
+
expect(row[1]).to eq("SUM(A10:A21)")
|
66
|
+
|
67
|
+
row = sheet.row(10)
|
68
|
+
expect(row[0]).to eq('17')
|
69
|
+
expect(row[1]).to be_nil
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
it "accepts aggregate in same column with function" do
|
74
|
+
|
75
|
+
doc = spreadsheet do
|
76
|
+
|
77
|
+
definitions do
|
78
|
+
|
79
|
+
set unit_price: 33
|
80
|
+
set quantity: 17
|
81
|
+
|
82
|
+
function price: unit_price * quantity
|
83
|
+
function total: sum(price[(1..-1).as_grouping])
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
sheet do
|
88
|
+
|
89
|
+
column value_of(:unit_price), :id => :up
|
90
|
+
column value_of(:quantity), :id => :q
|
91
|
+
column :price, :id => :p
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
sheet = doc.current
|
98
|
+
|
99
|
+
5.times {|i| sheet.row(i);}
|
100
|
+
|
101
|
+
row = sheet.row(6)
|
102
|
+
expect(row[0]).to eq('33')
|
103
|
+
expect(row[1]).to eq('17')
|
104
|
+
expect(row[2]).to eq('A6*B6')
|
105
|
+
|
106
|
+
sheet.replace_content :up, nil
|
107
|
+
sheet.replace_content :q, nil
|
108
|
+
sheet.replace_content :p, :total
|
109
|
+
|
110
|
+
row = sheet.row(7)
|
111
|
+
expect(row[2]).to eq("SUM(C1:C6)")
|
112
|
+
|
113
|
+
expect(row[0]).to eq('')
|
114
|
+
expect(row[1]).to eq('')
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
it "accepts aggregate in same column" do
|
119
|
+
|
120
|
+
doc = spreadsheet do
|
121
|
+
|
122
|
+
definitions do
|
123
|
+
|
124
|
+
set quantity: 17
|
125
|
+
|
126
|
+
function sub_total: sum(quantity[(1..-1).as_grouping])
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
sheet do
|
131
|
+
|
132
|
+
column value_of(:quantity), :id => :here
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
sheet = doc.current
|
139
|
+
|
140
|
+
row = sheet.row(10)
|
141
|
+
expect(row[0]).to eq('17')
|
142
|
+
|
143
|
+
sheet.replace_content :here, :sub_total
|
144
|
+
|
145
|
+
row = sheet.row(11)
|
146
|
+
expect(row[0]).to eq("SUM(A1:A10)")
|
147
|
+
|
148
|
+
sheet.restore_content
|
149
|
+
|
150
|
+
row = sheet.row(12)
|
151
|
+
expect(row[0]).to eq('17')
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
it "accepts range reference to following column" do
|
156
|
+
|
157
|
+
doc = spreadsheet do
|
158
|
+
|
159
|
+
definitions do
|
160
|
+
|
161
|
+
set quantity: 17
|
162
|
+
function total: sum(quantity[1..-1])
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
sheet do
|
167
|
+
|
168
|
+
column :total
|
169
|
+
column value_of(:quantity)
|
170
|
+
|
171
|
+
total.absolute_row = 1
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
sheet = doc.current
|
178
|
+
|
179
|
+
row = sheet.row(1)
|
180
|
+
expect(row[0]).to eq("SUM(B1:B#{Calco::DefaultEngine.row_infinity})")
|
181
|
+
expect(row[1]).to eq('17')
|
182
|
+
|
183
|
+
row = sheet.row(10)
|
184
|
+
expect(row[0]).to be_nil
|
185
|
+
expect(row[1]).to eq('17')
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'calco'
|
2
|
+
|
3
|
+
describe "Sheet selection" do
|
4
|
+
|
5
|
+
it "a default sheet exists" do
|
6
|
+
|
7
|
+
doc = spreadsheet do
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
expect(doc.current).to be_a(Calco::Sheet)
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
it "last sheet is the current one" do
|
16
|
+
|
17
|
+
doc = spreadsheet do
|
18
|
+
|
19
|
+
sheet "a" do
|
20
|
+
end
|
21
|
+
|
22
|
+
sheet "b" do
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
expect(doc.current).to be(doc.sheet["b"])
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
it "selecting a sheet makes it current" do
|
32
|
+
|
33
|
+
doc = spreadsheet do
|
34
|
+
|
35
|
+
sheet "a" do
|
36
|
+
end
|
37
|
+
|
38
|
+
sheet "b" do
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
doc.sheet("a")
|
44
|
+
|
45
|
+
expect(doc.current).to eq(doc.sheet["a"])
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
data/spec/sheet_spec.rb
ADDED
@@ -0,0 +1,229 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
require 'calco'
|
4
|
+
|
5
|
+
describe "Sheet" do
|
6
|
+
|
7
|
+
it "produces one cell with some assigned literal" do
|
8
|
+
|
9
|
+
doc = spreadsheet do
|
10
|
+
sheet do
|
11
|
+
column 77
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
expect(doc.row(1)).to include('77')
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
it "produces one cell with the value of a variable" do
|
20
|
+
|
21
|
+
doc = spreadsheet do
|
22
|
+
|
23
|
+
definitions do
|
24
|
+
|
25
|
+
set name: 'John'
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
sheet do
|
30
|
+
|
31
|
+
column value_of(:name)
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
expect(doc.row(1)).to include('"John"')
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
it "produces a reference to a variable" do
|
42
|
+
|
43
|
+
doc = spreadsheet do
|
44
|
+
|
45
|
+
definitions do
|
46
|
+
|
47
|
+
set name: 'John'
|
48
|
+
function john: name
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
sheet do
|
53
|
+
|
54
|
+
column value_of(:name)
|
55
|
+
column :john
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
row = doc.row(1)
|
62
|
+
|
63
|
+
expect(row[0]).to eq('"John"')
|
64
|
+
expect(row[1]).to eq('A1')
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
it "produces references with row index" do
|
69
|
+
|
70
|
+
doc = spreadsheet do
|
71
|
+
|
72
|
+
definitions do
|
73
|
+
|
74
|
+
set name: 'John'
|
75
|
+
function john: name
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
sheet do
|
80
|
+
|
81
|
+
column value_of(:name)
|
82
|
+
column :john
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
sheet = doc.current
|
89
|
+
|
90
|
+
sheet[:name] = 'James'
|
91
|
+
|
92
|
+
row = sheet.row(4)
|
93
|
+
|
94
|
+
expect(row[0]).to eq('"James"')
|
95
|
+
expect(row[1]).to eq('A4')
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
it "accepts references to higher cells" do
|
100
|
+
|
101
|
+
doc = spreadsheet do
|
102
|
+
|
103
|
+
definitions do
|
104
|
+
|
105
|
+
set name: 'John'
|
106
|
+
function john: name
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
sheet do
|
111
|
+
|
112
|
+
column :john
|
113
|
+
column value_of(:name)
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
sheet = doc.current
|
120
|
+
|
121
|
+
sheet[:name] = 'James'
|
122
|
+
|
123
|
+
row = sheet.row(2)
|
124
|
+
|
125
|
+
expect(row[0]).to eq('B2')
|
126
|
+
expect(row[1]).to eq('"James"')
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
it "supports blank cells" do
|
131
|
+
|
132
|
+
doc = spreadsheet do
|
133
|
+
|
134
|
+
definitions do
|
135
|
+
|
136
|
+
set name: 'John'
|
137
|
+
function john: name
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
sheet do
|
142
|
+
|
143
|
+
column value_of(:name)
|
144
|
+
column skip
|
145
|
+
column :john
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
row = doc.row(1)
|
152
|
+
|
153
|
+
expect(row[0]).to eq('"John"')
|
154
|
+
expect(row[1]).to eq('')
|
155
|
+
expect(row[2]).to eq('A1')
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
it "implements #record_assign" do
|
160
|
+
|
161
|
+
doc = spreadsheet do
|
162
|
+
|
163
|
+
definitions do
|
164
|
+
|
165
|
+
set name: ''
|
166
|
+
set last_name: ''
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
sheet do
|
171
|
+
|
172
|
+
column value_of(:name)
|
173
|
+
column value_of(:last_name)
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
sheet = doc.current
|
180
|
+
|
181
|
+
sheet.record_assign :name => "John", :last_name => "Smith"
|
182
|
+
|
183
|
+
row = doc.row(1)
|
184
|
+
expect(row[0]).to eq('"John"')
|
185
|
+
expect(row[1]).to eq('"Smith"')
|
186
|
+
|
187
|
+
sheet.record_assign :name => "James", :last_name => "Black"
|
188
|
+
|
189
|
+
row = doc.row(2)
|
190
|
+
expect(row[0]).to eq('"James"')
|
191
|
+
expect(row[1]).to eq('"Black"')
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
it "implements #write_row with data" do
|
196
|
+
|
197
|
+
doc = spreadsheet do
|
198
|
+
|
199
|
+
definitions do
|
200
|
+
|
201
|
+
set name: ''
|
202
|
+
set last_name: ''
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
sheet do
|
207
|
+
|
208
|
+
column value_of(:name)
|
209
|
+
column value_of(:last_name)
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
out = StringIO.new
|
216
|
+
|
217
|
+
doc.save out do |spreadsheet|
|
218
|
+
|
219
|
+
sheet = spreadsheet.current
|
220
|
+
|
221
|
+
sheet.write_row 1, :name => "John", :last_name => "Smith"
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
expect(out.string).to eq(%|A1: "John"\nB1: "Smith"\n\n|)
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|