reckon 0.5.0 → 0.5.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.
- 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: []
|