reckon 0.9.5 → 0.10.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 489081763b4a46c0bad41cb3ff3435c505a413e3e2cdf39984ff51b6488bbcc2
4
- data.tar.gz: c4f6cfc6bec319fd8366c5effabb6c23b086bd0b90a57f9f9f64742805a03fc1
3
+ metadata.gz: 9b0141e2c8428b74039967461cb7db1b06fd4acd63edb2db8921ec2c9a2895ec
4
+ data.tar.gz: 82c6d558f8e030988114db22d9ad59f580c7275a10a019b68c02355ea6639c71
5
5
  SHA512:
6
- metadata.gz: b995bbf4939b6901fa769188a4f329a549d0ea20048a968c331fdce40c7e79174d65454d1805f5ba9a66717c7269afbdeef931af8e961a52e1327a7edbbc80e1
7
- data.tar.gz: e5107209cf8e6da13c6484d064ec7e6e911502128d891d03590b04354ae5fa24670044e77aab2b90733882391cadb3a8adbd1a8645916b7dcb39d8811dba5070
6
+ metadata.gz: ec2519c7bd438012ae498b4e3a9517d6c63ae3de71bf7237043bdbeb60c589a88180a9171f0bcb5b25f1e7dda35c7f6eb9d99173d6c9905e2bc0913fe244cca6
7
+ data.tar.gz: 8f3ea58e38dc864cdcbb10b8ecb50f214f0dfa903eef6029b9c48057807d6910de3928b61c7bc4e14051dd9052f01fafcefab9bffca8dc107d516f17e5e4a427
data/.rubocop.yml CHANGED
@@ -18,3 +18,15 @@ Metrics/AbcSize:
18
18
 
19
19
  Style/NumericPredicate:
20
20
  Enabled: False
21
+
22
+ Metrics/PerceivedComplexity:
23
+ Enabled: False
24
+
25
+ Metrics/CyclomaticComplexity:
26
+ Enabled: False
27
+
28
+ Style/FormatString:
29
+ Enabled: False
30
+
31
+ Naming/MethodParameterName:
32
+ Enabled: False
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [v0.10.0](https://github.com/cantino/reckon/tree/v0.10.0) (2024-11-27)
4
+
5
+ [Full Changelog](https://github.com/cantino/reckon/compare/v0.9.6...v0.10.0)
6
+
7
+ **Closed issues:**
8
+
9
+ - Is it possible to use reckon with only no info about incoming or going out? [\#131](https://github.com/cantino/reckon/issues/131)
10
+ - Reckon fails immediately with error about uninitialized constant `Readline` [\#129](https://github.com/cantino/reckon/issues/129)
11
+
12
+ ## [v0.9.6](https://github.com/cantino/reckon/tree/v0.9.6) (2024-03-27)
13
+
14
+ [Full Changelog](https://github.com/cantino/reckon/compare/v0.9.5...v0.9.6)
15
+
16
+ **Closed issues:**
17
+
18
+ - reckon can't learn from a file with the "wrong" date format [\#130](https://github.com/cantino/reckon/issues/130)
19
+
3
20
  ## [v0.9.5](https://github.com/cantino/reckon/tree/v0.9.5) (2024-01-08)
4
21
 
5
22
  [Full Changelog](https://github.com/cantino/reckon/compare/v0.9.4...v0.9.5)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- reckon (0.9.5)
4
+ reckon (0.10.0)
5
5
  chronic (>= 0.3.0)
6
6
  highline (~> 2.0)
7
7
  matrix (>= 0.4.2)
data/README.md CHANGED
@@ -49,6 +49,8 @@ Learn more:
49
49
  Column number of the money columns, starts from 1 (1 or 2 columns)
50
50
  --raw-money
51
51
  Don't format money column (for stocks)
52
+ --sort DATE|DESC|AMT
53
+ Sort file by date, description, or amount
52
54
  --date-column 3
53
55
  Column number of the date column, starts from 1
54
56
  --contains-header [N]
data/Rakefile CHANGED
@@ -8,6 +8,7 @@ RSpec::Core::RakeTask.new(:spec)
8
8
 
9
9
  task default: :spec
10
10
 
11
+ desc "Run specs and integration tests"
11
12
  task :test_all do
12
13
  puts "#{`ledger --version |head -n1`}"
13
14
  puts "Running unit tests"
@@ -16,6 +17,7 @@ task :test_all do
16
17
  Rake::Task["test_integration"].invoke
17
18
  end
18
19
 
20
+ desc "Run integration tests"
19
21
  task :test_integration do
20
22
  cmd = 'prove -v ./spec/integration/test.sh'
21
23
  raise 'Integration tests failed' unless system(cmd)
@@ -28,5 +28,6 @@ gem build reckon.gemspec
28
28
  echo "Push changes and tags"
29
29
  echo "git push && git push --tags"
30
30
  echo "Push new gem"
31
- echo "gem push reckon-$VERSION.gem"
31
+ echo "gem push reckon-$VERSION.gem --otp (ykman oath accounts code -s rubygems.org)"
32
+ echo "Publish draft github release"
32
33
  gh release create "v$VERSION" "reckon-$VERSION.gem" --draft --generate-notes
data/lib/reckon/app.rb CHANGED
@@ -10,10 +10,11 @@ module Reckon
10
10
 
11
11
  def initialize(opts = {})
12
12
  self.options = opts
13
- LOGGER.level = Logger::INFO if options[:verbose]
13
+ LOGGER.level = options[:verbose] || Logger::WARN
14
14
 
15
15
  self.regexps = {}
16
16
  self.seen = Set.new
17
+ options[:sort] ||= :date
17
18
  @cli = HighLine.new
18
19
  @csv_parser = CSVParser.new(options)
19
20
  @matcher = CosineSimilarity.new(options)
@@ -80,7 +81,7 @@ module Reckon
80
81
 
81
82
  # Add tokens from account_tokens_file to accounts
82
83
  def extract_account_tokens(subtree, account = nil)
83
- if subtree.nil?
84
+ if subtree.nil? || !subtree
84
85
  puts "Warning: empty #{account} tree"
85
86
  {}
86
87
  elsif subtree.is_a?(Array)
@@ -141,6 +142,11 @@ module Reckon
141
142
  line2 = [options[:bank_account], row[:pretty_money]]
142
143
  end
143
144
 
145
+ if answer == '~~SKIP~~'
146
+ LOGGER.info "skipping transaction: #{row}"
147
+ next
148
+ end
149
+
144
150
  finish if %w[quit q].include?(answer)
145
151
  if %w[skip s].include?(answer)
146
152
  interactive_output "Skipping"
@@ -168,18 +174,29 @@ module Reckon
168
174
  :money => @csv_parser.money_for(index),
169
175
  :description => @csv_parser.description_for(index) }
170
176
  end
171
- rows.sort_by { |n| [n[:date], -n[:money], n[:description]] }.each { |row| yield row }
177
+ rows.sort_by do |n|
178
+ [n[options[:sort]], -n[:money], n[:description]]
179
+ end.each do |row|
180
+ yield row
181
+ end
172
182
  end
173
183
 
174
184
  def print_transaction(rows, fh = $stdout)
175
185
  str = "\n"
176
- header = %w[Date Amount Description Note]
186
+ header = %w[Date Amount Description]
187
+ header += ["Note"] if rows.map { |r| r[:note] }.any?
177
188
  maxes = header.map(&:length)
178
-
179
- rows = rows.map { |r| [r[:pretty_date], r[:pretty_money], r[:description], r[:note]] }
189
+ rows = rows.map do |r|
190
+ [r[:pretty_date], r[:pretty_money], r[:description], r[:note]].compact
191
+ end
180
192
 
181
193
  rows.each do |r|
182
- r.length.times { |i| l = r[i] ? r[i].length : 0; maxes[i] = l if maxes[i] < l }
194
+ r.length.times do |i|
195
+ l = 0
196
+ l = r[i].length if r[i]
197
+ maxes[i] ||= 0
198
+ maxes[i] = l if maxes[i] < l
199
+ end
183
200
  end
184
201
 
185
202
  header.each_with_index do |n, i|
@@ -199,6 +216,15 @@ module Reckon
199
216
  end
200
217
 
201
218
  def ask_account_question(msg, row)
219
+ # return account token if it matches
220
+ token_answer = most_specific_regexp_match(row)
221
+ if token_answer.any?
222
+ row[:note] = "Matched account token"
223
+ puts "NOTE: Matched account token"
224
+ puts token_answer[0]
225
+ return token_answer[0]
226
+ end
227
+
202
228
  possible_answers = suggest(row)
203
229
  LOGGER.info "possible_answers===> #{possible_answers.inspect}"
204
230
 
@@ -12,6 +12,7 @@ require 'set'
12
12
  # These weights and measures are used to suggest which account a transaction should be
13
13
  # assigned to.
14
14
  module Reckon
15
+ # Calculates cosine similarity for tf/idf
15
16
  class CosineSimilarity
16
17
  DocumentInfo = Struct.new(:tokens, :accounts)
17
18
 
@@ -64,13 +64,13 @@ module Reckon
64
64
  private
65
65
 
66
66
  def filter_csv
67
- if options[:ignore_columns]
68
- new_columns = []
69
- columns.each_with_index do |column, index|
70
- new_columns << column unless options[:ignore_columns].include?(index + 1)
71
- end
72
- @columns = new_columns
67
+ return unless options[:ignore_columns]
68
+
69
+ new_columns = []
70
+ columns.each_with_index do |column, index|
71
+ new_columns << (options[:ignore_columns].include?(index + 1) ? [''] * column.length : column)
73
72
  end
73
+ @columns = new_columns
74
74
  end
75
75
 
76
76
  def evaluate_columns(cols)
@@ -222,7 +222,8 @@ module Reckon
222
222
  # convert to a stringio object to handle multi-line fields
223
223
  parser_opts = {
224
224
  col_sep: separator,
225
- skip_blanks: true
225
+ skip_blanks: true,
226
+ row_sep: :auto
226
227
  }
227
228
  begin
228
229
  rows = CSV.parse(StringIO.new(data), **parser_opts)
@@ -235,7 +236,7 @@ module Reckon
235
236
  index = data.index("\n", index) + 1 # skip over newline character
236
237
  count += 1
237
238
  end
238
- rows = CSV.parse(StringIO.new(data[index..-1]), **parser_opts)
239
+ rows = CSV.parse(StringIO.new(data[index..]), **parser_opts)
239
240
  rows[0..-footer_lines_to_skip]
240
241
  end
241
242
  end
@@ -107,8 +107,8 @@
107
107
  require 'rubygems'
108
108
 
109
109
  module Reckon
110
+ # Parses ledger files
110
111
  class LedgerParser
111
-
112
112
  # ledger is an object that response to #each_line,
113
113
  # (i.e. a StringIO or an IO object)
114
114
  def initialize(options = {})
@@ -130,7 +130,7 @@ module Reckon
130
130
  next if entry =~ /^\s*[#{comment_chars}]/
131
131
 
132
132
  # (date, type, code, description), type and code are optional
133
- if (m = entry.match(%r{^(\d+[\d/-]+)\s+([*!])?\s*(\([^)]+\))?\s*(.*)$}))
133
+ if (m = entry.match(%r{^(\d+[^\s]+)\s+([*!])?\s*(\([^)]+\))?\s*(.*)$}))
134
134
  add_entry(entries, new_entry)
135
135
  new_entry = {
136
136
  date: try_parse_date(m[1]),
@@ -140,7 +140,7 @@ module Reckon
140
140
  accounts: []
141
141
  }
142
142
  elsif entry =~ /^\s*$/ && new_entry[:date]
143
- add_entry(entries,new_entry)
143
+ add_entry(entries, new_entry)
144
144
  new_entry = {}
145
145
  elsif new_entry[:date] && entry =~ /^\s+/
146
146
  LOGGER.info("Adding new account #{entry}")
@@ -175,13 +175,13 @@ module Reckon
175
175
  end
176
176
 
177
177
  def format_row(row, line1, line2)
178
- out = "#{row[:pretty_date]}\t#{row[:description]}#{row[:note] ? "\t; " + row[:note]: ""}\n"
178
+ note = row[:note] ? "\t; #{row[:note]}" : ""
179
+ out = "#{row[:pretty_date]}\t#{row[:description]}#{note}\n"
179
180
  out += "\t#{line1.first}\t\t\t#{line1.last}\n"
180
181
  out += "\t#{line2.first}\t\t\t#{line2.last}\n\n"
181
182
  out
182
183
  end
183
184
 
184
-
185
185
  private
186
186
 
187
187
  def add_entry(entries, entry)
data/lib/reckon/logger.rb CHANGED
@@ -1,4 +1,8 @@
1
1
  module Reckon
2
2
  LOGGER = Logger.new(STDERR)
3
3
  LOGGER.level = Logger::WARN
4
+
5
+ def log(tag, msg)
6
+ LOGGER.add(Logger::WARN, msg, tag)
7
+ end
4
8
  end
@@ -4,7 +4,6 @@ module Reckon
4
4
  # Singleton class for parsing command line flags
5
5
  class Options
6
6
  def self.parse_command_line_options(args = ARGV, stdin = $stdin)
7
- cli = HighLine.new
8
7
  options = { output_file: $stdout }
9
8
  OptionParser.new do |opts|
10
9
  opts.banner = "Usage: Reckon.rb [options]"
@@ -18,8 +17,17 @@ module Reckon
18
17
  options[:bank_account] = a
19
18
  end
20
19
 
21
- opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
22
- options[:verbose] = v
20
+ options[:verbose] = Logger::WARN
21
+ opts.on("-v", "--v", "Run verbosely (show info log messages)") do
22
+ options[:verbose] = Logger::INFO
23
+ end
24
+
25
+ opts.on("", "--verbose", "Run verbosely (show info log messages)") do
26
+ options[:verbose] = Logger::INFO
27
+ end
28
+
29
+ opts.on("", "--vv", "Run very verbosely (show debug log messages)") do
30
+ options[:verbose] = Logger::DEBUG
23
31
  end
24
32
 
25
33
  opts.on("-i", "--inverse", "Use the negative of each amount") do |v|
@@ -58,6 +66,19 @@ module Reckon
58
66
  options[:raw] = n
59
67
  end
60
68
 
69
+ options[:sort] = :date
70
+ opts.on("", "--sort DATE|DESC|AMT", "Sort file by date, description, or amount") do |s|
71
+ if s == 'DESC'
72
+ options[:sort] = :description
73
+ elsif s == 'AMT'
74
+ options[:sort] = :money
75
+ elsif s == 'DATE'
76
+ options[:sort] = :date
77
+ else
78
+ raise "'#{s}' is not valid. valid sort options are DATE, DESC, AMT"
79
+ end
80
+ end
81
+
61
82
  opts.on("", "--date-column 3", Integer,
62
83
  "Column number of the date column, starts from 1") do |col|
63
84
  options[:date_column] = col
@@ -161,26 +182,35 @@ module Reckon
161
182
  options[:string] = stdin.read
162
183
  end
163
184
 
185
+ validate_options(options)
186
+
187
+ return options
188
+ end
189
+
190
+ def self.validate_options(options)
191
+ cli = HighLine.new
164
192
  unless options[:file]
165
193
  options[:file] = cli.ask("What CSV file should I parse? ")
166
- unless options[:file].empty?
167
- puts "\nYou must provide a CSV file to parse.\n"
168
- puts parser
194
+ if options[:file].empty?
195
+ puts "\nERROR: You must provide a CSV file to parse.\n"
169
196
  exit
170
197
  end
171
198
  end
172
199
 
173
200
  unless options[:bank_account]
174
- raise "Must specify --account in unattended mode" if options[:unattended]
201
+ if options[:unattended]
202
+ puts "ERROR: Must specify --account in unattended mode"
203
+ exit
204
+ end
175
205
 
176
- options[:bank_account] = cli.ask("What is this account named in Ledger?\n") do |q|
206
+ options[:bank_account] = cli.ask("What is the Ledger account name?\n") do |q|
177
207
  q.readline = true
178
208
  q.validate = /^.{2,}$/
179
209
  q.default = "Assets:Bank:Checking"
180
210
  end
181
211
  end
182
212
 
183
- return options
213
+ return true
184
214
  end
185
215
  end
186
216
  end
@@ -1,3 +1,3 @@
1
1
  module Reckon
2
- VERSION = "0.9.5"
2
+ VERSION = "0.10.0"
3
3
  end
@@ -8,7 +8,7 @@ ledger_file = ARGV[0]
8
8
  account = ARGV[1]
9
9
  seed = ARGV[2] ? ARGV[2].to_i : Random.new_seed
10
10
 
11
- ledger = Reckon::LedgerParser.new(File.new(ledger_file))
11
+ ledger = Reckon::LedgerParser.new.parse(File.new(ledger_file))
12
12
  matcher = Reckon::CosineSimilarity.new({})
13
13
 
14
14
  train = []
@@ -50,3 +50,4 @@ end
50
50
  # pp result.compact
51
51
  puts "using #{seed} as random seed"
52
52
  puts "true: #{result.count(nil)} false: #{result.count { |v| !v.nil? }}"
53
+ puts(result.filter { |v| !v.nil? })
@@ -1,11 +1,11 @@
1
1
 
2
- Date | Amount | Description | Note |
3
- 2003-12-24 | $2,105.00 | CREDIT; Some Company vendorpymt PPD ID: 5KL3832735 | |
4
- 2004-12-24 | -$116.22 | CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL | |
5
- 2005-12-24 | -$0.96 | DEBIT; WEBSITE-BALANCE-10DEC09 12 12/10WEBSITE-BAL | |
6
- 2006-12-24 | $0.23 | DEBIT; WEBSITE-BALANCE-17DEC09 12 12/17WEBSITE-BAL | |
7
- 2007-12-24 | $1,558.52 | CREDIT; Blarg BLARG REVENUE PPD ID: 00jah78563 | |
8
- 2008-12-24 | $3,520.00 | CREDIT; Some Company vendorpymt PPD ID: 59728JSL20 | |
9
- 2009-12-24 | -$7.00 | DEBIT; GITHUB 041287430274 CA 12/22GITHUB 04 | |
10
- 2010-12-24 | -$20.00 | CHECK; CHECK 2656 | |
11
- 2011-12-24 | -$85.00 | DEBIT; HOST 037196321563 MO 12/22SLICEHOST | |
2
+ Date | Amount | Description |
3
+ 2003-12-24 | $2,105.00 | CREDIT; Some Company vendorpymt PPD ID: 5KL3832735 |
4
+ 2004-12-24 | -$116.22 | CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL |
5
+ 2005-12-24 | -$0.96 | DEBIT; WEBSITE-BALANCE-10DEC09 12 12/10WEBSITE-BAL |
6
+ 2006-12-24 | $0.23 | DEBIT; WEBSITE-BALANCE-17DEC09 12 12/17WEBSITE-BAL |
7
+ 2007-12-24 | $1,558.52 | CREDIT; Blarg BLARG REVENUE PPD ID: 00jah78563 |
8
+ 2008-12-24 | $3,520.00 | CREDIT; Some Company vendorpymt PPD ID: 59728JSL20 |
9
+ 2009-12-24 | -$7.00 | DEBIT; GITHUB 041287430274 CA 12/22GITHUB 04 |
10
+ 2010-12-24 | -$20.00 | CHECK; CHECK 2656 |
11
+ 2011-12-24 | -$85.00 | DEBIT; HOST 037196321563 MO 12/22SLICEHOST |
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- #encoding: utf-8
2
+ # encoding: utf-8
3
3
 
4
4
  require_relative "../spec_helper"
5
5
  require 'rubygems'
@@ -28,35 +28,42 @@ describe Reckon::LedgerParser do
28
28
  property_of do
29
29
  Rantly do
30
30
  description = Proc.new do
31
- sized(15){string}.tr(%q{'`:*\\},'').gsub(/\s+/, ' ').gsub(/^[!;<\[( #{comment_chars}]+/, '')
31
+ sized(15) {
32
+ string
33
+ }.tr(%q{'`:*\\}, '').gsub(/\s+/, ' ').gsub(/^[!;<\[( #{comment_chars}]+/, '')
32
34
  end
33
35
  currency = choose(*currencies) # to be consistent within the transaction
34
- single_line_comments = ";#|%*".split('').map { |n| "#{n} #{call(description)}" }
36
+ single_line_comments = ";#|%*".split('').map { |n|
37
+ "#{n} #{call(description)}"
38
+ }
35
39
  comments = ['', '; ', "\t;#{call(description)}", " ; #{call(description)}"]
36
40
  date = Time.at(range(0, 1_581_389_644)).strftime(choose(*formats))
37
41
  codes = [' ', " (#{string(:alnum).tr('()', '')}) "]
38
42
  account = Proc.new { choose(*delimiters) + call(description) }
39
43
  account_money = Proc.new do
40
- sprintf("%.02f", (float * range(5,10) + 1) * choose(1, -1))
44
+ sprintf("%.02f", (float * range(5, 10) + 1) * choose(1, -1))
41
45
  end
42
46
  account_line = Proc.new do
43
47
  call(account) + \
44
- choose(*delimiters) + \
45
- currency + \
46
- choose(*currency_delimiters) + \
47
- call(account_money) + \
48
- choose(*comments)
48
+ choose(*delimiters) + \
49
+ currency + \
50
+ choose(*currency_delimiters) + \
51
+ call(account_money) + \
52
+ choose(*comments)
49
53
  end
50
54
  ledger = "#{date}#{choose(*types)}#{choose(*codes)}#{call(description)}\n"
51
- range(1,5).times do
55
+ range(1, 5).times do
52
56
  ledger += "#{call(account_line)}\n"
53
57
  end
54
58
  ledger += "#{call(account)}\n"
55
59
  ledger += choose(*single_line_comments) + "\n"
56
60
  ledger
57
61
  end
58
- end.check(1000) do |s|
59
- filter_format = lambda { |n| [n['date'], n['desc'], n['name'], sprintf("%.02f", n['amount'])] }
62
+ end.check(100) do |s|
63
+ filter_format = lambda { |n|
64
+ [n['date'], n['desc'], n['name'],
65
+ sprintf("%.02f", n['amount'])]
66
+ }
60
67
  headers = %w[date code desc name currency amount type commend]
61
68
  safe_s = Shellwords.escape(s)
62
69
 
@@ -64,7 +71,8 @@ describe Reckon::LedgerParser do
64
71
  actual = CSV.parse(lp_csv, headers: headers).map(&filter_format)
65
72
 
66
73
  ledger_csv = `echo #{safe_s} | ledger csv --date-format '%Y/%m/%d' -f - `
67
- expected = CSV.parse(ledger_csv.gsub('\"', '""'), headers: headers).map(&filter_format)
74
+ expected = CSV.parse(ledger_csv.gsub('\"', '""'),
75
+ headers: headers).map(&filter_format)
68
76
  expected.length.times do |i|
69
77
  expect(actual[i]).to eq(expected[i])
70
78
  end
@@ -72,34 +80,35 @@ describe Reckon::LedgerParser do
72
80
  end
73
81
 
74
82
  it 'should filter block comments' do
75
- ledger = <<HERE
76
- 1970/11/01 Dinner should show up
77
- Assets:Checking -123.00
78
- Expenses:Restaurants
83
+ ledger = <<~HERE
84
+ 1970/11/01 Dinner should show up
85
+ Assets:Checking -123.00
86
+ Expenses:Restaurants
79
87
 
80
- comment
88
+ comment
81
89
 
82
- 1970/11/01 Lunch should NOT show up
83
- Assets:Checking -12.00
84
- Expenses:Restaurants
90
+ 1970/11/01 Lunch should NOT show up
91
+ Assets:Checking -12.00
92
+ Expenses:Restaurants
85
93
 
86
- end comment
87
- HERE
94
+ end comment
95
+ HERE
88
96
  entries = Reckon::LedgerParser.new.parse(StringIO.new(ledger))
89
97
  expect(entries.length).to eq(1)
90
98
  expect(entries.first[:desc]).to eq('Dinner should show up')
91
-
92
99
  end
93
100
 
94
101
  it 'should transaction comments' do
95
- ledger = <<HERE
96
- 2020-03-27 AMZN Mktp USX999H3203; Shopping; Sale
97
- Expenses:Household $82.77
98
- Liabilities:ChaseSapphire -$81.77
99
- # END FINANCE SCRIPT OUTPUT Thu 02 Apr 2020 12:05:54 PM EDT
100
- HERE
102
+ ledger = <<~HERE
103
+ 2020-03-27 AMZN Mktp USX999H3203; Shopping; Sale
104
+ Expenses:Household $82.77
105
+ Liabilities:ChaseSapphire -$81.77
106
+ # END FINANCE SCRIPT OUTPUT Thu 02 Apr 2020 12:05:54 PM EDT
107
+ HERE
101
108
  entries = Reckon::LedgerParser.new.parse(StringIO.new(ledger))
102
- expect(entries.first[:accounts].map { |n| n[:name] }).to eq(['Expenses:Household', 'Liabilities:ChaseSapphire'])
109
+ expect(entries.first[:accounts].map { |n|
110
+ n[:name]
111
+ }).to eq(['Expenses:Household', 'Liabilities:ChaseSapphire'])
103
112
  expect(entries.first[:accounts].size).to eq(2)
104
113
  expect(entries.length).to eq(1)
105
114
  end
@@ -123,80 +132,99 @@ HERE
123
132
  @entries.last[:accounts].last[:name].should == "Assets:Bank:Checking"
124
133
  @entries.last[:accounts].last[:amount].should == -20.24
125
134
  end
135
+
136
+ it "should parse dot-separated dates" do
137
+ ledger = <<~HERE
138
+ 2024.03.12 groceries; 11223344556; 32095205940
139
+ assets:bank:spending 530.00 NOK
140
+ assets:bank:co:groceries
141
+
142
+ 2024.03.13 autosave; 11223344555; 11223344556
143
+ assets:bank:savings
144
+ assets:bank:spending -10.00 NOK
145
+ HERE
146
+ options = { ledger_date_format: '%Y.%m.%d' }
147
+ entries = Reckon::LedgerParser.new(options).parse(StringIO.new(ledger))
148
+ expect(entries.first[:date]).to eq(Date.new(2024, 3, 12))
149
+ expect(entries.last[:date]).to eq(Date.new(2024, 3, 13))
150
+ expect(entries.length).to eq(2)
151
+ end
126
152
  end
127
153
 
128
154
  describe "balance" do
129
155
  it "it should balance out missing account values" do
130
156
  @ledger.send(:balance, [
131
- { :name => "Account1", :amount => 1000 },
132
- { :name => "Account2", :amount => nil }
133
- ]).should == [ { :name => "Account1", :amount => 1000 }, { :name => "Account2", :amount => -1000 } ]
157
+ { :name => "Account1", :amount => 1000 },
158
+ { :name => "Account2", :amount => nil }
159
+ ]).should == [{ :name => "Account1", :amount => 1000 },
160
+ { :name => "Account2", :amount => -1000 }]
134
161
  end
135
162
 
136
163
  it "it should balance out missing account values" do
137
164
  @ledger.send(:balance, [
138
- { :name => "Account1", :amount => 1000 },
139
- { :name => "Account2", :amount => 100 },
140
- { :name => "Account3", :amount => -200 },
141
- { :name => "Account4", :amount => nil }
142
- ]).should == [
143
- { :name => "Account1", :amount => 1000 },
144
- { :name => "Account2", :amount => 100 },
145
- { :name => "Account3", :amount => -200 },
146
- { :name => "Account4", :amount => -900 }
147
- ]
165
+ { :name => "Account1", :amount => 1000 },
166
+ { :name => "Account2", :amount => 100 },
167
+ { :name => "Account3", :amount => -200 },
168
+ { :name => "Account4", :amount => nil }
169
+ ]).should == [
170
+ { :name => "Account1", :amount => 1000 },
171
+ { :name => "Account2", :amount => 100 },
172
+ { :name => "Account3", :amount => -200 },
173
+ { :name => "Account4", :amount => -900 }
174
+ ]
148
175
  end
149
176
 
150
177
  it "it should work on normal values too" do
151
178
  @ledger.send(:balance, [
152
- { :name => "Account1", :amount => 1000 },
153
- { :name => "Account2", :amount => -1000 }
154
- ]).should == [ { :name => "Account1", :amount => 1000 }, { :name => "Account2", :amount => -1000 } ]
179
+ { :name => "Account1", :amount => 1000 },
180
+ { :name => "Account2", :amount => -1000 }
181
+ ]).should == [{ :name => "Account1", :amount => 1000 },
182
+ { :name => "Account2", :amount => -1000 }]
155
183
  end
156
184
  end
157
185
 
158
186
  # Data
159
187
 
160
- EXAMPLE_LEDGER = (<<-LEDGER).strip
161
- = /^Expenses:Books/
162
- (Liabilities:Taxes) -0.10
188
+ EXAMPLE_LEDGER = (<<~LEDGER).strip
189
+ = /^Expenses:Books/
190
+ (Liabilities:Taxes) -0.10
163
191
 
164
- ~ Monthly
165
- Assets:Bank:Checking $500.00
166
- Income:Salary
192
+ ~ Monthly
193
+ Assets:Bank:Checking $500.00
194
+ Income:Salary
167
195
 
168
- 2004-05-01 * Checking balance
169
- Assets:Bank:Checking $1,000.00
170
- Equity:Opening Balances
196
+ 2004-05-01 * Checking balance
197
+ Assets:Bank:Checking $1,000.00
198
+ Equity:Opening Balances
171
199
 
172
- 2004-05-01 * Checking balance
173
- Assets:Bank:Checking €1,000.00
174
- Equity:Opening Balances
200
+ 2004-05-01 * Checking balance
201
+ Assets:Bank:Checking €1,000.00
202
+ Equity:Opening Balances
175
203
 
176
- 2004-05-01 * Checking balance
177
- Assets:Bank:Checking 1,000.00 SEK
178
- Equity:Opening Balances
204
+ 2004-05-01 * Checking balance
205
+ Assets:Bank:Checking 1,000.00 SEK
206
+ Equity:Opening Balances
179
207
 
180
- 2004/05/01 * Investment balance
181
- Assets:Brokerage 50 AAPL @ $30.00
182
- Equity:Opening Balances
208
+ 2004/05/01 * Investment balance
209
+ Assets:Brokerage 50 AAPL @ $30.00
210
+ Equity:Opening Balances
183
211
 
184
- ; blah
185
- !account blah
212
+ ; blah
213
+ !account blah
186
214
 
187
- !end
215
+ !end
188
216
 
189
- D $1,000
217
+ D $1,000
190
218
 
191
- 2004/05/14 * Pay day
192
- Assets:Bank:Checking $500.00
193
- Income:Salary
219
+ 2004/05/14 * Pay day
220
+ Assets:Bank:Checking $500.00
221
+ Income:Salary
194
222
 
195
- 2004/05/27 Book Store
196
- Expenses:Books $20.00
197
- Liabilities:MasterCard
198
- 2004/05/27 (100) Credit card company
199
- Liabilities:MasterCard $20.24
200
- Assets:Bank:Checking
223
+ 2004/05/27 Book Store
224
+ Expenses:Books $20.00
225
+ Liabilities:MasterCard
226
+ 2004/05/27 (100) Credit card company
227
+ Liabilities:MasterCard $20.24
228
+ Assets:Bank:Checking
201
229
  LEDGER
202
230
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reckon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.5
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Cantino
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2024-01-08 00:00:00.000000000 Z
13
+ date: 2024-11-27 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rspec