reckon 0.7.1 → 0.8.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +7 -13
  3. data/.gitignore +3 -0
  4. data/CHANGELOG.md +43 -1
  5. data/Gemfile.lock +3 -1
  6. data/README.md +8 -5
  7. data/Rakefile +2 -2
  8. data/bin/reckon +3 -0
  9. data/lib/reckon/app.rb +6 -6
  10. data/lib/reckon/cosine_similarity.rb +67 -62
  11. data/lib/reckon/date_column.rb +3 -2
  12. data/lib/reckon/ledger_parser.rb +1 -1
  13. data/lib/reckon/money.rb +12 -5
  14. data/lib/reckon/options.rb +9 -1
  15. data/lib/reckon/version.rb +1 -1
  16. data/reckon.gemspec +1 -0
  17. data/spec/cosine_training_and_test.rb +52 -0
  18. data/spec/integration/another_bank_example/output.ledger +3 -3
  19. data/spec/integration/ask_for_account/cli_input.exp +33 -0
  20. data/spec/integration/ask_for_account/expected_output +11 -0
  21. data/spec/integration/ask_for_account/input.csv +9 -0
  22. data/spec/integration/ask_for_account/test_args +1 -0
  23. data/spec/integration/broker_canada_example/output.ledger +2 -2
  24. data/spec/integration/chase/account_tokens_and_regex/output.ledger +3 -3
  25. data/spec/integration/chase/default_account_names/output.ledger +3 -3
  26. data/spec/integration/chase/learn_from_existing/output.ledger +3 -3
  27. data/spec/integration/chase/simple/output.ledger +3 -3
  28. data/spec/integration/danish_kroner_nordea_example/output.ledger +1 -1
  29. data/spec/integration/extratofake/output.ledger +1 -1
  30. data/spec/integration/harder_date_example/output.ledger +2 -2
  31. data/spec/integration/ledger_date_format/compare_cmds +1 -0
  32. data/spec/integration/ledger_date_format/input.csv +3 -0
  33. data/spec/integration/ledger_date_format/output.ledger +12 -0
  34. data/spec/integration/ledger_date_format/test_args +1 -0
  35. data/spec/integration/test.sh +79 -27
  36. data/spec/reckon/app_spec.rb +1 -1
  37. data/spec/reckon/csv_parser_spec.rb +3 -3
  38. data/spec/reckon/date_column_spec.rb +12 -0
  39. data/spec/reckon/money_spec.rb +3 -3
  40. metadata +160 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13ec00f6b1277d98d6df6264af61020310ab5bf61197412a116a2f61901e6214
4
- data.tar.gz: cf1b1e21a64fac57a5796b64f53da971a48c4bb3f439be94fd847441c5b36a0f
3
+ metadata.gz: '08cdfad096b04db11f671775c0119fcecefa7db46134f1c43677e949628498db'
4
+ data.tar.gz: 5ed2722b2843a5147faabdd82b0027fe590ff9dd66000100e756fad6de13a116
5
5
  SHA512:
6
- metadata.gz: 450fb569cf8509563edd5fb36239965b9cd29b28d544399bcc10cf510a146d68fb56c3dd1737a1904cd7f23ab3453128d522fb07159068bbc6ed2d44cb0fe1d6
7
- data.tar.gz: 461bade5035f16c278131102df52b91e39a365b18c3e73875730616b5489f07f7c0528094da13a06558791b4238d1d6cc495452b14c917b15eef5eea2719e909
6
+ metadata.gz: 7ebe28c482f091424d405efa1dc4b94cdc76022b114ce6f0db22a96e3ec6104a6e1efecec833af981f9c97f4c705cbbbd7f791403b5b669452abccd2caf295c8
7
+ data.tar.gz: 7af0ef7c71bcca9b189d23939ebeb93eedbe0875373818419d878461c33035a5e92359204c939ac3d8933935ad29b8a9cec319ddb84ea13626e22f51897f2fea
@@ -21,23 +21,17 @@ jobs:
21
21
  matrix:
22
22
  ruby-version:
23
23
  # Current ruby stable version
24
+ - 3.1.2
25
+ # Ubuntu 22.04
24
26
  - 3.0
25
- # Ubuntu 20.10
27
+ # Ubuntu 20.04
26
28
  - 2.7
27
- # Ubuntu 19.10
28
- - 2.5
29
- # Mac v11 Big Sur
30
- # - 2.6?
31
- # Mac v10.15 Catalina
32
- - 2.6
33
- # Mac v10.14 Mojave
34
- - 2.3.7
35
29
  steps:
36
30
  - uses: actions/checkout@v2
31
+ - name: Update package
32
+ run: sudo apt-get update
37
33
  - name: Install packages
38
- run: sudo apt-get -y install ledger hledger
39
- - name: Install bundler
40
- run: sudo gem install -v 1.17.3 bundler
34
+ run: sudo apt-get -y install ledger hledger expect
41
35
  - name: Set up Ruby
42
36
  # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
43
37
  # change this to (see https://github.com/ruby/setup-ruby#versioning):
@@ -45,6 +39,6 @@ jobs:
45
39
  # uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
46
40
  with:
47
41
  ruby-version: ${{ matrix.ruby-version }}
48
- bundler-cache: true # runs 'bundle install' and caches installed gems automatically
42
+ bundler-cache: true # runs 'bundle install' and caches installed gems
49
43
  - name: Run tests
50
44
  run: bundle exec rake test_all
data/.gitignore CHANGED
@@ -28,6 +28,9 @@ private_tests
28
28
 
29
29
  ## Bundler
30
30
  vendor
31
+ .bundle
31
32
 
32
33
  test.log
33
34
  test_log.txt
35
+
36
+ .byebug_history
data/CHANGELOG.md CHANGED
@@ -1,6 +1,48 @@
1
1
  # Changelog
2
2
 
3
- ## [v0.7.1](https://github.com/cantino/reckon/tree/v0.7.1) (2021-02-06)
3
+ ## [v0.8.1](https://github.com/cantino/reckon/tree/v0.8.1) (2022-07-02)
4
+
5
+ [Full Changelog](https://github.com/cantino/reckon/compare/v0.8.0...v0.8.1)
6
+
7
+ **Closed issues:**
8
+
9
+ - Reckon files every transaction \(incoming and outgoing\) as Income:Unknown [\#116](https://github.com/cantino/reckon/issues/116)
10
+
11
+ **Merged pull requests:**
12
+
13
+ - fix bugs with test.sh [\#117](https://github.com/cantino/reckon/pull/117) ([benprew](https://github.com/benprew))
14
+
15
+ ## [v0.8.0](https://github.com/cantino/reckon/tree/v0.8.0) (2021-08-08)
16
+
17
+ [Full Changelog](https://github.com/cantino/reckon/compare/v0.7.2...v0.8.0)
18
+
19
+ **Closed issues:**
20
+
21
+ - --date-format '%d/%m/%Y' not working [\#113](https://github.com/cantino/reckon/issues/113)
22
+ - Reckon behaviour does not match what is explained on README.md [\#112](https://github.com/cantino/reckon/issues/112)
23
+ - --date-format '%d/%m/%Y' not working [\#111](https://github.com/cantino/reckon/issues/111)
24
+ - --date-format '%d/ [\#110](https://github.com/cantino/reckon/issues/110)
25
+
26
+ **Merged pull requests:**
27
+
28
+ - Add ledger-date-format option to specify ledger file date format [\#114](https://github.com/cantino/reckon/pull/114) ([benprew](https://github.com/benprew))
29
+
30
+ ## [v0.7.2](https://github.com/cantino/reckon/tree/v0.7.2) (2021-04-22)
31
+
32
+ [Full Changelog](https://github.com/cantino/reckon/compare/v0.7.1...v0.7.2)
33
+
34
+ **Closed issues:**
35
+
36
+ - \[feature request\] Better format for large transactions [\#108](https://github.com/cantino/reckon/issues/108)
37
+ - cosine similarity not comparing documents correctly [\#106](https://github.com/cantino/reckon/issues/106)
38
+
39
+ **Merged pull requests:**
40
+
41
+ - Add thousands separator in money output. Fixes \#108. [\#109](https://github.com/cantino/reckon/pull/109) ([benprew](https://github.com/benprew))
42
+ - Cosine similarity should use all docs tokens. not just matched tokens. [\#107](https://github.com/cantino/reckon/pull/107) ([benprew](https://github.com/benprew))
43
+ - Test getting expect working with actions [\#105](https://github.com/cantino/reckon/pull/105) ([benprew](https://github.com/benprew))
44
+
45
+ ## [v0.7.1](https://github.com/cantino/reckon/tree/v0.7.1) (2021-02-07)
4
46
 
5
47
  [Full Changelog](https://github.com/cantino/reckon/compare/v0.7.0...v0.7.1)
6
48
 
data/Gemfile.lock CHANGED
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- reckon (0.7.1)
4
+ reckon (0.8.1)
5
5
  chronic (>= 0.3.0)
6
6
  highline (>= 1.5.2)
7
+ matrix (>= 0.4.2)
7
8
  rchardet (>= 1.8.0)
8
9
 
9
10
  GEM
@@ -13,6 +14,7 @@ GEM
13
14
  coderay (1.1.2)
14
15
  diff-lcs (1.3)
15
16
  highline (2.0.3)
17
+ matrix (0.4.2)
16
18
  method_source (0.9.2)
17
19
  pry (0.12.2)
18
20
  coderay (~> 1.1.0)
data/README.md CHANGED
@@ -58,10 +58,13 @@ Learn more:
58
58
  --encoding 'UTF-8'
59
59
  Specify an encoding for the CSV file
60
60
  -c, --currency '$' Currency symbol to use - default $ (ex £, EUR)
61
- --date-format '%d/%m/%Y'
62
- Force the date format (see Ruby DateTime strftime)
61
+ --date-format FORMAT
62
+ CSV file date format (see `date` for format)
63
+ --ledger-date-format FORMAT
64
+ Ledger date format (see `date` for format)
63
65
  -u, --unattended Don't ask questions and guess all the accounts automatically. Use with --learn-from or --account-tokens options.
64
66
  -t, --account-tokens FILE YAML file with manually-assigned tokens for each account (see README)
67
+ --table-output-file FILE
65
68
  --default-into-account NAME
66
69
  Default into account
67
70
  --default-outof-account NAME
@@ -80,13 +83,13 @@ If you find CSV files that it can't parse, send me examples or pull requests!
80
83
  You can run reckon in a non-interactive mode.
81
84
  To guess the accounts reckon can use an existing ledger file or a token file with keywords.
82
85
 
83
- `reckon --unattended -l 2010.dat -f bank.csv -o ledger.dat`
86
+ `reckon --unattended -a Checking -l 2010.dat -f bank.csv -o ledger.dat`
84
87
 
85
- `reckon --unattended --account-tokens tokens.yaml -f bank.csv -o ledger.dat`
88
+ `reckon --unattended -a Checking --account-tokens tokens.yaml -f bank.csv -o ledger.dat`
86
89
 
87
90
  In unattended mode, you can use STDIN to read your csv data, by specifying `-` as the argument to `-f`.
88
91
 
89
- `csv_file_generator | reckon --unattended -l 2010.dat -o ledger.dat -f -`
92
+ `csv_file_generator | reckon --unattended -a Checking -l 2010.dat -o ledger.dat -f -`
90
93
 
91
94
  ### Account Tokens
92
95
 
data/Rakefile CHANGED
@@ -17,6 +17,6 @@ task :test_all do
17
17
  end
18
18
 
19
19
  task :integration_tests do
20
- puts `./spec/integration/test.sh`
21
- raise 'Integration tests failed' if $CHILD_STATUS.exitstatus != 0
20
+ cmd = 'prove -v ./spec/integration/test.sh'
21
+ raise 'Integration tests failed' unless system(cmd)
22
22
  end
data/bin/reckon CHANGED
@@ -13,6 +13,9 @@ reckon = Reckon::App.new(options)
13
13
 
14
14
  if options[:print_table]
15
15
  reckon.output_table
16
+ if options[:table_output_file]
17
+ File.open(options[:table_output_file], 'w') { |fh| reckon.output_table fh }
18
+ end
16
19
  exit
17
20
  end
18
21
 
data/lib/reckon/app.rb CHANGED
@@ -20,10 +20,10 @@ module Reckon
20
20
  learn!
21
21
  end
22
22
 
23
- def interactive_output(str)
23
+ def interactive_output(str, fh = $stdout)
24
24
  return if options[:unattended]
25
25
 
26
- puts str
26
+ fh.puts str
27
27
  end
28
28
 
29
29
  def learn!
@@ -161,7 +161,7 @@ module Reckon
161
161
  rows.sort_by { |n| [n[:date], -n[:money], n[:description]] }.each { |row| yield row }
162
162
  end
163
163
 
164
- def print_transaction(rows)
164
+ def print_transaction(rows, fh = $stdout)
165
165
  str = "\n"
166
166
  header = %w[Date Amount Description Note]
167
167
  maxes = header.map(&:length)
@@ -185,7 +185,7 @@ module Reckon
185
185
  str += "\n"
186
186
  end
187
187
 
188
- interactive_output str
188
+ interactive_output str, fh
189
189
  end
190
190
 
191
191
  def ask_account_question(msg, row)
@@ -280,12 +280,12 @@ module Reckon
280
280
  exit
281
281
  end
282
282
 
283
- def output_table
283
+ def output_table(fh = $stdout)
284
284
  rows = []
285
285
  each_row_backwards do |row|
286
286
  rows << row
287
287
  end
288
- print_transaction(rows)
288
+ print_transaction(rows, fh)
289
289
  end
290
290
  end
291
291
  end
@@ -1,47 +1,52 @@
1
1
  require 'matrix'
2
2
  require 'set'
3
3
 
4
- # Implementation of consine similarity using TF-IDF for vectorization.
5
- # Used to suggest which account a transaction should be assigned to
4
+ # Implementation of cosine similarity using TF-IDF for vectorization.
5
+ #
6
+ # In information retrieval, tf–idf, short for term frequency–inverse document frequency,
7
+ # is a numerical statistic that is intended to reflect how important a word is to a
8
+ # document in a collection or corpus
9
+ #
10
+ # Cosine Similarity a measurement to determine how similar 2 documents are to each other.
11
+ #
12
+ # These weights and measures are used to suggest which account a transaction should be
13
+ # assigned to.
6
14
  module Reckon
7
15
  class CosineSimilarity
16
+ DocumentInfo = Struct.new(:tokens, :accounts)
17
+
8
18
  def initialize(options)
19
+ @docs = DocumentInfo.new({}, {})
9
20
  @options = options
10
- @tokens = {}
11
- @accounts = Hash.new(0)
12
21
  end
13
22
 
14
23
  def add_document(account, doc)
15
- tokenize(doc).each do |n|
24
+ tokens = tokenize(doc)
25
+ LOGGER.info "doc tokens: #{tokens}"
26
+ tokens.each do |n|
16
27
  (token, count) = n
17
28
 
18
- @tokens[token] ||= {}
19
- @tokens[token][account] ||= 0
20
- @tokens[token][account] += count
21
- @accounts[account] += count
29
+ @docs.tokens[token] ||= Hash.new(0)
30
+ @docs.tokens[token][account] += count
31
+ @docs.accounts[account] ||= Hash.new(0)
32
+ @docs.accounts[account][token] += count
22
33
  end
23
34
  end
24
35
 
25
36
  # find most similar documents to query
26
37
  def find_similar(query)
27
- (query_scores, corpus_scores) = td_idf_scores_for(query)
38
+ LOGGER.info "find_similar #{query}"
28
39
 
29
- query_vector = Vector.elements(query_scores, false)
40
+ accounts = docs_to_check(query).map do |a|
41
+ [a, tfidf(@docs.accounts[a])]
42
+ end
30
43
 
31
- # For each doc, calculate the similarity to the query
32
- suggestions = corpus_scores.map do |account, scores|
33
- acct_vector = Vector.elements(scores, false)
44
+ q = tfidf(tokenize(query))
34
45
 
35
- acct_query_dp = acct_vector.inner_product(query_vector)
36
- # similarity is a float between 1 and -1, where 1 is exactly the same and -1 is
37
- # exactly opposite
38
- # see https://en.wikipedia.org/wiki/Cosine_similarity
39
- # cos(theta) = (A . B) / (||A|| ||B||)
40
- # where A . B is the "dot product" and ||A|| is the magnitude of A
41
- # ruby has the 'matrix' library we can use to do these calculations.
46
+ suggestions = accounts.map do |a, d|
42
47
  {
43
- similarity: acct_query_dp / (acct_vector.magnitude * query_vector.magnitude),
44
- account: account,
48
+ similarity: calc_similarity(q, d),
49
+ account: a
45
50
  }
46
51
  end.select { |n| n[:similarity] > 0 }.sort_by { |n| -n[:similarity] }
47
52
 
@@ -52,50 +57,51 @@ module Reckon
52
57
 
53
58
  private
54
59
 
55
- def td_idf_scores_for(query)
56
- query_tokens = tokenize(query)
57
- corpus = Set.new
58
- corpus_scores = {}
59
- query_scores = []
60
- num_docs = @accounts.length
61
-
62
- query_tokens.each do |n|
63
- (token, _count) = n
64
- next unless @tokens[token]
65
- corpus = corpus.union(Set.new(@tokens[token].keys))
60
+ def docs_to_check(query)
61
+ return tokenize(query).reduce(Set.new) do |corpus, t|
62
+ corpus.union(Set.new(@docs.tokens[t[0]]&.keys))
66
63
  end
64
+ end
67
65
 
68
- query_tokens.each do |n|
69
- (token, count) = n
70
-
71
- # if no other docs have token, ignore it
72
- next unless @tokens[token]
66
+ def tfidf(tokens)
67
+ scores = {}
73
68
 
74
- ## First, calculate scores for our query as we're building scores for the corpus
75
- query_scores << calc_tf_idf(
76
- count,
77
- query_tokens.length,
78
- @tokens[token].length,
79
- num_docs
69
+ tokens.each do |t, n|
70
+ scores[t] = calc_tf_idf(
71
+ n,
72
+ tokens.length,
73
+ @docs.tokens[t]&.length&.to_f || 0,
74
+ @docs.accounts.length
80
75
  )
81
-
82
- ## Next, calculate for the corpus, where our "account" is a document
83
- corpus.each do |account|
84
- corpus_scores[account] ||= []
85
-
86
- corpus_scores[account] << calc_tf_idf(
87
- (@tokens[token][account] || 0),
88
- @accounts[account].to_f,
89
- @tokens[token].length.to_f,
90
- num_docs
91
- )
92
- end
93
76
  end
94
- [query_scores, corpus_scores]
77
+
78
+ return scores
95
79
  end
96
80
 
97
- def calc_tf_idf(token_count, num_words_in_doc, df, num_docs)
81
+ # Cosine similarity is used to compare how similar 2 documents are. Returns a float
82
+ # between 1 and -1, where 1 is exactly the same and -1 is exactly opposite.
83
+ #
84
+ # see https://en.wikipedia.org/wiki/Cosine_similarity
85
+ # cos(theta) = (A . B) / (||A|| ||B||)
86
+ # where A . B is the "dot product" and ||A|| is the magnitude of A
87
+ #
88
+ # The variables A and B are the set of unique terms in q and d.
89
+ #
90
+ # For example, when q = "big red balloon" and d ="small green balloon" then the
91
+ # variables are (big,red,balloon,small,green) and a = (1,1,1,0,0) and b =
92
+ # (0,0,1,1,1).
93
+ #
94
+ # query and doc are hashes of token => tf/idf score
95
+ def calc_similarity(query, doc)
96
+ tokens = Set.new(query.keys + doc.keys)
97
+
98
+ a = Vector.elements(tokens.map { |n| query[n] || 0 }, false)
99
+ b = Vector.elements(tokens.map { |n| doc[n] || 0 }, false)
100
+
101
+ return a.inner_product(b) / (a.magnitude * b.magnitude)
102
+ end
98
103
 
104
+ def calc_tf_idf(token_count, num_words_in_doc, df, num_docs)
99
105
  # tf(t,d) = count of t in d / number of words in d
100
106
  tf = token_count / num_words_in_doc.to_f
101
107
 
@@ -109,14 +115,13 @@ module Reckon
109
115
  end
110
116
 
111
117
  def tokenize(str)
112
- mk_tokens(str).inject(Hash.new(0)) do |memo, n|
118
+ mk_tokens(str).each_with_object(Hash.new(0)) do |n, memo|
113
119
  memo[n] += 1
114
- memo
115
120
  end.to_a
116
121
  end
117
122
 
118
123
  def mk_tokens(str)
119
- str.downcase.tr(';', ' ').tr("'", '').split(/[^a-z0-9.]+/)
124
+ str.downcase.tr(';', ' ').tr("'", '').split(/[^a-z0-9.]+/).reject(&:empty?)
120
125
  end
121
126
  end
122
127
  end
@@ -2,12 +2,13 @@ module Reckon
2
2
  class DateColumn < Array
3
3
  attr_accessor :endian_precedence
4
4
  def initialize( arr = [], options = {} )
5
+ @options = options
5
6
  arr.each do |value|
6
7
  if options[:date_format]
7
8
  begin
8
9
  value = Date.strptime(value, options[:date_format])
9
10
  rescue
10
- puts "I'm having trouble parsing #{value} with the desired format: #{options[:date_format]}"
11
+ puts "I'm having trouble parsing '#{value}' with the desired format: #{options[:date_format]}"
11
12
  exit 1
12
13
  end
13
14
  else
@@ -53,7 +54,7 @@ module Reckon
53
54
  date = self.for(index)
54
55
  return "" if date.nil?
55
56
 
56
- date.iso8601
57
+ date.strftime(@options[:ledger_date_format] || '%Y-%m-%d')
57
58
  end
58
59
 
59
60
  def self.likelihood(entry)
@@ -114,7 +114,7 @@ module Reckon
114
114
 
115
115
  def initialize(ledger, options = {})
116
116
  @options = options
117
- @date_format = options[:date_format] || '%Y-%m-%d'
117
+ @date_format = options[:ledger_date_format] || options[:date_format] || '%Y-%m-%d'
118
118
  parse(ledger)
119
119
  end
120
120
 
data/lib/reckon/money.rb CHANGED
@@ -50,11 +50,18 @@ module Reckon
50
50
  return @amount_raw[0] == '-' ? @amount_raw[1..-1] : "-#{@amount_raw}"
51
51
  end
52
52
 
53
- if @suffixed
54
- (@amount >= 0 ? " " : "") + sprintf("%0.2f #{@currency}", @amount * (negate ? -1 : 1))
55
- else
56
- (@amount >= 0 ? " " : "") + sprintf("%0.2f", @amount * (negate ? -1 : 1)).gsub(/^((\-)|)(?=\d)/, "\\1#{@currency}")
57
- end
53
+ amt = pretty_amount(@amount * (negate ? -1 : 1))
54
+ amt = if @suffixed
55
+ "#{amt} #{@currency}"
56
+ else
57
+ amt.gsub(/^((-)|)(?=\d)/, "\\1#{@currency}")
58
+ end
59
+
60
+ return (@amount >= 0 ? " " : "") + amt
61
+ end
62
+
63
+ def pretty_amount(amount)
64
+ sprintf("%0.2f", amount).reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
58
65
  end
59
66
 
60
67
  def parse(value, options = {})
@@ -73,10 +73,14 @@ module Reckon
73
73
  options[:currency] = e
74
74
  end
75
75
 
76
- opts.on("", "--date-format '%d/%m/%Y'", "Force the date format (see Ruby DateTime strftime)") do |d|
76
+ opts.on("", "--date-format FORMAT", "CSV file date format (see `date` for format)") do |d|
77
77
  options[:date_format] = d
78
78
  end
79
79
 
80
+ opts.on("", "--ledger-date-format FORMAT", "Ledger date format (see `date` for format)") do |d|
81
+ options[:ledger_date_format] = d
82
+ end
83
+
80
84
  opts.on("-u", "--unattended", "Don't ask questions and guess all the accounts automatically. Use with --learn-from or --account-tokens options.") do |n|
81
85
  options[:unattended] = n
82
86
  end
@@ -85,6 +89,10 @@ module Reckon
85
89
  options[:account_tokens_file] = a
86
90
  end
87
91
 
92
+ opts.on("", "--table-output-file FILE") do |n|
93
+ options[:table_output_file] = n
94
+ end
95
+
88
96
  options[:default_into_account] = 'Expenses:Unknown'
89
97
  opts.on("", "--default-into-account NAME", "Default into account") do |a|
90
98
  options[:default_into_account] = a
@@ -1,3 +1,3 @@
1
1
  module Reckon
2
- VERSION="0.7.1"
2
+ VERSION="0.8.1"
3
3
  end
data/reckon.gemspec CHANGED
@@ -22,4 +22,5 @@ Gem::Specification.new do |s|
22
22
  s.add_runtime_dependency "chronic", ">= 0.3.0"
23
23
  s.add_runtime_dependency "highline", ">= 1.5.2"
24
24
  s.add_runtime_dependency "rchardet", ">= 1.8.0"
25
+ s.add_runtime_dependency "matrix", ">= 0.4.2"
25
26
  end
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pp'
4
+
5
+ require 'reckon'
6
+
7
+ ledger_file = ARGV[0]
8
+ account = ARGV[1]
9
+ seed = ARGV[2] ? ARGV[2].to_i : Random.new_seed
10
+
11
+ ledger = Reckon::LedgerParser.new(File.read(ledger_file))
12
+ matcher = Reckon::CosineSimilarity.new({})
13
+
14
+ train = []
15
+ test = []
16
+
17
+ def has_account(account, entry)
18
+ entry[:accounts].map { |a| a[:name] }.include?(account)
19
+ end
20
+
21
+ entries = ledger.entries.select { |e| has_account(account, e) }
22
+
23
+ r = Random.new(seed)
24
+ entries.length.times do |i|
25
+ r.rand < 0.9 ? train << i : test << i
26
+ end
27
+
28
+ train.each do |i|
29
+ entry = entries[i]
30
+ entry[:accounts].each do |a|
31
+ matcher.add_document(
32
+ a[:name],
33
+ [entry[:desc], a[:amount]].join(" ")
34
+ )
35
+ end
36
+ end
37
+
38
+ result = [nil] * test.length
39
+ test.each do |i|
40
+ entry = entries[i]
41
+ matches = matcher.find_similar(
42
+ entry[:desc] + " " + entry[:accounts][0][:amount].to_s
43
+ )
44
+
45
+ if !matches[0] || !has_account(matches[0][:account], entry)
46
+ result[i] = [entry, matches]
47
+ end
48
+ end
49
+
50
+ # pp result.compact
51
+ puts "using #{seed} as random seed"
52
+ puts "true: #{result.count(nil)} false: #{result.count { |v| !v.nil? }}"
@@ -1,5 +1,5 @@
1
1
  2003-12-24 CREDIT; Some Company vendorpymt PPD ID: 5KL3832735
2
- Assets:Bank:Checking $2105.00
2
+ Assets:Bank:Checking $2,105.00
3
3
  Income:Unknown
4
4
 
5
5
  2004-12-24 CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL
@@ -15,11 +15,11 @@
15
15
  Expenses:Unknown
16
16
 
17
17
  2007-12-24 CREDIT; Blarg BLARG REVENUE PPD ID: 00jah78563
18
- Assets:Bank:Checking $1558.52
18
+ Assets:Bank:Checking $1,558.52
19
19
  Income:Unknown
20
20
 
21
21
  2008-12-24 CREDIT; Some Company vendorpymt PPD ID: 59728JSL20
22
- Assets:Bank:Checking $3520.00
22
+ Assets:Bank:Checking $3,520.00
23
23
  Income:Unknown
24
24
 
25
25
  2009-12-24 DEBIT; GITHUB 041287430274 CA 12/22GITHUB 04
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/expect -f
2
+
3
+
4
+ set timeout 7
5
+ match_max 100000
6
+ expect "What is this account named in Ledger |Assets:Bank:Checking|?\r
7
+ \[1G▽\[6n"
8
+ send -- "\[45;2R"
9
+ expect -exact "\[1G\[K\[6n"
10
+ send -- "\[45;1R"
11
+ expect "\[1G\[K\[1G\[1G"
12
+ send -- "T"
13
+ expect "\[1GT\[K\[1G\[2G"
14
+ send -- "e"
15
+ expect "\[1GTe\[K\[1G\[3G"
16
+ send -- "s"
17
+ expect "\[1GTes\[K\[1G\[4G"
18
+ send -- "t"
19
+ expect "\[1GTest\[K\[1G\[5G"
20
+ send -- ":"
21
+ expect "\[1GTest:\[K\[1G\[6G"
22
+ send -- ":"
23
+ expect "\[1GTest::\[K\[1G\[7G"
24
+ send -- "B"
25
+ expect "\[1GTest::B\[K\[1G\[8G"
26
+ send -- "a"
27
+ expect "\[1GTest::Ba\[K\[1G\[9G"
28
+ send -- "n"
29
+ expect "\[1GTest::Ban\[K\[1G\[10G"
30
+ send -- "k"
31
+ expect "\[1GTest::Bank\[K\[1G\[11G"
32
+ send -- "\r"
33
+ expect eof
@@ -0,0 +1,11 @@
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 | |
@@ -0,0 +1,9 @@
1
+ DEBIT,2011/12/24,"HOST 037196321563 MO 12/22SLICEHOST",($85.00)
2
+ CHECK,2010/12/24,"CHECK 2656",($20.00)
3
+ DEBIT,2009/12/24,"GITHUB 041287430274 CA 12/22GITHUB 04",($7.00)
4
+ CREDIT,2008/12/24,"Some Company vendorpymt PPD ID: 59728JSL20",$3520.00
5
+ CREDIT,2007/12/24,"Blarg BLARG REVENUE PPD ID: 00jah78563",$1558.52
6
+ DEBIT,2006/12/24,"WEBSITE-BALANCE-17DEC09 12 12/17WEBSITE-BAL",$.23
7
+ DEBIT,2005/12/24,"WEBSITE-BALANCE-10DEC09 12 12/10WEBSITE-BAL",($0.96)
8
+ CREDIT,2004/12/24,"PAYPAL TRANSFER PPD ID: PAYPALSDSL",($116.22)
9
+ CREDIT,2003/12/24,"Some Company vendorpymt PPD ID: 5KL3832735",$2105.00
@@ -0,0 +1 @@
1
+ -f input.csv -p
@@ -8,7 +8,7 @@
8
8
 
9
9
  2013-06-19 2013-06-24; Buy; ISHARES S&P/TSX CAPPED REIT IN; XRE; 300; 15.90; CDN; CAD
10
10
  Income:Unknown
11
- Assets:Bank:Checking -$4779.95
11
+ Assets:Bank:Checking -$4,779.95
12
12
 
13
13
  2013-06-27 2013-06-27; Dividend; ICICI BK SPONSORED ADR; IBN; 100; USD
14
14
  Assets:Bank:Checking $66.70
@@ -19,7 +19,7 @@
19
19
  Income:Unknown
20
20
 
21
21
  2014-01-07 2014-01-10; Sell; BMO NASDAQ 100 EQTY HEDGED TO; ZQQ; -300; 27.44; CDN; CAD
22
- Assets:Bank:Checking $8222.05
22
+ Assets:Bank:Checking $8,222.05
23
23
  Income:Unknown
24
24
 
25
25
  2014-01-07 2014-01-07; Interest; BMO S&P/TSX EQUAL WEIGHT BKS I; ZEB; 250; CAD
@@ -1,5 +1,5 @@
1
1
  2009-12-10 CREDIT; Some Company vendorpymt PPD ID: 5KL3832735
2
- Assets:Bank:Checking $2105.00
2
+ Assets:Bank:Checking $2,105.00
3
3
  Income:Unknown
4
4
 
5
5
  2009-12-11 CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL
@@ -15,11 +15,11 @@
15
15
  Assets:Bank:Checking -$12.23
16
16
 
17
17
  2009-12-23 CREDIT; Some Company vendorpymt PPD ID: 59728JSL20
18
- Assets:Bank:Checking $3520.00
18
+ Assets:Bank:Checking $3,520.00
19
19
  Income:Unknown
20
20
 
21
21
  2009-12-23 CREDIT; Blarg BLARG REVENUE PPD ID: 00jah78563
22
- Assets:Bank:Checking $1558.52
22
+ Assets:Bank:Checking $1,558.52
23
23
  Income:Unknown
24
24
 
25
25
  2009-12-24 DEBIT; GITHUB 041287430274 CA 12/22GITHUB 04
@@ -1,5 +1,5 @@
1
1
  2009-12-10 CREDIT; Some Company vendorpymt PPD ID: 5KL3832735
2
- Assets:Bank:Checking $2105.00
2
+ Assets:Bank:Checking $2,105.00
3
3
  Income:Default
4
4
 
5
5
  2009-12-11 CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL
@@ -15,11 +15,11 @@
15
15
  Assets:Bank:Checking -$12.23
16
16
 
17
17
  2009-12-23 CREDIT; Some Company vendorpymt PPD ID: 59728JSL20
18
- Assets:Bank:Checking $3520.00
18
+ Assets:Bank:Checking $3,520.00
19
19
  Income:Default
20
20
 
21
21
  2009-12-23 CREDIT; Blarg BLARG REVENUE PPD ID: 00jah78563
22
- Assets:Bank:Checking $1558.52
22
+ Assets:Bank:Checking $1,558.52
23
23
  Income:Default
24
24
 
25
25
  2009-12-24 DEBIT; GITHUB 041287430274 CA 12/22GITHUB 04
@@ -1,5 +1,5 @@
1
1
  2009-12-10 CREDIT; Some Company vendorpymt PPD ID: 5KL3832735
2
- Assets:Bank:Checking $2105.00
2
+ Assets:Bank:Checking $2,105.00
3
3
  Income:Unknown
4
4
 
5
5
  2009-12-11 CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL
@@ -15,11 +15,11 @@
15
15
  Assets:Bank:Checking -$12.23
16
16
 
17
17
  2009-12-23 CREDIT; Some Company vendorpymt PPD ID: 59728JSL20
18
- Assets:Bank:Checking $3520.00
18
+ Assets:Bank:Checking $3,520.00
19
19
  Income:Unknown
20
20
 
21
21
  2009-12-23 CREDIT; Blarg BLARG REVENUE PPD ID: 00jah78563
22
- Assets:Bank:Checking $1558.52
22
+ Assets:Bank:Checking $1,558.52
23
23
  Income:Unknown
24
24
 
25
25
  2009-12-24 DEBIT; GITHUB 041287430274 CA 12/22GITHUB 04
@@ -1,5 +1,5 @@
1
1
  2009-12-10 CREDIT; Some Company vendorpymt PPD ID: 5KL3832735
2
- Assets:Bank:Checking $2105.00
2
+ Assets:Bank:Checking $2,105.00
3
3
  Income:Unknown
4
4
 
5
5
  2009-12-11 CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL
@@ -15,11 +15,11 @@
15
15
  Assets:Bank:Checking -$12.23
16
16
 
17
17
  2009-12-23 CREDIT; Some Company vendorpymt PPD ID: 59728JSL20
18
- Assets:Bank:Checking $3520.00
18
+ Assets:Bank:Checking $3,520.00
19
19
  Income:Unknown
20
20
 
21
21
  2009-12-23 CREDIT; Blarg BLARG REVENUE PPD ID: 00jah78563
22
- Assets:Bank:Checking $1558.52
22
+ Assets:Bank:Checking $1,558.52
23
23
  Income:Unknown
24
24
 
25
25
  2009-12-24 DEBIT; GITHUB 041287430274 CA 12/22GITHUB 04
@@ -4,7 +4,7 @@
4
4
 
5
5
  2012-09-12 Dankort-nota B.J. TRADING E 14660; 12-09-2012; 26164,80
6
6
  Expenses:Unknown
7
- Assets:Bank:Checking -$3452.90
7
+ Assets:Bank:Checking -$3,452.90
8
8
 
9
9
  2012-10-12 Visa kob DKK 995,00 WWW.ASOS.COM 00000; 12-10-2012; 27939,54
10
10
  Expenses:Unknown
@@ -3,7 +3,7 @@
3
3
  Income:Unknown
4
4
 
5
5
  2012-11-01 Proventos; 496774
6
- Assets:Bank:Checking $1000.00
6
+ Assets:Bank:Checking $1,000.00
7
7
  Income:Unknown
8
8
 
9
9
  2012-11-01 0000-9; Transferência on line - 01/11 4885 256620-6 XXXXXXXXXXXXX; 224885000256620
@@ -1,5 +1,5 @@
1
1
  2009-11-04 TRANSFER CREDIT INTERNET TRANSFER; INTERNET TRANSFER; 1234.00
2
- Assets:Bank:Checking $1234.00
2
+ Assets:Bank:Checking $1,234.00
3
3
  Income:Unknown
4
4
 
5
5
  2009-11-10 TRANSFER DEBIT INTERNET TRANSFER; INTERNET TRANSFER MORTGAGE; 0.00
@@ -16,5 +16,5 @@
16
16
 
17
17
  2011-11-04 TRANSFER DEBIT INTERNET TRANSFER; INTERNET TRANSFER SAV TO MECU; 0.00
18
18
  Income:Unknown
19
- Assets:Bank:Checking -$1234.00
19
+ Assets:Bank:Checking -$1,234.00
20
20
 
@@ -0,0 +1 @@
1
+ ledger --input-date-format '%d/%m/%Y'
@@ -0,0 +1,3 @@
1
+ 02/12/2009,Check - 0000000122,122,-$76.00,"","$1,750.06"
2
+ 02/12/2009,BLARG R SH 456930,"","",+$327.49,"$1,826.06"
3
+ 02/12/2009,Check - 0000000112,112,-$800.00,"","$1,498.57"
@@ -0,0 +1,12 @@
1
+ 02/12/2009 BLARG R SH 456930; $1,826.06
2
+ Assets:Bank:Checking $327.49
3
+ Income:Unknown
4
+
5
+ 02/12/2009 Check - 0000000122; 122; $1,750.06
6
+ Income:Unknown
7
+ Assets:Bank:Checking -$76.00
8
+
9
+ 02/12/2009 Check - 0000000112; 112; $1,498.57
10
+ Income:Unknown
11
+ Assets:Bank:Checking -$800.00
12
+
@@ -0,0 +1 @@
1
+ -f input.csv --unattended --account Assets:Bank:Checking --date-format '%d/%m/%Y' --ledger-date-format '%d/%m/%Y'
@@ -1,13 +1,13 @@
1
1
  #!/bin/bash
2
2
 
3
- # set -x
4
-
5
3
  set -Euo pipefail
6
4
 
5
+
7
6
  SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
8
7
  TEST_DIFF=""
9
8
  OUTPUT=""
10
- RECKON_CMD="env RUBYLIB=$SCRIPT_DIR/../../lib:$RUBYLIB reckon -v"
9
+ RECKON_CMD="reckon -v"
10
+ export RUBYLIB=$SCRIPT_DIR/../../lib:${RUBYLIB:-}
11
11
  export PATH="$SCRIPT_DIR/../../bin:$PATH"
12
12
 
13
13
  main () {
@@ -21,28 +21,64 @@ main () {
21
21
 
22
22
  echo > test.log
23
23
 
24
+ NUM_TESTS=$(echo "$TESTS" |wc -l |awk '{print $1}')
25
+
26
+ echo "1..$NUM_TESTS"
27
+
28
+ I=1
29
+
24
30
  for t in $TESTS; do
25
- OUTPUT_FILE=$(mktemp)
26
31
  TEST_DIR=$(dirname "$t")
32
+ TEST_LOG=$(mktemp)
27
33
  pushd "$TEST_DIR" >/dev/null || exit 1
28
- echo "$TEST_DIR Running..."
29
- TEST_CMD="$RECKON_CMD -o $OUTPUT_FILE $(cat test_args)"
30
- TEST_LOG=$(eval "$TEST_CMD" 2>&1)
31
- ERROR=0
32
-
33
- compare_output "$OUTPUT_FILE"
34
+ if [[ -e "cli_input.exp" ]]; then
35
+ cli_test >$TEST_LOG 2>&1
36
+ else
37
+ unattended_test >$TEST_LOG 2>&1
38
+ fi
34
39
 
35
40
  popd >/dev/null || exit 1
36
41
  # have to save output after popd
37
42
  echo -e "\n\n======>$TEST_DIR" >> test.log
38
- echo -e "TEST_CMD\n$TEST_LOG" >> test.log
43
+ echo -e "TEST_CMD: $TEST_CMD" >> test.log
44
+ cat $TEST_LOG >> test.log
39
45
 
40
46
  if [[ $ERROR -ne 0 ]]; then
47
+ echo -e "not ok $I - $TEST_DIR"
48
+ tail -n25 test.log
41
49
  exit 1
50
+ else
51
+ echo -e "ok $I - $TEST_DIR"
42
52
  fi
53
+ I=$(($I + 1))
43
54
  done
44
55
  }
45
56
 
57
+ cli_test () {
58
+ OUTPUT_FILE=$(mktemp)
59
+ CLI_CMD="$RECKON_CMD --table-output-file $OUTPUT_FILE $(cat test_args)"
60
+ TEST_CMD="expect -d -c 'spawn $CLI_CMD' cli_input.exp"
61
+ eval "$TEST_CMD" 2>&1
62
+ ERROR=0
63
+ TEST_DIFF=$(diff -u "$OUTPUT_FILE" expected_output)
64
+
65
+ # ${#} is character length, test that there was no output from diff
66
+ if [ ${#TEST_DIFF} -eq 0 ]; then
67
+ ERROR=0
68
+ else
69
+ ERROR=1
70
+ fi
71
+ }
72
+
73
+ unattended_test() {
74
+ OUTPUT_FILE=$(mktemp)
75
+ TEST_CMD="$RECKON_CMD -o $OUTPUT_FILE $(cat test_args)"
76
+ eval "$TEST_CMD" 2>&1
77
+ ERROR=0
78
+
79
+ compare_output "$OUTPUT_FILE"
80
+ }
81
+
46
82
  test_fail () {
47
83
  STATUS=$?
48
84
  if [[ $STATUS -ne 0 ]]; then
@@ -51,11 +87,42 @@ test_fail () {
51
87
  fi
52
88
  }
53
89
 
90
+ compare_output () {
91
+ OUTPUT_FILE=$1
92
+ pwd
93
+ if [[ -e compare_cmds ]]; then
94
+ COMPARE_CMDS=$(cat compare_cmds)
95
+ else
96
+ COMPARE_CMDS=$'ledger\nhledger'
97
+ fi
98
+
99
+ ERROR=1
100
+ while IFS= read -r n; do
101
+ if compare_output_for "$OUTPUT_FILE" "$n"; then
102
+ ERROR=0
103
+ else
104
+ ERROR=1
105
+ break
106
+ fi
107
+ done <<< "$COMPARE_CMDS"
108
+ }
109
+
54
110
  compare_output_for () {
55
111
  OUTPUT_FILE=$1
56
112
  LEDGER=$2
57
113
 
58
- TEST_DIFF=$(diff -u <($LEDGER -f output.ledger r --date-format %F 2>&1) <($LEDGER -f "$OUTPUT_FILE" r --date-format %F 2>&1) )
114
+ EXPECTED_FILE=$(mktemp)
115
+ ACTUAL_FILE=$(mktemp)
116
+
117
+ EXPECTED_CMD="$LEDGER -f output.ledger r >$EXPECTED_FILE"
118
+ echo "$EXPECTED_CMD"
119
+ eval "$EXPECTED_CMD" || return 1
120
+
121
+ ACTUAL_CMD="$LEDGER -f \"$OUTPUT_FILE\" r"
122
+ echo "running $ACTUAL_CMD"
123
+ eval $ACTUAL_CMD >$ACTUAL_FILE || return 1
124
+
125
+ TEST_DIFF=$(diff -u "$EXPECTED_FILE" "$ACTUAL_FILE")
59
126
 
60
127
  # ${#} is character length, test that there was no output from diff
61
128
  if [ ${#TEST_DIFF} -eq 0 ]; then
@@ -65,19 +132,4 @@ compare_output_for () {
65
132
  fi
66
133
  }
67
134
 
68
- compare_output () {
69
- OUTPUT_FILE=$1
70
-
71
- for n in {ledger,hledger}; do
72
- echo -n " - $n..."
73
- if compare_output_for "$OUTPUT_FILE" "$n"; then
74
- echo "SUCCESS!"
75
- else
76
- echo "FAILED!"
77
- ERROR=1
78
- return 0
79
- fi
80
- done
81
- }
82
-
83
135
  main "$@"
@@ -16,7 +16,7 @@ describe Reckon::App do
16
16
  describe "each_row_backwards" do
17
17
  it "should return rows with hashes" do
18
18
  @rows[0][:pretty_date].should == "2009-12-10"
19
- @rows[0][:pretty_money].should == " $2105.00"
19
+ @rows[0][:pretty_money].should == " $2,105.00"
20
20
  @rows[0][:description].should == "CREDIT; Some Company vendorpymt PPD ID: 5KL3832735"
21
21
  @rows[1][:pretty_date].should == "2009-12-11"
22
22
  @rows[1][:pretty_money].should == " $116.22"
@@ -242,7 +242,7 @@ describe Reckon::CSVParser do
242
242
  describe "pretty_money_for" do
243
243
  it "work with negative and positive numbers" do
244
244
  some_other_bank.pretty_money_for(1).should == "-$20.00"
245
- some_other_bank.pretty_money_for(4).should == " $1558.52"
245
+ some_other_bank.pretty_money_for(4).should == " $1,558.52"
246
246
  some_other_bank.pretty_money_for(7).should == "-$116.22"
247
247
  some_other_bank.pretty_money_for(5).should == " $0.23"
248
248
  some_other_bank.pretty_money_for(6).should == "-$0.96"
@@ -251,7 +251,7 @@ describe Reckon::CSVParser do
251
251
  it "work with other currencies such as €" do
252
252
  euro_bank = Reckon::CSVParser.new(file: fixture_path('some_other.csv'), currency: "€", suffixed: false )
253
253
  euro_bank.pretty_money_for(1).should == "-€20.00"
254
- euro_bank.pretty_money_for(4).should == " €1558.52"
254
+ euro_bank.pretty_money_for(4).should == " €1,558.52"
255
255
  euro_bank.pretty_money_for(7).should == "-€116.22"
256
256
  euro_bank.pretty_money_for(5).should == " €0.23"
257
257
  euro_bank.pretty_money_for(6).should == "-€0.96"
@@ -260,7 +260,7 @@ describe Reckon::CSVParser do
260
260
  it "work with suffixed currencies such as SEK" do
261
261
  swedish_bank = Reckon::CSVParser.new(file: fixture_path('some_other.csv'), currency: 'SEK', suffixed: true )
262
262
  swedish_bank.pretty_money_for(1).should == "-20.00 SEK"
263
- swedish_bank.pretty_money_for(4).should == " 1558.52 SEK"
263
+ swedish_bank.pretty_money_for(4).should == " 1,558.52 SEK"
264
264
  swedish_bank.pretty_money_for(7).should == "-116.22 SEK"
265
265
  swedish_bank.pretty_money_for(5).should == " 0.23 SEK"
266
266
  swedish_bank.pretty_money_for(6).should == "-0.96 SEK"
@@ -38,4 +38,16 @@ describe Reckon::DateColumn do
38
38
  .to eq(Date.new(2013, 2, 1))
39
39
  end
40
40
  end
41
+
42
+ describe "#pretty_for" do
43
+ it 'should use ledger_date_format' do
44
+ expect(Reckon::DateColumn.new(%w[13/02/2013], {ledger_date_format: '%d/%m/%Y'}).pretty_for(0))
45
+ .to eq('13/02/2013')
46
+ end
47
+
48
+ it 'should default to is' do
49
+ expect(Reckon::DateColumn.new(%w[13/12/2013]).pretty_for(0))
50
+ .to eq('2013-12-13')
51
+ end
52
+ end
41
53
  end
@@ -32,17 +32,17 @@ describe Reckon::Money do
32
32
  describe "pretty" do
33
33
  it "work with negative and positive numbers" do
34
34
  expect(Reckon::Money.new(-20.00).pretty).to eq("-$20.00")
35
- expect(Reckon::Money.new(1558.52).pretty).to eq(" $1558.52")
35
+ expect(Reckon::Money.new(1558.52).pretty).to eq(" $1,558.52")
36
36
  end
37
37
 
38
38
  it "work with other currencies such as €" do
39
39
  expect(Reckon::Money.new(-20.00, currency: "€", suffixed: false).pretty).to eq("-€20.00")
40
- expect(Reckon::Money.new(1558.52, currency: "€", suffixed: false).pretty).to eq(" €1558.52")
40
+ expect(Reckon::Money.new(1558.52, currency: "€", suffixed: false).pretty).to eq(" €1,558.52")
41
41
  end
42
42
 
43
43
  it "work with suffixed currencies such as SEK" do
44
44
  expect(Reckon::Money.new(-20.00, currency: "SEK", suffixed: true).pretty).to eq("-20.00 SEK")
45
- expect(Reckon::Money.new(1558.52, currency: "SEK", suffixed: true).pretty).to eq(" 1558.52 SEK")
45
+ expect(Reckon::Money.new(1558.52, currency: "SEK", suffixed: true).pretty).to eq(" 1,558.52 SEK")
46
46
  end
47
47
  end
48
48
 
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.7.1
4
+ version: 0.8.1
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: 2021-02-07 00:00:00.000000000 Z
13
+ date: 2022-07-02 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rspec
@@ -96,6 +96,20 @@ dependencies:
96
96
  - - ">="
97
97
  - !ruby/object:Gem::Version
98
98
  version: 1.8.0
99
+ - !ruby/object:Gem::Dependency
100
+ name: matrix
101
+ requirement: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: 0.4.2
106
+ type: :runtime
107
+ prerelease: false
108
+ version_requirements: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 0.4.2
99
113
  description: Reckon automagically converts CSV files for use with the command-line
100
114
  accounting tool Ledger. It also helps you to select the correct accounts associated
101
115
  with the CSV data using Bayesian machine learning.
@@ -130,6 +144,7 @@ files:
130
144
  - lib/reckon/options.rb
131
145
  - lib/reckon/version.rb
132
146
  - reckon.gemspec
147
+ - spec/cosine_training_and_test.rb
133
148
  - spec/data_fixtures/51-sample.csv
134
149
  - spec/data_fixtures/51-tokens.yml
135
150
  - spec/data_fixtures/73-sample.csv
@@ -162,6 +177,10 @@ files:
162
177
  - spec/integration/another_bank_example/input.csv
163
178
  - spec/integration/another_bank_example/output.ledger
164
179
  - spec/integration/another_bank_example/test_args
180
+ - spec/integration/ask_for_account/cli_input.exp
181
+ - spec/integration/ask_for_account/expected_output
182
+ - spec/integration/ask_for_account/input.csv
183
+ - spec/integration/ask_for_account/test_args
165
184
  - spec/integration/austrian_example/input.csv
166
185
  - spec/integration/austrian_example/output.ledger
167
186
  - spec/integration/austrian_example/test_args
@@ -212,6 +231,10 @@ files:
212
231
  - spec/integration/inversed_credit_card/input.csv
213
232
  - spec/integration/inversed_credit_card/output.ledger
214
233
  - spec/integration/inversed_credit_card/test_args
234
+ - spec/integration/ledger_date_format/compare_cmds
235
+ - spec/integration/ledger_date_format/input.csv
236
+ - spec/integration/ledger_date_format/output.ledger
237
+ - spec/integration/ledger_date_format/test_args
215
238
  - spec/integration/nationwide/input.csv
216
239
  - spec/integration/nationwide/output.ledger
217
240
  - spec/integration/nationwide/test_args
@@ -273,9 +296,142 @@ required_rubygems_version: !ruby/object:Gem::Requirement
273
296
  - !ruby/object:Gem::Version
274
297
  version: '0'
275
298
  requirements: []
276
- rubygems_version: 3.2.3
299
+ rubygems_version: 3.3.7
277
300
  signing_key:
278
301
  specification_version: 4
279
302
  summary: Utility for interactively converting and labeling CSV files for the Ledger
280
303
  accounting tool.
281
- test_files: []
304
+ test_files:
305
+ - spec/cosine_training_and_test.rb
306
+ - spec/data_fixtures/51-sample.csv
307
+ - spec/data_fixtures/51-tokens.yml
308
+ - spec/data_fixtures/73-sample.csv
309
+ - spec/data_fixtures/73-tokens.yml
310
+ - spec/data_fixtures/73-transactions.ledger
311
+ - spec/data_fixtures/85-date-example.csv
312
+ - spec/data_fixtures/austrian_example.csv
313
+ - spec/data_fixtures/bom_utf8_file.csv
314
+ - spec/data_fixtures/broker_canada_example.csv
315
+ - spec/data_fixtures/chase.csv
316
+ - spec/data_fixtures/danish_kroner_nordea_example.csv
317
+ - spec/data_fixtures/english_date_example.csv
318
+ - spec/data_fixtures/extratofake.csv
319
+ - spec/data_fixtures/french_example.csv
320
+ - spec/data_fixtures/german_date_example.csv
321
+ - spec/data_fixtures/harder_date_example.csv
322
+ - spec/data_fixtures/ing.csv
323
+ - spec/data_fixtures/intuit_mint_example.csv
324
+ - spec/data_fixtures/invalid_header_example.csv
325
+ - spec/data_fixtures/inversed_credit_card.csv
326
+ - spec/data_fixtures/nationwide.csv
327
+ - spec/data_fixtures/simple.csv
328
+ - spec/data_fixtures/some_other.csv
329
+ - spec/data_fixtures/spanish_date_example.csv
330
+ - spec/data_fixtures/suntrust.csv
331
+ - spec/data_fixtures/test_money_column.csv
332
+ - spec/data_fixtures/tokens.yaml
333
+ - spec/data_fixtures/two_money_columns.csv
334
+ - spec/data_fixtures/yyyymmdd_date_example.csv
335
+ - spec/integration/another_bank_example/input.csv
336
+ - spec/integration/another_bank_example/output.ledger
337
+ - spec/integration/another_bank_example/test_args
338
+ - spec/integration/ask_for_account/cli_input.exp
339
+ - spec/integration/ask_for_account/expected_output
340
+ - spec/integration/ask_for_account/input.csv
341
+ - spec/integration/ask_for_account/test_args
342
+ - spec/integration/austrian_example/input.csv
343
+ - spec/integration/austrian_example/output.ledger
344
+ - spec/integration/austrian_example/test_args
345
+ - spec/integration/bom_utf8_file/input.csv
346
+ - spec/integration/bom_utf8_file/output.ledger
347
+ - spec/integration/bom_utf8_file/test_args
348
+ - spec/integration/broker_canada_example/input.csv
349
+ - spec/integration/broker_canada_example/output.ledger
350
+ - spec/integration/broker_canada_example/test_args
351
+ - spec/integration/chase/account_tokens_and_regex/output.ledger
352
+ - spec/integration/chase/account_tokens_and_regex/test_args
353
+ - spec/integration/chase/account_tokens_and_regex/tokens.yml
354
+ - spec/integration/chase/default_account_names/output.ledger
355
+ - spec/integration/chase/default_account_names/test_args
356
+ - spec/integration/chase/input.csv
357
+ - spec/integration/chase/learn_from_existing/learn.ledger
358
+ - spec/integration/chase/learn_from_existing/output.ledger
359
+ - spec/integration/chase/learn_from_existing/test_args
360
+ - spec/integration/chase/simple/output.ledger
361
+ - spec/integration/chase/simple/test_args
362
+ - spec/integration/danish_kroner_nordea_example/input.csv
363
+ - spec/integration/danish_kroner_nordea_example/output.ledger
364
+ - spec/integration/danish_kroner_nordea_example/test_args
365
+ - spec/integration/english_date_example/input.csv
366
+ - spec/integration/english_date_example/output.ledger
367
+ - spec/integration/english_date_example/test_args
368
+ - spec/integration/extratofake/input.csv
369
+ - spec/integration/extratofake/output.ledger
370
+ - spec/integration/extratofake/test_args
371
+ - spec/integration/french_example/input.csv
372
+ - spec/integration/french_example/output.ledger
373
+ - spec/integration/french_example/test_args
374
+ - spec/integration/german_date_example/input.csv
375
+ - spec/integration/german_date_example/output.ledger
376
+ - spec/integration/german_date_example/test_args
377
+ - spec/integration/harder_date_example/input.csv
378
+ - spec/integration/harder_date_example/output.ledger
379
+ - spec/integration/harder_date_example/test_args
380
+ - spec/integration/ing/input.csv
381
+ - spec/integration/ing/output.ledger
382
+ - spec/integration/ing/test_args
383
+ - spec/integration/intuit_mint_example/input.csv
384
+ - spec/integration/intuit_mint_example/output.ledger
385
+ - spec/integration/intuit_mint_example/test_args
386
+ - spec/integration/invalid_header_example/input.csv
387
+ - spec/integration/invalid_header_example/output.ledger
388
+ - spec/integration/invalid_header_example/test_args
389
+ - spec/integration/inversed_credit_card/input.csv
390
+ - spec/integration/inversed_credit_card/output.ledger
391
+ - spec/integration/inversed_credit_card/test_args
392
+ - spec/integration/ledger_date_format/compare_cmds
393
+ - spec/integration/ledger_date_format/input.csv
394
+ - spec/integration/ledger_date_format/output.ledger
395
+ - spec/integration/ledger_date_format/test_args
396
+ - spec/integration/nationwide/input.csv
397
+ - spec/integration/nationwide/output.ledger
398
+ - spec/integration/nationwide/test_args
399
+ - spec/integration/regression/issue_51_account_tokens/input.csv
400
+ - spec/integration/regression/issue_51_account_tokens/output.ledger
401
+ - spec/integration/regression/issue_51_account_tokens/test_args
402
+ - spec/integration/regression/issue_51_account_tokens/tokens.yml
403
+ - spec/integration/regression/issue_64_date_column/input.csv
404
+ - spec/integration/regression/issue_64_date_column/output.ledger
405
+ - spec/integration/regression/issue_64_date_column/test_args
406
+ - spec/integration/regression/issue_73_account_token_matching/input.csv
407
+ - spec/integration/regression/issue_73_account_token_matching/output.ledger
408
+ - spec/integration/regression/issue_73_account_token_matching/test_args
409
+ - spec/integration/regression/issue_73_account_token_matching/tokens.yml
410
+ - spec/integration/regression/issue_85_date_example/input.csv
411
+ - spec/integration/regression/issue_85_date_example/output.ledger
412
+ - spec/integration/regression/issue_85_date_example/test_args
413
+ - spec/integration/spanish_date_example/input.csv
414
+ - spec/integration/spanish_date_example/output.ledger
415
+ - spec/integration/spanish_date_example/test_args
416
+ - spec/integration/suntrust/input.csv
417
+ - spec/integration/suntrust/output.ledger
418
+ - spec/integration/suntrust/test_args
419
+ - spec/integration/test.sh
420
+ - spec/integration/test_money_column/input.csv
421
+ - spec/integration/test_money_column/output.ledger
422
+ - spec/integration/test_money_column/test_args
423
+ - spec/integration/two_money_columns/input.csv
424
+ - spec/integration/two_money_columns/output.ledger
425
+ - spec/integration/two_money_columns/test_args
426
+ - spec/integration/yyyymmdd_date_example/input.csv
427
+ - spec/integration/yyyymmdd_date_example/output.ledger
428
+ - spec/integration/yyyymmdd_date_example/test_args
429
+ - spec/reckon/app_spec.rb
430
+ - spec/reckon/csv_parser_spec.rb
431
+ - spec/reckon/date_column_spec.rb
432
+ - spec/reckon/ledger_parser_spec.rb
433
+ - spec/reckon/money_column_spec.rb
434
+ - spec/reckon/money_spec.rb
435
+ - spec/reckon/options_spec.rb
436
+ - spec/spec.opts
437
+ - spec/spec_helper.rb