Class: RVGP::Journal

Inherits:
Object
  • Object
show all
Defined in:
lib/rvgp/journal/journal.rb,
lib/rvgp/journal/pricer.rb,
lib/rvgp/journal/posting.rb,
lib/rvgp/journal/currency.rb,
lib/rvgp/journal/commodity.rb,
lib/rvgp/journal/complex_commodity.rb

Overview

This class parses a pta journal, and offers that journal in its constitutent parts. See the Journal.parse for the typical entry point, into this class. This class itself, really only offers the one method, .parse, to parse a pta journal’s contents. Most of the functionality in this class, is provided by the classes contained within it.

Defined Under Namespace

Classes: Commodity, ComplexCommodity, Currency, Posting, Pricer

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(postings) ⇒ Journal

Declare and initialize this Journal.

Parameters:



36
37
38
# File 'lib/rvgp/journal/journal.rb', line 36

def initialize(postings)
  @postings = postings
end

Instance Attribute Details

#postingsArray<RVGP::Journal::Posting> (readonly)

The postings that were encountered in this journal

Returns:



10
11
12
# File 'lib/rvgp/journal/journal.rb', line 10

def postings
  @postings
end

Class Method Details

.parse(contents) ⇒ RVGP::Journal

Given a pta journal, already read from the filesystem, return a parsed representation of its contents.

Parameters:

  • contents (String)

    A pta journal, as a string

Returns:

  • (RVGP::Journal)

    The parsed representation of the provided string



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/rvgp/journal/journal.rb', line 49

def self.parse(contents)
  postings = []

  posting = nil
  cite = nil
  contents.lines.each_with_index do |line, i|
    line_number = i + 1
    cite = [line_number, line.inspect] # in case we run into an error
    line_comment = nil

    # Here, we separate the line into non-comment lvalue and comment rvalue:
    # NOTE: We're not supporting escaped semicolons, at this time
    case line
    when /\A.*[^\\];.*[^\\];.*\Z/
      raise StandardError, format(MSG_TOO_MANY_SEMICOLONS, cite)
    when /\A( *.*?) *;[ \t]*(.*)\Z/
      line = ::Regexp.last_match(1)
      line_comment = ::Regexp.last_match(2)
    end

    # This case parses anything to the left of a comment:
    case line
    when /\A([^ \n].*)\Z/
      # This is a post declaration line
      raise StandardError, MSG_MISSING_POSTING_SEPARATOR % cite if posting
      unless %r{\A(\d{4})[/-](\d{2})[/-](\d{2}) +(.+?) *\Z}.match ::Regexp.last_match(1)
        raise StandardError, MSG_UNRECOGNIZED_HEADER % cite
      end

      begin
        date = Date.new ::Regexp.last_match(1).to_i, ::Regexp.last_match(2).to_i, ::Regexp.last_match(3).to_i
      rescue Date::Error
        raise StandardError, MSG_INVALID_DATE % cite
      end

      posting = Posting.new date, ::Regexp.last_match(4), line_number: line_number
    when /\A[ \t]+([^ ].+)\Z/
      # This is a transfer line, to be appended to the current posting
      raise StandardError, MSG_UNEXPECTED_TRANSFER % cite unless posting

      # NOTE: We chose 2 or more spaces as the separator between
      # the account and the commodity, mostly because this was the smallest
      # we could find in the official ledger documentation
      unless /\A(.+?)(?: {2,}([^ ].+)| *)\Z/.match ::Regexp.last_match(1)
        raise StandardError, format(MSG_UNPARSEABLE_TRANSFER, cite)
      end

      begin
        posting.append_transfer ::Regexp.last_match(1), ::Regexp.last_match(2)
      rescue RVGP::Journal::Commodity::Error
        raise StandardError, MSG_INVALID_TRANSFER_COMMODITY % cite
      end
    when /\A[ \t]*\Z/
      if line_comment.nil? && posting
        unless posting.valid?
          posting.transfers.each do |transfer|
            puts format('  - Not valid. account %<acct>s commodity: %<commodity>s complex_commodity: %<complex>s',
                        acct: transfer..inspect,
                        commodity: transfer.commodity.inspect,
                        complex: transfer.complex_commodity.inspect)
          end
        end

        raise StandardError, MSG_INVALID_POSTING % cite unless posting.valid?

        # This is a blank line
        postings << posting
        posting = nil
      end
    else
      raise StandardError, MSG_UNEXPECTED_LINE % cite unless posting
    end

    next unless line_comment && posting

    tags = line_comment.scan(/(?:[^ ]+: *[^,]*|:[^ \t]+:)/).map do |declaration|
      /\A:?(.+):\Z/.match(declaration) ? ::Regexp.last_match(1).split(':') : declaration
    end.flatten

    tags.each { |tag| posting.append_tag tag }
  end

  # The last line could be \n, which, makes this unnecessary
  if posting
    raise StandardError, MSG_INVALID_POSTING % cite unless posting.valid?

    postings << posting
  end

  new postings
end

Instance Method Details

#to_sString

Unparse this journal, and return the parsed objects in their serialized form.

Returns:

  • (String)

    A pta journal. Presumably, the same one we were initialized from



42
43
44
# File 'lib/rvgp/journal/journal.rb', line 42

def to_s
  @postings.map(&:to_ledger).join "\n\n"
end