reckon 0.6.0 → 0.6.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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +50 -0
  3. data/.gitignore +2 -0
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +23 -3
  6. data/Gemfile.lock +1 -1
  7. data/README.md +72 -16
  8. data/Rakefile +17 -1
  9. data/lib/reckon/app.rb +10 -5
  10. data/lib/reckon/csv_parser.rb +2 -7
  11. data/lib/reckon/date_column.rb +10 -0
  12. data/lib/reckon/money.rb +48 -48
  13. data/lib/reckon/version.rb +1 -1
  14. data/spec/integration/another_bank_example/input.csv +9 -0
  15. data/spec/integration/another_bank_example/output.ledger +36 -0
  16. data/spec/integration/another_bank_example/test_args +1 -0
  17. data/spec/integration/austrian_example/input.csv +13 -0
  18. data/spec/integration/austrian_example/output.ledger +52 -0
  19. data/spec/integration/austrian_example/test_args +2 -0
  20. data/spec/integration/bom_utf8_file/input.csv +3 -0
  21. data/spec/integration/bom_utf8_file/output.ledger +4 -0
  22. data/spec/integration/bom_utf8_file/test_args +3 -0
  23. data/spec/integration/broker_canada_example/input.csv +12 -0
  24. data/spec/integration/broker_canada_example/output.ledger +48 -0
  25. data/spec/integration/broker_canada_example/test_args +1 -0
  26. data/spec/integration/chase/account_tokens_and_regex/output.ledger +36 -0
  27. data/spec/integration/chase/account_tokens_and_regex/test_args +2 -0
  28. data/spec/integration/chase/account_tokens_and_regex/tokens.yml +16 -0
  29. data/spec/integration/chase/default_account_names/output.ledger +36 -0
  30. data/spec/integration/chase/default_account_names/test_args +3 -0
  31. data/spec/integration/chase/input.csv +9 -0
  32. data/spec/integration/chase/learn_from_existing/learn.ledger +7 -0
  33. data/spec/integration/chase/learn_from_existing/output.ledger +36 -0
  34. data/spec/integration/chase/learn_from_existing/test_args +1 -0
  35. data/spec/integration/chase/simple/output.ledger +36 -0
  36. data/spec/integration/chase/simple/test_args +1 -0
  37. data/spec/integration/danish_kroner_nordea_example/input.csv +6 -0
  38. data/spec/integration/danish_kroner_nordea_example/output.ledger +24 -0
  39. data/spec/integration/danish_kroner_nordea_example/test_args +1 -0
  40. data/spec/integration/english_date_example/input.csv +3 -0
  41. data/spec/integration/english_date_example/output.ledger +12 -0
  42. data/spec/integration/english_date_example/test_args +1 -0
  43. data/spec/integration/extratofake/input.csv +24 -0
  44. data/spec/integration/extratofake/output.ledger +92 -0
  45. data/spec/integration/extratofake/test_args +1 -0
  46. data/spec/integration/french_example/input.csv +9 -0
  47. data/spec/integration/french_example/output.ledger +36 -0
  48. data/spec/integration/french_example/test_args +2 -0
  49. data/spec/integration/german_date_example/input.csv +3 -0
  50. data/spec/integration/german_date_example/output.ledger +12 -0
  51. data/spec/integration/german_date_example/test_args +1 -0
  52. data/spec/integration/harder_date_example/input.csv +5 -0
  53. data/spec/integration/harder_date_example/output.ledger +20 -0
  54. data/spec/integration/harder_date_example/test_args +1 -0
  55. data/spec/integration/ing/input.csv +3 -0
  56. data/spec/integration/ing/output.ledger +12 -0
  57. data/spec/integration/ing/test_args +1 -0
  58. data/spec/integration/intuit_mint_example/input.csv +7 -0
  59. data/spec/integration/intuit_mint_example/output.ledger +28 -0
  60. data/spec/integration/intuit_mint_example/test_args +1 -0
  61. data/spec/integration/invalid_header_example/input.csv +6 -0
  62. data/spec/integration/invalid_header_example/output.ledger +8 -0
  63. data/spec/integration/invalid_header_example/test_args +1 -0
  64. data/spec/integration/inversed_credit_card/input.csv +16 -0
  65. data/spec/integration/inversed_credit_card/output.ledger +64 -0
  66. data/spec/integration/inversed_credit_card/test_args +1 -0
  67. data/spec/integration/nationwide/input.csv +4 -0
  68. data/spec/integration/nationwide/output.ledger +16 -0
  69. data/spec/integration/nationwide/test_args +1 -0
  70. data/spec/integration/regression/issue_51_account_tokens/input.csv +8 -0
  71. data/spec/integration/regression/issue_51_account_tokens/output.ledger +32 -0
  72. data/spec/integration/regression/issue_51_account_tokens/test_args +4 -0
  73. data/spec/integration/regression/issue_51_account_tokens/tokens.yml +9 -0
  74. data/spec/integration/regression/issue_64_date_column/input.csv +3 -0
  75. data/spec/integration/regression/issue_64_date_column/output.ledger +8 -0
  76. data/spec/integration/regression/issue_64_date_column/test_args +1 -0
  77. data/spec/integration/regression/issue_73_account_token_matching/input.csv +2 -0
  78. data/spec/integration/regression/issue_73_account_token_matching/output.ledger +4 -0
  79. data/spec/integration/regression/issue_73_account_token_matching/test_args +6 -0
  80. data/spec/integration/regression/issue_73_account_token_matching/tokens.yml +8 -0
  81. data/spec/integration/regression/issue_85_date_example/input.csv +2 -0
  82. data/spec/integration/regression/issue_85_date_example/output.ledger +8 -0
  83. data/spec/integration/regression/issue_85_date_example/test_args +1 -0
  84. data/spec/integration/spanish_date_example/input.csv +3 -0
  85. data/spec/integration/spanish_date_example/output.ledger +12 -0
  86. data/spec/integration/spanish_date_example/test_args +1 -0
  87. data/spec/integration/suntrust/input.csv +7 -0
  88. data/spec/integration/suntrust/output.ledger +28 -0
  89. data/spec/integration/suntrust/test_args +1 -0
  90. data/spec/integration/test.sh +82 -0
  91. data/spec/integration/test_money_column/input.csv +3 -0
  92. data/spec/integration/test_money_column/output.ledger +8 -0
  93. data/spec/integration/test_money_column/test_args +1 -0
  94. data/spec/integration/two_money_columns/input.csv +5 -0
  95. data/spec/integration/two_money_columns/output.ledger +20 -0
  96. data/spec/integration/two_money_columns/test_args +1 -0
  97. data/spec/integration/yyyymmdd_date_example/input.csv +1 -0
  98. data/spec/integration/yyyymmdd_date_example/output.ledger +4 -0
  99. data/spec/integration/yyyymmdd_date_example/test_args +1 -0
  100. data/spec/reckon/money_column_spec.rb +24 -24
  101. data/spec/reckon/money_spec.rb +13 -32
  102. metadata +93 -7
  103. data/.travis.yml +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f25c7df49774d944ebc40a73374ac593ebeac2e0b0a201e0d29f9c5ace2642e9
4
- data.tar.gz: 27acd960b5210e7e0fb88c50acf35def784411d0b8112f0f0b8e6c4e2f5ab621
3
+ metadata.gz: aebd4114b69fd94cc45e9cf425da57269ae80508b90f781fbbc5c96c6912b100
4
+ data.tar.gz: 0de1e3fb308bc2dad8cfa679056189092a4f07ece989b8e3b57c8acca100c77c
5
5
  SHA512:
6
- metadata.gz: ddd5fe0ef08b2718e8083cdbd9695e936737b9ff6b258808787192496675f9a371c302e9936069019821f1c865a3280488aa4396185f1e0bf6e7d84b78dc5c0a
7
- data.tar.gz: f611a48dad98a6a4764518dcb2e72969898d6dfbdaf5e06a77f36ab071429ac6f3bd0b0f51200dc8fbaaca913a95519a6bb38e6ec203dc0af61d861802d101f6
6
+ metadata.gz: c59e8ae6ecbf11534086bd9fc179ff5e8aa52066b8fb0cd5ddbce28b6a4a01b32659fdf51cdaa00403a86be2b3023e390184b76924fb13a6ed5288b8d637c8c0
7
+ data.tar.gz: 75a5921df0c427351768c803bbe019b3bdf7a12ec8f8d714779086a73ab850ea50b49708c9430ac823b97c0ef35493b5888b5980cdbfab4691cecc05b759224e
@@ -0,0 +1,50 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Build Status
9
+
10
+ on:
11
+ push:
12
+ branches: [ master ]
13
+ pull_request:
14
+ branches: [ master ]
15
+
16
+ jobs:
17
+ test:
18
+
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ matrix:
22
+ ruby-version:
23
+ # Current ruby stable version
24
+ - 3.0
25
+ # Ubuntu 20.10
26
+ - 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
+ steps:
36
+ - uses: actions/checkout@v2
37
+ - 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
41
+ - name: Set up Ruby
42
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
43
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
44
+ uses: ruby/setup-ruby@v1
45
+ # uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
46
+ with:
47
+ ruby-version: ${{ matrix.ruby-version }}
48
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
49
+ - name: Run tests
50
+ run: bundle exec rake test_all
data/.gitignore CHANGED
@@ -28,3 +28,5 @@ private_tests
28
28
 
29
29
  ## Bundler
30
30
  vendor
31
+
32
+ test.log
@@ -1 +1 @@
1
- 2.0.0-p648
1
+ 3.0.0
@@ -1,8 +1,28 @@
1
1
  # Changelog
2
2
 
3
- ## [0.6.0](https://github.com/cantino/reckon/tree/0.6.0) (2020-09-03)
3
+ ## [0.6.1](https://github.com/cantino/reckon/tree/0.6.1) (2021-01-23)
4
4
 
5
- [Full Changelog](https://github.com/cantino/reckon/compare/v0.5.4...0.6.0)
5
+ [Full Changelog](https://github.com/cantino/reckon/compare/v0.6.0...0.6.1)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - \[Feature Request\] Note flag --add-notes in CLI to allow additional notes for each ledger entry [\#86](https://github.com/cantino/reckon/issues/86)
10
+
11
+ **Closed issues:**
12
+
13
+ - spaces in tokens [\#97](https://github.com/cantino/reckon/issues/97)
14
+ - Migrate CI system from travis-ci.org [\#93](https://github.com/cantino/reckon/issues/93)
15
+ - \[Feature Request\] Pipe ledger file input to the bayesian predictor \(instead of csv\) [\#91](https://github.com/cantino/reckon/issues/91)
16
+
17
+ **Merged pull requests:**
18
+
19
+ - Add github actions [\#100](https://github.com/cantino/reckon/pull/100) ([benprew](https://github.com/benprew))
20
+ - Add documentation for doing a substring match. Fixes \#97 [\#99](https://github.com/cantino/reckon/pull/99) ([benprew](https://github.com/benprew))
21
+ - Test fixes [\#94](https://github.com/cantino/reckon/pull/94) ([benprew](https://github.com/benprew))
22
+
23
+ ## [v0.6.0](https://github.com/cantino/reckon/tree/v0.6.0) (2020-09-04)
24
+
25
+ [Full Changelog](https://github.com/cantino/reckon/compare/v0.5.4...v0.6.0)
6
26
 
7
27
  **Fixed bugs:**
8
28
 
@@ -198,6 +218,7 @@
198
218
 
199
219
  **Merged pull requests:**
200
220
 
221
+ - Updated the sources to allow for custom curreny [\#11](https://github.com/cantino/reckon/pull/11) ([ghost](https://github.com/ghost))
201
222
  - Add --account option on the commandline [\#10](https://github.com/cantino/reckon/pull/10) ([copiousfreetime](https://github.com/copiousfreetime))
202
223
 
203
224
  ## [v0.3.6](https://github.com/cantino/reckon/tree/v0.3.6) (2013-04-30)
@@ -231,7 +252,6 @@
231
252
 
232
253
  **Merged pull requests:**
233
254
 
234
- - Updated the sources to allow for custom curreny [\#11](https://github.com/cantino/reckon/pull/11) ([ghost](https://github.com/ghost))
235
255
  - adds support for Nordea csv files [\#1](https://github.com/cantino/reckon/pull/1) ([x2q](https://github.com/x2q))
236
256
 
237
257
  ## [v0.3.3](https://github.com/cantino/reckon/tree/v0.3.3) (2013-01-13)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- reckon (0.6.0)
4
+ reckon (0.6.1)
5
5
  chronic (>= 0.3.0)
6
6
  highline (>= 1.5.2)
7
7
  rchardet (>= 1.8.0)
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Reckon
2
2
 
3
- [![Build Status](https://travis-ci.org/cantino/reckon.png?branch=master)](https://travis-ci.org/cantino/reckon)
3
+ ![Build Status](https://github.com/cantino/reckon/workflows/Build%20Status/badge.svg)
4
4
 
5
5
  Reckon automagically converts CSV files for use with the command-line accounting tool [Ledger](http://www.ledger-cli.org/). It also helps you to select the correct accounts associated with the CSV data using Bayesian machine learning.
6
6
 
@@ -34,9 +34,8 @@ Learn more:
34
34
 
35
35
  Usage: Reckon.rb [options]
36
36
 
37
-
38
37
  -f, --file FILE The CSV file to parse
39
- -a, --account name The Ledger Account this file is for
38
+ -a, --account NAME The Ledger Account this file is for
40
39
  -v, --[no-]verbose Run verbosely
41
40
  -i, --inverse Use the negative of each amount
42
41
  -p, --print-table Print out the parsed CSV in table form
@@ -44,6 +43,12 @@ Learn more:
44
43
  -l, --learn-from FILE An existing ledger file to learn accounts from
45
44
  --ignore-columns 1,2,5
46
45
  Columns to ignore in the CSV file - the first column is column 1
46
+ --money-column 2
47
+ Specify the money column instead of letting Reckon guess - the first column is column 1
48
+ --raw-money
49
+ Don't format money column (for stocks)
50
+ --date-column 3
51
+ Specify the date column instead of letting Reckon guess - the first column is column 1
47
52
  --contains-header [N]
48
53
  The first row of the CSV is a header and should be skipped. Optionally add the number of rows to skip.
49
54
  --csv-separator ','
@@ -57,9 +62,9 @@ Learn more:
57
62
  Force the date format (see Ruby DateTime strftime)
58
63
  -u, --unattended Don't ask questions and guess all the accounts automatically. Used with --learn-from or --account-tokens options.
59
64
  -t, --account-tokens FILE YAML file with manually-assigned tokens for each account (see README)
60
- --default-into-account name
65
+ --default-into-account NAME
61
66
  Default into account
62
- --default-outof-account name
67
+ --default-outof-account NAME
63
68
  Default 'out of' account
64
69
  --suffixed
65
70
  If --currency should be used as a suffix. Defaults to false.
@@ -77,6 +82,16 @@ To guess the accounts reckon can use an existing ledger file or a token file wit
77
82
 
78
83
  `reckon --unattended --account-tokens tokens.yaml -f bank.csv -o ledger.dat`
79
84
 
85
+ ### Account Tokens
86
+
87
+ The account tokens file provides a way to teach reckon about what tokens are associated with an account. As an example, this `tokens.yaml` file:
88
+
89
+ Expenses:
90
+ Bank:
91
+ - 'ING Direct Deposit'
92
+
93
+ Would tokenize to 'ING', 'Direct' and 'Deposit'. The matcher would then suggest matches to transactions that included those tokens. (ex 'Chase Direct Deposit')
94
+
80
95
  Here's an example of `tokens.yaml`:
81
96
 
82
97
  ```
@@ -96,21 +111,62 @@ Expenses:
96
111
  - '4433221100' # Your own account number
97
112
  ```
98
113
 
99
- If reckon can not guess the accounts it will use `Income:Unknown` or `Expenses:Unknown` names.
100
- You can override them with `--default_outof_account` and `--default_into_account` options.
114
+ Reckon will use `Income:Unknown` or `Expenses:Unknown` if it can't match a transaction to an account.
115
+
116
+ You can override these names with the `--default_outof_account` and `--default_into_account` options.
117
+
118
+ ### Substring Match
119
+
120
+ If, in the above example, you'd prefer to match any transaction that contains the string 'ING Direct Deposit' you have to use a regex:
121
+
122
+ Expenses:
123
+ Bank:
124
+ - /ING Direct Deposit/
125
+
126
+ ## Contributing
127
+
128
+ We encourage you to contribute to Reckon! Here is some information to help you.
129
+
130
+ ### Patches/Pull Requests Process
131
+
132
+ 1. Fork the project.
133
+ 2. Make your feature addition or bug fix.
134
+ 3. Add tests for it. This is important so I don't break it in a
135
+ 4. future version unintentionally.
136
+ 5. Commit, do not mess with rakefile, version, or history.
137
+ - (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
138
+ 6. Send me a pull request. Bonus points for topic branches.
101
139
 
102
- ## Note on Patches/Pull Requests
140
+ ### Integration Tests
141
+
142
+ Reckon has integration test located in `spec/integration`. These are integration and regression tests for reckon.
143
+
144
+ Run all the tests:
145
+
146
+ ./spec/integration/test.sh
147
+
148
+ Run a single test
149
+
150
+ ./spec/integration/test.sh chase/account_tokens_and_regex
151
+
152
+ #### Add a new integration test
153
+
154
+ Each test has it's own directory, which you can add any files you want, but the following files are required:
155
+
156
+ - `test_args` - arguments to add to the reckon command to test against, can specify `--unattended`, `-f input.csv`, etc
157
+ - `output.ledger` - the expected ledger file output
158
+
159
+ If the result of running reckon with `test_args` does not match `output.ledger`, then the test fails.
160
+
161
+ Most tests will specify `--unattended`, otherwise reckon prompts for keyboard input.
162
+
163
+ The convention is to use `input.csv` as the input file, and `tokens.yml` as the tokens file, but it is not required.
103
164
 
104
- * Fork the project.
105
- * Make your feature addition or bug fix.
106
- * Add tests for it. This is important so I don't break it in a
107
- future version unintentionally.
108
- * Commit, do not mess with rakefile, version, or history.
109
- (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
110
- * Send me a pull request. Bonus points for topic branches.
111
165
 
112
166
  ## Copyright
113
167
 
114
- Copyright (c) 2013 Andrew Cantino. See LICENSE for details.
168
+ Copyright (c) 2013 Andrew Cantino (@cantino). See LICENSE for details.
115
169
 
116
170
  Thanks to @BlackEdder for many contributions!
171
+
172
+ Currently maintained by @benprew. Thank you!
data/Rakefile CHANGED
@@ -1,6 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require 'rspec/core/rake_task'
5
+ require 'English'
3
6
 
4
7
  RSpec::Core::RakeTask.new(:spec)
5
8
 
6
- task :default => :spec
9
+ task default: :spec
10
+
11
+ task :test_all do
12
+ puts "#{`ledger --version |head -n1`}"
13
+ puts "Running unit tests"
14
+ Rake::Task["spec"].invoke
15
+ puts "Running integration tests"
16
+ Rake::Task["integration_tests"].invoke
17
+ end
18
+
19
+ task :integration_tests do
20
+ puts `./spec/integration/test.sh`
21
+ raise 'Integration tests failed' if $CHILD_STATUS.exitstatus != 0
22
+ end
@@ -8,9 +8,10 @@ module Reckon
8
8
  attr_accessor :options, :seen, :csv_parser, :regexps, :matcher
9
9
  @@cli = HighLine.new
10
10
 
11
- def initialize(options = {})
11
+ def initialize(opts = {})
12
+ self.options = opts
12
13
  LOGGER.level = Logger::INFO if options[:verbose]
13
- self.options = options
14
+
14
15
  self.regexps = {}
15
16
  self.seen = Set.new
16
17
  self.options[:currency] ||= '$'
@@ -157,7 +158,7 @@ module Reckon
157
158
  :money => @csv_parser.money_for(index),
158
159
  :description => @csv_parser.description_for(index) }
159
160
  end
160
- rows.sort_by { |n| n[:date] }.each { |row| yield row }
161
+ rows.sort_by { |n| [n[:date], -n[:money], n[:description]] }.each { |row| yield row }
161
162
  end
162
163
 
163
164
  def print_transaction(rows)
@@ -252,7 +253,7 @@ module Reckon
252
253
  end
253
254
 
254
255
  def ledger_format(row, line1, line2)
255
- out = "#{row[:pretty_date]}\t#{row[:description]}\t; #{row[:note]}\n"
256
+ out = "#{row[:pretty_date]}\t#{row[:description]}#{row[:note] ? "\t; " + row[:note]: ""}\n"
256
257
  out += "\t#{line1.first}\t\t\t#{line1.last}\n"
257
258
  out += "\t#{line2.first}\t\t\t#{line2.last}\n\n"
258
259
  out
@@ -327,6 +328,10 @@ module Reckon
327
328
  options[:money_column] = column_number
328
329
  end
329
330
 
331
+ opts.on("", "--raw-money", "Don't format money column (for stocks)") do |n|
332
+ options[:raw] = n
333
+ end
334
+
330
335
  opts.on("", "--date-column 3", Integer, "Specify the date column instead of letting Reckon guess - the first column is column 1") do |column_number|
331
336
  options[:date_column] = column_number
332
337
  end
@@ -399,7 +404,7 @@ module Reckon
399
404
  end
400
405
 
401
406
  unless options[:bank_account]
402
- fail "Please specify --account for the unattended mode" if options[:unattended]
407
+ fail "Please specify --account in unattended mode" if options[:unattended]
403
408
 
404
409
  options[:bank_account] = @@cli.ask("What is the account name of this bank account in Ledger? ") do |q|
405
410
  q.readline = true
@@ -89,12 +89,7 @@ module Reckon
89
89
  money_score += Money::likelihood( entry )
90
90
  possible_neg_money_count += 1 if entry =~ /^\$?[\-\(]\$?\d+/
91
91
  possible_pos_money_count += 1 if entry =~ /^\+?\$?\+?\d+/
92
- date_score += 10 if entry =~ /\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i
93
- date_score += 5 if entry =~ /^[\-\/\.\d:\[\]]+$/
94
- date_score += entry.gsub(/[^\-\/\.\d:\[\]]/, '').length if entry.gsub(/[^\-\/\.\d:\[\]]/, '').length > 3
95
- date_score -= entry.gsub(/[\-\/\.\d:\[\]]/, '').length
96
- date_score += 30 if entry =~ /^\d+[:\/\.-]\d+[:\/\.-]\d+([ :]\d+[:\/\.]\d+)?$/
97
- date_score += 10 if entry =~ /^\d+\[\d+:GMT\]$/i
92
+ date_score += DateColumn.likelihood(entry)
98
93
 
99
94
  # Try to determine if this is a balance column
100
95
  entry_as_num = entry.gsub(/[^\-\d\.]/, '').to_f
@@ -168,7 +163,7 @@ module Reckon
168
163
  results = evaluate_columns(columns)
169
164
 
170
165
  if options[:money_column]
171
- self.money_column_indices = [ options[:money_column] - 1 ]
166
+ self.money_column_indices = [options[:money_column] - 1]
172
167
  else
173
168
  self.money_column_indices = results.select { |n| n[:is_money_column] }.map { |n| n[:index] }
174
169
  if self.money_column_indices.length == 1
@@ -56,5 +56,15 @@ module Reckon
56
56
  date.iso8601
57
57
  end
58
58
 
59
+ def self.likelihood(entry)
60
+ date_score = 0
61
+ date_score += 10 if entry =~ /\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i
62
+ date_score += 5 if entry =~ /^[\-\/\.\d:\[\]]+$/
63
+ date_score += entry.gsub(/[^\-\/\.\d:\[\]]/, '').length if entry.gsub(/[^\-\/\.\d:\[\]]/, '').length > 3
64
+ date_score -= entry.gsub(/[\-\/\.\d:\[\]]/, '').length
65
+ date_score += 30 if entry =~ /^\d+[:\/\.-]\d+[:\/\.-]\d+([ :]\d+[:\/\.]\d+)?$/
66
+ date_score += 10 if entry =~ /^\d+\[\d+:GMT\]$/i
67
+ return date_score
68
+ end
59
69
  end
60
70
  end
@@ -5,12 +5,13 @@ module Reckon
5
5
  class Money
6
6
  include Comparable
7
7
  attr_accessor :amount, :currency, :suffixed
8
- def initialize( amount, options = {} )
9
- if options[:inverse]
10
- @amount = -1*amount.to_f
11
- else
12
- @amount = amount.to_f
13
- end
8
+ def initialize(amount, options = {})
9
+ @options = options
10
+ @amount_raw = amount
11
+ @raw = options[:raw]
12
+
13
+ @amount = parse(amount, options)
14
+ @amount = -@amount if options[:inverse]
14
15
  @currency = options[:currency] || "$"
15
16
  @suffixed = options[:suffixed]
16
17
  end
@@ -19,11 +20,19 @@ module Reckon
19
20
  return @amount
20
21
  end
21
22
 
23
+ def to_s
24
+ return @options[:raw] ? "#{@amount_raw} | #{@amount}" : @amount
25
+ end
26
+
27
+ # unary minus
28
+ # ex
29
+ # m = Money.new
30
+ # -m
22
31
  def -@
23
- Money.new( -@amount, :currency => @currency, :suffixed => @suffixed )
32
+ Money.new(-@amount, :currency => @currency, :suffixed => @suffixed)
24
33
  end
25
34
 
26
- def <=>( mon )
35
+ def <=>(mon)
27
36
  other_amount = mon.to_f
28
37
  if @amount < other_amount
29
38
  -1
@@ -34,7 +43,13 @@ module Reckon
34
43
  end
35
44
  end
36
45
 
37
- def pretty( negate = false )
46
+ def pretty(negate = false)
47
+ if @raw
48
+ return @amount_raw unless negate
49
+
50
+ return @amount_raw[0] == '-' ? @amount_raw[1..-1] : "-#{@amount_raw}"
51
+ end
52
+
38
53
  if @suffixed
39
54
  (@amount >= 0 ? " " : "") + sprintf("%0.2f #{@currency}", @amount * (negate ? -1 : 1))
40
55
  else
@@ -42,34 +57,20 @@ module Reckon
42
57
  end
43
58
  end
44
59
 
45
- def Money::from_s( value, options = {} )
60
+ def parse(value, options = {})
61
+ value = value.to_s
46
62
  # Empty string is treated as money with value 0
47
- return Money.new( 0.00, options ) if value.empty?
48
-
49
- # Remove 1000 separaters and replace , with . if comma_separates_cents
50
- # 1.000,00 -> 1000.00
51
- value = value.gsub(/\./, '').gsub(/,/, '.') if options[:comma_separates_cents]
52
- value = value.gsub(/,/, '')
53
-
54
- money_format_regex = /^(.*?)(\d+\.\d\d)/ # Money has two decimal precision
55
- any_number_regex = /^(.*?)([\d\.]+)/
56
-
57
- # Prefer matching the money_format, match any number otherwise
58
- m = value.match( money_format_regex ) ||
59
- value.match( any_number_regex )
60
- if m
61
- amount = m[2].to_f
62
- # Check whether the money had a - or (, which indicates negative amounts
63
- if (m[1].match( /^[\(-]/ ) || m[1].match( /-$/ ))
64
- amount *= -1
65
- end
66
- return Money.new( amount, options )
67
- else
68
- return nil
69
- end
63
+ return value.to_f if value.to_s.empty?
64
+
65
+ invert = value.match(/^\(.*\)$/)
66
+ value = value.gsub(/[^0-9,.-]/, '')
67
+ value = value.tr('.', '').tr(',', '.') if options[:comma_separates_cents]
68
+ value = value.tr(',', '')
69
+ value = value.to_f
70
+ return invert ? -value : value
70
71
  end
71
72
 
72
- def Money::likelihood( entry )
73
+ def Money::likelihood(entry)
73
74
  money_score = 0
74
75
  # digits separated by , or . with no more than 2 trailing digits
75
76
  money_score += 40 if entry.match(/\d+[,.]\d{2}[^\d]*$/)
@@ -83,31 +84,30 @@ module Reckon
83
84
  end
84
85
 
85
86
  class MoneyColumn < Array
86
- def initialize( arr = [], options = {} )
87
- arr.each { |str| self.push( Money.from_s( str, options ) ) }
87
+ def initialize(arr = [], options = {})
88
+ arr.each { |str| push(Money.new(str, options)) }
88
89
  end
89
90
 
90
91
  def positive?
91
- self.each do |money|
92
- return false if money < 0 if money
92
+ each do |money|
93
+ return false if money && money < 0
93
94
  end
94
95
  true
95
96
  end
96
97
 
97
- def merge!( other_column )
98
+ def merge!(other_column)
98
99
  invert = false
99
- invert = true if self.positive? && other_column.positive?
100
- self.each_with_index do |mon, i|
100
+ invert = true if positive? && other_column.positive?
101
+ each_with_index do |mon, i|
101
102
  other = other_column[i]
102
- return nil if (!mon || !other)
103
- if mon != 0.00 && other == 0.0
104
- if invert
105
- self[i]= -mon
106
- end
107
- elsif mon == 0.00 && other != 0.00
103
+ return nil if !mon || !other
104
+
105
+ if mon != 0.0 && other == 0.0
106
+ self[i] = -mon if invert
107
+ elsif mon == 0.0 && other != 0.0
108
108
  self[i] = other
109
109
  else
110
- return nil
110
+ self[i] = Money.new(0)
111
111
  end
112
112
  end
113
113
  self