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.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +26 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +22 -0
- data/README.md +360 -0
- data/Rakefile +1 -0
- data/calco.gemspec +23 -0
- data/examples/ages.ods +0 -0
- data/examples/compute_cells.rb +61 -0
- data/examples/data.csv +8 -0
- data/examples/example.rb +97 -0
- data/examples/multiplication_tables.ods +0 -0
- data/examples/multiplication_tables.rb +73 -0
- data/examples/register_function.rb +44 -0
- data/examples/using_date_functions.rb +42 -0
- data/examples/write_csv.rb +68 -0
- data/examples/write_ods.rb +69 -0
- data/lib/calco.rb +17 -0
- data/lib/calco/core_ext/fixnum.rb +22 -0
- data/lib/calco/core_ext/float.rb +22 -0
- data/lib/calco/core_ext/range.rb +15 -0
- data/lib/calco/core_ext/string.rb +20 -0
- data/lib/calco/date_functions.rb +13 -0
- data/lib/calco/definition_dsl.rb +127 -0
- data/lib/calco/elements/aggregator.rb +17 -0
- data/lib/calco/elements/builtin_function.rb +84 -0
- data/lib/calco/elements/constant.rb +31 -0
- data/lib/calco/elements/current.rb +19 -0
- data/lib/calco/elements/element.rb +31 -0
- data/lib/calco/elements/empty.rb +9 -0
- data/lib/calco/elements/formula.rb +42 -0
- data/lib/calco/elements/if.rb +26 -0
- data/lib/calco/elements/operation.rb +34 -0
- data/lib/calco/elements/operator.rb +17 -0
- data/lib/calco/elements/or.rb +26 -0
- data/lib/calco/elements/value_extractor.rb +42 -0
- data/lib/calco/elements/variable.rb +35 -0
- data/lib/calco/engines/calculator_builtin_functions.rb +32 -0
- data/lib/calco/engines/csv_engine.rb +80 -0
- data/lib/calco/engines/default_engine.rb +140 -0
- data/lib/calco/engines/office_engine.rb +263 -0
- data/lib/calco/engines/simple_calculator_engine.rb +151 -0
- data/lib/calco/math_functions.rb +9 -0
- data/lib/calco/sheet.rb +363 -0
- data/lib/calco/spreadsheet.rb +172 -0
- data/lib/calco/string_functions.rb +9 -0
- data/lib/calco/style.rb +15 -0
- data/lib/calco/time_functions.rb +12 -0
- data/lib/calco/version.rb +3 -0
- data/spec/absolute_references_spec.rb +86 -0
- data/spec/builtin_functions_spec.rb +161 -0
- data/spec/calculator_engine_spec.rb +251 -0
- data/spec/conditions_spec.rb +118 -0
- data/spec/content_change_spec.rb +190 -0
- data/spec/csv_engine_spec.rb +324 -0
- data/spec/default_engine_spec.rb +135 -0
- data/spec/definitions_spec.rb +65 -0
- data/spec/errors_spec.rb +189 -0
- data/spec/functions_spec.rb +251 -0
- data/spec/header_row_spec.rb +63 -0
- data/spec/range_spec.rb +189 -0
- data/spec/sheet_selections_spec.rb +49 -0
- data/spec/sheet_spec.rb +229 -0
- data/spec/smart_types_spec.rb +43 -0
- data/spec/spreadsheet_spec.rb +80 -0
- data/spec/styles_spec.rb +29 -0
- data/spec/variables_spec.rb +41 -0
- 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
|
data/lib/calco/style.rb
ADDED
@@ -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
|