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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b58a745c730c0dfaf022a98c19dfaf8568c0b3091548d70c9202f8f7c0d78ada
4
- data.tar.gz: 030d2036524a19c5eff981608133e8aac15f185b57a891e91a6f1fdabd3796c7
3
+ metadata.gz: a7e9512d0b15c14548a04e80f5dd3a87f50ae9ff2654827b5970def94e174667
4
+ data.tar.gz: f858c96b4b5f2a7b2c85845803109e566281c0fc945c46ba39758121701d0e9a
5
5
  SHA512:
6
- metadata.gz: 1daf76a19078f1707e847a7ac54aad5836ee3d49ce9c2f612bfefeb86f8c1bf9c9d1f97b7bbecb309f9f44f8097f8f2dbe0d6b6062bc376a62fc88f45bab1dfc
7
- data.tar.gz: 4784ac871dbdb6160dc53ef8a7f5d69f31ccc7f7f7ea876b97bf60941abb2d13a968f5229747dd74d0b574ecc12326485e1eaba7ea368a91162d36d3ef4f9f86
6
+ metadata.gz: 15526cfe3504d50859de7b652285b31f639b11daecd8536732b97e1f84d03814cb8040c8db830386f803de7836e11712b7069758a77bf290fab26699436440f2
7
+ data.tar.gz: 99ae732793adeaa40f5c7235e690dfb80d839a31ecba52f989e0dea2d339cf012623f858c926261e4bd5eb731e2a3ff508370509d03560c64c2bd7cf4a737a7e
data/.gitignore CHANGED
@@ -25,3 +25,6 @@ pkg
25
25
  .idea
26
26
  reckon_local
27
27
  private_tests
28
+
29
+ ## Bundler
30
+ vendor
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- reckon (0.5.0)
4
+ reckon (0.5.1)
5
5
  chronic (>= 0.3.0)
6
6
  highline (>= 1.5.2)
7
7
  rchardet (>= 1.8.0)
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
- (sudo) gem install reckon
11
+ gem install --user reckon
12
12
 
13
13
  ## Example Usage
14
14
 
@@ -10,12 +10,13 @@ require 'terminal-table'
10
10
  require 'time'
11
11
  require 'logger'
12
12
 
13
- LOGGER = Logger.new(STDOUT)
14
- LOGGER.level = Logger::ERROR
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
- require File.expand_path(File.join(File.dirname(__FILE__), "reckon", "app"))
19
- require File.expand_path(File.join(File.dirname(__FILE__), "reckon", "ledger_parser"))
20
- require File.expand_path(File.join(File.dirname(__FILE__), "reckon", "csv_parser"))
21
- require File.expand_path(File.join(File.dirname(__FILE__), "reckon", "money"))
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'
@@ -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.sort { |a, b| a[:date] <=> b[:date] }.each do |row|
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 )
@@ -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( index ).pretty( negate )
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( index )
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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Reckon
2
+ VERSION = "0.5.1"
3
+ end
@@ -1,5 +1,5 @@
1
1
  $:.push File.expand_path("../lib", __FILE__)
2
- require 'reckon/version'
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")
@@ -0,0 +1,3 @@
1
+ "Date","Note","Amount"
2
+ "2012/3/22","DEPOSIT","50.00"
3
+ "2012/3/23","TRANSFER TO SAVINGS","-10.00"
@@ -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( ["13/12/2013"] ).for( 0 ).should ==
24
- Time.new( 2013, 12, 13, 12 )
25
- Reckon::DateColumn.new( ["01/14/2013"] ).for( 0 ).should ==
26
- Time.new( 2013, 01, 14, 12 )
27
- Reckon::DateColumn.new( ["13/12/2013", "21/11/2013"] ).for( 1 ).should ==
28
- Time.new( 2013, 11, 21, 12 )
29
- Reckon::DateColumn.new( ["2013-11-21"] ).for( 0 ).should ==
30
- Time.new( 2013, 11, 21, 12 )
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( ["01/02/2013", "01/14/2013"] ).for(0).should ==
36
- Time.new( 2013, 01, 02, 12 )
37
- Reckon::DateColumn.new( ["01/02/2013", "14/01/2013"] ).for(0).should ==
38
- Time.new( 2013, 02, 01, 12 )
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.0
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-19 00:00:00.000000000 Z
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: []