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,172 @@
1
+ require 'singleton'
2
+
3
+ require_relative 'sheet'
4
+ require_relative 'definition_dsl'
5
+ require_relative 'engines/default_engine'
6
+
7
+ module Calco
8
+
9
+ class Spreadsheet
10
+
11
+ def initialize engine = DefaultEngine.new
12
+ @engine = engine
13
+ @sheets = []
14
+ @sheets_by_name = {}
15
+ @named_styles = {}
16
+ @definitions = Definitions.new(self)
17
+ end
18
+
19
+ def save to_filename, &data_iterator
20
+ @engine.save self, to_filename, &data_iterator
21
+ end
22
+
23
+ def definitions &block
24
+
25
+ @block_def = block
26
+ @definitions.instance_eval(&block)
27
+
28
+ end
29
+
30
+ def sheet name = nil, &block
31
+
32
+ if @sheets_by_name[name]
33
+ @current = sheet = @sheets_by_name[name]
34
+ else
35
+
36
+ sheet = create_sheet(name)
37
+
38
+ @sheets << sheet
39
+
40
+ name = "Sheet #{@sheets.size}" unless name
41
+
42
+ @sheets_by_name[name] = sheet
43
+
44
+ end
45
+
46
+ sheet.instance_eval(&block) if block_given?
47
+ sheet.compile
48
+
49
+ self
50
+
51
+ end
52
+
53
+ def percentage_style name, decimal_places, min_digits, text
54
+ @named_styles[name] = {type: :percentage, decimal_places: decimal_places, min_digits: min_digits, text: text}
55
+ end
56
+
57
+ def cell_style name, other_styles = {}
58
+
59
+ other_styles[:type] = :cell
60
+
61
+ @named_styles[name] = other_styles
62
+
63
+ end
64
+
65
+ def column_style name, width
66
+ @named_styles[name] = {type: :column, width: width}
67
+ end
68
+
69
+ def row_style name, width
70
+ @named_styles[name] = {type: :row, height: width}
71
+ end
72
+
73
+ def table_style name, styles = {}
74
+
75
+ @named_styles[:type] = :table
76
+
77
+ @named_styles[name] = styles
78
+
79
+ end
80
+
81
+ def row row_number
82
+ @current.row row_number
83
+ end
84
+
85
+ def current name_or_index = nil
86
+
87
+ if name_or_index
88
+ @current = find_sheet!(name_or_index)
89
+ elsif @current
90
+ @current
91
+ else
92
+ sheet
93
+ @current
94
+ end
95
+
96
+ end
97
+
98
+ def current? name_or_index
99
+ @current == find_sheet(name_or_index)
100
+ end
101
+
102
+ def [] name_or_index
103
+ find_sheet(name_or_index)
104
+ end
105
+
106
+ def find_sheet name_or_index
107
+
108
+ if name_or_index.is_a?(Fixnum)
109
+ @sheets[name_or_index]
110
+ else
111
+ @sheets_by_name[name_or_index]
112
+ end
113
+
114
+ end
115
+
116
+ def find_sheet! name_or_index
117
+
118
+ sheet = find_sheet(name_or_index)
119
+
120
+ raise "Unknown sheet '#{name_or_index}'" unless sheet
121
+
122
+ sheet
123
+
124
+ end
125
+
126
+ def row_to_hash row_number
127
+
128
+ hash = {}
129
+
130
+ cells = row(row_number)
131
+
132
+ cells.each_index do |i|
133
+
134
+ cell = cells[i]
135
+
136
+ hash[COLUMNS[i]] = cell
137
+
138
+ end
139
+
140
+ hash
141
+
142
+ end
143
+
144
+ def engine= new_engine
145
+
146
+ @engine = new_engine
147
+
148
+ @sheets.each do |sheet|
149
+ sheet.compile @engine
150
+ end
151
+
152
+ end
153
+
154
+ private
155
+
156
+ def create_sheet name
157
+
158
+ definitions = Definitions.new(self)
159
+
160
+ definitions.instance_eval(&@block_def) if @block_def
161
+
162
+ @current = Sheet.new(name, @engine)
163
+ @current.definitions = definitions
164
+ @current.owner = self
165
+
166
+ @current
167
+
168
+ end
169
+
170
+ end
171
+
172
+ end
@@ -0,0 +1,9 @@
1
+ module Calco
2
+
3
+ class BuiltinFunction
4
+
5
+ declare :left, 2, String
6
+
7
+ end
8
+
9
+ end
@@ -0,0 +1,15 @@
1
+ module Calco
2
+
3
+ class Style
4
+
5
+ def initialize statement
6
+ @statement = statement
7
+ end
8
+
9
+ def generate row
10
+ @engine.style(@statement, row)
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,12 @@
1
+ require 'time'
2
+
3
+ module Calco
4
+
5
+ class BuiltinFunction
6
+
7
+ declare :timevalue, 1, Time
8
+ declare :now, 0, Date
9
+
10
+ end
11
+
12
+ end
@@ -0,0 +1,3 @@
1
+ module Calco
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,86 @@
1
+
2
+ require 'calco'
3
+
4
+ describe 'Absolute references' do
5
+
6
+ it "supports absolute references" do
7
+
8
+ doc = spreadsheet do
9
+
10
+ definitions do
11
+
12
+ set price: 14.4
13
+ set tax: 13
14
+
15
+ function total: price * (tax / 100 + 1)
16
+
17
+ end
18
+
19
+ sheet do
20
+
21
+ column value_of(:price)
22
+ column skip
23
+ column :total
24
+
25
+ column skip
26
+ column skip
27
+
28
+ column value_of(:tax, :absolute)
29
+
30
+ tax.absolute_row = 1
31
+
32
+ end
33
+
34
+ end
35
+
36
+ row = doc.row(1)
37
+
38
+ expect(row[0]).to eq('14.4')
39
+
40
+ expect(row[2]).to eq('A1*(($F$1/100)+1)')
41
+
42
+ expect(row[5]).to eq('13')
43
+
44
+ end
45
+
46
+ it "only sets cell content for the absolute row" do
47
+
48
+ doc = spreadsheet do
49
+
50
+ definitions do
51
+
52
+ set tax: 13
53
+ set some_val: 77
54
+
55
+ end
56
+
57
+ sheet do
58
+
59
+ column value_of(:tax, :absolute)
60
+ column value_of(:some_val)
61
+ column :some_val
62
+
63
+ tax.absolute_row = 7
64
+
65
+ end
66
+
67
+ end
68
+
69
+ row = doc.row(1)
70
+ expect(row[0]).to be_nil
71
+ expect(row[1]).to eq('77')
72
+ expect(row[2]).to eq("C1")
73
+
74
+ row = doc.row(7)
75
+ expect(row[0]).to eq('13')
76
+ expect(row[1]).to eq('77')
77
+ expect(row[2]).to eq("C7")
78
+
79
+ row = doc.row(8)
80
+ expect(row[0]).to be_nil
81
+ expect(row[1]).to eq('77')
82
+ expect(row[2]).to eq("C8")
83
+
84
+ end
85
+
86
+ end
@@ -0,0 +1,161 @@
1
+ require 'calco'
2
+
3
+ describe 'Spreadsheet built-in functions' do
4
+
5
+ it "supports 'LEFT'" do
6
+
7
+ doc = spreadsheet do
8
+
9
+ definitions do
10
+
11
+ set name: "Miles"
12
+
13
+ function initial: left(name, 1)
14
+
15
+ end
16
+
17
+ sheet do
18
+
19
+ column value_of(:name), title: 'Name'
20
+ column :initial, title: 'Initial'
21
+
22
+ end
23
+
24
+ end
25
+
26
+ row = doc.row(1)
27
+
28
+ expect(row[1]).to eq('LEFT(A1, 1)')
29
+
30
+ end
31
+
32
+ it "supports 'TODAY' and 'YEAR'" do
33
+
34
+ doc = spreadsheet do
35
+
36
+ definitions do
37
+
38
+ set birth_date: "2004-09-18"
39
+
40
+ function age: year(today()) - year(birth_date)
41
+
42
+ end
43
+
44
+ sheet do
45
+
46
+ column value_of(:birth_date), title: 'Birth date'
47
+ column :age, title: 'Age'
48
+
49
+ end
50
+
51
+ end
52
+
53
+ row = doc.row(1)
54
+
55
+ expect(row[1]).to eq('YEAR(TODAY())-YEAR(A1)')
56
+
57
+ end
58
+
59
+ it "registers new functions" do
60
+
61
+ Calco::BuiltinFunction.declare :my_func, 0, Integer
62
+
63
+ doc = spreadsheet do
64
+
65
+ definitions do
66
+ function x: my_func
67
+ end
68
+
69
+ sheet do
70
+ column :x
71
+ end
72
+
73
+ end
74
+
75
+ row = doc.row(1)
76
+
77
+ expect(row[0]).to eq('MY_FUNC()')
78
+
79
+ end
80
+
81
+ it "complains if function does not exist" do
82
+
83
+ expect {
84
+
85
+ spreadsheet do
86
+
87
+ definitions do
88
+ function x: my_func(13)
89
+ end
90
+
91
+ end
92
+
93
+ }.to raise_error("Unknown function or variable 'my_func'")
94
+
95
+
96
+ end
97
+
98
+ it "complains if wrong number of arguments" do
99
+
100
+ Calco::BuiltinFunction.declare :my_func, 0, Integer
101
+
102
+ expect {
103
+
104
+ spreadsheet do
105
+
106
+ definitions do
107
+
108
+ function x: my_func(13)
109
+
110
+ end
111
+
112
+ end
113
+
114
+ }.to raise_error(ArgumentError, "Function MY_FUNC requires 0, was 1 ([13])")
115
+
116
+
117
+ end
118
+
119
+ it "complains if missing arguments" do
120
+
121
+ Calco::BuiltinFunction.declare :my_func, 2, Integer
122
+
123
+ expect {
124
+
125
+ spreadsheet do
126
+
127
+ definitions do
128
+
129
+ function x: my_func(13)
130
+
131
+ end
132
+
133
+ end
134
+
135
+ }.to raise_error(ArgumentError, "Function MY_FUNC requires 2, was 1 ([13])")
136
+
137
+
138
+ end
139
+
140
+ it "accepts variable arity for functions" do
141
+
142
+ Calco::BuiltinFunction.declare :my_func, :n, Integer
143
+
144
+ spreadsheet do
145
+
146
+ definitions do
147
+
148
+ function x: my_func(13, 12, 3)
149
+ function y: my_func
150
+
151
+ end
152
+
153
+ end
154
+
155
+ end
156
+
157
+ after do
158
+ Calco::BuiltinFunction.undeclare :my_func
159
+ end
160
+
161
+ end