reckon 0.5.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/lib/reckon.rb +7 -6
- data/lib/reckon/app.rb +5 -3
- data/lib/reckon/csv_parser.rb +9 -2
- data/lib/reckon/date_column.rb +60 -0
- data/lib/reckon/money.rb +0 -56
- data/lib/reckon/version.rb +3 -0
- data/reckon.gemspec +2 -1
- data/spec/data_fixtures/test_money_column.csv +3 -0
- data/spec/reckon/app_spec.rb +14 -0
- data/spec/reckon/date_column_spec.rb +12 -13
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a7e9512d0b15c14548a04e80f5dd3a87f50ae9ff2654827b5970def94e174667
|
4
|
+
data.tar.gz: f858c96b4b5f2a7b2c85845803109e566281c0fc945c46ba39758121701d0e9a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 15526cfe3504d50859de7b652285b31f639b11daecd8536732b97e1f84d03814cb8040c8db830386f803de7836e11712b7069758a77bf290fab26699436440f2
|
7
|
+
data.tar.gz: 99ae732793adeaa40f5c7235e690dfb80d839a31ecba52f989e0dea2d339cf012623f858c926261e4bd5eb731e2a3ff508370509d03560c64c2bd7cf4a737a7e
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -8,7 +8,7 @@ Reckon automagically converts CSV files for use with the command-line accounting
|
|
8
8
|
|
9
9
|
Assuming you have Ruby and [Rubygems](http://rubygems.org/pages/download) installed on your system, simply run
|
10
10
|
|
11
|
-
|
11
|
+
gem install --user reckon
|
12
12
|
|
13
13
|
## Example Usage
|
14
14
|
|
data/lib/reckon.rb
CHANGED
@@ -10,12 +10,13 @@ require 'terminal-table'
|
|
10
10
|
require 'time'
|
11
11
|
require 'logger'
|
12
12
|
|
13
|
-
LOGGER = Logger.new(
|
14
|
-
LOGGER.level = Logger::
|
13
|
+
LOGGER = Logger.new(STDERR)
|
14
|
+
LOGGER.level = Logger::WARN
|
15
15
|
|
16
16
|
require_relative 'reckon/version'
|
17
17
|
require_relative 'reckon/cosine_similarity'
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
require_relative 'reckon/date_column'
|
19
|
+
require_relative 'reckon/money'
|
20
|
+
require_relative 'reckon/ledger_parser'
|
21
|
+
require_relative 'reckon/csv_parser'
|
22
|
+
require_relative 'reckon/app'
|
data/lib/reckon/app.rb
CHANGED
@@ -156,6 +156,10 @@ module Reckon
|
|
156
156
|
def each_row_backwards
|
157
157
|
rows = []
|
158
158
|
(0...@csv_parser.columns.first.length).to_a.each do |index|
|
159
|
+
if @csv_parser.date_for(index).nil?
|
160
|
+
LOGGER.warn("Skipping row: '#{@csv_parser.row(index)}' that doesn't have a valid date")
|
161
|
+
next
|
162
|
+
end
|
159
163
|
rows << { :date => @csv_parser.date_for(index),
|
160
164
|
:pretty_date => @csv_parser.pretty_date_for(index),
|
161
165
|
:pretty_money => @csv_parser.pretty_money_for(index),
|
@@ -163,9 +167,7 @@ module Reckon
|
|
163
167
|
:money => @csv_parser.money_for(index),
|
164
168
|
:description => @csv_parser.description_for(index) }
|
165
169
|
end
|
166
|
-
rows.
|
167
|
-
yield row
|
168
|
-
end
|
170
|
+
rows.sort_by { |n| n[:date] }.each {|row| yield row }
|
169
171
|
end
|
170
172
|
|
171
173
|
def most_specific_regexp_match( row )
|
data/lib/reckon/csv_parser.rb
CHANGED
@@ -12,6 +12,10 @@ module Reckon
|
|
12
12
|
detect_columns
|
13
13
|
end
|
14
14
|
|
15
|
+
def row(index)
|
16
|
+
csv_data[index].join(", ")
|
17
|
+
end
|
18
|
+
|
15
19
|
def filter_csv
|
16
20
|
if options[:ignore_columns]
|
17
21
|
new_columns = []
|
@@ -27,7 +31,10 @@ module Reckon
|
|
27
31
|
end
|
28
32
|
|
29
33
|
def pretty_money_for(index, negate = false)
|
30
|
-
money_for(
|
34
|
+
money = money_for(index)
|
35
|
+
return 0 if money.nil?
|
36
|
+
|
37
|
+
money.pretty(negate)
|
31
38
|
end
|
32
39
|
|
33
40
|
def pretty_money(amount, negate = false)
|
@@ -35,7 +42,7 @@ module Reckon
|
|
35
42
|
end
|
36
43
|
|
37
44
|
def date_for(index)
|
38
|
-
@date_column.for(
|
45
|
+
@date_column.for(index)
|
39
46
|
end
|
40
47
|
|
41
48
|
def pretty_date_for(index)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Reckon
|
2
|
+
class DateColumn < Array
|
3
|
+
attr_accessor :endian_precedence
|
4
|
+
def initialize( arr = [], options = {} )
|
5
|
+
arr.each do |value|
|
6
|
+
if options[:date_format]
|
7
|
+
begin
|
8
|
+
value = Date.strptime(value, options[:date_format])
|
9
|
+
rescue
|
10
|
+
puts "I'm having trouble parsing #{value} with the desired format: #{options[:date_format]}"
|
11
|
+
exit 1
|
12
|
+
end
|
13
|
+
else
|
14
|
+
value = [$1, $2, $3].join("/") if value =~ /^(\d{4})(\d{2})(\d{2})\d+\[\d+\:GMT\]$/ # chase format
|
15
|
+
value = [$3, $2, $1].join("/") if value =~ /^(\d{2})\.(\d{2})\.(\d{4})$/ # german format
|
16
|
+
value = [$3, $2, $1].join("/") if value =~ /^(\d{2})\-(\d{2})\-(\d{4})$/ # nordea format
|
17
|
+
value = [$1, $2, $3].join("/") if value =~ /^(\d{4})\-(\d{2})\-(\d{2})$/ # yyyy-mm-dd format
|
18
|
+
value = [$1, $2, $3].join("/") if value =~ /^(\d{4})(\d{2})(\d{2})/ # yyyymmdd format
|
19
|
+
|
20
|
+
|
21
|
+
unless @endian_precedence # Try to detect endian_precedence
|
22
|
+
reg_match = value.match( /^(\d\d)\/(\d\d)\/\d\d\d?\d?/ )
|
23
|
+
# If first one is not \d\d/\d\d/\d\d\d?\d set it to default
|
24
|
+
if !reg_match
|
25
|
+
@endian_precedence = [:middle, :little]
|
26
|
+
elsif reg_match[1].to_i > 12
|
27
|
+
@endian_precedence = [:little]
|
28
|
+
elsif reg_match[2].to_i > 12
|
29
|
+
@endian_precedence = [:middle]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
self.push( value )
|
34
|
+
end
|
35
|
+
# if endian_precedence still nil, raise error
|
36
|
+
unless @endian_precedence || options[:date_format]
|
37
|
+
raise( "Unable to determine date format. Please specify using --date-format" )
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def for( index )
|
42
|
+
value = self.at( index )
|
43
|
+
guess = Chronic.parse(value, :context => :past,
|
44
|
+
:endian_precedence => @endian_precedence )
|
45
|
+
if guess.to_i < 953236800 && value =~ /\//
|
46
|
+
guess = Chronic.parse((value.split("/")[0...-1] + [(2000 + value.split("/").last.to_i).to_s]).join("/"), :context => :past,
|
47
|
+
:endian_precedence => @endian_precedence)
|
48
|
+
end
|
49
|
+
guess && guess.to_date
|
50
|
+
end
|
51
|
+
|
52
|
+
def pretty_for(index)
|
53
|
+
date = self.for(index)
|
54
|
+
return "" if date.nil?
|
55
|
+
|
56
|
+
date.iso8601
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
data/lib/reckon/money.rb
CHANGED
@@ -112,60 +112,4 @@ module Reckon
|
|
112
112
|
self
|
113
113
|
end
|
114
114
|
end
|
115
|
-
|
116
|
-
class DateColumn < Array
|
117
|
-
attr_accessor :endian_precedence
|
118
|
-
def initialize( arr = [], options = {} )
|
119
|
-
arr.each do |value|
|
120
|
-
if options[:date_format]
|
121
|
-
begin
|
122
|
-
value = Date.strptime(value, options[:date_format])
|
123
|
-
rescue
|
124
|
-
puts "I'm having trouble parsing #{value} with the desired format: #{options[:date_format]}"
|
125
|
-
exit 1
|
126
|
-
end
|
127
|
-
else
|
128
|
-
value = [$1, $2, $3].join("/") if value =~ /^(\d{4})(\d{2})(\d{2})\d+\[\d+\:GMT\]$/ # chase format
|
129
|
-
value = [$3, $2, $1].join("/") if value =~ /^(\d{2})\.(\d{2})\.(\d{4})$/ # german format
|
130
|
-
value = [$3, $2, $1].join("/") if value =~ /^(\d{2})\-(\d{2})\-(\d{4})$/ # nordea format
|
131
|
-
value = [$1, $2, $3].join("/") if value =~ /^(\d{4})\-(\d{2})\-(\d{2})$/ # yyyy-mm-dd format
|
132
|
-
value = [$1, $2, $3].join("/") if value =~ /^(\d{4})(\d{2})(\d{2})/ # yyyymmdd format
|
133
|
-
|
134
|
-
|
135
|
-
unless @endian_precedence # Try to detect endian_precedence
|
136
|
-
reg_match = value.match( /^(\d\d)\/(\d\d)\/\d\d\d?\d?/ )
|
137
|
-
# If first one is not \d\d/\d\d/\d\d\d?\d set it to default
|
138
|
-
if !reg_match
|
139
|
-
@endian_precedence = [:middle, :little]
|
140
|
-
elsif reg_match[1].to_i > 12
|
141
|
-
@endian_precedence = [:little]
|
142
|
-
elsif reg_match[2].to_i > 12
|
143
|
-
@endian_precedence = [:middle]
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
self.push( value )
|
148
|
-
end
|
149
|
-
# if endian_precedence still nil, raise error
|
150
|
-
unless @endian_precedence || options[:date_format]
|
151
|
-
raise( "Unable to determine date format. Please specify using --date-format" )
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
def for( index )
|
156
|
-
value = self.at( index )
|
157
|
-
guess = Chronic.parse(value, :context => :past,
|
158
|
-
:endian_precedence => @endian_precedence )
|
159
|
-
if guess.to_i < 953236800 && value =~ /\//
|
160
|
-
guess = Chronic.parse((value.split("/")[0...-1] + [(2000 + value.split("/").last.to_i).to_s]).join("/"), :context => :past,
|
161
|
-
:endian_precedence => @endian_precedence)
|
162
|
-
end
|
163
|
-
guess
|
164
|
-
end
|
165
|
-
|
166
|
-
def pretty_for(index)
|
167
|
-
self.for(index).to_date.iso8601
|
168
|
-
end
|
169
|
-
|
170
|
-
end
|
171
115
|
end
|
data/reckon.gemspec
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
$:.push File.expand_path("../lib", __FILE__)
|
2
|
-
|
2
|
+
require_relative 'lib/reckon/version'
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = %q{reckon}
|
@@ -9,6 +9,7 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.homepage = %q{https://github.com/cantino/reckon}
|
10
10
|
s.description = %q{Reckon automagically converts CSV files for use with the command-line accounting tool Ledger. It also helps you to select the correct accounts associated with the CSV data using Bayesian machine learning.}
|
11
11
|
s.summary = %q{Utility for interactively converting and labeling CSV files for the Ledger accounting tool.}
|
12
|
+
s.licenses = ['MIT']
|
12
13
|
|
13
14
|
s.files = `git ls-files`.split("\n")
|
14
15
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
data/spec/reckon/app_spec.rb
CHANGED
@@ -107,6 +107,20 @@ describe Reckon::App do
|
|
107
107
|
end
|
108
108
|
end
|
109
109
|
|
110
|
+
context "Issue #64 - regression test" do
|
111
|
+
it 'should work for simple file' do
|
112
|
+
rows = []
|
113
|
+
app = Reckon::App.new(file: fixture_path('test_money_column.csv'))
|
114
|
+
expect { app.each_row_backwards { |n| rows << n } }
|
115
|
+
.to output(/Skipping row: 'Date, Note, Amount'/).to_stderr_from_any_process
|
116
|
+
expect(rows.length).to eq(2)
|
117
|
+
expect(rows[0][:pretty_date]).to eq('2012-03-22')
|
118
|
+
expect(rows[0][:pretty_money]).to eq(' $50.00')
|
119
|
+
expect(rows[1][:pretty_date]).to eq('2012-03-23')
|
120
|
+
expect(rows[1][:pretty_money]).to eq('-$10.00')
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
110
124
|
#DATA
|
111
125
|
BANK_CSV = (<<-CSV).strip
|
112
126
|
DEBIT,20091224120000[0:GMT],"HOST 037196321563 MO 12/22SLICEHOST",-85.00
|
@@ -20,23 +20,22 @@ describe Reckon::DateColumn do
|
|
20
20
|
end
|
21
21
|
describe "for" do
|
22
22
|
it "should detect the date" do
|
23
|
-
Reckon::DateColumn.new(
|
24
|
-
|
25
|
-
Reckon::DateColumn.new(
|
26
|
-
|
27
|
-
Reckon::DateColumn.new(
|
28
|
-
|
29
|
-
Reckon::DateColumn.new( ["2013-11-21"] ).for( 0 )
|
30
|
-
|
23
|
+
expect(Reckon::DateColumn.new(%w[13/12/2013]).for(0))
|
24
|
+
.to eq(Date.new(2013, 12, 13))
|
25
|
+
expect(Reckon::DateColumn.new(%w[01/14/2013]).for(0))
|
26
|
+
.to eq(Date.new(2013, 1, 14))
|
27
|
+
expect(Reckon::DateColumn.new(%w[13/12/2013 21/11/2013]).for(1))
|
28
|
+
.to eq(Date.new(2013, 11, 21))
|
29
|
+
expect(Reckon::DateColumn.new( ["2013-11-21"] ).for( 0 ))
|
30
|
+
.to eq(Date.new(2013, 11, 21))
|
31
31
|
|
32
32
|
end
|
33
33
|
|
34
34
|
it "should correctly use endian_precedence" do
|
35
|
-
Reckon::DateColumn.new(
|
36
|
-
|
37
|
-
Reckon::DateColumn.new(
|
38
|
-
|
35
|
+
expect(Reckon::DateColumn.new(%w[01/02/2013 01/14/2013]).for(0))
|
36
|
+
.to eq(Date.new(2013, 1, 2))
|
37
|
+
expect(Reckon::DateColumn.new(%w[01/02/2013 14/01/2013]).for(0))
|
38
|
+
.to eq(Date.new(2013, 2, 1))
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
42
|
-
|
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.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Cantino
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2020-02-
|
13
|
+
date: 2020-02-25 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rspec
|
@@ -135,8 +135,10 @@ files:
|
|
135
135
|
- lib/reckon/app.rb
|
136
136
|
- lib/reckon/cosine_similarity.rb
|
137
137
|
- lib/reckon/csv_parser.rb
|
138
|
+
- lib/reckon/date_column.rb
|
138
139
|
- lib/reckon/ledger_parser.rb
|
139
140
|
- lib/reckon/money.rb
|
141
|
+
- lib/reckon/version.rb
|
140
142
|
- reckon.gemspec
|
141
143
|
- spec/data_fixtures/73-sample.csv
|
142
144
|
- spec/data_fixtures/73-tokens.yml
|
@@ -160,6 +162,7 @@ files:
|
|
160
162
|
- spec/data_fixtures/some_other.csv
|
161
163
|
- spec/data_fixtures/spanish_date_example.csv
|
162
164
|
- spec/data_fixtures/suntrust.csv
|
165
|
+
- spec/data_fixtures/test_money_column.csv
|
163
166
|
- spec/data_fixtures/tokens.yaml
|
164
167
|
- spec/data_fixtures/two_money_columns.csv
|
165
168
|
- spec/data_fixtures/yyyymmdd_date_example.csv
|
@@ -172,7 +175,8 @@ files:
|
|
172
175
|
- spec/spec.opts
|
173
176
|
- spec/spec_helper.rb
|
174
177
|
homepage: https://github.com/cantino/reckon
|
175
|
-
licenses:
|
178
|
+
licenses:
|
179
|
+
- MIT
|
176
180
|
metadata: {}
|
177
181
|
post_install_message:
|
178
182
|
rdoc_options: []
|