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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +34 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +26 -0
  5. data/LICENSE +21 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +360 -0
  8. data/Rakefile +1 -0
  9. data/calco.gemspec +23 -0
  10. data/examples/ages.ods +0 -0
  11. data/examples/compute_cells.rb +61 -0
  12. data/examples/data.csv +8 -0
  13. data/examples/example.rb +97 -0
  14. data/examples/multiplication_tables.ods +0 -0
  15. data/examples/multiplication_tables.rb +73 -0
  16. data/examples/register_function.rb +44 -0
  17. data/examples/using_date_functions.rb +42 -0
  18. data/examples/write_csv.rb +68 -0
  19. data/examples/write_ods.rb +69 -0
  20. data/lib/calco.rb +17 -0
  21. data/lib/calco/core_ext/fixnum.rb +22 -0
  22. data/lib/calco/core_ext/float.rb +22 -0
  23. data/lib/calco/core_ext/range.rb +15 -0
  24. data/lib/calco/core_ext/string.rb +20 -0
  25. data/lib/calco/date_functions.rb +13 -0
  26. data/lib/calco/definition_dsl.rb +127 -0
  27. data/lib/calco/elements/aggregator.rb +17 -0
  28. data/lib/calco/elements/builtin_function.rb +84 -0
  29. data/lib/calco/elements/constant.rb +31 -0
  30. data/lib/calco/elements/current.rb +19 -0
  31. data/lib/calco/elements/element.rb +31 -0
  32. data/lib/calco/elements/empty.rb +9 -0
  33. data/lib/calco/elements/formula.rb +42 -0
  34. data/lib/calco/elements/if.rb +26 -0
  35. data/lib/calco/elements/operation.rb +34 -0
  36. data/lib/calco/elements/operator.rb +17 -0
  37. data/lib/calco/elements/or.rb +26 -0
  38. data/lib/calco/elements/value_extractor.rb +42 -0
  39. data/lib/calco/elements/variable.rb +35 -0
  40. data/lib/calco/engines/calculator_builtin_functions.rb +32 -0
  41. data/lib/calco/engines/csv_engine.rb +80 -0
  42. data/lib/calco/engines/default_engine.rb +140 -0
  43. data/lib/calco/engines/office_engine.rb +263 -0
  44. data/lib/calco/engines/simple_calculator_engine.rb +151 -0
  45. data/lib/calco/math_functions.rb +9 -0
  46. data/lib/calco/sheet.rb +363 -0
  47. data/lib/calco/spreadsheet.rb +172 -0
  48. data/lib/calco/string_functions.rb +9 -0
  49. data/lib/calco/style.rb +15 -0
  50. data/lib/calco/time_functions.rb +12 -0
  51. data/lib/calco/version.rb +3 -0
  52. data/spec/absolute_references_spec.rb +86 -0
  53. data/spec/builtin_functions_spec.rb +161 -0
  54. data/spec/calculator_engine_spec.rb +251 -0
  55. data/spec/conditions_spec.rb +118 -0
  56. data/spec/content_change_spec.rb +190 -0
  57. data/spec/csv_engine_spec.rb +324 -0
  58. data/spec/default_engine_spec.rb +135 -0
  59. data/spec/definitions_spec.rb +65 -0
  60. data/spec/errors_spec.rb +189 -0
  61. data/spec/functions_spec.rb +251 -0
  62. data/spec/header_row_spec.rb +63 -0
  63. data/spec/range_spec.rb +189 -0
  64. data/spec/sheet_selections_spec.rb +49 -0
  65. data/spec/sheet_spec.rb +229 -0
  66. data/spec/smart_types_spec.rb +43 -0
  67. data/spec/spreadsheet_spec.rb +80 -0
  68. data/spec/styles_spec.rb +29 -0
  69. data/spec/variables_spec.rb +41 -0
  70. metadata +158 -0
@@ -0,0 +1,251 @@
1
+ require 'date'
2
+ require 'stringio'
3
+
4
+ require 'calco/engines/simple_calculator_engine'
5
+
6
+ module CalculatorSpecHelpers
7
+
8
+ def compute n = nil
9
+
10
+ buffer = StringIO.new
11
+
12
+ @doc.save(buffer) do |spreadsheet|
13
+
14
+ sheet = spreadsheet.current
15
+
16
+ if n == 0
17
+
18
+ sheet.write_row 0
19
+
20
+ elsif n
21
+
22
+ n.each {|i| sheet.write_row i}
23
+
24
+ else
25
+
26
+ sheet.write_row 1
27
+
28
+ end
29
+
30
+ end
31
+
32
+ @result = buffer.string.split("\n").map {|s| s.strip}
33
+
34
+ end
35
+
36
+ end
37
+
38
+ module Calco
39
+
40
+ describe SimpleCalculatorEngine do
41
+
42
+ include CalculatorSpecHelpers
43
+
44
+ it "does not return header" do
45
+
46
+ create_spreadsheet :default
47
+
48
+ compute 0
49
+
50
+ @result.should be_empty
51
+
52
+ end
53
+
54
+ it "computes simple function" do
55
+
56
+ create_spreadsheet :default
57
+
58
+ compute
59
+
60
+ @result.last.should == "C1 = 20"
61
+
62
+ end
63
+
64
+ it "uses variable names as names" do
65
+
66
+ create_spreadsheet :default
67
+
68
+ compute
69
+
70
+ @result == ["price = 10", "quantity = 2", "C0 = 20"]
71
+
72
+ end
73
+
74
+ it "uses titles as names" do
75
+
76
+ create_spreadsheet :with_title
77
+
78
+ compute
79
+
80
+ @result.should == ["price = 10", "quantity = 2", "P*Q = 20"]
81
+
82
+ end
83
+
84
+ it "computes sheets with absolute cell references" do
85
+
86
+ create_spreadsheet :with_title, :add_absolute
87
+
88
+ compute 1..3
89
+
90
+ @result[3].should == "tax = 100"
91
+
92
+ @result[11].should == "price = 10"
93
+ @result[12].should == "quantity = 2"
94
+ @result[13].should == "P*Q = 20"
95
+ @result[14].should == "F3 = 120"
96
+
97
+ end
98
+
99
+ it "uses string values" do
100
+
101
+ @doc = spreadsheet(@engine) do
102
+
103
+ definitions do
104
+
105
+ set first_name: "John"
106
+ set last_name: "Smith"
107
+
108
+ function name: first_name + " " + last_name
109
+
110
+ end
111
+
112
+ sheet do
113
+
114
+ column value_of(:first_name)
115
+ column value_of(:last_name)
116
+
117
+ column :name, title: 'name'
118
+
119
+ end
120
+
121
+ end
122
+
123
+ compute
124
+
125
+ @result.last.should == 'name = "John Smith"'
126
+
127
+ end
128
+
129
+ before do
130
+ @engine = Calco::SimpleCalculatorEngine.new
131
+ end
132
+
133
+ def create_spreadsheet which, add_absolute_row = false
134
+
135
+ @doc = spreadsheet(@engine) do
136
+
137
+ definitions do
138
+
139
+ set price: 10
140
+ set quantity: 2
141
+
142
+ function total: price * quantity
143
+
144
+ if add_absolute_row == :add_absolute
145
+
146
+ set tax: 100
147
+
148
+ function more: total + tax
149
+
150
+ end
151
+
152
+ end
153
+
154
+ sheet do
155
+
156
+ column value_of(:price)
157
+ column value_of(:quantity)
158
+
159
+ column :total if which == :default
160
+ column :total, title: 'P*Q' if which == :with_title
161
+
162
+ if add_absolute_row == :add_absolute
163
+
164
+ column skip
165
+
166
+ column value_of(:tax, :absolute)
167
+ column :more
168
+
169
+ tax.absolute_row = 1
170
+
171
+ end
172
+
173
+ end
174
+
175
+ end
176
+
177
+ end
178
+
179
+ end
180
+
181
+ describe SimpleCalculatorEngine do
182
+
183
+ include CalculatorSpecHelpers
184
+
185
+ it "computes functions with today and year" do
186
+
187
+ @doc = spreadsheet(@engine) do
188
+
189
+ definitions do
190
+
191
+ set some_date: "2013/09/06"
192
+
193
+ function some_year: year(some_date)
194
+ function today: year(today)
195
+
196
+ end
197
+
198
+ sheet do
199
+
200
+ column value_of(:some_date)
201
+
202
+ column :some_year
203
+ column :today
204
+
205
+ end
206
+
207
+ end
208
+
209
+ compute
210
+
211
+ year = Date.today.year
212
+
213
+ @result.should == ['some_date = "2013-09-06"', "B1 = 2013", "C1 = #{year}"]
214
+
215
+ end
216
+
217
+ it "computes functions with left" do
218
+
219
+ @doc = spreadsheet(@engine) do
220
+
221
+ definitions do
222
+
223
+ set name: "James"
224
+
225
+ function initials: left(name, 1)
226
+
227
+ end
228
+
229
+ sheet do
230
+
231
+ column value_of(:name)
232
+
233
+ column :initials
234
+
235
+ end
236
+
237
+ end
238
+
239
+ compute
240
+
241
+ @result.should == ['name = "James"', 'B1 = "J"']
242
+
243
+ end
244
+
245
+ before do
246
+ @engine = Calco::SimpleCalculatorEngine.new
247
+ end
248
+
249
+ end
250
+
251
+ end
@@ -0,0 +1,118 @@
1
+ require 'calco'
2
+
3
+ describe "Spreadsheet with conditions" do
4
+
5
+ it "supports conditional functions" do
6
+
7
+ doc = spreadsheet do
8
+
9
+ definitions do
10
+
11
+ set score: 23
12
+ function opinion: _if(score < 20, 'Oops...', 'Great!')
13
+
14
+ end
15
+
16
+ sheet do
17
+
18
+ column value_of(:score)
19
+ column :opinion
20
+
21
+ end
22
+
23
+ end
24
+
25
+ row = doc.row(1)
26
+
27
+ expect(row[0]).to eq('23')
28
+ expect(row[1]).to eq('IF(A1<20;"Oops...";"Great!")')
29
+
30
+ end
31
+
32
+ it "supports conditions with strings" do
33
+
34
+ doc = spreadsheet do
35
+
36
+ definitions do
37
+
38
+ set who: "John"
39
+ function bonus: _if(who == 'John', 200, 0)
40
+
41
+ end
42
+
43
+ sheet do
44
+
45
+ column value_of(:who)
46
+ column :bonus
47
+
48
+ end
49
+
50
+ end
51
+
52
+ row = doc.row(1)
53
+
54
+ expect(row[0]).to eq('"John"')
55
+ expect(row[1]).to eq('IF(A1="John";200;0)')
56
+
57
+ end
58
+
59
+ it "accepts conditions within conditions" do
60
+
61
+ doc = spreadsheet do
62
+
63
+ definitions do
64
+
65
+ set age: 34
66
+ set who: "John"
67
+
68
+ function bonus: _if(who == 'John', _if(age > 25, 200, 0), 10)
69
+
70
+ end
71
+
72
+ sheet do
73
+
74
+ column value_of(:who)
75
+ column value_of(:age)
76
+
77
+ column :bonus
78
+
79
+ end
80
+
81
+ end
82
+
83
+ row = doc.row(1)
84
+
85
+ expect(row[0]).to eq('"John"')
86
+ expect(row[1]).to eq('34')
87
+ expect(row[2]).to eq('IF(A1="John";IF(B1>25;200;0);10)')
88
+
89
+ end
90
+
91
+ it "supports conditions with or" do
92
+
93
+ doc = spreadsheet do
94
+
95
+ definitions do
96
+
97
+ set who: "John"
98
+ function bonus: _if(_or(who == 'John', who == 'Mary'), 200, 0)
99
+
100
+ end
101
+
102
+ sheet do
103
+
104
+ column value_of(:who)
105
+ column :bonus
106
+
107
+ end
108
+
109
+ end
110
+
111
+ row = doc.row(1)
112
+
113
+ expect(row[0]).to eq('"John"')
114
+ expect(row[1]).to eq('IF(OR((A1="John");(A1="Mary"));200;0)')
115
+
116
+ end
117
+
118
+ end
@@ -0,0 +1,190 @@
1
+ require 'calco'
2
+
3
+ RSpec.configure do |c|
4
+ c.alias_example_to :change_by
5
+ end
6
+
7
+ describe 'Content change' do
8
+
9
+ change_by "replacing content with a function" do
10
+
11
+ doc = spreadsheet do
12
+
13
+ definitions do
14
+
15
+ set empty: ''
16
+ set quantity: 17
17
+
18
+ function double: quantity * 2
19
+
20
+ end
21
+
22
+ sheet do
23
+
24
+ column value_of(:quantity)
25
+ column value_of(:empty), :id => :here
26
+
27
+ end
28
+
29
+ end
30
+
31
+ sheet = doc.current
32
+
33
+ sheet.replace_content :here, :double
34
+
35
+ row = sheet.row(2)
36
+ expect(row[0]).to eq('17')
37
+ expect(row[1]).to eq("A2*2")
38
+
39
+ end
40
+
41
+ change_by "replacing content with a variable" do
42
+
43
+ doc = spreadsheet do
44
+
45
+ definitions do
46
+
47
+ set yes: 'Yes'
48
+ set empty: ''
49
+ set quantity: 17
50
+
51
+ end
52
+
53
+ sheet do
54
+
55
+ column value_of(:quantity)
56
+ column value_of(:empty), :id => :here
57
+
58
+ end
59
+
60
+ end
61
+
62
+ sheet = doc.current
63
+
64
+ sheet.replace_content :here, :yes
65
+
66
+ row = sheet.row(2)
67
+ expect(row[0]).to eq('17')
68
+ expect(row[1]).to eq('"Yes"')
69
+
70
+ end
71
+
72
+ change_by "replacing content with a value" do
73
+
74
+ doc = spreadsheet do
75
+
76
+ definitions do
77
+
78
+ set yes: 'Yes'
79
+ set empty: ''
80
+ set quantity: 17
81
+
82
+ end
83
+
84
+ sheet do
85
+
86
+ column value_of(:quantity)
87
+ column value_of(:empty), :id => :here
88
+
89
+ end
90
+
91
+ end
92
+
93
+ sheet = doc.current
94
+
95
+ sheet.replace_content :here, 32
96
+
97
+ row = sheet.row(2)
98
+ expect(row[0]).to eq('17')
99
+ expect(row[1]).to eq("32")
100
+
101
+ end
102
+
103
+ change_by "changing and resetting content" do
104
+
105
+ doc = spreadsheet do
106
+
107
+ definitions do
108
+
109
+ set empty: ''
110
+ set quantity: 17
111
+
112
+ function double: quantity * 2
113
+ function triple: quantity * 3
114
+
115
+ end
116
+
117
+ sheet do
118
+
119
+ column value_of(:quantity)
120
+ column value_of(:empty), :id => :here
121
+ column value_of(:empty), :id => :here_also
122
+
123
+ end
124
+
125
+ end
126
+
127
+ sheet = doc.current
128
+
129
+ row = sheet.row(1)
130
+ expect(row[0]).to eq('17')
131
+ expect(row[1]).to eq('""')
132
+ expect(row[2]).to eq('""')
133
+
134
+ sheet.replace_content :here, :double
135
+ sheet.replace_content :here_also, :triple
136
+
137
+ row = sheet.row(2)
138
+ expect(row[0]).to eq('17')
139
+ expect(row[1]).to eq("A2*2")
140
+ expect(row[2]).to eq("A2*3")
141
+
142
+ sheet.restore_content
143
+
144
+ sheet[:empty] = "Yes"
145
+
146
+ row = sheet.row(3)
147
+ expect(row[0]).to eq('17')
148
+ expect(row[1]).to eq('"Yes"')
149
+ expect(row[2]).to eq('"Yes"')
150
+
151
+ end
152
+
153
+ change_by "mass-replacing content with values and nil values" do
154
+
155
+ doc = spreadsheet do
156
+
157
+ definitions do
158
+
159
+ set yes: 'Yes'
160
+ set empty: ''
161
+ set quantity: 17
162
+
163
+ end
164
+
165
+ sheet do
166
+
167
+ column value_of(:quantity)
168
+ column value_of(:empty), :id => :one
169
+ column value_of(:yes)
170
+ column value_of(:empty), :id => :two
171
+ column value_of(:quantity), :id => :three
172
+
173
+ end
174
+
175
+ end
176
+
177
+ sheet = doc.current
178
+
179
+ sheet.replace_and_clear :one => 19, :three => 7
180
+
181
+ row = sheet.row(2)
182
+ expect(row[0]).to eq('')
183
+ expect(row[1]).to eq("19")
184
+ expect(row[2]).to eq('')
185
+ expect(row[3]).to eq('')
186
+ expect(row[4]).to eq('7')
187
+
188
+ end
189
+
190
+ end