reckon 0.7.1 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
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