Class: RVGP::Journal::ComplexCommodity

Inherits:
Object
  • Object
show all
Defined in:
lib/rvgp/journal/complex_commodity.rb

Overview

These ‘complex currency’ specifications appear to be mostly for non-register and non-balance reports. The ledger manual labels these ‘Cost Expressions’. We really don’t use these much, and I’m not entirely sure the parsing rules make sense. Some of the rules in the documentation even seem a bit inconsistent (compare complex expressions vs comments).

Here’s some examples of Complex Commodities: “‘ 10 AAPL @@ $500.00 10 AAPL @ ($500.00 / 10) (5 AAPL * 2) @ ($500.00 / 10) 1000 AAPL (@) $1 -10 AAPL {$500{$500.00} @@ $750.00 10 AAPL =$50=$50.00 -5 AAPL $50$50.00 [2012-04-10] @@ $375.00 -5 AAPL $50$50.00 [2012-04-10] (Oh my!) @@ $375.00 -5 AAPL $50$50.00 ((ten_dollars)) @@ $375.00 -5 AAPL $50$50.00 ((s, d, t -> market($10, date, t))) @@ $375.00 “`

We ended up needing most of this class to run Validations::DuplicateTagsValidation. And, to ensure that we’re able to mostly-validate the syntax of the journals. We don’t actually use many code paths here, otherwise. (Though we do use it to serialize currency conversion in the file shorthand/investment.rb)

I’m not entirely sure what attribute names to use. We could go with intent position, or with class. Either path seems to introduce exceptions. Possibly some of these attributes should just go into the transfer class. I’m also not sure that the left_/right_/operation system makes sense.

I also think we need some adjustments here to cover all parsing cases. But, for now this works well enough, again mostly because we’re not using most of these code paths… Lets see if/how this evolves.

Defined Under Namespace

Classes: Error

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ ComplexCommodity

Create a complex commodity, from constituent parts

Parameters:

  • opts (Hash) (defaults to: {})

    The parts of this complex commodity

Options Hash (opts):



100
101
102
103
104
# File 'lib/rvgp/journal/complex_commodity.rb', line 100

def initialize(opts = {})
  ATTRIBUTES.select { |attr| opts.key? attr }.each do |attr|
    instance_variable_set("@#{attr}".to_sym, opts[attr])
  end
end

Instance Attribute Details

#leftRVGP::Commodity::Journal (readonly)

The ‘left’ component of the complex commodity

Returns:

  • (RVGP::Commodity::Journal)

    the current value of left



54
55
56
# File 'lib/rvgp/journal/complex_commodity.rb', line 54

def left
  @left
end

#left_dateDate (readonly)

The ‘left_date’ component of the complex commodity

Returns:

  • (Date)

    the current value of left_date



54
55
56
# File 'lib/rvgp/journal/complex_commodity.rb', line 54

def left_date
  @left_date
end

#left_expressionString (readonly)

The ‘left_expression’ component of the complex commodity

Returns:

  • (String)

    the current value of left_expression



54
55
56
# File 'lib/rvgp/journal/complex_commodity.rb', line 54

def left_expression
  @left_expression
end

#left_is_equalTrueClass, FalseClass (readonly)

The ‘left_is_equal’ component of the complex commodity

Returns:

  • (TrueClass, FalseClass)

    the current value of left_is_equal



54
55
56
# File 'lib/rvgp/journal/complex_commodity.rb', line 54

def left_is_equal
  @left_is_equal
end

#left_lambdaString (readonly)

The ‘left_lambda’ component of the complex commodity

Returns:

  • (String)

    the current value of left_lambda



54
55
56
# File 'lib/rvgp/journal/complex_commodity.rb', line 54

def left_lambda
  @left_lambda
end

#left_lotString (readonly)

The ‘left_lot’ component of the complex commodity

Returns:

  • (String)

    the current value of left_lot



54
55
56
# File 'lib/rvgp/journal/complex_commodity.rb', line 54

def left_lot
  @left_lot
end

#left_lot_is_equalTrueClass, FalseClass (readonly)

The ‘left_lot_is_equal’ component of the complex commodity

Returns:

  • (TrueClass, FalseClass)

    the current value of left_lot_is_equal



54
55
56
# File 'lib/rvgp/journal/complex_commodity.rb', line 54

def left_lot_is_equal
  @left_lot_is_equal
end

#left_lot_operationSymbol (readonly)

The ‘left_lot_operation’ component of the complex commodity, either :per_unit, or :per_lot

Returns:

  • (Symbol)

    the current value of left_lot_operation



54
55
56
# File 'lib/rvgp/journal/complex_commodity.rb', line 54

def left_lot_operation
  @left_lot_operation
end

#operationSymbol (readonly)

The ‘operation’ component of the complex commodity, either :right_expression, :left_expression, :per_unit, or :per_lot

Returns:

  • (Symbol)

    the current value of operation



54
55
56
# File 'lib/rvgp/journal/complex_commodity.rb', line 54

def operation
  @operation
end

#rightRVGP::Commodity::Journal (readonly)

The ‘right’ component of the complex commodity

Returns:

  • (RVGP::Commodity::Journal)

    the current value of right



54
55
56
# File 'lib/rvgp/journal/complex_commodity.rb', line 54

def right
  @right
end

#right_expressionString (readonly)

The ‘right_expression’ component of the complex commodity

Returns:

  • (String)

    the current value of right_expression



54
55
56
# File 'lib/rvgp/journal/complex_commodity.rb', line 54

def right_expression
  @right_expression
end

#right_is_equalTrueClass, FalseClass (readonly)

The ‘right_is_equal’ component of the complex commodity

Returns:

  • (TrueClass, FalseClass)

    the current value of right_is_equal



54
55
56
# File 'lib/rvgp/journal/complex_commodity.rb', line 54

def right_is_equal
  @right_is_equal
end

Class Method Details

.ensure_not_too_many!(opts, key, string) ⇒ Object

This mostly just saves us some typing above in the from_s()

Raises:



209
210
211
# File 'lib/rvgp/journal/complex_commodity.rb', line 209

def self.ensure_not_too_many!(opts, key, string)
  raise Error, format(MSG_TOO_MANY, key.to_s, string) if opts.key? key
end

.from_s(string) ⇒ RVGP::Journal::ComplexCommodity

Given a string, in one of the supported formats, construct and return a commodity representation.

Parameters:

  • string (String)

    The commodity, as would be found in a PTA journal

Returns:



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/rvgp/journal/complex_commodity.rb', line 144

def self.from_s(string)
  tmp = string.dup
  opts = {}

  # We treat string like a kind of stack, and we pop off what we can parse
  # from the left, heading to the right
  until tmp.empty?
    case tmp
    when WHITESPACE_MATCH
      tmp = ::Regexp.last_match(1)
    when EQUAL_MATCH
      side = opts[:operation] ? :right_is_equal : :left_is_equal
      ensure_not_too_many! opts, side, string
      opts[side] = true
      tmp = ::Regexp.last_match(1)
    when LOT_MATCH
      ensure_not_too_many! opts, :left_lot, string
      opts[:left_lot_operation] = to_operator ::Regexp.last_match(1)
      opts[:left_lot_is_equal] = (::Regexp.last_match(2) == '=')
      opts[:left_lot] = ::Regexp.last_match(3).to_commodity
      tmp = ::Regexp.last_match(4)
    when LAMBDA_MATCH
      ensure_not_too_many! opts, :left_lambda, string
      opts[:left_lambda] = ::Regexp.last_match(1)
      tmp = ::Regexp.last_match(2)
    when DATE_MATCH
      ensure_not_too_many! opts, :left_date, string
      opts[:left_date] = Date.new(*(1..3).map { |i| ::Regexp.last_match(i).to_i })
      tmp = ::Regexp.last_match(4)
    when OP_MATCH
      ensure_not_too_many! opts, :operation, string
      opts[:operation] = to_operator ::Regexp.last_match(1)
      tmp = ::Regexp.last_match(2)
    when COMMENT_MATCH
      side = opts[:operation] ? :right_expression : :left_expression
      ensure_not_too_many! opts, side, string
      opts[side] = ::Regexp.last_match(1)
      tmp = ::Regexp.last_match(2)
    else
      begin
        commodity, tmp = RVGP::Journal::Commodity.from_s_with_remainder tmp
      rescue RVGP::Journal::Commodity::Error
        raise Error, MSG_UNPARSEABLE % string
      end

      side = opts[:operation] ? :right : :left
      ensure_not_too_many! opts, side, string
      opts[side] = commodity
    end
  end

  new opts
end

Instance Method Details

#invert!Object

For now, we simply delegate this message to the :left commodity.



116
117
118
119
# File 'lib/rvgp/journal/complex_commodity.rb', line 116

def invert!
  left.invert!
  self
end

#positive?Boolean

For now, we simply delegate these messages to the :left commodity. This path is only in use in the reconciler, which, determines whether the commodity is income or expense. (and/or inverts the amount) It’s conceivable that we may want to test the right commodity at some point here, and determine if the net operation is positive or negative.

Returns:

  • (Boolean)


111
112
113
# File 'lib/rvgp/journal/complex_commodity.rb', line 111

def positive?
  left.positive?
end

#to_sString

De-parse this ComplexCommodity, back into its string representation

Returns:



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/rvgp/journal/complex_commodity.rb', line 123

def to_s
  [left_is_equal ? '=' : nil,
   left ? left.to_s : nil,
   if left_lot_operation && left_lot
     format(left_lot_operation == :per_unit ? '{%s}' : '{{%s}}',
            *[left_lot_is_equal ? '=' : nil, left_lot.to_s].compact.join)
   end,
   left_date ? format('[%s]', left_date.to_s) : nil,
   left_expression ? format('(%s)', left_expression.to_s) : nil,
   left_lambda ? format('((%s))', left_lambda.to_s) : nil,
   if operation
     operation == :per_unit ? '@' : '@@'
   end,
   right_is_equal ? '=' : nil,
   right ? right.to_s : nil,
   right_expression ? format('(%s)', right_expression.to_s) : nil].compact.join(' ')
end