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 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'calco/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "calco"
8
+ gem.version = Calco::VERSION
9
+ gem.authors = ["Jean Lazarou"]
10
+ gem.email = ["jean.lazarou@alef1.org"]
11
+ gem.description = %q{Implements a DSL used to create and define the content of spreadsheet documents}
12
+ gem.summary = %q{DSL for spreadsheet documents}
13
+ gem.homepage = "https://github.com/jeanlazarou/calco"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency('rspec')
21
+
22
+ gem.add_dependency 'rubyzip', '~> 1.1.0'
23
+ end
Binary file
@@ -0,0 +1,61 @@
1
+ require 'calco/engines/simple_calculator_engine'
2
+
3
+ #
4
+ # Next example uses a SimpleCaclulatorEngine that does not produce a spreadsheet
5
+ # with formulas but computes the calculations instead.
6
+ #
7
+
8
+ engine = Calco::SimpleCalculatorEngine.new
9
+
10
+ doc = spreadsheet(engine) do
11
+
12
+ definitions do
13
+
14
+ set tax_rate: 0
15
+
16
+ set basic_price: 0
17
+ set quantity: 1
18
+
19
+ function price: (basic_price * quantity) * (1 + (tax_rate / 100.0))
20
+
21
+ end
22
+
23
+ sheet do
24
+
25
+ column value_of(:tax_rate, :absolute)
26
+
27
+ column value_of(:basic_price)
28
+ column value_of(:quantity)
29
+
30
+ column skip
31
+
32
+ column :price, title: 'price'
33
+
34
+ tax_rate.absolute_row = 1
35
+
36
+ end
37
+
38
+ end
39
+
40
+ data = [
41
+ [12, 3],
42
+ [10.3, 2],
43
+ [34, 4]
44
+ ]
45
+
46
+ doc.save($stdout) do |spreadsheet|
47
+
48
+ sheet = spreadsheet.current
49
+
50
+ sheet[:tax_rate] = 100
51
+
52
+ data.each_with_index do |item, i|
53
+
54
+ sheet[:basic_price] = item[0]
55
+ sheet[:quantity] = item[1]
56
+
57
+ sheet.write_row i + 1
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,8 @@
1
+ name,birth_date
2
+ Joe,2002/07/11
3
+ Max,1987/06/06
4
+ Lou,1998/11/26
5
+ Phil,2003/01/22
6
+ Jack,2000/02/02
7
+ Mary,1976/12/31
8
+ Louise,1994/09/3
@@ -0,0 +1,97 @@
1
+ require 'calco'
2
+
3
+ #
4
+ # Next example shows different uses with formulas (functions) and styles.
5
+ #
6
+ # It 'implements' a calculator that evaluates which of the train or the car
7
+ # is better, depending on several parameters.
8
+ #
9
+ # It prints out the definition.
10
+ # It then prints out a CSV format after changing the engine.
11
+ #
12
+
13
+ require 'calco/engines/csv_engine'
14
+
15
+ doc = spreadsheet do
16
+
17
+ definitions do
18
+
19
+ set distance: 20 # km
20
+ set fuel_price: 1.4 # €
21
+ set consumption: 6 # l/100km
22
+ set trip_duration: 50 # min
23
+
24
+ set train_price: 8 # €
25
+ set train_duration: 65 # min
26
+
27
+ function trip_consumption: consumption * (distance / 100)
28
+
29
+ function trip_price: consumption * fuel_price * (distance / 100)
30
+ function benefit: (train_price - trip_price) / train_price
31
+
32
+ function best_choice: _if(trip_duration < train_duration, 'car', 'train')
33
+
34
+ end
35
+
36
+ sheet do
37
+
38
+ column value_of(:distance), :title => 'km'
39
+ column value_of(:trip_duration), :title => 'min'
40
+ column skip
41
+
42
+ column value_of(:train_price), type: '$EUR', :title => 'train'
43
+ column value_of(:train_duration), :title => 'min'
44
+ column skip
45
+
46
+ column :trip_consumption, :title => 'L'
47
+ column :trip_price, style: _if(current > train_price, 'Red', 'default'), type: '$EUR', :title => 'price'
48
+ column :benefit, :type => '%', :title => 'benefit'
49
+ column skip
50
+
51
+ column :best_choice, :title => 'choice'
52
+ column skip
53
+
54
+ column value_of(:fuel_price), type: '$EUR', :title => 'fuel price (1L)'
55
+ column value_of(:consumption), :title => 'L/100km'
56
+
57
+ end
58
+
59
+ end
60
+
61
+ puts "Layout definition:"
62
+
63
+ doc.current[:distance] = 40
64
+ cells = doc.row(3)
65
+
66
+ cells.each_index do |i|
67
+
68
+ print " #{Calco::COLUMNS[i]}: "
69
+
70
+ cell = cells[i]
71
+
72
+ puts cell
73
+
74
+ end
75
+
76
+ puts "\nCSV output:"
77
+
78
+ data = [
79
+ {:distance => 40, :trip_duration => 30, :train_price => 10, :train_duration => 30, :fuel_price => 1.8, :consumption => 7},
80
+ {:distance => 25, :trip_duration => 30, :train_price => 19, :train_duration => 70, :fuel_price => 1.3, :consumption => 5.4},
81
+ ]
82
+
83
+ doc.engine = Calco::CSVEngine.new
84
+
85
+ doc.save($stdout) do |spreadsheet|
86
+
87
+ sheet = doc.current
88
+
89
+ sheet.write_row 0
90
+
91
+ data.each_with_index do |entry, i|
92
+
93
+ sheet.write_row i + 1, entry
94
+
95
+ end
96
+
97
+ end
@@ -0,0 +1,73 @@
1
+ require 'date'
2
+ require 'tmpdir'
3
+
4
+ #
5
+ # Next example shows the use of the office engine, no headers and empty row.
6
+ #
7
+ # It saves an office file in the temporary directory named "res.ods" and uses
8
+ # a template file named "multiplication_tables.ods"
9
+ #
10
+
11
+ require 'calco/engines/office_engine'
12
+
13
+ output_file = File.join(Dir.tmpdir, "res.ods")
14
+
15
+ def relative_path file
16
+ File.join(File.dirname(__FILE__), file)
17
+ end
18
+
19
+ engine = Calco::OfficeEngine.new(relative_path('multiplication_tables.ods'), false)
20
+
21
+ doc = spreadsheet(engine) do
22
+
23
+ definitions do
24
+
25
+ set multiplicand: 7
26
+ set multiplier: 0
27
+
28
+ function result: multiplicand * multiplier
29
+
30
+ end
31
+
32
+ sheet('x') do
33
+
34
+ column value_of(:multiplier)
35
+ column 'x'
36
+ column value_of(:multiplicand)
37
+ column '='
38
+
39
+ column :result
40
+
41
+ end
42
+
43
+ end
44
+
45
+ count = 0
46
+
47
+ doc.save(output_file) do |spreadsheet|
48
+
49
+ sheet = doc.current
50
+
51
+ 7.step(10) do |multiplicand|
52
+
53
+ sheet[:multiplicand] = multiplicand
54
+
55
+ 1.step(10) do |multiplier|
56
+
57
+ sheet[:multiplier] = multiplier
58
+
59
+ sheet.write_row count
60
+
61
+ count += 1
62
+
63
+ end
64
+
65
+ count += 1
66
+
67
+ sheet.empty_row
68
+
69
+ end
70
+
71
+ end
72
+
73
+ puts "Wrote #{output_file} (#{count} rows)"
@@ -0,0 +1,44 @@
1
+ require 'date'
2
+
3
+ require 'calco'
4
+
5
+ #
6
+ # Next example shows how to extend the DSL with new built-in functions.
7
+ # It outputs as simple text the resulting spreadsheet (two rows: the headers
8
+ # and one row of data)
9
+ #
10
+
11
+ # Next functions does not exist in a spreadsheet software.
12
+ # we only show how to declare functions that are missing...
13
+ Calco::BuiltinFunction.declare :now, 0, Integer
14
+ Calco::BuiltinFunction.declare :age, 1, Integer
15
+
16
+ doc = spreadsheet do
17
+
18
+ definitions do
19
+
20
+ set some_date: Date.today
21
+
22
+ function now: now
23
+ function age: age(some_date)
24
+
25
+ end
26
+
27
+ sheet do
28
+
29
+ column :now, :title => "Today"
30
+
31
+ column value_of(:some_date)
32
+ column :age, :title => "Age"
33
+
34
+ end
35
+
36
+ end
37
+
38
+ hash_printer = proc {|k, v| puts "#{k}: #{v}"}
39
+
40
+ doc.row_to_hash(0).each &hash_printer
41
+
42
+ doc.current[:some_date] = Date.new(1934, 10, 3)
43
+ puts
44
+ doc.row_to_hash(1).each &hash_printer
@@ -0,0 +1,42 @@
1
+ require 'date'
2
+
3
+ require 'calco'
4
+
5
+ #
6
+ # Next example uses date functions and outputs to the console the result as
7
+ # simple text.
8
+ #
9
+
10
+ doc = spreadsheet do
11
+
12
+ definitions do
13
+
14
+ set some_date: Date.today
15
+
16
+ function some_year: year(some_date)
17
+ function age: year(today) - year(some_date)
18
+
19
+ end
20
+
21
+ sheet do
22
+
23
+ column value_of(:some_date)
24
+
25
+ column :some_year
26
+ column :age
27
+
28
+ end
29
+
30
+ end
31
+
32
+ doc.save($stdout) do
33
+
34
+ sheet = doc.current
35
+
36
+ sheet[:some_date] = Date.new(1934, 10, 3)
37
+ sheet.write_row 3
38
+
39
+ sheet[:some_date] = Date.new(2004, 6, 19)
40
+ sheet.write_row 5
41
+
42
+ end
@@ -0,0 +1,68 @@
1
+ require 'time'
2
+
3
+ require 'calco/engines/csv_engine'
4
+
5
+ #
6
+ # Next example presents uses the CSV engine that outputs formulas and save time
7
+ # values as formulas. LibreOffice opens CSV files containing formulas and is
8
+ # able to apply them.
9
+ #
10
+ # The example shows how to define an aggregation function (sum) using a Range
11
+ # object marked with the as_grouping method (extension added by Calco to the
12
+ # Ruby Range class).
13
+ #
14
+ # The example uses the Sheet#replace_and_clear method that changes the
15
+ # definition set for some column.
16
+ #
17
+ # The spreadsheet is a kind ot timesheet.
18
+ #
19
+
20
+ doc = spreadsheet(Calco::CSVEngine.new) do
21
+
22
+ definitions do
23
+
24
+ set start_time: ''
25
+ set end_time: ''
26
+
27
+ function duration: end_time - start_time
28
+
29
+ function total: sum(duration[(1..-1).as_grouping])
30
+
31
+ end
32
+
33
+ sheet do
34
+
35
+ column value_of(:start_time), :title => "Start"
36
+ column value_of(:end_time), :title => "End"
37
+
38
+ column :duration, :title => "Duration", :id => :duration
39
+
40
+ end
41
+
42
+ end
43
+
44
+ data = [
45
+ {:start_time => Time.parse("12:10"), :end_time => Time.parse("15:30")},
46
+ {:start_time => Time.parse("11:00"), :end_time => Time.parse("16:30")},
47
+ {:start_time => Time.parse("10:01"), :end_time => Time.parse("12:05")},
48
+ ]
49
+
50
+ doc.save($stdout) do |spreadsheet|
51
+
52
+ sheet = doc.current
53
+
54
+ sheet.write_row 0
55
+
56
+ data.each_with_index do |entry, i|
57
+
58
+ sheet.write_row i + 1, entry
59
+
60
+ end
61
+
62
+ sheet.empty_row
63
+
64
+ sheet.replace_and_clear :duration => :total
65
+
66
+ sheet.write_row data.length + 1
67
+
68
+ end
@@ -0,0 +1,69 @@
1
+ require 'csv'
2
+ require 'date'
3
+ require 'tmpdir'
4
+
5
+ #
6
+ # Next example uses the use of the office engine and reads data from a CSV file.
7
+ #
8
+ # It saves an office file in the temporary directory named "res.ods" and uses
9
+ # a template file named "ages.ods"
10
+ #
11
+ # It shows the definition of a cell with a conditional style. Depending on the
12
+ # current value of the cell the style name "adult" or "default" is applied.
13
+ # The "adult" style should be defined in the template file.
14
+ #
15
+
16
+ require 'calco/engines/office_engine'
17
+
18
+ output_file = File.join(Dir.tmpdir, "res.ods")
19
+
20
+ def relative_path file
21
+ File.join(File.dirname(__FILE__), file)
22
+ end
23
+
24
+ engine = Calco::OfficeEngine.new(relative_path('ages.ods'))
25
+
26
+ doc = spreadsheet(engine) do
27
+
28
+ definitions do
29
+
30
+ set name: ''
31
+ set birth_date: ''
32
+
33
+ function age: year(today) - year(birth_date)
34
+
35
+ end
36
+
37
+ sheet('Main') do
38
+
39
+ has_titles true
40
+
41
+ column value_of(:name)
42
+ column value_of(:birth_date)
43
+
44
+ column :age, style: _if(current > 19, '"adult"', '"default"')
45
+
46
+ end
47
+
48
+ end
49
+
50
+ count = 0
51
+
52
+ doc.save(output_file) do |spreadsheet|
53
+
54
+ sheet = doc.current
55
+
56
+ CSV.foreach(relative_path('data.csv'), :headers => true) do |row|
57
+
58
+ count += 1
59
+
60
+ sheet[:name] = row[0]
61
+ sheet[:birth_date] = Date.parse(row[1])
62
+
63
+ sheet.write_row count
64
+
65
+ end
66
+
67
+ end
68
+
69
+ puts "Wrote #{output_file} (#{count} rows)"