class Reckon::LedgerParser
Attributes
entries[RW]
Public Class Methods
new(ledger, options = {})
click to toggle source
# File lib/reckon/ledger_parser.rb, line 115 def initialize(ledger, options = {}) @options = options @date_format = options[:ledger_date_format] || options[:date_format] || '%Y-%m-%d' parse(ledger) end
Public Instance Methods
parse(ledger)
click to toggle source
# File lib/reckon/ledger_parser.rb, line 121 def parse(ledger) @entries = [] new_entry = {} in_comment = false comment_chars = ';#%*|' ledger.strip.split("\n").each do |entry| # strip comment lines in_comment = true if entry == 'comment' in_comment = false if entry == 'end comment' next if in_comment next if entry =~ /^\s*[#{comment_chars}]/ # (date, type, code, description), type and code are optional if (m = entry.match(%r{^(\d+[\d/-]+)\s+([*!])?\s*(\([^)]+\))?\s*(.*)$})) add_entry(new_entry) new_entry = { date: try_parse_date(m[1]), type: m[2] || "", code: m[3] && m[3].tr('()', '') || "", desc: m[4].strip, accounts: [] } elsif entry =~ /^\s*$/ && new_entry[:date] add_entry(new_entry) new_entry = {} elsif new_entry[:date] && entry =~ /^\s+/ LOGGER.info("Adding new account #{entry}") new_entry[:accounts] << parse_account_line(entry) else LOGGER.info("Unknown entry type: #{entry}") add_entry(new_entry) new_entry = {} end end add_entry(new_entry) end
to_csv()
click to toggle source
roughly matches ledger csv format
# File lib/reckon/ledger_parser.rb, line 159 def to_csv return @entries.flat_map do |n| n[:accounts].map do |a| row = [ n[:date].strftime(@date_format), n[:code], n[:desc], a[:name], "", # currency (not implemented) a[:amount], n[:type], "", # account comment (not implemented) ] CSV.generate_line(row).strip end end end
Private Instance Methods
add_entry(entry)
click to toggle source
# File lib/reckon/ledger_parser.rb, line 179 def add_entry(entry) return unless entry[:date] && entry[:accounts].length > 1 entry[:accounts] = balance(entry[:accounts]) @entries << entry end
balance(accounts)
click to toggle source
# File lib/reckon/ledger_parser.rb, line 210 def balance(accounts) return accounts unless accounts.any? { |i| i[:amount].nil? } sum = accounts.reduce(0) { |m, n| m + (n[:amount] || 0) } count = 0 accounts.each do |account| next unless account[:amount].nil? count += 1 account[:amount] = -sum end if count > 1 puts "Warning: unparsable entry due to more than one missing money value." p accounts puts end accounts end
clean_money(money)
click to toggle source
# File lib/reckon/ledger_parser.rb, line 230 def clean_money(money) return nil if money.nil? || money.empty? money.gsub(/[^0-9.-]/, '').to_f end
parse_account_line(entry)
click to toggle source
# File lib/reckon/ledger_parser.rb, line 195 def parse_account_line(entry) (account_name, rest) = entry.strip.split(/\s{2,}|\t+/, 2) return { name: account_name, amount: clean_money("") } if rest.nil? || rest.empty? (value, _comment) = rest.split(/;/) return { name: account_name, amount: clean_money(value || "") } end
try_parse_date(date_str)
click to toggle source
# File lib/reckon/ledger_parser.rb, line 186 def try_parse_date(date_str) date = Date.parse(date_str) return nil if date.year > 9999 || date.year < 1000 date rescue ArgumentError nil end