reckon 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/lib/reckon/cosine_similarity.rb +1 -0
- data/lib/reckon/csv_parser.rb +60 -115
- data/lib/reckon/money.rb +4 -3
- data/lib/reckon/version.rb +1 -1
- data/spec/data_fixtures/51-sample.csv +8 -0
- data/spec/data_fixtures/51-tokens.yml +9 -0
- data/spec/data_fixtures/85-date-example.csv +2 -0
- data/spec/reckon/app_spec.rb +16 -0
- data/spec/reckon/csv_parser_spec.rb +124 -129
- data/spec/reckon/money_spec.rb +42 -29
- data/spec/spec_helper.rb +19 -0
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70bc1d3d98a4ba08a3bca57069073f165e68041a5e628475b6c6076550f6e419
|
4
|
+
data.tar.gz: 99daf95abf45fd4dd5549d08bb9f3816af596cd3790667ca224baf7d46053ed0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c0790695ec045e5210d20de85e17da49868b396ad78e334f24fdae7511969552a72df90b649b781c8b0e7ddf046cf8f84e2b47e212c576048f86798389bb449
|
7
|
+
data.tar.gz: 18babc20d956315d9abed0cec59503f0859844a48e3cf5ee59d743dda487de762a9ab9787074e7553128d41834938f5eedeb2f92e5fda4fb1ab73939f81d0eb2
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,27 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v0.5.2](https://github.com/cantino/reckon/tree/v0.5.2) (2020-03-07)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/cantino/reckon/compare/v0.5.1...v0.5.2)
|
6
|
+
|
7
|
+
**Closed issues:**
|
8
|
+
|
9
|
+
- \[BUG\] Reckon appears not to be parsing ISO standard date yyyy-mm-dd? [\#85](https://github.com/cantino/reckon/issues/85)
|
10
|
+
- \[Bug\]? Reckon fails to run on ruby 2.7.0 on Catalina [\#83](https://github.com/cantino/reckon/issues/83)
|
11
|
+
- --account-tokens issue [\#51](https://github.com/cantino/reckon/issues/51)
|
12
|
+
|
13
|
+
## [v0.5.1](https://github.com/cantino/reckon/tree/v0.5.1) (2020-02-25)
|
14
|
+
|
15
|
+
[Full Changelog](https://github.com/cantino/reckon/compare/v0.5.0...v0.5.1)
|
16
|
+
|
17
|
+
**Closed issues:**
|
18
|
+
|
19
|
+
- Error Importing [\#64](https://github.com/cantino/reckon/issues/64)
|
20
|
+
|
21
|
+
**Merged pull requests:**
|
22
|
+
|
23
|
+
- guard against rows that don't parse dates [\#82](https://github.com/cantino/reckon/pull/82) ([benprew](https://github.com/benprew))
|
24
|
+
|
3
25
|
## [v0.5.0](https://github.com/cantino/reckon/tree/v0.5.0) (2020-02-19)
|
4
26
|
|
5
27
|
[Full Changelog](https://github.com/cantino/reckon/compare/v0.4.4...v0.5.0)
|
data/lib/reckon/csv_parser.rb
CHANGED
@@ -12,24 +12,39 @@ module Reckon
|
|
12
12
|
detect_columns
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
16
|
-
|
15
|
+
def columns
|
16
|
+
@columns ||=
|
17
|
+
begin
|
18
|
+
last_row_length = nil
|
19
|
+
csv_data.inject([]) do |memo, row|
|
20
|
+
unless row.all? { |i| i.nil? || i.length == 0 }
|
21
|
+
row.each_with_index do |entry, index|
|
22
|
+
memo[index] ||= []
|
23
|
+
memo[index] << (entry || '').strip
|
24
|
+
end
|
25
|
+
last_row_length = row.length
|
26
|
+
end
|
27
|
+
memo
|
28
|
+
end
|
29
|
+
end
|
17
30
|
end
|
18
31
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
@columns = new_columns
|
26
|
-
end
|
32
|
+
def date_for(index)
|
33
|
+
@date_column.for(index)
|
34
|
+
end
|
35
|
+
|
36
|
+
def pretty_date_for(index)
|
37
|
+
@date_column.pretty_for( index )
|
27
38
|
end
|
28
39
|
|
29
40
|
def money_for(index)
|
30
41
|
@money_column[index]
|
31
42
|
end
|
32
43
|
|
44
|
+
def pretty_money(amount, negate = false)
|
45
|
+
Money.new( amount, @options ).pretty( negate )
|
46
|
+
end
|
47
|
+
|
33
48
|
def pretty_money_for(index, negate = false)
|
34
49
|
money = money_for(index)
|
35
50
|
return 0 if money.nil?
|
@@ -37,20 +52,24 @@ module Reckon
|
|
37
52
|
money.pretty(negate)
|
38
53
|
end
|
39
54
|
|
40
|
-
def
|
41
|
-
|
55
|
+
def description_for(index)
|
56
|
+
description_column_indices.map { |i| columns[i][index] }.reject(&:empty?).join("; ").squeeze(" ").gsub(/(;\s+){2,}/, '').strip
|
42
57
|
end
|
43
58
|
|
44
|
-
def
|
45
|
-
|
59
|
+
def row(index)
|
60
|
+
csv_data[index].join(", ")
|
46
61
|
end
|
47
62
|
|
48
|
-
|
49
|
-
@date_column.pretty_for( index )
|
50
|
-
end
|
63
|
+
private
|
51
64
|
|
52
|
-
def
|
53
|
-
|
65
|
+
def filter_csv
|
66
|
+
if options[:ignore_columns]
|
67
|
+
new_columns = []
|
68
|
+
columns.each_with_index do |column, index|
|
69
|
+
new_columns << column unless options[:ignore_columns].include?(index + 1)
|
70
|
+
end
|
71
|
+
@columns = new_columns
|
72
|
+
end
|
54
73
|
end
|
55
74
|
|
56
75
|
def evaluate_columns(cols)
|
@@ -94,48 +113,24 @@ module Reckon
|
|
94
113
|
results << { :index => index, :money_score => money_score, :date_score => date_score }
|
95
114
|
end
|
96
115
|
|
97
|
-
|
98
|
-
end
|
116
|
+
results.sort_by! { |n| -n[:money_score] }
|
99
117
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
.map { |m| m.amount.to_s }
|
107
|
-
output_columns << new_column
|
108
|
-
elsif index == b
|
109
|
-
# skip
|
110
|
-
else
|
111
|
-
output_columns << column
|
112
|
-
end
|
118
|
+
# check if it looks like a 2-column file with a balance field
|
119
|
+
if results.length >= 3 && results[1][:money_score] + results[2][:money_score] >= results[0][:money_score]
|
120
|
+
results[1][:is_money_column] = true
|
121
|
+
results[2][:is_money_column] = true
|
122
|
+
else
|
123
|
+
results[0][:is_money_column] = true
|
113
124
|
end
|
114
|
-
output_columns
|
115
|
-
end
|
116
125
|
|
117
|
-
|
118
|
-
merged_columns = merge_columns( id1, id2 )
|
119
|
-
results, found_likely_money_column = evaluate_columns( merged_columns )
|
120
|
-
if !found_likely_money_column
|
121
|
-
new_res = results.find { |el| el[:index] == id1 }
|
122
|
-
old_res1 = unmerged_results.find { |el| el[:index] == id1 }
|
123
|
-
old_res2 = unmerged_results.find { |el| el[:index] == id2 }
|
124
|
-
if new_res[:money_score] > old_res1[:money_score] &&
|
125
|
-
new_res[:money_score] > old_res2[:money_score]
|
126
|
-
found_likely_money_column = true
|
127
|
-
end
|
128
|
-
end
|
129
|
-
[results, found_likely_money_column]
|
126
|
+
return results.sort_by { |n| n[:index] }
|
130
127
|
end
|
131
128
|
|
132
|
-
def found_double_money_column(
|
133
|
-
self.money_column_indices = [
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
puts "please report this issue to us so we can take a look!\n"
|
138
|
-
end
|
129
|
+
def found_double_money_column(id1, id2)
|
130
|
+
self.money_column_indices = [id1, id2]
|
131
|
+
puts "It looks like this CSV has two seperate columns for money, one of which shows positive"
|
132
|
+
puts "changes and one of which shows negative changes. If this is true, great. Otherwise,"
|
133
|
+
puts "please report this issue to us so we can take a look!\n"
|
139
134
|
end
|
140
135
|
|
141
136
|
# Some csv files negative/positive amounts are indicated in separate account
|
@@ -165,41 +160,18 @@ module Reckon
|
|
165
160
|
end
|
166
161
|
|
167
162
|
def detect_columns
|
168
|
-
results
|
163
|
+
results = evaluate_columns(columns)
|
164
|
+
|
169
165
|
if options[:money_column]
|
170
|
-
found_likely_money_column = true
|
171
166
|
self.money_column_indices = [ options[:money_column] - 1 ]
|
172
167
|
else
|
173
|
-
self.money_column_indices =
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
_, found_likely_double_money_columns = evaluate_columns(merge_columns(i, i+1))
|
181
|
-
if found_likely_double_money_columns
|
182
|
-
found_double_money_column( i, i + 1 )
|
183
|
-
break
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
if !found_likely_double_money_columns
|
189
|
-
0.upto(columns.length - 2) do |i|
|
190
|
-
if MoneyColumn.new( columns[i] ).merge!( MoneyColumn.new( columns[i+1] ) )
|
191
|
-
# Try a more specific test
|
192
|
-
_, found_likely_double_money_columns = evaluate_two_money_columns( columns, i, i+1, results )
|
193
|
-
if found_likely_double_money_columns
|
194
|
-
found_double_money_column( i, i + 1 )
|
195
|
-
break
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
if !found_likely_double_money_columns && !settings[:testing]
|
202
|
-
puts "I didn't find a high-likelyhood money column, but I'm taking my best guess with column #{money_column_indices.first + 1}."
|
168
|
+
self.money_column_indices = results.select { |n| n[:is_money_column] }.map { |n| n[:index] }
|
169
|
+
if self.money_column_indices.length == 1
|
170
|
+
puts "Using column #{money_column_indices.first + 1} as the money column. Use --money-colum to specify a different one."
|
171
|
+
elsif self.money_column_indices.length == 2
|
172
|
+
found_double_money_column(*self.money_column_indices)
|
173
|
+
else
|
174
|
+
puts "Unable to determine a money column, use --money-column to specify the column reckon should use."
|
203
175
|
end
|
204
176
|
end
|
205
177
|
|
@@ -223,23 +195,6 @@ module Reckon
|
|
223
195
|
self.description_column_indices = results.map { |i| i[:index] }
|
224
196
|
end
|
225
197
|
|
226
|
-
def columns
|
227
|
-
@columns ||= begin
|
228
|
-
last_row_length = nil
|
229
|
-
csv_data.inject([]) do |memo, row|
|
230
|
-
# fail "Input CSV must have consistent row lengths." if last_row_length && row.length != last_row_length
|
231
|
-
unless row.all? { |i| i.nil? || i.length == 0 }
|
232
|
-
row.each_with_index do |entry, index|
|
233
|
-
memo[index] ||= []
|
234
|
-
memo[index] << (entry || '').strip
|
235
|
-
end
|
236
|
-
last_row_length = row.length
|
237
|
-
end
|
238
|
-
memo
|
239
|
-
end
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
198
|
def parse(data, filename=nil)
|
244
199
|
# Use force_encoding to convert the string to utf-8 with as few invalid characters
|
245
200
|
# as possible.
|
@@ -281,15 +236,5 @@ module Reckon
|
|
281
236
|
end
|
282
237
|
m && m[1]
|
283
238
|
end
|
284
|
-
|
285
|
-
@settings = { :testing => false }
|
286
|
-
|
287
|
-
def self.settings
|
288
|
-
@settings
|
289
|
-
end
|
290
|
-
|
291
|
-
def settings
|
292
|
-
self.class.settings
|
293
|
-
end
|
294
239
|
end
|
295
240
|
end
|
data/lib/reckon/money.rb
CHANGED
@@ -71,12 +71,13 @@ module Reckon
|
|
71
71
|
|
72
72
|
def Money::likelihood( entry )
|
73
73
|
money_score = 0
|
74
|
-
|
74
|
+
# digits separated by , or . with no more than 2 trailing digits
|
75
|
+
money_score += 40 if entry.match(/\d+[,.]\d{2}[^\d]*$/)
|
75
76
|
money_score += 10 if entry[/^\$?\-?\$?\d+[\.,\d]*?[\.,]\d\d$/]
|
76
77
|
money_score += 10 if entry[/\d+[\.,\d]*?[\.,]\d\d$/]
|
77
78
|
money_score += entry.gsub(/[^\d\.\-\+,\(\)]/, '').length if entry.length < 7
|
78
|
-
money_score -= entry.length if entry.length >
|
79
|
-
money_score -= 20 if entry !~ /^[\$\+\.\-,\d\(\)]+$/
|
79
|
+
money_score -= entry.length if entry.length > 12
|
80
|
+
money_score -= 20 if (entry !~ /^[\$\+\.\-,\d\(\)]+$/) && entry.length > 0
|
80
81
|
money_score
|
81
82
|
end
|
82
83
|
end
|
data/lib/reckon/version.rb
CHANGED
@@ -0,0 +1,8 @@
|
|
1
|
+
01/09/2015,05354 SUBWAY,8.19,,1000.00
|
2
|
+
02/18/2015,WENDY'S #6338,8.55,,1000.00
|
3
|
+
02/25/2015,WENDY'S #6338,8.55,,1000.00
|
4
|
+
02/25/2015,WENDY'S #6338,9.14,,1000.00
|
5
|
+
02/27/2015,WENDY'S #6338,5.85,,1000.00
|
6
|
+
03/09/2015,WENDY'S #6338,17.70,,1000.00
|
7
|
+
03/16/2015,WENDY'S #6338,11.15,,1000.00
|
8
|
+
03/23/2015,WENDY'S,10.12,,1000.00
|
data/spec/reckon/app_spec.rb
CHANGED
@@ -121,6 +121,22 @@ describe Reckon::App do
|
|
121
121
|
end
|
122
122
|
end
|
123
123
|
|
124
|
+
context 'Issue #51 - regression test' do
|
125
|
+
it 'should assign correct accounts with tokens' do
|
126
|
+
output = StringIO.new
|
127
|
+
Reckon::App.new(
|
128
|
+
file: fixture_path('51-sample.csv'),
|
129
|
+
unattended: true,
|
130
|
+
account_tokens_file: fixture_path('51-tokens.yml'),
|
131
|
+
ignore_columns: [5],
|
132
|
+
bank_account: 'Assets:Chequing',
|
133
|
+
output_file: output
|
134
|
+
).walk_backwards
|
135
|
+
expect(output.string).not_to include('Income:Unknown')
|
136
|
+
expect(output.string.scan('Expenses:Dining:Resturant').size).to eq(8)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
124
140
|
#DATA
|
125
141
|
BANK_CSV = (<<-CSV).strip
|
126
142
|
DEBIT,20091224120000[0:GMT],"HOST 037196321563 MO 12/22SLICEHOST",-85.00
|
@@ -5,38 +5,29 @@ require_relative "../spec_helper"
|
|
5
5
|
require 'rubygems'
|
6
6
|
require_relative '../../lib/reckon'
|
7
7
|
|
8
|
-
Reckon::CSVParser.settings[:testing] = true
|
9
|
-
|
10
8
|
describe Reckon::CSVParser do
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
@intuit_mint = Reckon::CSVParser.new(file: fixture_path('intuit_mint_example.csv'))
|
28
|
-
end
|
29
|
-
|
30
|
-
it "should be in testing mode" do
|
31
|
-
@chase.settings[:testing].should be true
|
32
|
-
Reckon::CSVParser.settings[:testing].should be true
|
33
|
-
end
|
9
|
+
let(:chase) { Reckon::CSVParser.new(file: fixture_path('chase.csv')) }
|
10
|
+
let(:some_other_bank) { Reckon::CSVParser.new(file: fixture_path('some_other.csv')) }
|
11
|
+
let(:two_money_columns) { Reckon::CSVParser.new(file: fixture_path('two_money_columns.csv')) }
|
12
|
+
let(:suntrust_csv) { Reckon::CSVParser.new(file: fixture_path('suntrust.csv')) }
|
13
|
+
let(:simple_csv) { Reckon::CSVParser.new(file: fixture_path('simple.csv')) }
|
14
|
+
let(:nationwide) { Reckon::CSVParser.new(file: fixture_path('nationwide.csv'), csv_separator: ',', suffixed: true, currency: "POUND") }
|
15
|
+
let(:german_date) { Reckon::CSVParser.new(file: fixture_path('german_date_example.csv')) }
|
16
|
+
let(:danish_kroner_nordea) { Reckon::CSVParser.new(file: fixture_path('danish_kroner_nordea_example.csv'), csv_separator: ';', comma_separates_cents: true) }
|
17
|
+
let(:yyyymmdd_date) { Reckon::CSVParser.new(file: fixture_path('yyyymmdd_date_example.csv')) }
|
18
|
+
let(:spanish_date) { Reckon::CSVParser.new(file: fixture_path('spanish_date_example.csv'), date_format: '%d/%m/%Y') }
|
19
|
+
let(:english_date) { Reckon::CSVParser.new(file: fixture_path('english_date_example.csv')) }
|
20
|
+
let(:ing_csv) { Reckon::CSVParser.new(file: fixture_path('ing.csv'), comma_separates_cents: true ) }
|
21
|
+
let(:austrian_csv) { Reckon::CSVParser.new(file: fixture_path('austrian_example.csv'), comma_separates_cents: true, csv_separator: ';' ) }
|
22
|
+
let(:french_csv) { Reckon::CSVParser.new(file: fixture_path('french_example.csv'), csv_separator: ';', comma_separates_cents: true) }
|
23
|
+
let(:broker_canada) { Reckon::CSVParser.new(file: fixture_path('broker_canada_example.csv')) }
|
24
|
+
let(:intuit_mint) { Reckon::CSVParser.new(file: fixture_path('intuit_mint_example.csv')) }
|
34
25
|
|
35
26
|
describe "parse" do
|
36
27
|
it "should use binary encoding if none specified and chardet fails" do
|
37
28
|
allow(CharDet).to receive(:detect).and_return({'encoding' => nil})
|
38
29
|
app = Reckon::CSVParser.new(file: fixture_path("extratofake.csv"))
|
39
|
-
expect(app.try_encoding
|
30
|
+
expect(app.send(:try_encoding, "foobarbaz")).to eq("BINARY")
|
40
31
|
end
|
41
32
|
|
42
33
|
it "should work with foreign character encodings" do
|
@@ -76,8 +67,8 @@ describe Reckon::CSVParser do
|
|
76
67
|
|
77
68
|
describe "columns" do
|
78
69
|
it "should return the csv transposed" do
|
79
|
-
|
80
|
-
|
70
|
+
simple_csv.columns.should == [["entry1", "entry4"], ["entry2", "entry5"], ["entry3", "entry6"]]
|
71
|
+
chase.columns.length.should == 4
|
81
72
|
end
|
82
73
|
|
83
74
|
it "should be ok with empty lines" do
|
@@ -88,46 +79,44 @@ describe Reckon::CSVParser do
|
|
88
79
|
end
|
89
80
|
|
90
81
|
describe "detect_columns" do
|
91
|
-
|
92
|
-
@harder_date_example_csv = Reckon::CSVParser.new(file: fixture_path('harder_date_example.csv'))
|
93
|
-
end
|
82
|
+
let(:harder_date_example_csv) { Reckon::CSVParser.new(file: fixture_path('harder_date_example.csv')) }
|
94
83
|
|
95
84
|
it "should detect the money column" do
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
85
|
+
chase.money_column_indices.should == [3]
|
86
|
+
some_other_bank.money_column_indices.should == [3]
|
87
|
+
two_money_columns.money_column_indices.should == [3, 4]
|
88
|
+
suntrust_csv.money_column_indices.should == [3, 4]
|
89
|
+
nationwide.money_column_indices.should == [3, 4]
|
90
|
+
harder_date_example_csv.money_column_indices.should == [1]
|
91
|
+
danish_kroner_nordea.money_column_indices.should == [3]
|
92
|
+
yyyymmdd_date.money_column_indices.should == [3]
|
93
|
+
ing_csv.money_column_indices.should == [6]
|
94
|
+
austrian_csv.money_column_indices.should == [4]
|
95
|
+
french_csv.money_column_indices.should == [4]
|
96
|
+
broker_canada.money_column_indices.should == [8]
|
97
|
+
intuit_mint.money_column_indices.should == [3]
|
109
98
|
end
|
110
99
|
|
111
100
|
it "should detect the date column" do
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
101
|
+
chase.date_column_index.should == 1
|
102
|
+
some_other_bank.date_column_index.should == 1
|
103
|
+
two_money_columns.date_column_index.should == 0
|
104
|
+
harder_date_example_csv.date_column_index.should == 0
|
105
|
+
danish_kroner_nordea.date_column_index.should == 0
|
106
|
+
yyyymmdd_date.date_column_index.should == 1
|
107
|
+
french_csv.date_column_index.should == 1
|
108
|
+
broker_canada.date_column_index.should == 0
|
109
|
+
intuit_mint.date_column_index.should == 0
|
121
110
|
Reckon::CSVParser.new(:string => '2014-01-13,"22211100000",-10').date_column_index.should == 0
|
122
111
|
end
|
123
112
|
|
124
113
|
it "should consider all other columns to be description columns" do
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
114
|
+
chase.description_column_indices.should == [0, 2]
|
115
|
+
some_other_bank.description_column_indices.should == [0, 2]
|
116
|
+
two_money_columns.description_column_indices.should == [1, 2, 5]
|
117
|
+
harder_date_example_csv.description_column_indices.should == [2, 3, 4, 5, 6, 7]
|
118
|
+
danish_kroner_nordea.description_column_indices.should == [1, 2, 4]
|
119
|
+
yyyymmdd_date.description_column_indices.should == [0, 2]
|
131
120
|
end
|
132
121
|
end
|
133
122
|
|
@@ -143,36 +132,36 @@ describe Reckon::CSVParser do
|
|
143
132
|
|
144
133
|
describe "money_for" do
|
145
134
|
it "should return the appropriate fields" do
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
135
|
+
chase.money_for(1).should == -20
|
136
|
+
chase.money_for(4).should == 1558.52
|
137
|
+
chase.money_for(7).should == -116.22
|
138
|
+
some_other_bank.money_for(1).should == -20
|
139
|
+
some_other_bank.money_for(4).should == 1558.52
|
140
|
+
some_other_bank.money_for(7).should == -116.22
|
141
|
+
two_money_columns.money_for(0).should == -76
|
142
|
+
two_money_columns.money_for(1).should == 327.49
|
143
|
+
two_money_columns.money_for(2).should == -800
|
144
|
+
two_money_columns.money_for(3).should == -88.55
|
145
|
+
two_money_columns.money_for(4).should == 88.55
|
146
|
+
nationwide.money_for(0).should == 500.00
|
147
|
+
nationwide.money_for(1).should == -20.00
|
148
|
+
danish_kroner_nordea.money_for(0).should == -48.00
|
149
|
+
danish_kroner_nordea.money_for(1).should == -79.00
|
150
|
+
danish_kroner_nordea.money_for(2).should == 497.90
|
151
|
+
danish_kroner_nordea.money_for(3).should == -995.00
|
152
|
+
danish_kroner_nordea.money_for(4).should == -3452.90
|
153
|
+
danish_kroner_nordea.money_for(5).should == -655.00
|
154
|
+
yyyymmdd_date.money_for(0).should == -123.45
|
155
|
+
ing_csv.money_for(0).should == -136.13
|
156
|
+
ing_csv.money_for(1).should == 375.00
|
157
|
+
austrian_csv.money_for(0).should == -18.00
|
158
|
+
austrian_csv.money_for(2).should == 120.00
|
159
|
+
french_csv.money_for(0).should == -10.00
|
160
|
+
french_csv.money_for(1).should == -5.76
|
161
|
+
broker_canada.money_for(0).should == 12.55
|
162
|
+
broker_canada.money_for(1).should == -81.57
|
163
|
+
intuit_mint.money_for(0).should == 0.01
|
164
|
+
intuit_mint.money_for(1).should == -331.63
|
176
165
|
end
|
177
166
|
|
178
167
|
it "should handle the comma_separates_cents option correctly" do
|
@@ -200,59 +189,58 @@ describe Reckon::CSVParser do
|
|
200
189
|
|
201
190
|
describe "date_for" do
|
202
191
|
it "should return a parsed date object" do
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
192
|
+
chase.date_for(1).year.should == Time.parse("2009/12/24").year
|
193
|
+
chase.date_for(1).month.should == Time.parse("2009/12/24").month
|
194
|
+
chase.date_for(1).day.should == Time.parse("2009/12/24").day
|
195
|
+
some_other_bank.date_for(1).year.should == Time.parse("2010/12/24").year
|
196
|
+
some_other_bank.date_for(1).month.should == Time.parse("2010/12/24").month
|
197
|
+
some_other_bank.date_for(1).day.should == Time.parse("2010/12/24").day
|
198
|
+
german_date.date_for(1).year.should == Time.parse("2009/12/24").year
|
199
|
+
german_date.date_for(1).month.should == Time.parse("2009/12/24").month
|
200
|
+
german_date.date_for(1).day.should == Time.parse("2009/12/24").day
|
201
|
+
danish_kroner_nordea.date_for(0).year.should == Time.parse("2012/11/16").year
|
202
|
+
danish_kroner_nordea.date_for(0).month.should == Time.parse("2012/11/16").month
|
203
|
+
danish_kroner_nordea.date_for(0).day.should == Time.parse("2012/11/16").day
|
204
|
+
yyyymmdd_date.date_for(0).year.should == Time.parse("2012/12/31").year
|
205
|
+
yyyymmdd_date.date_for(0).month.should == Time.parse("2012/12/31").month
|
206
|
+
yyyymmdd_date.date_for(0).day.should == Time.parse("2012/12/31").day
|
207
|
+
spanish_date.date_for(1).year.should == Time.parse("2009/12/02").year
|
208
|
+
spanish_date.date_for(1).month.should == Time.parse("2009/12/02").month
|
209
|
+
spanish_date.date_for(1).day.should == Time.parse("2009/12/02").day
|
210
|
+
english_date.date_for(1).year.should == Time.parse("2009/12/24").year
|
211
|
+
english_date.date_for(1).month.should == Time.parse("2009/12/24").month
|
212
|
+
english_date.date_for(1).day.should == Time.parse("2009/12/24").day
|
213
|
+
nationwide.date_for(1).month.should == 10
|
214
|
+
ing_csv.date_for(1).month.should == Time.parse("2012/11/12").month
|
215
|
+
ing_csv.date_for(1).day.should == Time.parse("2012/11/12").day
|
216
|
+
broker_canada.date_for(5).year.should == 2014
|
217
|
+
broker_canada.date_for(5).month.should == 1
|
218
|
+
broker_canada.date_for(5).day.should == 7
|
219
|
+
intuit_mint.date_for(1).year.should == 2014
|
220
|
+
intuit_mint.date_for(1).month.should == 2
|
221
|
+
intuit_mint.date_for(1).day.should == 3
|
233
222
|
end
|
234
223
|
end
|
235
224
|
|
236
225
|
describe "description_for" do
|
237
226
|
it "should return the combined fields that are not money for date fields" do
|
238
|
-
|
239
|
-
|
227
|
+
chase.description_for(1).should == "CHECK; CHECK 2656"
|
228
|
+
chase.description_for(7).should == "CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL"
|
240
229
|
end
|
241
230
|
|
242
231
|
it "should not append empty description column" do
|
243
232
|
parser = Reckon::CSVParser.new(:string => '01/09/2015,05354 SUBWAY,8.19,,',:date_format => '%d/%m/%Y')
|
244
|
-
parser.description_column_indices.should == [1, 4]
|
245
233
|
parser.description_for(0).should == '05354 SUBWAY'
|
246
234
|
end
|
247
235
|
end
|
248
236
|
|
249
237
|
describe "pretty_money_for" do
|
250
238
|
it "work with negative and positive numbers" do
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
239
|
+
some_other_bank.pretty_money_for(1).should == "-$20.00"
|
240
|
+
some_other_bank.pretty_money_for(4).should == " $1558.52"
|
241
|
+
some_other_bank.pretty_money_for(7).should == "-$116.22"
|
242
|
+
some_other_bank.pretty_money_for(5).should == " $0.23"
|
243
|
+
some_other_bank.pretty_money_for(6).should == "-$0.96"
|
256
244
|
end
|
257
245
|
|
258
246
|
it "work with other currencies such as €" do
|
@@ -274,8 +262,15 @@ describe Reckon::CSVParser do
|
|
274
262
|
end
|
275
263
|
|
276
264
|
it "should work with merge columns" do
|
277
|
-
|
278
|
-
|
265
|
+
nationwide.pretty_money_for(0).should == " 500.00 POUND"
|
266
|
+
nationwide.pretty_money_for(1).should == "-20.00 POUND"
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
describe '85 regression test' do
|
271
|
+
it 'should detect correct date column' do
|
272
|
+
p = Reckon::CSVParser.new(file:fixture_path('85-date-example.csv'))
|
273
|
+
expect(p.date_column_index).to eq(2)
|
279
274
|
end
|
280
275
|
end
|
281
276
|
end
|
data/spec/reckon/money_spec.rb
CHANGED
@@ -8,79 +8,92 @@ require 'reckon'
|
|
8
8
|
describe Reckon::Money do
|
9
9
|
describe "from_s" do
|
10
10
|
it "should handle currency indicators" do
|
11
|
-
Reckon::Money::from_s( "$2.00" ).
|
12
|
-
Reckon::Money::from_s(
|
13
|
-
Reckon::Money::from_s(
|
11
|
+
expect(Reckon::Money::from_s( "$2.00" )).to eq(2.00)
|
12
|
+
expect(Reckon::Money::from_s("-$1025.67")).to eq(-1025.67)
|
13
|
+
expect(Reckon::Money::from_s("$-1025.67")).to eq(-1025.67)
|
14
14
|
end
|
15
15
|
|
16
16
|
it "should handle the comma_separates_cents option correctly" do
|
17
|
-
Reckon::Money::from_s(
|
18
|
-
Reckon::Money::from_s(
|
19
|
-
Reckon::Money::from_s(
|
17
|
+
expect(Reckon::Money::from_s("$2,00", :comma_separates_cents => true)).to eq(2.00)
|
18
|
+
expect(Reckon::Money::from_s("-$1025,67", :comma_separates_cents => true )).to eq(-1025.67)
|
19
|
+
expect(Reckon::Money::from_s("$-1025,67", :comma_separates_cents => true )).to eq(-1025.67)
|
20
20
|
end
|
21
21
|
|
22
22
|
it "should return 0 for an empty string" do
|
23
|
-
Reckon::Money::from_s(
|
23
|
+
expect(Reckon::Money::from_s("")).to eq(0)
|
24
24
|
end
|
25
25
|
|
26
26
|
it "should handle 1000 indicators correctly" do
|
27
|
-
Reckon::Money::from_s(
|
28
|
-
Reckon::Money::from_s(
|
27
|
+
expect(Reckon::Money::from_s("$2.000,00", :comma_separates_cents => true)).to eq(2000.00)
|
28
|
+
expect(Reckon::Money::from_s("-$1,025.67")).to eq(-1025.67)
|
29
29
|
end
|
30
30
|
|
31
31
|
it "should keep numbers together" do
|
32
|
-
Reckon::Money::from_s(
|
32
|
+
expect(Reckon::Money::from_s("1A1")).to eq(1)
|
33
33
|
end
|
34
34
|
|
35
35
|
it "should prefer numbers with precision of two" do
|
36
|
-
Reckon::Money::from_s(
|
37
|
-
Reckon::Money::from_s(
|
36
|
+
expect(Reckon::Money::from_s("1A2.00")).to eq(2)
|
37
|
+
expect(Reckon::Money::from_s("2.00A1")).to eq(2)
|
38
38
|
end
|
39
39
|
|
40
40
|
it "should handle arbitrary prefixes and postfixes" do
|
41
|
-
Reckon::Money::from_s(
|
42
|
-
Reckon::Money::from_s(
|
43
|
-
Reckon::Money::from_s(
|
41
|
+
expect(Reckon::Money::from_s("AB1.00C")).to eq(1)
|
42
|
+
expect(Reckon::Money::from_s("AB0C")).to eq(0)
|
43
|
+
expect(Reckon::Money::from_s("AB-2.00C")).to eq(-2)
|
44
44
|
end
|
45
45
|
|
46
46
|
it "should return nil if no numbers are found" do
|
47
|
-
Reckon::Money::from_s(
|
47
|
+
expect(Reckon::Money::from_s("BAC")).to be_nil()
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
51
|
describe "pretty" do
|
52
52
|
it "work with negative and positive numbers" do
|
53
|
-
Reckon::Money.new(
|
54
|
-
Reckon::Money.new(
|
53
|
+
expect(Reckon::Money.new(-20.00).pretty).to eq("-$20.00")
|
54
|
+
expect(Reckon::Money.new(1558.52).pretty).to eq(" $1558.52")
|
55
55
|
end
|
56
56
|
|
57
57
|
it "work with other currencies such as €" do
|
58
|
-
Reckon::Money.new(
|
59
|
-
Reckon::Money.new(
|
58
|
+
expect(Reckon::Money.new(-20.00, currency: "€", suffixed: false).pretty).to eq("-€20.00")
|
59
|
+
expect(Reckon::Money.new(1558.52, currency: "€", suffixed: false).pretty).to eq(" €1558.52")
|
60
60
|
end
|
61
61
|
|
62
62
|
it "work with suffixed currencies such as SEK" do
|
63
|
-
Reckon::Money.new( -20.00, :currency => "SEK", :suffixed => true ).pretty.
|
64
|
-
Reckon::Money.new( 1558.52, :currency => "SEK", :suffixed => true ).pretty.
|
63
|
+
expect(Reckon::Money.new( -20.00, :currency => "SEK", :suffixed => true ).pretty).to eq("-20.00 SEK")
|
64
|
+
expect(Reckon::Money.new( 1558.52, :currency => "SEK", :suffixed => true ).pretty).to eq(" 1558.52 SEK")
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
68
|
describe "likelihood" do
|
69
69
|
it "should return the likelihood that a string represents money" do
|
70
|
-
Reckon::Money::likelihood( "$20.00" ).
|
70
|
+
expect(Reckon::Money::likelihood( "$20.00" )).to eq(65)
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should return neutral for empty string" do
|
74
|
+
expect(Reckon::Money::likelihood("")).to eq(0)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should recognize non-us currencies" do
|
78
|
+
expect(Reckon::Money::likelihood("£480.00")).to eq(30)
|
79
|
+
expect(Reckon::Money::likelihood("£1.480,00")).to eq(30)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should not identify date columns as money' do
|
83
|
+
expect(Reckon::Money::likelihood("22.01.2014")).to eq(0)
|
71
84
|
end
|
72
85
|
end
|
73
86
|
|
74
87
|
describe "equality" do
|
75
88
|
it "should be comparable to other money" do
|
76
|
-
Reckon::Money.new(
|
77
|
-
Reckon::Money.new(
|
78
|
-
Reckon::Money.new(
|
89
|
+
expect(Reckon::Money.new(2.0)).to eq(Reckon::Money.new(2.0))
|
90
|
+
expect(Reckon::Money.new(1.0)).to be <= Reckon::Money.new(2.0)
|
91
|
+
expect(Reckon::Money.new(3.0)).to be > Reckon::Money.new(2.0)
|
79
92
|
end
|
80
93
|
it "should be comparable to other float" do
|
81
|
-
Reckon::Money.new(
|
82
|
-
Reckon::Money.new(
|
83
|
-
Reckon::Money.new(
|
94
|
+
expect(Reckon::Money.new(2.0)).to eq(2.0)
|
95
|
+
expect(Reckon::Money.new(1.0)).to be <= 2.0
|
96
|
+
expect(Reckon::Money.new(3.0)).to be > 2.0
|
84
97
|
end
|
85
98
|
end
|
86
99
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -3,7 +3,26 @@ require 'rspec'
|
|
3
3
|
require 'reckon'
|
4
4
|
|
5
5
|
RSpec.configure do |config|
|
6
|
+
config.before(:all, &:silence_output)
|
7
|
+
config.after(:all, &:enable_output)
|
6
8
|
def fixture_path(file)
|
7
9
|
File.expand_path(File.join(File.dirname(__FILE__), "data_fixtures", file))
|
8
10
|
end
|
9
11
|
end
|
12
|
+
|
13
|
+
public
|
14
|
+
|
15
|
+
# Redirects stderr and stout to /dev/null.txt
|
16
|
+
def silence_output
|
17
|
+
# Store the original stderr and stdout in order to restore them later
|
18
|
+
@original_stdout = $stdout
|
19
|
+
|
20
|
+
# Redirect stderr and stdout
|
21
|
+
$stdout = File.new(File.join(File.dirname(__FILE__), 'test_log.txt'), 'w')
|
22
|
+
end
|
23
|
+
|
24
|
+
# Replace stderr and stdout so anything else is output correctly
|
25
|
+
def enable_output
|
26
|
+
$stdout = @original_stdout
|
27
|
+
@original_stdout = nil
|
28
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reckon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.2
|
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: 2020-
|
13
|
+
date: 2020-03-07 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rspec
|
@@ -140,9 +140,12 @@ files:
|
|
140
140
|
- lib/reckon/money.rb
|
141
141
|
- lib/reckon/version.rb
|
142
142
|
- reckon.gemspec
|
143
|
+
- spec/data_fixtures/51-sample.csv
|
144
|
+
- spec/data_fixtures/51-tokens.yml
|
143
145
|
- spec/data_fixtures/73-sample.csv
|
144
146
|
- spec/data_fixtures/73-tokens.yml
|
145
147
|
- spec/data_fixtures/73-transactions.ledger
|
148
|
+
- spec/data_fixtures/85-date-example.csv
|
146
149
|
- spec/data_fixtures/austrian_example.csv
|
147
150
|
- spec/data_fixtures/bom_utf8_file.csv
|
148
151
|
- spec/data_fixtures/broker_canada_example.csv
|
@@ -193,8 +196,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
193
196
|
- !ruby/object:Gem::Version
|
194
197
|
version: '0'
|
195
198
|
requirements: []
|
196
|
-
|
197
|
-
rubygems_version: 2.7.6.2
|
199
|
+
rubygems_version: 3.0.6
|
198
200
|
signing_key:
|
199
201
|
specification_version: 4
|
200
202
|
summary: Utility for interactively converting and labeling CSV files for the Ledger
|