reckon 0.3.10 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 17af5e887336c4c5b8a010ea22ad169c3df1efdd
4
- data.tar.gz: 7b57480844bc08468e254b8b4633ff78e25a020e
3
+ metadata.gz: 701e85b9ec558657003c57d9d33744840fcdcb55
4
+ data.tar.gz: d343b374bf5375ec34a915a84b7a27724b53b995
5
5
  SHA512:
6
- metadata.gz: 93ac7ab7c77cd1b411efc86fd7b373c644301d26dc468751a11ab536f4389c2b212437354e7f105ced0705943ecf196a692f5453345c4ddc4e1771376e49d134
7
- data.tar.gz: e7fa2a91210cf228cb8b4f96789212f380e9723119c5d25d58a1b3115cde6b1a63cbe7f09adce4104e4a8b5a8480f8695cbe1702fcf3aa392e18dd8cdadabe46
6
+ metadata.gz: 8489408851bf979388afb2f0ed8da24d30d738b82c83116f1551d8d1f43aebf17f4aeb9ad05a835323ca73199568b74265a556bb25e05c5f4f4585508d4aa863
7
+ data.tar.gz: 1a6845f390988e12286716351029a015165fa5dc8230aa1d0021616aa4a4e108a8fea64b0249176698a0c8edf8b4e4b4991517028e6a156a2dfecfd04fe067a3
@@ -1 +1 @@
1
- ruby-2.0.0-p353
1
+ ruby-2.2.0
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- reckon (0.3.10)
4
+ reckon (0.4.0)
5
5
  chronic (>= 0.3.0)
6
6
  fastercsv (>= 1.5.1)
7
7
  highline (>= 1.5.2)
data/README.md CHANGED
@@ -15,25 +15,26 @@ Assuming you have Ruby and [Rubygems](http://rubygems.org/pages/download) instal
15
15
  First, login to your bank and export your transaction data as a CSV file.
16
16
 
17
17
  To see how the CSV parses:
18
-
18
+
19
19
  reckon -f bank.csv -p
20
20
 
21
21
  If your CSV file has a header on the first line, include `--contains-header`.
22
22
 
23
23
  To convert to ledger format and label everything, do:
24
-
24
+
25
25
  reckon -f bank.csv -o output.dat
26
26
 
27
27
  To have reckon learn from an existing ledger file, provide it with -l:
28
-
28
+
29
29
  reckon -f bank.csv -l 2010.dat -o output.dat
30
30
 
31
31
  Learn more:
32
32
 
33
33
  > reckon -h
34
-
34
+
35
35
  Usage: Reckon.rb [options]
36
36
 
37
+
37
38
  -f, --file FILE The CSV file to parse
38
39
  -a, --account name The Ledger Account this file is for
39
40
  -v, --[no-]verbose Run verbosely
@@ -43,17 +44,23 @@ Learn more:
43
44
  -l, --learn-from FILE An existing ledger file to learn accounts from
44
45
  --ignore-columns 1,2,5
45
46
  Columns to ignore in the CSV file - the first column is column 1
46
- --contains-header
47
- The first row of the CSV is a header and should be skipped
47
+ --contains-header [N]
48
+ The first row of the CSV is a header and should be skipped. Optionally add the number of rows to skip.
48
49
  --csv-separator ','
49
50
  Separator for parsing the CSV - default is comma.
50
51
  --comma-separates-cents
51
52
  Use comma instead of period to deliminate dollars from cents when parsing ($100,50 instead of $100.50)
52
- --encoding
53
- Specify an encoding for the CSV file
53
+ --encoding 'UTF-8'
54
+ Specify an encoding for the CSV file; not usually needed
54
55
  -c, --currency '$' Currency symbol to use, defaults to $ (£, EUR)
55
56
  --date-format '%d/%m/%Y'
56
57
  Force the date format (see Ruby DateTime strftime)
58
+ -u, --unattended Don't ask questions and guess all the accounts automatically. Used with --learn-from or --account-tokens options.
59
+ -t, --account-tokens FILE YAML file with manually-assigned tokens for each account (see README)
60
+ --default-into-account name
61
+ Default into account
62
+ --default-outof-account name
63
+ Default 'out of' account
57
64
  --suffixed
58
65
  If --currency should be used as a suffix. Defaults to false.
59
66
  -h, --help Show this message
@@ -61,6 +68,35 @@ Learn more:
61
68
 
62
69
  If you find CSV files that it can't parse, send me examples or pull requests!
63
70
 
71
+ ## Unattended mode
72
+
73
+ You can run reckon in a non-interactive mode.
74
+ To guess the accounts reckon can use an existing ledger file or a token file with keywords.
75
+
76
+ `reckon --unattended -l 2010.dat -f bank.csv -o ledger.dat`
77
+
78
+ `reckon --unattended --account-tokens tokens.yaml -f bank.csv -o ledger.dat`
79
+
80
+ Here's an example of `tokens.yaml`:
81
+
82
+ ```
83
+ Income:
84
+ Salary:
85
+ - 'LÖN'
86
+ - 'Salary'
87
+ Expenses:
88
+ Bank:
89
+ - 'Comission'
90
+ - 'MasterCard'
91
+ Rent:
92
+ - '0011223344' # Landlord bank number
93
+ '[Internal:Transfer]': # Virtual account
94
+ - '4433221100' # Your own account number
95
+ ```
96
+
97
+ If reckon can not guess the accounts it will use `Income:Unknown` or `Expenses:Unknown` names.
98
+ You can override them with `--default_outof_account` and `--default_into_account` options.
99
+
64
100
  ## Note on Patches/Pull Requests
65
101
 
66
102
  * Fork the project.
@@ -1,5 +1,6 @@
1
1
  #coding: utf-8
2
2
  require 'pp'
3
+ require 'yaml'
3
4
 
4
5
  module Reckon
5
6
  class App
@@ -17,6 +18,11 @@ module Reckon
17
18
  learn!
18
19
  end
19
20
 
21
+ def interactive_output(str)
22
+ return if options[:unattended]
23
+ puts str
24
+ end
25
+
20
26
  def learn_from(ledger)
21
27
  LedgerParser.new(ledger).entries.each do |entry|
22
28
  entry[:accounts].each do |account|
@@ -32,12 +38,26 @@ module Reckon
32
38
  seen[row[:pretty_date]] && seen[row[:pretty_date]][row[:pretty_money]]
33
39
  end
34
40
 
41
+ def extract_account_tokens(subtree, account = nil)
42
+ if subtree.is_a?(Array)
43
+ { account => subtree }
44
+ else
45
+ at = subtree.map { |k, v| extract_account_tokens(v, [account, k].compact.join(':')) }
46
+ at.inject({}) { |k, v| k = k.merge(v)}
47
+ end
48
+ end
49
+
35
50
  def learn!
36
- if options[:existing_ledger_file]
37
- fail "#{options[:existing_ledger_file]} doesn't exist!" unless File.exists?(options[:existing_ledger_file])
38
- ledger_data = File.read(options[:existing_ledger_file])
39
- learn_from(ledger_data)
51
+ if options[:account_tokens_file]
52
+ fail "#{options[:account_tokens_file]} doesn't exist!" unless File.exists?(options[:account_tokens_file])
53
+ extract_account_tokens(YAML.load_file(options[:account_tokens_file])).each do |account, tokens|
54
+ tokens.each { |t| learn_about_account(account, t) }
55
+ end
40
56
  end
57
+ return unless options[:existing_ledger_file]
58
+ fail "#{options[:existing_ledger_file]} doesn't exist!" unless File.exists?(options[:existing_ledger_file])
59
+ ledger_data = File.read(options[:existing_ledger_file])
60
+ learn_from(ledger_data)
41
61
  end
42
62
 
43
63
  def learn_about_account(account, data)
@@ -57,23 +77,34 @@ module Reckon
57
77
  def walk_backwards
58
78
  seen_anything_new = false
59
79
  each_row_backwards do |row|
60
- puts Terminal::Table.new(:rows => [ [ row[:pretty_date], row[:pretty_money], row[:description] ] ])
80
+ interactive_output Terminal::Table.new(:rows => [ [ row[:pretty_date], row[:pretty_money], row[:description] ] ])
61
81
 
62
82
  if already_seen?(row)
63
- puts "NOTE: This row is very similar to a previous one!"
83
+ interactive_output "NOTE: This row is very similar to a previous one!"
64
84
  if !seen_anything_new
65
- puts "Skipping..."
85
+ interactive_output "Skipping..."
66
86
  next
67
87
  end
68
88
  else
69
89
  seen_anything_new = true
70
90
  end
71
91
 
92
+ possible_answers = weighted_account_match( row ).map! { |a| a[:account] }
93
+
72
94
  ledger = if row[:money] > 0
73
- out_of_account = ask("Which account provided this income? ([account]/[q]uit/[s]kip) ") { |q| q.default = guess_account(row) }
95
+ if options[:unattended]
96
+ out_of_account = possible_answers.first || options[:default_outof_account] || 'Income:Unknown'
97
+ else
98
+ out_of_account = ask("Which account provided this income? ([account]/[q]uit/[s]kip) ") { |q|
99
+ q.completion = possible_answers
100
+ q.readline = true
101
+ q.default = possible_answers.first
102
+ }
103
+ end
104
+
74
105
  finish if out_of_account == "quit" || out_of_account == "q"
75
106
  if out_of_account == "skip" || out_of_account == "s"
76
- puts "Skipping"
107
+ interactive_output "Skipping"
77
108
  next
78
109
  end
79
110
 
@@ -81,10 +112,18 @@ module Reckon
81
112
  [options[:bank_account], row[:pretty_money]],
82
113
  [out_of_account, row[:pretty_money_negated]] )
83
114
  else
84
- into_account = ask("To which account did this money go? ([account]/[q]uit/[s]kip) ") { |q| q.default = guess_account(row) }
115
+ if options[:unattended]
116
+ into_account = possible_answers.first || options[:default_into_account] || 'Expenses:Unknown'
117
+ else
118
+ into_account = ask("To which account did this money go? ([account]/[q]uit/[s]kip) ") { |q|
119
+ q.completion = possible_answers
120
+ q.readline = true
121
+ q.default = possible_answers.first
122
+ }
123
+ end
85
124
  finish if into_account == "quit" || into_account == 'q'
86
125
  if into_account == "skip" || into_account == 's'
87
- puts "Skipping"
126
+ interactive_output "Skipping"
88
127
  next
89
128
  end
90
129
 
@@ -93,14 +132,14 @@ module Reckon
93
132
  [options[:bank_account], row[:pretty_money]] )
94
133
  end
95
134
 
96
- learn_from(ledger)
135
+ learn_from(ledger) unless options[:account_tokens_file]
97
136
  output(ledger)
98
137
  end
99
138
  end
100
139
 
101
140
  def finish
102
141
  options[:output_file].close unless options[:output_file] == STDOUT
103
- puts "Exiting."
142
+ interactive_output "Exiting."
104
143
  exit
105
144
  end
106
145
 
@@ -109,7 +148,8 @@ module Reckon
109
148
  options[:output_file].flush
110
149
  end
111
150
 
112
- def guess_account(row)
151
+ # Weigh accounts by how well they match the row
152
+ def weighted_account_match( row )
113
153
  query_tokens = tokenize(row[:description])
114
154
 
115
155
  search_vector = []
@@ -133,9 +173,16 @@ module Reckon
133
173
  { :cosine => (0...account_vector.length).to_a.inject(0) { |m, i| m + search_vector[i] * account_vector[i] },
134
174
  :account => account }
135
175
  end
136
-
137
176
  account_vectors.sort! {|a, b| b[:cosine] <=> a[:cosine] }
138
- account_vectors.first && account_vectors.first[:account]
177
+
178
+ # Return empty set if no accounts matched so that we can fallback to the defaults in the unattended mode
179
+ if options[:unattended]
180
+ if account_vectors.first && account_vectors.first[:account]
181
+ account_vectors = [] if account_vectors.first[:cosine] == 0
182
+ end
183
+ end
184
+
185
+ return account_vectors
139
186
  end
140
187
 
141
188
  def ledger_format(row, line1, line2)
@@ -152,15 +199,15 @@ module Reckon
152
199
  t << [ row[:pretty_date], row[:pretty_money], row[:description] ]
153
200
  end
154
201
  end
155
- puts output
202
+ interactive_output output
156
203
  end
157
204
 
158
205
  def each_row_backwards
159
206
  rows = []
160
207
  (0...@csv_parser.columns.first.length).to_a.each do |index|
161
- rows << { :date => @csv_parser.date_for(index),
208
+ rows << { :date => @csv_parser.date_for(index),
162
209
  :pretty_date => @csv_parser.pretty_date_for(index),
163
- :pretty_money => @csv_parser.pretty_money_for(index),
210
+ :pretty_money => @csv_parser.pretty_money_for(index),
164
211
  :pretty_money_negated => @csv_parser.pretty_money_for(index, :negate),
165
212
  :money => @csv_parser.money_for(index),
166
213
  :description => @csv_parser.description_for(index) }
@@ -221,7 +268,7 @@ module Reckon
221
268
  options[:comma_separates_cents] = c
222
269
  end
223
270
 
224
- opts.on("", "--encoding e", "Specify an encoding for the CSV file") do |e|
271
+ opts.on("", "--encoding 'UTF-8'", "Specify an encoding for the CSV file; not usually needed") do |e|
225
272
  options[:encoding] = e
226
273
  end
227
274
 
@@ -233,6 +280,22 @@ module Reckon
233
280
  options[:date_format] = d
234
281
  end
235
282
 
283
+ opts.on("-u", "--unattended", "Don't ask questions and guess all the accounts automatically. Used with --learn-from or --account-tokens options.") do |n|
284
+ options[:unattended] = n
285
+ end
286
+
287
+ opts.on("-t", "--account-tokens FILE", "YAML file with manually-assigned tokens for each account (see README)") do |a|
288
+ options[:account_tokens_file] = a
289
+ end
290
+
291
+ opts.on("", "--default-into-account name", "Default into account") do |a|
292
+ options[:default_into_account] = a
293
+ end
294
+
295
+ opts.on("", "--default-outof-account name", "Default 'out of' account") do |a|
296
+ options[:default_outof_account] = a
297
+ end
298
+
236
299
  opts.on("", "--suffixed", "If --currency should be used as a suffix. Defaults to false.") do |e|
237
300
  options[:suffixed] = e
238
301
  end
@@ -260,7 +323,11 @@ module Reckon
260
323
  end
261
324
 
262
325
  unless options[:bank_account]
326
+
327
+ fail "Please specify --account for the unattended mode" if options[:unattended]
328
+
263
329
  options[:bank_account] = ask("What is the account name of this bank account in Ledger? ") do |q|
330
+ q.readline = true
264
331
  q.validate = /^.{2,}$/
265
332
  q.default = "Assets:Bank:Checking"
266
333
  end
@@ -2,7 +2,7 @@
2
2
  require 'pp'
3
3
 
4
4
  module Reckon
5
- class CSVParser
5
+ class CSVParser
6
6
  attr_accessor :options, :csv_data, :money_column_indices, :date_column_index, :description_column_indices, :money_column, :date_column
7
7
 
8
8
  def initialize(options = {})
@@ -63,7 +63,7 @@ module Reckon
63
63
  date_score += 5 if entry =~ /^[\-\/\.\d:\[\]]+$/
64
64
  date_score += entry.gsub(/[^\-\/\.\d:\[\]]/, '').length if entry.gsub(/[^\-\/\.\d:\[\]]/, '').length > 3
65
65
  date_score -= entry.gsub(/[\-\/\.\d:\[\]]/, '').length
66
- date_score += 30 if entry =~ /^\d+[:\/\.]\d+[:\/\.]\d+([ :]\d+[:\/\.]\d+)?$/
66
+ date_score += 30 if entry =~ /^\d+[:\/\.-]\d+[:\/\.-]\d+([ :]\d+[:\/\.]\d+)?$/
67
67
  date_score += 10 if entry =~ /^\d+\[\d+:GMT\]$/i
68
68
 
69
69
  # Try to determine if this is a balance column
@@ -132,7 +132,7 @@ module Reckon
132
132
  puts "please report this issue to us so we can take a look!\n"
133
133
  end
134
134
  end
135
-
135
+
136
136
  # Some csv files negative/positive amounts are indicated in separate account
137
137
  def detect_sign_column
138
138
  return if columns[0].length <= 2 # This test needs requires more than two rows otherwise will lead to false positives
@@ -141,13 +141,13 @@ module Reckon
141
141
  column = columns[ @money_column_indices[0] - 1 ]
142
142
  signs = column.uniq
143
143
  end
144
- if (signs.length != 2 &&
144
+ if (signs.length != 2 &&
145
145
  (@money_column_indices[0] + 1 < columns.length))
146
146
  column = columns[ @money_column_indices[0] + 1 ]
147
147
  signs = column.uniq
148
148
  end
149
149
  if signs.length == 2
150
- negative_first = true
150
+ negative_first = true
151
151
  negative_first = false if signs[0] == "Bij" || signs[0].downcase =~ /^cr/ # look for known debit indicators
152
152
  @money_column.each_with_index do |money, i|
153
153
  if negative_first && column[i] == signs[0]
@@ -39,7 +39,7 @@ module Reckon
39
39
  (@amount >= 0 ? " " : "") + sprintf("%0.2f #{@currency}", @amount * (negate ? -1 : 1))
40
40
  else
41
41
  (@amount >= 0 ? " " : "") + sprintf("%0.2f", @amount * (negate ? -1 : 1)).gsub(/^((\-)|)(?=\d)/, "\\1#{@currency}")
42
- end
42
+ end
43
43
  end
44
44
 
45
45
  def Money::from_s( value, options = {} )
@@ -108,11 +108,13 @@ module Reckon
108
108
  value = [$1, $2, $3].join("/") if value =~ /^(\d{4})(\d{2})(\d{2})\d+\[\d+\:GMT\]$/ # chase format
109
109
  value = [$3, $2, $1].join("/") if value =~ /^(\d{2})\.(\d{2})\.(\d{4})$/ # german format
110
110
  value = [$3, $2, $1].join("/") if value =~ /^(\d{2})\-(\d{2})\-(\d{4})$/ # nordea format
111
+ value = [$1, $2, $3].join("/") if value =~ /^(\d{4})\-(\d{2})\-(\d{2})$/ # yyyy-mm-dd format
111
112
  value = [$1, $2, $3].join("/") if value =~ /^(\d{4})(\d{2})(\d{2})/ # yyyymmdd format
112
113
 
114
+
113
115
  unless @endian_precedence # Try to detect endian_precedence
114
116
  reg_match = value.match( /^(\d\d)\/(\d\d)\/\d\d\d?\d?/ )
115
- # If first one is not \d\d/\d\d/\d\d\d?\d set it to default
117
+ # If first one is not \d\d/\d\d/\d\d\d?\d set it to default
116
118
  if !reg_match
117
119
  @endian_precedence = [:middle, :little]
118
120
  elsif reg_match[1].to_i > 12
@@ -122,7 +124,7 @@ module Reckon
122
124
  end
123
125
  end
124
126
  end
125
- self.push( value )
127
+ self.push( value )
126
128
  end
127
129
  # if endian_precedence still nil, raise error
128
130
  unless @endian_precedence || options[:date_format]
@@ -132,10 +134,10 @@ module Reckon
132
134
 
133
135
  def for( index )
134
136
  value = self.at( index )
135
- guess = Chronic.parse(value, :context => :past,
137
+ guess = Chronic.parse(value, :context => :past,
136
138
  :endian_precedence => @endian_precedence )
137
139
  if guess.to_i < 953236800 && value =~ /\//
138
- guess = Chronic.parse((value.split("/")[0...-1] + [(2000 + value.split("/").last.to_i).to_s]).join("/"), :context => :past,
140
+ guess = Chronic.parse((value.split("/")[0...-1] + [(2000 + value.split("/").last.to_i).to_s]).join("/"), :context => :past,
139
141
  :endian_precedence => @endian_precedence)
140
142
  end
141
143
  guess
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = %q{reckon}
6
- s.version = "0.3.10"
6
+ s.version = "0.4.0"
7
7
  s.authors = ["Andrew Cantino", "BlackEdder"]
8
8
  s.email = %q{andrew@iterationlabs.com}
9
9
  s.homepage = %q{https://github.com/cantino/reckon}
@@ -0,0 +1,14 @@
1
+ Income:
2
+ Salary:
3
+ - 'LÖN'
4
+ - 'Salary'
5
+ Expenses:
6
+ Bank:
7
+ - 'Comission'
8
+ - 'MasterCard'
9
+ Rent:
10
+ - '0011223344' # Landlord bank number
11
+ Books:
12
+ - 'Book'
13
+ '[Internal:Transfer]': # Virtual account
14
+ - '4433221100' # Your own account number
@@ -6,27 +6,77 @@ require 'rubygems'
6
6
  require 'reckon'
7
7
 
8
8
  describe Reckon::App do
9
- before do
10
- @chase = Reckon::App.new(:string => BANK_CSV)
11
- @rows = []
12
- @chase.each_row_backwards { |row| @rows.push( row ) }
9
+ context 'with chase csv input' do
10
+ before do
11
+ @chase = Reckon::App.new(:string => BANK_CSV)
12
+ @chase.learn_from( BANK_LEDGER )
13
+ @rows = []
14
+ @chase.each_row_backwards { |row| @rows.push( row ) }
15
+ end
16
+
17
+ describe "each_row_backwards" do
18
+ it "should return rows with hashes" do
19
+ @rows[0][:pretty_date].should == "2009/12/10"
20
+ @rows[0][:pretty_money].should == " $2105.00"
21
+ @rows[0][:description].should == "CREDIT; Some Company vendorpymt PPD ID: 5KL3832735"
22
+ @rows[1][:pretty_date].should == "2009/12/11"
23
+ @rows[1][:pretty_money].should == "-$116.22"
24
+ @rows[1][:description].should == "CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL"
25
+ end
26
+ end
27
+
28
+ describe "weighted_account_match" do
29
+ it "should guess the correct account" do
30
+ @chase.weighted_account_match( @rows[7] ).first[:account].should == "Expenses:Books"
31
+ end
32
+ end
13
33
  end
14
34
 
15
- describe "each_row_backwards" do
16
- it "should return rows with hashes" do
17
- @rows[0][:pretty_date].should == "2009/12/10"
18
- @rows[0][:pretty_money].should == " $2105.00"
19
- @rows[0][:description].should == "CREDIT; Some Company vendorpymt PPD ID: 5KL3832735"
20
- @rows[1][:pretty_date].should == "2009/12/11"
21
- @rows[1][:pretty_money].should == "-$116.22"
22
- @rows[1][:description].should == "CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL"
35
+ context 'unattended mode with chase csv input' do
36
+ before do
37
+ @output_file = StringIO.new
38
+ @chase = Reckon::App.new(:string => BANK_CSV, :unattended => true, :output_file => @output_file)
39
+ end
40
+
41
+ describe 'walk backwards' do
42
+ it 'should assign Income:Unknown and Expenses:Unknown by default' do
43
+ @chase.walk_backwards
44
+ @output_file.string.scan('Expenses:Unknown').count.should == 6
45
+ @output_file.string.scan('Income:Unknown').count.should == 3
46
+ end
47
+
48
+ it 'should change default account names' do
49
+ @chase = Reckon::App.new(:string => BANK_CSV,
50
+ :unattended => true,
51
+ :output_file => @output_file,
52
+ :default_into_account => 'Expenses:Default',
53
+ :default_outof_account => 'Income:Default')
54
+ @chase.walk_backwards
55
+ @output_file.string.scan('Expenses:Default').count.should == 6
56
+ @output_file.string.scan('Income:Default').count.should == 3
57
+ end
58
+
59
+ it 'should learn from a ledger file' do
60
+ @chase.learn_from( BANK_LEDGER )
61
+ @chase.walk_backwards
62
+ @output_file.string.scan('Expenses:Books').count.should == 1
63
+ end
64
+
65
+ it 'should learn from an account tokens file' do
66
+ @chase = Reckon::App.new(:string => BANK_CSV,
67
+ :unattended => true,
68
+ :output_file => @output_file,
69
+ :account_tokens_file => 'spec/data_fixtures/tokens.yaml')
70
+ @chase.walk_backwards
71
+ @output_file.string.scan('Expenses:Books').count.should == 1
72
+ end
23
73
  end
24
74
  end
25
-
75
+
26
76
  #DATA
27
77
  BANK_CSV = (<<-CSV).strip
28
78
  DEBIT,20091224120000[0:GMT],"HOST 037196321563 MO 12/22SLICEHOST",-85.00
29
- CHECK,20091224120000[0:GMT],"CHECK 2656",-20.00
79
+ CHECK,20091224120000[0:GMT],"Book Store",-20.00
30
80
  DEBIT,20091224120000[0:GMT],"GITHUB 041287430274 CA 12/22GITHUB 04",-7.00
31
81
  CREDIT,20091223120000[0:GMT],"Some Company vendorpymt PPD ID: 59728JSL20",3520.00
32
82
  CREDIT,20091223120000[0:GMT],"Blarg BLARG REVENUE PPD ID: 00jah78563",1558.52
@@ -35,4 +85,15 @@ describe Reckon::App do
35
85
  CREDIT,20091211120000[0:GMT],"PAYPAL TRANSFER PPD ID: PAYPALSDSL",-116.22
36
86
  CREDIT,20091210120000[0:GMT],"Some Company vendorpymt PPD ID: 5KL3832735",2105.00
37
87
  CSV
88
+
89
+ BANK_LEDGER = (<<-LEDGER).strip
90
+ 2004/05/14 * Pay day
91
+ Assets:Bank:Checking $500.00
92
+ Income:Salary
93
+
94
+ 2004/05/27 Book Store
95
+ Expenses:Books $20.00
96
+ Liabilities:MasterCard
97
+ LEDGER
98
+
38
99
  end
@@ -30,7 +30,7 @@ describe Reckon::CSVParser do
30
30
  @chase.settings[:testing].should be_true
31
31
  Reckon::CSVParser.settings[:testing].should be_true
32
32
  end
33
-
33
+
34
34
  describe "parse" do
35
35
  it "should work with foreign character encodings" do
36
36
  app = Reckon::CSVParser.new(:file => File.expand_path(File.join(File.dirname(__FILE__), "..", "data_fixtures", "extratofake.csv")))
@@ -48,7 +48,7 @@ describe Reckon::CSVParser do
48
48
  @simple_csv.columns.should == [["entry1", "entry4"], ["entry2", "entry5"], ["entry3", "entry6"]]
49
49
  @chase.columns.length.should == 4
50
50
  end
51
-
51
+
52
52
  it "should be ok with empty lines" do
53
53
  lambda {
54
54
  Reckon::CSVParser.new(:string => "one,two\nthree,four\n\n\n\n\n").columns.should == [['one', 'three'], ['two', 'four']]
@@ -60,7 +60,7 @@ describe Reckon::CSVParser do
60
60
  before do
61
61
  @harder_date_example_csv = Reckon::CSVParser.new(:string => HARDER_DATE_EXAMPLE)
62
62
  end
63
-
63
+
64
64
  it "should detect the money column" do
65
65
  @chase.money_column_indices.should == [3]
66
66
  @some_other_bank.money_column_indices.should == [3]
@@ -86,6 +86,7 @@ describe Reckon::CSVParser do
86
86
  @french_csv.date_column_index.should == 2
87
87
  @broker_canada.date_column_index.should == 0
88
88
  @intuit_mint.date_column_index.should == 0
89
+ Reckon::CSVParser.new(:string => '2014-01-13,"22211100000",-10').date_column_index.should == 0
89
90
  end
90
91
 
91
92
  it "should consider all other columns to be description columns" do
@@ -121,7 +122,7 @@ describe Reckon::CSVParser do
121
122
  @danish_kroner_nordea.money_for(5).should == -655.00
122
123
  @yyyymmdd_date.money_for(0).should == -123.45
123
124
  @ing_csv.money_for(0).should == -136.13
124
- @ing_csv.money_for(1).should == 375.00
125
+ @ing_csv.money_for(1).should == 375.00
125
126
  @austrian_csv.money_for(0).should == -18.00
126
127
  @austrian_csv.money_for(2).should == 120.00
127
128
  @french_csv.money_for(0).should == -10.00
@@ -294,8 +295,8 @@ describe Reckon::CSVParser do
294
295
 
295
296
  ING_CSV = (<<-CSV).strip
296
297
  20121115,From1,Acc,T1,IC,Af,"136,13",Incasso,SEPA Incasso, Opm1
297
- 20121112,Names,NL28 INGB 1200 3244 16,21817,GT,Bij,"375,00", Opm2
298
- 20091117,Names,NL28 INGB 1200 3244 16,21817,GT,Af,"257,50", Opm3
298
+ 20121112,Names,NL28 INGB 1200 3244 16,21817,GT,Bij,"375,00", Opm2
299
+ 20091117,Names,NL28 INGB 1200 3244 16,21817,GT,Af,"257,50", Opm3
299
300
  CSV
300
301
 
301
302
  HARDER_DATE_EXAMPLE = (<<-CSV).strip
@@ -377,7 +378,7 @@ describe Reckon::CSVParser do
377
378
  2013-06-27,2013-06-27,Dividend,ICICI BK SPONSORED ADR,IBN,100,,,66.70,USD
378
379
  2013-06-19,2013-06-24,Buy,ISHARES S&P/TSX CAPPED REIT IN,XRE,300,15.90,CDN,-4779.95,CAD
379
380
  2013-06-17,2013-06-17,Contribution,CONTRIBUTION,,,,,600.00,CAD
380
- 2013-05-22,2013-05-22,Dividend,NATBK,NA,70,,,58.10,CAD
381
+ 2013-05-22,2013-05-22,Dividend,NATBK,NA,70,,,58.10,CAD
381
382
  CSV
382
383
 
383
384
  INTUIT_MINT_EXAMPLE = (<<-CSV).strip
@@ -390,4 +391,5 @@ describe Reckon::CSVParser do
390
391
  "1/30/2014","Costco","[PR]COSTCO WHOLESAL","559.96","debit","Business Services","Chequing","",""
391
392
  CSV
392
393
 
394
+
393
395
  end
@@ -8,11 +8,11 @@ require 'reckon'
8
8
  describe Reckon::DateColumn do
9
9
  describe "initialize" do
10
10
  it "should detect us and world time" do
11
- Reckon::DateColumn.new( ["01/02/2013", "01/14/2013"] ).endian_precedence.should == [:middle]
12
- Reckon::DateColumn.new( ["01/02/2013", "14/01/2013"] ).endian_precedence.should == [:little]
11
+ Reckon::DateColumn.new( ["01/02/2013", "01/14/2013"] ).endian_precedence.should == [:middle]
12
+ Reckon::DateColumn.new( ["01/02/2013", "14/01/2013"] ).endian_precedence.should == [:little]
13
13
  end
14
14
  it "should set endian_precedence to default when date format cannot be misinterpreted" do
15
- Reckon::DateColumn.new( ["2013/01/02"] ).endian_precedence.should == [:middle,:little]
15
+ Reckon::DateColumn.new( ["2013/01/02"] ).endian_precedence.should == [:middle,:little]
16
16
  end
17
17
  it "should raise an error when in doubt" do
18
18
  expect{ Reckon::DateColumn.new( ["01/02/2013", "01/03/2013"] )}.to raise_error( StandardError )
@@ -20,20 +20,23 @@ 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 )
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 )
31
+
29
32
  end
30
33
 
31
34
  it "should correctly use endian_precedence" do
32
- Reckon::DateColumn.new( ["01/02/2013", "01/14/2013"] ).for(0).should ==
35
+ Reckon::DateColumn.new( ["01/02/2013", "01/14/2013"] ).for(0).should ==
33
36
  Time.new( 2013, 01, 02, 12 )
34
- Reckon::DateColumn.new( ["01/02/2013", "14/01/2013"] ).for(0).should ==
37
+ Reckon::DateColumn.new( ["01/02/2013", "14/01/2013"] ).for(0).should ==
35
38
  Time.new( 2013, 02, 01, 12 )
36
39
  end
37
40
  end
38
41
  end
39
-
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.3.10
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Cantino
@@ -9,76 +9,76 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-08-16 00:00:00.000000000 Z
12
+ date: 2015-06-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - '>='
18
+ - - ">="
19
19
  - !ruby/object:Gem::Version
20
20
  version: 1.2.9
21
21
  type: :development
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
- - - '>='
25
+ - - ">="
26
26
  - !ruby/object:Gem::Version
27
27
  version: 1.2.9
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: fastercsv
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - '>='
32
+ - - ">="
33
33
  - !ruby/object:Gem::Version
34
34
  version: 1.5.1
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - '>='
39
+ - - ">="
40
40
  - !ruby/object:Gem::Version
41
41
  version: 1.5.1
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: chronic
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
- - - '>='
46
+ - - ">="
47
47
  - !ruby/object:Gem::Version
48
48
  version: 0.3.0
49
49
  type: :runtime
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - '>='
53
+ - - ">="
54
54
  - !ruby/object:Gem::Version
55
55
  version: 0.3.0
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: highline
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
- - - '>='
60
+ - - ">="
61
61
  - !ruby/object:Gem::Version
62
62
  version: 1.5.2
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
- - - '>='
67
+ - - ">="
68
68
  - !ruby/object:Gem::Version
69
69
  version: 1.5.2
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: terminal-table
72
72
  requirement: !ruby/object:Gem::Requirement
73
73
  requirements:
74
- - - '>='
74
+ - - ">="
75
75
  - !ruby/object:Gem::Version
76
76
  version: 1.4.2
77
77
  type: :runtime
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
- - - '>='
81
+ - - ">="
82
82
  - !ruby/object:Gem::Version
83
83
  version: 1.4.2
84
84
  description: Reckon automagically converts CSV files for use with the command-line
@@ -90,11 +90,11 @@ executables:
90
90
  extensions: []
91
91
  extra_rdoc_files: []
92
92
  files:
93
- - .document
94
- - .gitignore
95
- - .ruby-gemset
96
- - .ruby-version
97
- - .travis.yml
93
+ - ".document"
94
+ - ".gitignore"
95
+ - ".ruby-gemset"
96
+ - ".ruby-version"
97
+ - ".travis.yml"
98
98
  - CHANGES.md
99
99
  - Gemfile
100
100
  - Gemfile.lock
@@ -109,6 +109,7 @@ files:
109
109
  - lib/reckon/money.rb
110
110
  - reckon.gemspec
111
111
  - spec/data_fixtures/extratofake.csv
112
+ - spec/data_fixtures/tokens.yaml
112
113
  - spec/reckon/app_spec.rb
113
114
  - spec/reckon/csv_parser_spec.rb
114
115
  - spec/reckon/date_column_spec.rb
@@ -126,23 +127,24 @@ require_paths:
126
127
  - lib
127
128
  required_ruby_version: !ruby/object:Gem::Requirement
128
129
  requirements:
129
- - - '>='
130
+ - - ">="
130
131
  - !ruby/object:Gem::Version
131
132
  version: '0'
132
133
  required_rubygems_version: !ruby/object:Gem::Requirement
133
134
  requirements:
134
- - - '>='
135
+ - - ">="
135
136
  - !ruby/object:Gem::Version
136
137
  version: '0'
137
138
  requirements: []
138
139
  rubyforge_project:
139
- rubygems_version: 2.1.11
140
+ rubygems_version: 2.4.6
140
141
  signing_key:
141
142
  specification_version: 4
142
143
  summary: Utility for interactively converting and labeling CSV files for the Ledger
143
144
  accounting tool.
144
145
  test_files:
145
146
  - spec/data_fixtures/extratofake.csv
147
+ - spec/data_fixtures/tokens.yaml
146
148
  - spec/reckon/app_spec.rb
147
149
  - spec/reckon/csv_parser_spec.rb
148
150
  - spec/reckon/date_column_spec.rb