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,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