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 @@
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)"