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.
- checksums.yaml +7 -0
 - data/.rspec +4 -0
 - data/.rubocop.yml +9 -0
 - data/CHANGELOG.md +29 -0
 - data/CONTRIBUTING.md +110 -0
 - data/LICENSE +22 -0
 - data/QUICK_START.md +187 -0
 - data/README.md +372 -0
 - data/Rakefile +18 -0
 - data/SETUP.md +178 -0
 - data/lib/enumerable.rb +47 -0
 - data/lib/zaxcel/README.md +37 -0
 - data/lib/zaxcel/arithmetic.rb +88 -0
 - data/lib/zaxcel/binary_expression.rb +74 -0
 - data/lib/zaxcel/binary_expressions/addition.rb +36 -0
 - data/lib/zaxcel/binary_expressions/division.rb +24 -0
 - data/lib/zaxcel/binary_expressions/multiplication.rb +24 -0
 - data/lib/zaxcel/binary_expressions/subtraction.rb +41 -0
 - data/lib/zaxcel/binary_expressions.rb +38 -0
 - data/lib/zaxcel/cell.rb +141 -0
 - data/lib/zaxcel/cell_formula.rb +16 -0
 - data/lib/zaxcel/column.rb +142 -0
 - data/lib/zaxcel/document.rb +136 -0
 - data/lib/zaxcel/function.rb +6 -0
 - data/lib/zaxcel/functions/abs.rb +18 -0
 - data/lib/zaxcel/functions/and.rb +23 -0
 - data/lib/zaxcel/functions/average.rb +17 -0
 - data/lib/zaxcel/functions/choose.rb +20 -0
 - data/lib/zaxcel/functions/concatenate.rb +20 -0
 - data/lib/zaxcel/functions/if.rb +38 -0
 - data/lib/zaxcel/functions/if_error.rb +25 -0
 - data/lib/zaxcel/functions/index.rb +20 -0
 - data/lib/zaxcel/functions/len.rb +16 -0
 - data/lib/zaxcel/functions/match/match_type.rb +13 -0
 - data/lib/zaxcel/functions/match.rb +27 -0
 - data/lib/zaxcel/functions/max.rb +17 -0
 - data/lib/zaxcel/functions/min.rb +17 -0
 - data/lib/zaxcel/functions/negate.rb +26 -0
 - data/lib/zaxcel/functions/or.rb +23 -0
 - data/lib/zaxcel/functions/round.rb +20 -0
 - data/lib/zaxcel/functions/sum.rb +18 -0
 - data/lib/zaxcel/functions/sum_if.rb +20 -0
 - data/lib/zaxcel/functions/sum_ifs.rb +34 -0
 - data/lib/zaxcel/functions/sum_product.rb +18 -0
 - data/lib/zaxcel/functions/text.rb +17 -0
 - data/lib/zaxcel/functions/unique.rb +23 -0
 - data/lib/zaxcel/functions/x_lookup.rb +28 -0
 - data/lib/zaxcel/functions/xirr.rb +27 -0
 - data/lib/zaxcel/functions.rb +169 -0
 - data/lib/zaxcel/if_builder.rb +22 -0
 - data/lib/zaxcel/lang.rb +23 -0
 - data/lib/zaxcel/reference.rb +28 -0
 - data/lib/zaxcel/references/cell.rb +42 -0
 - data/lib/zaxcel/references/column.rb +49 -0
 - data/lib/zaxcel/references/range.rb +35 -0
 - data/lib/zaxcel/references/row.rb +34 -0
 - data/lib/zaxcel/references.rb +5 -0
 - data/lib/zaxcel/roundable.rb +14 -0
 - data/lib/zaxcel/row.rb +93 -0
 - data/lib/zaxcel/sheet.rb +425 -0
 - data/lib/zaxcel/sorbet/enumerizable_enum.rb +50 -0
 - data/lib/zaxcel/version.rb +6 -0
 - data/lib/zaxcel.rb +73 -0
 - data/zaxcel.gemspec +73 -0
 - 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
         
     | 
    
        data/lib/zaxcel/cell.rb
    ADDED
    
    | 
         @@ -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
         
     |