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