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,17 @@
1
+ require "calco/version"
2
+
3
+ require "calco/spreadsheet"
4
+ require "calco/time_functions"
5
+ require "calco/date_functions"
6
+ require "calco/math_functions"
7
+ require "calco/string_functions"
8
+
9
+ def spreadsheet engine = Calco::DefaultEngine.new, &block
10
+
11
+ document = Calco::Spreadsheet.new(engine)
12
+
13
+ document.instance_eval(&block)
14
+
15
+ document
16
+
17
+ end
@@ -0,0 +1,22 @@
1
+ class Fixnum
2
+
3
+ alias_method '_calco_original_+', :+
4
+ alias_method '_calco_original_-', :-
5
+ alias_method '_calco_original_*', :*
6
+ alias_method '_calco_original_/', :/
7
+
8
+ %w[+ - / *].each do |op|
9
+
10
+ define_method(op) do |arg|
11
+
12
+ if arg.respond_to?(:generate)
13
+ Calco::Operation.new(op, self, arg)
14
+ else
15
+ self.send("_calco_original_#{op}", arg)
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,22 @@
1
+ class Float
2
+
3
+ alias_method '_calco_original_+', :+
4
+ alias_method '_calco_original_-', :-
5
+ alias_method '_calco_original_*', :*
6
+ alias_method '_calco_original_/', :/
7
+
8
+ %w[+ - / *].each do |op|
9
+
10
+ define_method(op) do |arg|
11
+
12
+ if arg.respond_to?(:generate)
13
+ Calco::Operation.new(op, self, arg)
14
+ else
15
+ self.send("_calco_original_#{op}", arg)
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,15 @@
1
+ class Range
2
+
3
+ def as_grouping
4
+
5
+ @grouping_range = true
6
+
7
+ self
8
+
9
+ end
10
+
11
+ def grouping_range?
12
+ ! @grouping_range.nil?
13
+ end
14
+
15
+ end
@@ -0,0 +1,20 @@
1
+ class String
2
+
3
+ alias_method '_calco_original_+', :+
4
+ alias_method '_calco_original_*', :*
5
+
6
+ %w[+ *].each do |op|
7
+
8
+ define_method(op) do |arg|
9
+
10
+ if arg.respond_to?(:generate)
11
+ Calco::Operation.new(op, self, arg)
12
+ else
13
+ self.send("_calco_original_#{op}", arg)
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,13 @@
1
+ require 'date'
2
+
3
+ module Calco
4
+
5
+ class BuiltinFunction
6
+
7
+ declare :today, 0, Date
8
+ declare :year, 1, Integer
9
+ declare :datevalue, 1, Date
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,127 @@
1
+ require 'date'
2
+ require 'time'
3
+
4
+ require_relative 'elements/if'
5
+ require_relative 'elements/or'
6
+ require_relative 'elements/empty'
7
+ require_relative 'elements/constant'
8
+ require_relative 'elements/current'
9
+ require_relative 'elements/element'
10
+ require_relative 'elements/formula'
11
+ require_relative 'elements/variable'
12
+ require_relative 'elements/operation'
13
+ require_relative 'elements/value_extractor'
14
+ require_relative 'elements/builtin_function'
15
+
16
+ require_relative 'core_ext/float'
17
+ require_relative 'core_ext/range'
18
+ require_relative 'core_ext/fixnum'
19
+ require_relative 'core_ext/string'
20
+
21
+ module Calco
22
+
23
+ module DefinitionDSL
24
+
25
+ def set variable_assign
26
+
27
+ name = variable_assign.first[0]
28
+ value = variable_assign.first[1]
29
+
30
+ raise "Variable '#{name}' already set" if @variables.include?(name)
31
+
32
+ if value =~ /^\d{1,2}:\d{1,2}(:\d{1,2})?$/
33
+ value = Time.parse(value)
34
+ elsif value =~ /^\d{1,4}[-\/]\d{1,2}[-\/]\d{1,2}$/
35
+ value = Date.parse(value)
36
+ end
37
+
38
+ @variables[name] = Variable.new(name, value)
39
+
40
+ end
41
+
42
+ def function formula_definition
43
+
44
+ name = formula_definition.first[0]
45
+ value = formula_definition.first[1]
46
+
47
+ raise "Function '#{name}' already defined" if @formulas.include?(name)
48
+
49
+ @formulas[name] = Formula.new(value)
50
+
51
+ end
52
+
53
+ end
54
+
55
+ module BuilderDSL
56
+
57
+ def _if condition, then_, else_
58
+ If.new(condition, then_, else_)
59
+ end
60
+
61
+ def _or condition1, condition2
62
+ Or.new(condition1, condition2)
63
+ end
64
+
65
+ def builtin_function? sym
66
+ BuiltinFunction.exists_function?(sym)
67
+ end
68
+
69
+ def builtin_function sym, args
70
+ BuiltinFunction.create_function(sym, args)
71
+ end
72
+
73
+ end
74
+
75
+ class Definitions
76
+
77
+ include BuilderDSL
78
+ include DefinitionDSL
79
+
80
+ attr_reader :variables, :formulas
81
+
82
+ def initialize spreadsheet
83
+
84
+ @formulas = {}
85
+ @variables = {}
86
+
87
+ @spreadsheet = spreadsheet
88
+
89
+ end
90
+
91
+ def resolve! sym, *args
92
+
93
+ if @variables.include?(sym)
94
+ @variables[sym]
95
+ elsif @formulas.include?(sym)
96
+ @formulas[sym]
97
+ elsif builtin_function?(sym)
98
+ builtin_function(sym, args)
99
+ else
100
+ raise "Unknown function or variable '#{sym}'"
101
+ end
102
+
103
+ end
104
+
105
+ def formula? name
106
+ @formulas.include?(name)
107
+ end
108
+
109
+ def formula name
110
+ @formulas[name]
111
+ end
112
+
113
+ def variable? name
114
+ @variables.include?(name)
115
+ end
116
+
117
+ def variable name
118
+ @variables[name]
119
+ end
120
+
121
+ def method_missing sym, *args
122
+ resolve! sym, *args
123
+ end
124
+
125
+ end
126
+
127
+ end
@@ -0,0 +1,17 @@
1
+ require_relative 'element'
2
+
3
+ module Calco
4
+
5
+ class Aggregator < Element
6
+
7
+ def initialize variable, range
8
+ @variable, @range = variable, range
9
+ end
10
+
11
+ def generate row
12
+ @engine.range_reference(@variable, row, @range)
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,84 @@
1
+ require_relative 'element'
2
+
3
+ module Calco
4
+
5
+ class BuiltinFunction < Element
6
+
7
+ @@builtin_functions = {}
8
+
9
+ # arity is an integer or :n for a variable number of arguments
10
+ # type is the return type and expected to be a class object or an array
11
+ # of class objects
12
+ def self.declare name, arity, type = :any
13
+
14
+ unless arity == :n or arity.is_a?(Integer)
15
+ raise ArgumentError, "Artity must be an integer or :n but was a #{arity.class}"
16
+ end
17
+ unless type.is_a?(Class) or (type.respond_to?(:all?) and type.all?{|t| t.is_a?(Class)})
18
+ raise ArgumentError, "Type should be a Class or an array of Class objects"
19
+ end
20
+
21
+ @@builtin_functions[name.to_s.downcase] = [arity, type]
22
+
23
+ end
24
+
25
+ def self.undeclare name
26
+ @@builtin_functions.delete(name.to_s.downcase)
27
+ end
28
+
29
+ def self.exists_function? name
30
+ @@builtin_functions.include?(name.to_s.downcase)
31
+ end
32
+
33
+ def self.create_function name, args
34
+
35
+ raise NameError, "Builtin function not found '#{name}'" unless exists_function?(name)
36
+
37
+ name = name.to_s.upcase
38
+
39
+ definition = @@builtin_functions[name.to_s.downcase]
40
+
41
+ self.new(name, definition, args)
42
+
43
+ end
44
+
45
+ include Operators
46
+
47
+ def initialize name, definition, args
48
+
49
+ @arity = definition[0]
50
+ @return_type = definition[1]
51
+
52
+ unless @arity == :n
53
+
54
+ if @arity != args.length
55
+ raise ArgumentError, "Function #{name} requires #{@arity}, was #{args.length} (#{args.inspect})"
56
+ end
57
+
58
+ end
59
+
60
+ @name, @args = name, args
61
+
62
+ end
63
+
64
+ def generate row
65
+
66
+ sep = ''
67
+ arguments = ''
68
+
69
+ @args.each do |arg|
70
+
71
+ arguments << sep
72
+ arguments << (arg.respond_to?(:generate) ? arg.generate(row) : @engine.value(arg))
73
+
74
+ sep = ', '
75
+
76
+ end
77
+
78
+ "#{@name}(#{arguments})"
79
+
80
+ end
81
+
82
+ end
83
+
84
+ end
@@ -0,0 +1,31 @@
1
+ require_relative 'element'
2
+
3
+ module Calco
4
+
5
+ class Constant < Element
6
+
7
+ include Operators
8
+
9
+ attr_accessor :value
10
+
11
+ def initialize value
12
+ @value = value
13
+ end
14
+
15
+ def generate row
16
+ @engine.value(self)
17
+ end
18
+
19
+ def self.wrap value
20
+
21
+ if value.respond_to?(:generate)
22
+ value
23
+ else
24
+ Constant.new(value)
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,19 @@
1
+ require_relative 'element'
2
+
3
+ module Calco
4
+
5
+ class Current < Element
6
+
7
+ include Operators
8
+
9
+ def initialize
10
+ @reference_type = :current
11
+ end
12
+
13
+ def generate row
14
+ @engine.current
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,31 @@
1
+ require_relative 'operator'
2
+
3
+ module Calco
4
+
5
+ class Element
6
+
7
+ attr_accessor :reference_type, :column, :absolute_row
8
+
9
+ def initialize
10
+ @absolute_row = nil
11
+ @reference_type = :normal
12
+ end
13
+
14
+ def absolute_row= row_number
15
+ @absolute_row = row_number
16
+ @reference_type = :absolute
17
+ end
18
+
19
+ def generate_operand o, row
20
+
21
+ if o.respond_to?(:as_operand)
22
+ o.as_operand(row)
23
+ else
24
+ o.generate(row)
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,9 @@
1
+ require_relative 'element'
2
+
3
+ module Calco
4
+
5
+ class Empty < Element
6
+ include Singleton
7
+ end
8
+
9
+ end
@@ -0,0 +1,42 @@
1
+ require_relative 'element'
2
+
3
+ module Calco
4
+
5
+ class Formula < Element
6
+
7
+ include Operators
8
+
9
+ def initialize statement
10
+
11
+ super()
12
+
13
+ @statement = Constant.wrap(statement)
14
+
15
+ end
16
+
17
+ def [] range
18
+
19
+ raise ArgumentError, "Expected Range got #{range.class}" unless range.is_a?(Range)
20
+ raise ArgumentError, "Invalid start of range (must be > 0, was #{range.first})" unless range.first > 0
21
+
22
+ Aggregator.new(self, range)
23
+
24
+ end
25
+
26
+ def generate row
27
+
28
+ if self.reference_type == :absolute and self.absolute_row != row
29
+ nil
30
+ else
31
+ @statement.generate(row)
32
+ end
33
+
34
+ end
35
+
36
+ def as_operand row
37
+ @engine.column_reference(self, row)
38
+ end
39
+
40
+ end
41
+
42
+ end