zaxcel 0.1.1

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 (65) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +4 -0
  3. data/.rubocop.yml +9 -0
  4. data/CHANGELOG.md +29 -0
  5. data/CONTRIBUTING.md +110 -0
  6. data/LICENSE +22 -0
  7. data/QUICK_START.md +187 -0
  8. data/README.md +372 -0
  9. data/Rakefile +18 -0
  10. data/SETUP.md +178 -0
  11. data/lib/enumerable.rb +47 -0
  12. data/lib/zaxcel/README.md +37 -0
  13. data/lib/zaxcel/arithmetic.rb +88 -0
  14. data/lib/zaxcel/binary_expression.rb +74 -0
  15. data/lib/zaxcel/binary_expressions/addition.rb +36 -0
  16. data/lib/zaxcel/binary_expressions/division.rb +24 -0
  17. data/lib/zaxcel/binary_expressions/multiplication.rb +24 -0
  18. data/lib/zaxcel/binary_expressions/subtraction.rb +41 -0
  19. data/lib/zaxcel/binary_expressions.rb +38 -0
  20. data/lib/zaxcel/cell.rb +141 -0
  21. data/lib/zaxcel/cell_formula.rb +16 -0
  22. data/lib/zaxcel/column.rb +142 -0
  23. data/lib/zaxcel/document.rb +136 -0
  24. data/lib/zaxcel/function.rb +6 -0
  25. data/lib/zaxcel/functions/abs.rb +18 -0
  26. data/lib/zaxcel/functions/and.rb +23 -0
  27. data/lib/zaxcel/functions/average.rb +17 -0
  28. data/lib/zaxcel/functions/choose.rb +20 -0
  29. data/lib/zaxcel/functions/concatenate.rb +20 -0
  30. data/lib/zaxcel/functions/if.rb +38 -0
  31. data/lib/zaxcel/functions/if_error.rb +25 -0
  32. data/lib/zaxcel/functions/index.rb +20 -0
  33. data/lib/zaxcel/functions/len.rb +16 -0
  34. data/lib/zaxcel/functions/match/match_type.rb +13 -0
  35. data/lib/zaxcel/functions/match.rb +27 -0
  36. data/lib/zaxcel/functions/max.rb +17 -0
  37. data/lib/zaxcel/functions/min.rb +17 -0
  38. data/lib/zaxcel/functions/negate.rb +26 -0
  39. data/lib/zaxcel/functions/or.rb +23 -0
  40. data/lib/zaxcel/functions/round.rb +20 -0
  41. data/lib/zaxcel/functions/sum.rb +18 -0
  42. data/lib/zaxcel/functions/sum_if.rb +20 -0
  43. data/lib/zaxcel/functions/sum_ifs.rb +34 -0
  44. data/lib/zaxcel/functions/sum_product.rb +18 -0
  45. data/lib/zaxcel/functions/text.rb +17 -0
  46. data/lib/zaxcel/functions/unique.rb +23 -0
  47. data/lib/zaxcel/functions/x_lookup.rb +28 -0
  48. data/lib/zaxcel/functions/xirr.rb +27 -0
  49. data/lib/zaxcel/functions.rb +169 -0
  50. data/lib/zaxcel/if_builder.rb +22 -0
  51. data/lib/zaxcel/lang.rb +23 -0
  52. data/lib/zaxcel/reference.rb +28 -0
  53. data/lib/zaxcel/references/cell.rb +42 -0
  54. data/lib/zaxcel/references/column.rb +49 -0
  55. data/lib/zaxcel/references/range.rb +35 -0
  56. data/lib/zaxcel/references/row.rb +34 -0
  57. data/lib/zaxcel/references.rb +5 -0
  58. data/lib/zaxcel/roundable.rb +14 -0
  59. data/lib/zaxcel/row.rb +93 -0
  60. data/lib/zaxcel/sheet.rb +425 -0
  61. data/lib/zaxcel/sorbet/enumerizable_enum.rb +50 -0
  62. data/lib/zaxcel/version.rb +6 -0
  63. data/lib/zaxcel.rb +73 -0
  64. data/zaxcel.gemspec +73 -0
  65. metadata +266 -0
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module Zaxcel::Arithmetic
5
+ include Kernel
6
+ include ActiveSupport::Tryable
7
+ extend T::Sig
8
+
9
+ class CoercedValue < T::Struct
10
+ extend T::Sig
11
+ include Zaxcel::Arithmetic
12
+
13
+ const(:value, T.any(Numeric, Money))
14
+
15
+ sig { params(on_sheet: String).returns(T.any(Numeric, Money)) }
16
+ def format(on_sheet:)
17
+ value
18
+ end
19
+
20
+ sig { returns(T::Boolean) }
21
+ def additive_identity?
22
+ object_id == Zero.object_id
23
+ end
24
+ end
25
+
26
+ Zero = CoercedValue.new(value: 0)
27
+ private_constant :Zero
28
+
29
+ #
30
+ # https://docs.ruby-lang.org/en/master/Numeric.html
31
+ #
32
+ # > Classes which inherit from Numeric must implement coerce, which returns a two-member Array containing an object
33
+ # > that has been coerced into an instance of the new class and self (see coerce).
34
+ # >
35
+ # > Inheriting classes should also implement arithmetic operator methods (+, -, * and /) and the <=> operator
36
+ # > (see Comparable). These methods may rely on coerce to ensure interoperability with instances of other numeric
37
+ # > classes.
38
+ #
39
+ # Inheriting from Numeric allows us to add Zaxcel objects like cell references and formulas to Ruby numerics.
40
+ # For example:
41
+ # `1 + cell_ref` will call coerce on `1` and return a `Zaxcel::CellFormula` object representing the sum.
42
+
43
+ sig { params(other: T.any(Numeric, Money)).returns([CoercedValue, Zaxcel::Arithmetic]) }
44
+ def coerce(other)
45
+ [CoercedValue.new(value: other), self]
46
+ end
47
+
48
+ # These need to be implemented as part of the Numeric interface.
49
+
50
+ sig { returns(Zaxcel::CellFormula) }
51
+ def -@
52
+ Zaxcel::Functions::Negate.new(self)
53
+ end
54
+
55
+ sig { params(other: T.any(Numeric, Money, Zaxcel::Arithmetic)).returns(Zaxcel::Arithmetic) }
56
+ def +(other)
57
+ Zaxcel::BinaryExpressions::Addition.new(self, other)
58
+ end
59
+
60
+ sig { params(other: T.any(Numeric, Money, Zaxcel::Arithmetic)).returns(Zaxcel::CellFormula) }
61
+ def -(other)
62
+ Zaxcel::BinaryExpressions::Subtraction.new(self, other)
63
+ end
64
+
65
+ sig { params(other: T.any(Numeric, Money, Zaxcel::Arithmetic)).returns(Zaxcel::CellFormula) }
66
+ def *(other)
67
+ Zaxcel::BinaryExpressions::Multiplication.new(self, other)
68
+ end
69
+
70
+ sig { params(other: T.any(Numeric, Money, Zaxcel::Arithmetic)).returns(Zaxcel::CellFormula) }
71
+ def /(other)
72
+ Zaxcel::BinaryExpressions::Division.new(self, other)
73
+ end
74
+
75
+ sig { returns(TrueClass) }
76
+ def present?
77
+ true
78
+ end
79
+
80
+ class << self
81
+ extend T::Sig
82
+
83
+ sig { returns(Zaxcel::Arithmetic) }
84
+ def zero
85
+ Zero
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ class Zaxcel::BinaryExpression < Zaxcel::CellFormula
5
+ extend T::Sig
6
+
7
+ sig { returns(String) }
8
+ attr_reader :operator
9
+
10
+ sig do
11
+ params(
12
+ operator: String,
13
+ lh_value: Zaxcel::Cell::ValueType,
14
+ rh_value: Zaxcel::Cell::ValueType,
15
+ ).void
16
+ end
17
+ def initialize(operator, lh_value, rh_value)
18
+ @operator = operator
19
+ @lh_value = lh_value
20
+ @rh_value = rh_value
21
+ end
22
+
23
+ sig { override.params(on_sheet: String).returns(T.nilable(T.any(Numeric, Money, String))) }
24
+ def format(on_sheet:)
25
+ formatted_lh_value = format_value(@lh_value, on_sheet: on_sheet)
26
+ formatted_rh_value = format_value(@rh_value, on_sheet: on_sheet)
27
+
28
+ "#{formatted_lh_value}#{@operator}#{formatted_rh_value}"
29
+ end
30
+
31
+ private
32
+
33
+ sig do
34
+ params(
35
+ value: Zaxcel::Cell::ValueType,
36
+ on_sheet: String,
37
+ ).returns(T.any(Numeric, Money, String))
38
+ end
39
+ def format_value(value, on_sheet:)
40
+ base_format = Zaxcel::Cell.format(value, on_sheet: on_sheet)
41
+ # If a cell reference doesn't resolve, we want to handle it gracefully and not produce bad formulas. If we just
42
+ # print an empty string, we will get a bad formula, so instead swap for zero, since it is reasonable to assume that
43
+ # if a cell does not exist, its value is zero.
44
+ # In the future, we might consider adding a default behavior of erroring on missing cell references, with the
45
+ # ability to add an if_error formula which replaces a nil cell reference with a default value.
46
+ return '0' if base_format.nil?
47
+
48
+ base_format = "(#{base_format})" if wrap_value?(value)
49
+ base_format
50
+ end
51
+
52
+ # Operators don't always associate with each other nicely with parentheses. For instance,
53
+ # 3 * (1 + 2) != 3 * 1 + 2,
54
+ # so we need some rules around when to wrap in parens when the left or right hand value have operators.
55
+ # The general rules are:
56
+ # 1. When it's a basic value or a reference, such as string or number, don't wrap, e.g. "hello", 1, or A1:B1.
57
+ # 2. When it's a function, don't wrap since the function is a single token, e.g. SUM(...), MIN(...), or ROUND(...).
58
+ # 3. When it's a binary expression, wrap if the operator distributes over the inner operator.
59
+ # https://en.wikipedia.org/wiki/Distributive_property
60
+ sig { params(value: Zaxcel::Cell::ValueType).returns(T::Boolean) }
61
+ def wrap_value?(value)
62
+ return false if !value.is_a?(Zaxcel::CellFormula)
63
+ return true if value.is_a?(Zaxcel::Functions::Negate)
64
+ return false if value.is_a?(Zaxcel::Function)
65
+ return distributive?(value.operator) if value.is_a?(Zaxcel::BinaryExpression)
66
+
67
+ true
68
+ end
69
+
70
+ sig { overridable.params(inner_operator: String).returns(T::Boolean) }
71
+ def distributive?(inner_operator)
72
+ false
73
+ end
74
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ class Zaxcel::BinaryExpressions::Addition < Zaxcel::BinaryExpression
5
+ extend T::Sig
6
+
7
+ sig do
8
+ params(
9
+ lh_value: Zaxcel::Cell::ValueType,
10
+ rh_value: Zaxcel::Cell::ValueType,
11
+ ).void
12
+ end
13
+ def initialize(lh_value, rh_value)
14
+ super('+', lh_value, rh_value)
15
+ end
16
+
17
+ sig { override.params(on_sheet: String).returns(T.nilable(T.any(Numeric, Money, String))) }
18
+ def format(on_sheet:)
19
+ # Don't call `format_value` here to avoid wrapping in parens, which is unnecessary.
20
+ if @lh_value.is_a?(Zaxcel::Arithmetic::CoercedValue) && @lh_value.additive_identity?
21
+ return Zaxcel::Cell.format(@rh_value, on_sheet: on_sheet)
22
+ elsif @rh_value.is_a?(Zaxcel::Arithmetic::CoercedValue) && @rh_value.additive_identity?
23
+ return Zaxcel::Cell.format(@lh_value, on_sheet: on_sheet)
24
+ end
25
+
26
+ super
27
+ end
28
+
29
+ private
30
+
31
+ # sum never distributes over anything (other things distribute over it)
32
+ sig { override.params(inner_operator: String).returns(T::Boolean) }
33
+ def distributive?(inner_operator)
34
+ false
35
+ end
36
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ class Zaxcel::BinaryExpressions::Division < Zaxcel::BinaryExpression
5
+ extend T::Sig
6
+
7
+ sig do
8
+ params(
9
+ lh_value: Zaxcel::Cell::ValueType,
10
+ rh_value: Zaxcel::Cell::ValueType,
11
+ ).void
12
+ end
13
+ def initialize(lh_value, rh_value)
14
+ super('/', lh_value, rh_value)
15
+ end
16
+
17
+ private
18
+
19
+ # division distributes over addition / subtraction
20
+ sig { override.params(inner_operator: String).returns(T::Boolean) }
21
+ def distributive?(inner_operator)
22
+ ['+', '-'].include?(inner_operator)
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ class Zaxcel::BinaryExpressions::Multiplication < Zaxcel::BinaryExpression
5
+ extend T::Sig
6
+
7
+ sig do
8
+ params(
9
+ lh_value: Zaxcel::Cell::ValueType,
10
+ rh_value: Zaxcel::Cell::ValueType,
11
+ ).void
12
+ end
13
+ def initialize(lh_value, rh_value)
14
+ super('*', lh_value, rh_value)
15
+ end
16
+
17
+ private
18
+
19
+ # multiplication distributes over addition / subtraction
20
+ sig { override.params(inner_operator: String).returns(T::Boolean) }
21
+ def distributive?(inner_operator)
22
+ ['+', '-'].include?(inner_operator)
23
+ end
24
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ class Zaxcel::BinaryExpressions::Subtraction < Zaxcel::BinaryExpression
5
+ extend T::Sig
6
+
7
+ sig do
8
+ params(
9
+ lh_value: Zaxcel::Cell::ValueType,
10
+ rh_value: Zaxcel::Cell::ValueType,
11
+ ).void
12
+ end
13
+ def initialize(lh_value, rh_value)
14
+ super('-', lh_value, rh_value)
15
+ end
16
+
17
+ sig { override.params(on_sheet: String).returns(T.nilable(T.any(Numeric, Money, String))) }
18
+ def format(on_sheet:)
19
+ # Don't call `format_value` here to avoid wrapping in parens, which is unnecessary.
20
+ if @lh_value.is_a?(Zaxcel::Arithmetic::CoercedValue) && @lh_value.additive_identity?
21
+ # stupid, but ValueType technically can be true, false, date, time, etc.
22
+ if @rh_value.is_a?(Numeric) || @rh_value.is_a?(Money) || @rh_value.is_a?(Zaxcel::Arithmetic)
23
+ @rh_value = -@rh_value
24
+ end
25
+
26
+ return Zaxcel::Cell.format(@rh_value, on_sheet: on_sheet)
27
+ elsif @rh_value.is_a?(Zaxcel::Arithmetic::CoercedValue) && @rh_value.additive_identity?
28
+ return Zaxcel::Cell.format(@lh_value, on_sheet: on_sheet)
29
+ end
30
+
31
+ super
32
+ end
33
+
34
+ private
35
+
36
+ # subtraction distributes over addition / subtraction (technically it's -1 * (...))
37
+ sig { override.params(inner_operator: String).returns(T::Boolean) }
38
+ def distributive?(inner_operator)
39
+ ['+', '-'].include?(inner_operator)
40
+ end
41
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ module Zaxcel::BinaryExpressions
5
+ class << self
6
+ extend T::Sig
7
+
8
+ sig { params(lh_value: Zaxcel::Cell::ValueType, rh_value: Zaxcel::Cell::ValueType).returns(Zaxcel::BinaryExpression) }
9
+ def less_than(lh_value, rh_value)
10
+ Zaxcel::BinaryExpression.new('<', lh_value, rh_value)
11
+ end
12
+
13
+ sig { params(lh_value: Zaxcel::Cell::ValueType, rh_value: Zaxcel::Cell::ValueType).returns(Zaxcel::BinaryExpression) }
14
+ def less_than_equal(lh_value, rh_value)
15
+ Zaxcel::BinaryExpression.new('<=', lh_value, rh_value)
16
+ end
17
+
18
+ sig { params(lh_value: Zaxcel::Cell::ValueType, rh_value: Zaxcel::Cell::ValueType).returns(Zaxcel::BinaryExpression) }
19
+ def greater_than(lh_value, rh_value)
20
+ Zaxcel::BinaryExpression.new('>', lh_value, rh_value)
21
+ end
22
+
23
+ sig { params(lh_value: Zaxcel::Cell::ValueType, rh_value: Zaxcel::Cell::ValueType).returns(Zaxcel::BinaryExpression) }
24
+ def greater_than_equal(lh_value, rh_value)
25
+ Zaxcel::BinaryExpression.new('>=', lh_value, rh_value)
26
+ end
27
+
28
+ sig { params(lh_value: Zaxcel::Cell::ValueType, rh_value: Zaxcel::Cell::ValueType).returns(Zaxcel::BinaryExpression) }
29
+ def equal(lh_value, rh_value)
30
+ Zaxcel::BinaryExpression.new('=', lh_value, rh_value)
31
+ end
32
+
33
+ sig { params(lh_value: Zaxcel::Cell::ValueType, rh_value: Zaxcel::Cell::ValueType).returns(Zaxcel::BinaryExpression) }
34
+ def not_equal(lh_value, rh_value)
35
+ Zaxcel::BinaryExpression.new('<>', lh_value, rh_value)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ class Zaxcel::Cell
5
+ extend T::Sig
6
+
7
+ ConstantValueType = T.type_alias { T.any(NilClass, Numeric, Money, String, Date, Time, TrueClass, FalseClass) }
8
+ ValueType = T.type_alias { T.any(ConstantValueType, Zaxcel::Arithmetic) }
9
+ CellExtractionKey = T.type_alias { [String, Symbol, Symbol] }
10
+
11
+ EMPTY_VALUE = ''
12
+
13
+ sig { returns(Symbol) }
14
+ attr_reader :style
15
+
16
+ sig { returns(T.nilable(ValueType)) }
17
+ attr_reader :value
18
+
19
+ sig { returns(Zaxcel::Column) }
20
+ attr_reader :column
21
+
22
+ sig { returns(Zaxcel::Row) }
23
+ attr_reader :row
24
+
25
+ sig { returns(T::Boolean) }
26
+ attr_reader :to_extract
27
+
28
+ sig { params(column: Zaxcel::Column, row: Zaxcel::Row, style: T.nilable(Symbol), value: ValueType, to_extract: T::Boolean).void }
29
+ def initialize(column:, row:, style:, value:, to_extract: false)
30
+ @column = column
31
+ @row = row
32
+ @value = value
33
+ @to_extract = to_extract
34
+ # style can be nil, the name of a style, or the name of a style group
35
+ # if it's nil, fall back to the style_group on the row
36
+ style ||= row.style_group
37
+ # first check if it's a style group, if not, then it's a style
38
+ style = column.try(style) || style
39
+
40
+ @style = T.let(style, Symbol)
41
+ end
42
+
43
+ sig { returns(String) }
44
+ def to_excel
45
+ if col_position.nil? && row_position.nil?
46
+ return '0' if @value.nil? || @value.try(:zero?)
47
+
48
+ raise 'Cannot call to_excel until col and row indices are set. Call position! first'
49
+ end
50
+
51
+ "#{@column.to_excel}#{@row.to_excel}"
52
+ end
53
+
54
+ sig { returns(T.nilable(Integer)) }
55
+ def row_position
56
+ @row.position
57
+ end
58
+
59
+ sig { returns(T.nilable(Integer)) }
60
+ def col_position
61
+ @column.position
62
+ end
63
+
64
+ sig { returns(T.nilable(Integer)) }
65
+ def estimated_formatted_character_length
66
+ self.class.estimated_character_length_from_cell_value(@value)
67
+ end
68
+
69
+ sig { returns(T::Boolean) }
70
+ def to_extract?
71
+ @to_extract
72
+ end
73
+
74
+ sig { returns(CellExtractionKey) }
75
+ def extraction_key
76
+ [column.sheet.name, row.name, column.name]
77
+ end
78
+
79
+ class << self
80
+ extend T::Sig
81
+
82
+ sig { params(value: T.nilable(ValueType)).returns(T.nilable(Integer)) }
83
+ def estimated_character_length_from_cell_value(value)
84
+ case value
85
+ when String
86
+ value.length
87
+ when Integer, Float, Money, BigDecimal
88
+ # Add 2 for potential negative sign and decimal point
89
+ # Add 1 character per 3 digits for commas
90
+ num_str = value.to_s.gsub(/[.-]/, '')
91
+ num_str.length + 2 + (num_str.length / 3.0).ceil
92
+ when Time
93
+ value.strftime('%m/%d/%Y %H:%M:%S').length
94
+ when Date
95
+ format_date(value).length
96
+ when TrueClass, FalseClass
97
+ value.to_s.length
98
+ when Zaxcel::CellFormula, Zaxcel::References::Cell
99
+ # For formulas and references, we don't know the length until we evaluate them
100
+ nil
101
+ end
102
+ end
103
+
104
+ sig do
105
+ params(
106
+ value: T.nilable(T.any(ValueType, Zaxcel::References::Range)),
107
+ on_sheet: String,
108
+ quote_strings: T::Boolean,
109
+ ).returns(T.nilable(T.any(Numeric, Money, String)))
110
+ end
111
+ def format(value, on_sheet:, quote_strings: true)
112
+ case value
113
+ when String
114
+ # don't set empty cells to empty string - this allows other cells to overflow into them
115
+ return if value.empty?
116
+
117
+ # Strings inside of formulas need to be escaped with quotes, otherwise they cause errors. Strings printed
118
+ # directly into the document (not as part of a formula) should not be escaped.
119
+ quote_strings ? "\"#{value}\"" : value
120
+ when Numeric, Money
121
+ value
122
+ # this must come before date because Time < Date
123
+ when Time
124
+ "DATE(#{value.year}, #{value.month}, #{value.day})+TIME(#{value.hour}, #{value.min}, #{value.sec})"
125
+ when Date
126
+ format_date(value)
127
+ when TrueClass
128
+ 'TRUE'
129
+ when FalseClass
130
+ 'FALSE'
131
+ when Zaxcel::CellFormula, Zaxcel::References::Cell, Zaxcel::References::Range, Zaxcel::Arithmetic::CoercedValue
132
+ value.format(on_sheet: on_sheet)
133
+ end
134
+ end
135
+
136
+ sig { params(value: Date).returns(String) }
137
+ def format_date(value)
138
+ value.strftime('%m/%d/%Y')
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ class Zaxcel::CellFormula
5
+ extend T::Sig
6
+ extend T::Helpers
7
+ include Zaxcel::Arithmetic
8
+ include Zaxcel::Roundable
9
+
10
+ abstract!
11
+
12
+ EXCEL_EPOCH = Date.new(1900, 1, 1)
13
+
14
+ sig { abstract.params(on_sheet: String).returns(T.nilable(T.any(Numeric, Money, String))) }
15
+ def format(on_sheet:); end
16
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+ # typed: strict
3
+
4
+ class Zaxcel::Column
5
+ extend T::Sig
6
+
7
+ DEFAULT_COLUMN_WIDTH = 20
8
+ class ComputedColumnWidth < T::Enum
9
+ enums do
10
+ MaxContent = new
11
+ Header = new
12
+ HeaderTwoLines = new
13
+ end
14
+ end
15
+
16
+ sig { returns(Symbol) }
17
+ attr_reader :name
18
+
19
+ sig { returns(String) }
20
+ attr_reader :header
21
+
22
+ sig { returns(Symbol) }
23
+ attr_reader :header_style
24
+
25
+ sig { returns(Symbol) }
26
+ attr_reader :row_style
27
+
28
+ sig { returns(Symbol) }
29
+ attr_reader :alt_row_style
30
+
31
+ sig { returns(Symbol) }
32
+ attr_reader :first_row_style
33
+
34
+ sig { returns(T.nilable(Symbol)) }
35
+ attr_reader :total_style
36
+
37
+ # You can specify `nil` and CAXLSX will auto-calculate an appropriate width based on the column's contents.
38
+ sig { returns(T.nilable(T.any(Integer, Float, ComputedColumnWidth))) }
39
+ attr_reader :width
40
+
41
+ sig { returns(T.nilable(T.any(Integer, Float))) }
42
+ attr_reader :min_width
43
+
44
+ sig { returns(Zaxcel::Sheet) }
45
+ attr_reader :sheet
46
+
47
+ sig { params(sheet: Zaxcel::Sheet, name: T.any(Symbol, String), header: String, header_style: Symbol, row_style: Symbol, first_row_style: Symbol, total_style: T.nilable(Symbol), alt_row_style: T.nilable(Symbol), width: T.nilable(T.any(Integer, Float, ComputedColumnWidth)), min_width: T.nilable(T.any(Integer, Float))).void }
48
+ def initialize(sheet:, name:, header:, header_style:, row_style:, first_row_style:, total_style:, alt_row_style: nil, width: DEFAULT_COLUMN_WIDTH, min_width: 0)
49
+ @sheet = sheet
50
+ @name = T.let(name.to_sym, Symbol)
51
+ @header = header
52
+ @header_style = header_style
53
+ @row_style = row_style
54
+ @alt_row_style = T.let(alt_row_style || row_style, Symbol)
55
+ @first_row_style = first_row_style
56
+ @total_style = total_style
57
+ @width = width
58
+ @min_width = min_width
59
+ @print_boundary = T.let(false, T::Boolean)
60
+ @hidden = T.let(false, T::Boolean)
61
+ end
62
+
63
+ sig { params(row_name: T.any(String, Symbol), sheet_name: T.nilable(String)).returns(Zaxcel::References::Cell) }
64
+ def ref(row_name, sheet_name: nil)
65
+ Zaxcel::References::Cell.new(document: @sheet.document, sheet_name: sheet_name || @sheet.name, col_name: @name, row_name: row_name.to_sym)
66
+ end
67
+
68
+ sig { params(row: Zaxcel::Row, value: T.nilable(Zaxcel::Cell::ValueType), style: T.nilable(Symbol), to_extract: T::Boolean).returns(Zaxcel::Cell) }
69
+ def add_cell!(row:, value: nil, style: nil, to_extract: false)
70
+ cell_by_row_name[row.name] = Zaxcel::Cell.new(
71
+ column: self,
72
+ row: row,
73
+ style: style,
74
+ value: value,
75
+ to_extract: to_extract,
76
+ )
77
+ end
78
+
79
+ sig { params(row_name: T.any(Symbol, String)).returns(T.nilable(Zaxcel::Cell)) }
80
+ def cell(row_name)
81
+ cell_by_row_name[row_name.to_sym]
82
+ end
83
+
84
+ sig { params(column_position: Integer).void }
85
+ def position!(column_position)
86
+ @position = T.let(column_position, T.nilable(Integer))
87
+ end
88
+
89
+ sig { returns(T.nilable(Integer)) }
90
+ def position
91
+ @position
92
+ end
93
+
94
+ # Excel uses letters for column names. When it reaches 'Z', it starts over with 'AA', 'AB', etc.
95
+ # until it hits 'AZ' and then moves on to 'BZ'. Eventually, it will reach 'ZZ' and start 'AAA'.
96
+ # This method converts the column position to the appropriate sequence of letters.
97
+ sig { returns(String) }
98
+ def to_excel
99
+ raise 'Must position cells before calling to_excel' if @position.nil?
100
+
101
+ self.class.base_26_string(@position)
102
+ end
103
+
104
+ sig { returns(T::Hash[Symbol, Zaxcel::Cell]) }
105
+ def cell_by_row_name
106
+ @cell_by_row_name ||= T.let({}, T.nilable(T::Hash[Symbol, Zaxcel::Cell]))
107
+ end
108
+
109
+ sig { void }
110
+ def hide!
111
+ @hidden = true
112
+ end
113
+
114
+ sig { returns(T::Boolean) }
115
+ def hidden?
116
+ @hidden
117
+ end
118
+
119
+ sig { void }
120
+ def set_print_boundary!
121
+ raise 'Print boundary column already exists' if @sheet.print_boundary_column.present?
122
+
123
+ @print_boundary = true
124
+ end
125
+
126
+ sig { returns(T::Boolean) }
127
+ def print_boundary?
128
+ @print_boundary
129
+ end
130
+
131
+ class << self
132
+ extend T::Sig
133
+
134
+ sig { params(number: Integer).returns(String) }
135
+ def base_26_string(number)
136
+ first_character = (number % 26 + 'A'.ord).chr
137
+ return first_character if number < 26
138
+
139
+ "#{base_26_string((number / 26).to_i - 1)}#{(number % 26 + 'A'.ord).chr}"
140
+ end
141
+ end
142
+ end