calco 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4dff627df1a1920dd84f04840b38bd72f6ad821d
4
+ data.tar.gz: 53b3752b2a38c3549832f653572ccc093bb72916
5
+ SHA512:
6
+ metadata.gz: 53853ba1e0e3cbf02b859e99189cbc14c8f9dc0d5fdeed55336140630b538390d0d0c01b96d765eabdd6fb01adc1421c044a65ad67d288090219d52de526d670
7
+ data.tar.gz: 549d49ceb97b325170072cb6250984f01c80564cf7f675b3bd9de5feacd6a216c68a33adc1e8c5a9245eb8ae71cf46bf8d51b0d4e703607c273f9de33948e209
@@ -0,0 +1,34 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /lib/bundler/man/
26
+
27
+ # for a library or gem, you might want to ignore these files since the code is
28
+ # intended to run in multiple environments; otherwise, check them in:
29
+ # Gemfile.lock
30
+ # .ruby-version
31
+ # .ruby-gemset
32
+
33
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in calco.gemspec
4
+ gemspec
@@ -0,0 +1,26 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ calco (0.1.0)
5
+ rubyzip (~> 1.1.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.2.4)
11
+ rspec (2.14.1)
12
+ rspec-core (~> 2.14.0)
13
+ rspec-expectations (~> 2.14.0)
14
+ rspec-mocks (~> 2.14.0)
15
+ rspec-core (2.14.7)
16
+ rspec-expectations (2.14.3)
17
+ diff-lcs (>= 1.1.3, < 2.0)
18
+ rspec-mocks (2.14.4)
19
+ rubyzip (1.1.0)
20
+
21
+ PLATFORMS
22
+ ruby
23
+
24
+ DEPENDENCIES
25
+ calco!
26
+ rspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Jean Lazarou
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jean Lazarou
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,360 @@
1
+ # Calco
2
+
3
+ I started this project after spending time once again on code generating `CSV`
4
+ files that we open with a spreadsheet software, add formatting, calculations,
5
+ etc. The final spreadsheet document is a tool for the users with the current
6
+ data snapshot.
7
+
8
+ The code generating the `CSV` file can run several times but we have to manually
9
+ re-create the spreadsheet document.
10
+
11
+ *Calco* tries to separate the data from the spreadsheet presentation and the all
12
+ the calculations.
13
+
14
+ *Calco* implements a DSL (domain specific language) that abstracts the
15
+ calculations and the basic needs for styling and formatting.
16
+
17
+ It generates a spreadsheet document in different formats depending on the
18
+ selected engine.
19
+
20
+ The output depends on the engine. The office ([*LibreOffice*](https://www.libreoffice.org)
21
+ or [*OpenOffice*](http://www.openoffice.org/)) engine uses an input document as
22
+ template for more sophisticated layouts. The `DefaultEngine` writes simple text
23
+ useful to check the spreadsheet definition.
24
+
25
+ A spreadsheet contains one or more sheets.
26
+
27
+ A sheet contain cells (viewed as rows and columns).
28
+
29
+ A cell can contain:
30
+
31
+ * literal values (like numbers, dates, times or strings)
32
+ * references to other cells
33
+ * formulas or functions combining literal values, references, conditionals,
34
+ arithmetics expressions and calls to built-in functions
35
+
36
+ ## Installation
37
+
38
+ Add this line to your application's Gemfile:
39
+
40
+ ```ruby
41
+ gem 'calco'
42
+ ```
43
+
44
+ And then execute:
45
+
46
+ ```bash
47
+ $ bundle
48
+ ```
49
+
50
+ Or install it as:
51
+
52
+ ```bash
53
+ $ gem install calco
54
+ ```
55
+
56
+ ## Usage
57
+
58
+ ### Running the specification:
59
+
60
+ ```bash
61
+ $ rspec spec
62
+ ```
63
+
64
+ Try running specifications with format output...
65
+
66
+ ```bash
67
+ $ rspec -f d
68
+ ```
69
+
70
+ ### Running the examples:
71
+
72
+ ```bash
73
+ $ ruby examples/example.rb
74
+ ```
75
+
76
+ Replace the `example.rb` with any file of the example directory.
77
+
78
+ #### Example files (in order of *complexity*)
79
+
80
+ * [examples/using_date_functions.rb](examples/using_date_functions.rb)
81
+ * [examples/example.rb](examples/example.rb)
82
+ * [examples/multiplication_tables.rb](examples/multiplication_tables.rb)
83
+ * [examples/compute_cells.rb](examples/compute_cells.rb)
84
+ * [examples/write_csv.rb](examples/write_csv.rb)
85
+ * [examples/write_ods.rb](examples/write_ods.rb)
86
+ * [examples/register_function.rb](examples/register_function.rb)
87
+
88
+ ## Quick start
89
+
90
+ Let's review the [using_date_functions.rb](examples/using_date_functions.rb)
91
+ example.
92
+
93
+ The code must first require some files, like `date` as the example is using
94
+ dates.
95
+
96
+ ```ruby
97
+ require 'date'
98
+ require 'calco'
99
+ ```
100
+
101
+ Now, create the document definition.
102
+
103
+ ```ruby
104
+ doc = spreadsheet do
105
+
106
+ definitions do
107
+
108
+ set some_date: Date.today
109
+
110
+ function some_year: year(some_date)
111
+ function age: year(today) - year(some_date)
112
+
113
+ end
114
+
115
+ sheet do
116
+
117
+ column value_of(:some_date)
118
+
119
+ column :some_year
120
+ column :age
121
+
122
+ end
123
+
124
+ end
125
+ ```
126
+
127
+ The above code uses the spreadsheet method that returns a document created using
128
+ the given definition (the code block).
129
+
130
+ The definition contains two parts: the `definitions` and the `sheet`.
131
+
132
+ The *definitions* part contains all the *variable* declarations (see
133
+ `set some_date`) and the functions (see `some_year` and `age`).
134
+
135
+ The *sheet* part describes the content of the sheet columns. Here the first
136
+ columns is going to contain the value of the `some_date` variable and the next
137
+ two columns will contain the functions, so that the final spreadsheet is going
138
+ to compute the values using the functions/formulas.
139
+
140
+ The last part writes the result to the console (using the `$stdout` variable).
141
+
142
+ ```ruby
143
+ doc.save($stdout) do |spreadsheet|
144
+
145
+ sheet = spreadsheet.current
146
+
147
+ sheet[:some_date] = Date.new(1934, 10, 3)
148
+ sheet.write_row 3
149
+
150
+ sheet[:some_date] = Date.new(2004, 6, 19)
151
+ sheet.write_row 5
152
+
153
+ end
154
+ ```
155
+
156
+ Saving a document involves calling the `save` method with a block. The block
157
+ receives a spreadsheet object. A spreadsheet object has a current sheet. We can
158
+ assign values to the existing variables (see `sheet[:some_date]` statements) and
159
+ ask the spreadsheet to write a row (passing the index of the row), see
160
+ `sheet.write_row 3`.
161
+
162
+ The object passed to the block is the same as the one called to save, next code
163
+ is doing the same thing.
164
+
165
+ ```ruby
166
+ doc.save($stdout) do
167
+
168
+ sheet = doc.current
169
+
170
+ sheet[:some_date] = Date.new(1934, 10, 3)
171
+ sheet.write_row 3
172
+
173
+ sheet[:some_date] = Date.new(2004, 6, 19)
174
+ sheet.write_row 5
175
+
176
+ end
177
+ ```
178
+
179
+ The final output is:
180
+
181
+ A3: 1934-10-03
182
+ B3: YEAR(A3)
183
+ C3: YEAR(TODAY())-YEAR(A3)
184
+
185
+ A5: 2004-06-19
186
+ B5: YEAR(A5)
187
+ C5: YEAR(TODAY())-YEAR(A5)
188
+
189
+ ## Definitions
190
+
191
+ As explained in the previous example, building a spreadsheet object requires to
192
+ setup the definitions. What can a definition block contain?
193
+
194
+ First see the definitions specs: [spec/definitions_spec.rb](spec/definitions_spec.rb)
195
+
196
+ ### Values, variables, references and functions
197
+
198
+ See specifications
199
+
200
+ * [spec/variables_spec.rb](spec/variables_spec.rb)
201
+ * [spec/absolute_references_spec.rb](spec/absolute_references_spec.rb)
202
+ * [spec/conditions_spec.rb](spec/conditions_spec.rb)
203
+ * [spec/functions_spec.rb](spec/functions_spec.rb)
204
+ * [spec/errors_spec.rb](spec/errors_spec.rb)
205
+ * [spec/smart_types_spec.rb](spec/smart_types_spec.rb)
206
+ * [spec/builtin_functions_spec.rb](spec/builtin_functions_spec.rb)
207
+
208
+ ### Aggregations...
209
+
210
+ See specifications
211
+
212
+ * [spec/range_spec.rb](spec/range_spec.rb)
213
+
214
+ ### Sheet manipulation
215
+
216
+ See specifications
217
+
218
+ * [spec/sheet_spec.rb](spec/sheet_spec.rb)
219
+ * [spec/sheet_selections_spec.rb](spec/sheet_selections_spec.rb)
220
+ * [spec/spreadsheet_spec.rb](spec/spreadsheet_spec.rb)
221
+ * [spec/content_change_spec.rb](spec/content_change_spec.rb)
222
+
223
+ ### Styles
224
+
225
+ See specifications
226
+
227
+ * [spec/styles_spec.rb](spec/styles_spec.rb)
228
+
229
+ ### Engines
230
+
231
+ See specifications
232
+
233
+ * [spec/default_engine_spec.rb](spec/default_engine_spec.rb)
234
+ * [spec/csv_engine_spec.rb](spec/csv_engine_spec.rb)
235
+ * [spec/calculator_engine_spec.rb](spec/calculator_engine_spec.rb)
236
+
237
+ ## Tips
238
+
239
+ ### Titles row...
240
+
241
+ The words *title* or *header* to refer to the first row of a sheet that
242
+ represent the columns names or labels...
243
+
244
+ Because spreadsheets do not use row 0, row 0 (using the Sheet#row method)
245
+ returns headers or empty if no header is set (see
246
+ [spec/header_row_spec.rb](spec/header_row_spec.rb)).
247
+
248
+ A sheet is marked as having a header row (a first row with titles) by using
249
+ the `:title` option or the `has_title` method.
250
+
251
+ The use of the header row depends on the engine (the office engine does not
252
+ write the column titles).
253
+
254
+ The effect is that, if the sheet is marked as having a titles row, the data
255
+ output starts at index 2, remember 0 is the header row. Otherwise the data
256
+ starts at index 1. It is important because the formulas contain references like
257
+ `A<n>` and `n` must start at 1 if the first row does not contain headers.
258
+
259
+ ### Saving as CSV files
260
+
261
+ The CSV engine writes files containing formulas (functions) instead of computing
262
+ the values. Also, for values like dates and times, it uses function instead of
263
+ plain strings. If you open the CSV file with *LibreOffice* (or *OpenOffice*) it
264
+ recognizes the functions so that you do no loose the benefit of having
265
+ formulas/functions.
266
+
267
+ As an example the output of [examples/write_csv.rb](examples/write_csv.rb) is
268
+
269
+ ```
270
+ Start,End,Duration
271
+ "=TIMEVALUE(""12:10:00"")","=TIMEVALUE(""15:30:00"")",=B2-A2
272
+ "=TIMEVALUE(""11:00:00"")","=TIMEVALUE(""16:30:00"")",=B3-A3
273
+ "=TIMEVALUE(""10:01:00"")","=TIMEVALUE(""12:05:00"")",=B4-A4
274
+ "","",=SUM(C1:C4)
275
+ ```
276
+
277
+ If you open the file with *LibreOffice* you get something like
278
+
279
+ ```
280
+ Start End Duration
281
+ 12:10:00 15:30:00 03:20:00
282
+ 11:00:00 16:30:00 05:30:00
283
+ 10:01:00 12:05:00 02:04:00
284
+ 10:54:00
285
+ ```
286
+
287
+ The time values are real time values, not strings. The formulas are computed.
288
+
289
+ ## Todo
290
+
291
+ 0. office engine
292
+ * problems with UTF-8
293
+ 0. change styles when generating...
294
+ 2. specs for office engine
295
+ * currencies
296
+ * percentages
297
+ * time
298
+ * date, now
299
+ * absolute $A$5
300
+ 3. CSV engine (using the calculator)
301
+
302
+ ## Done
303
+
304
+ 1. specs: func not found, add func, func arity (also check error)
305
+ 2. build-in function, improve with function registration with types and args,
306
+ create `date_functions.rb`, etc.
307
+ 3. replace "Left" function implementation with build-in mechanism
308
+ 4. add example showing the function registration
309
+ 5. split into files...
310
+ 6. improve ugly code in 'element.rb)'
311
+ 7. refactor document 'save'
312
+ 8. 'DefaultEngine' should implement 'save' method
313
+ 9. add a 'calculator' engine (as an example)
314
+ 1. simple calculator engine should use internal context for computation
315
+ 1. specs for simple calculator engine
316
+ * string values
317
+ * with titles and function
318
+ * column title/names => used in output
319
+ * absolute cells reference
320
+ * skip cells
321
+ 12. spreadsheets have no row '0', 0 always refers to header row
322
+ 13. specs for errors
323
+ * err: unknown var
324
+ * err: unknown function
325
+ * err: assign the same var twice
326
+ * err: declare the function and var twice
327
+ * err: ArgumentError => `column :price, 'pp'` ('title:' missing)
328
+ * err: column id not found
329
+ 14. specs for sheet
330
+ * accept expressions like: `function actual_price: 1 + (tax_rate / 100)`
331
+ * last sheet is current
332
+ * setting current sheet => `doc.sheet("a").current`
333
+ 15. ids for columns
334
+ * err: id for column not found
335
+ 16. spec for CSV engine
336
+ * `=DOLLAR(A1,2)`
337
+ * `=SUM(A1:A3)`
338
+ * DOLLAR for formula cell
339
+ * DOLLAR and include conditional style `=DOLLAR((A3/100)+STYLE(IF(CURRENT()>3,"Red","Green")))`
340
+ * % type specification
341
+ 17. specs for Spreadsheet
342
+ * Spreadsheet#row(n) returns value of current sheet
343
+ 18. removed engine explicit dependency for Element(s), engine is injected by
344
+ sheets using Sheet#compile
345
+ 19. fixed elements generate methods that did not return values using engine
346
+ 20. spec for default engine
347
+ * skip columns, sheet#row returns ''
348
+ 21. API to change engine
349
+ 22. changed office example to use conditional styles
350
+ 23. added `empty_row` (in all engines)
351
+ 24. explained examples in top of files
352
+ 25. wrote a gem description, reused examples
353
+
354
+ ## Contributing
355
+
356
+ 1. Fork it
357
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
358
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
359
+ 4. Push to the branch (`git push origin my-new-feature`)
360
+ 5. Create new Pull Request