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