reckon 0.2.3 → 0.3.0

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.
data/.gitignore CHANGED
@@ -21,3 +21,4 @@ pkg
21
21
  ## PROJECT::SPECIFIC
22
22
  .idea
23
23
  reckon_local
24
+ private_tests
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.3
1
+ 0.3.0
data/lib/reckon/app.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  module Reckon
2
2
  class App
3
3
  VERSION = "Reckon 0.1"
4
-
5
4
  attr_accessor :options, :csv_data, :accounts, :tokens, :money_column_indices, :date_column_index, :description_column_indices, :seen
6
5
 
7
6
  def initialize(options = {})
@@ -155,6 +154,7 @@ module Reckon
155
154
 
156
155
  def money_for(index)
157
156
  value = money_column_indices.inject("") { |m, i| m + columns[i][index] }
157
+ value = value.gsub(/\./, '').gsub(/,/, '.') if options[:comma_separates_cents]
158
158
  cleaned_value = value.gsub(/[^\d\.]/, '').to_f
159
159
  cleaned_value *= -1 if value =~ /[\(\-]/
160
160
  cleaned_value
@@ -184,7 +184,7 @@ module Reckon
184
184
  end
185
185
 
186
186
  def description_for(index)
187
- description_column_indices.map { |i| columns[i][index] }.join("; ").squeeze(" ")
187
+ description_column_indices.map { |i| columns[i][index] }.join("; ").squeeze(" ").gsub(/(;\s+){2,}/, '').strip
188
188
  end
189
189
 
190
190
  def output_table
@@ -202,11 +202,14 @@ module Reckon
202
202
  found_likely_money_column = false
203
203
  cols.each_with_index do |column, index|
204
204
  money_score = date_score = possible_neg_money_count = possible_pos_money_count = 0
205
- column.each do |entry|
205
+ last = nil
206
+ column.reverse.each_with_index do |entry, row_from_bottom|
207
+ row = csv_data[csv_data.length - 1 - row_from_bottom]
206
208
  entry = entry.strip
207
- money_score += 10 if entry[/^[\-\+\(]{0,2}\$/]
208
- money_score += entry.gsub(/[^\d\.\-\+,\(\)]/, '').length
209
- money_score -= 100 if entry.length > 17
209
+ money_score += 20 if entry[/^[\-\+\(]{0,2}\$/]
210
+ money_score += 20 if entry[/^\$?\-?\$?\d+[\.,\d]*?[\.,]\d\d$/]
211
+ money_score += entry.gsub(/[^\d\.\-\+,\(\)]/, '').length if entry.length < 7
212
+ money_score -= entry.length if entry.length > 8
210
213
  money_score -= 20 if entry !~ /^[\$\+\.\-,\d\(\)]+$/
211
214
  possible_neg_money_count += 1 if entry =~ /^\$?[\-\(]\$?\d+/
212
215
  possible_pos_money_count += 1 if entry =~ /^\+?\$?\+?\d+/
@@ -216,6 +219,19 @@ module Reckon
216
219
  date_score -= entry.gsub(/[\-\/\.\d:\[\]]/, '').length
217
220
  date_score += 30 if entry =~ /^\d+[:\/\.]\d+[:\/\.]\d+([ :]\d+[:\/\.]\d+)?$/
218
221
  date_score += 10 if entry =~ /^\d+\[\d+:GMT\]$/i
222
+
223
+ # Try to determine if this is a balance column
224
+ entry_as_num = entry.gsub(/[^\-\d\.]/, '').to_f
225
+ if last && entry_as_num != 0 && last != 0
226
+ row.each do |row_entry|
227
+ row_entry = row_entry.to_s.gsub(/[^\-\d\.]/, '').to_f
228
+ if row_entry != 0 && last + row_entry == entry_as_num
229
+ money_score -= 10
230
+ break
231
+ end
232
+ end
233
+ end
234
+ last = entry_as_num
219
235
  end
220
236
 
221
237
  if possible_neg_money_count > (column.length / 5.0) && possible_pos_money_count > (column.length / 5.0)
@@ -235,7 +251,7 @@ module Reckon
235
251
  if index == a
236
252
  new_column = []
237
253
  column.each_with_index do |row, row_index|
238
- new_column << row + " " + columns[b][row_index]
254
+ new_column << row + " " + (columns[b][row_index] || '')
239
255
  end
240
256
  output_columns << new_column
241
257
  elsif index == b
@@ -247,29 +263,37 @@ module Reckon
247
263
  output_columns
248
264
  end
249
265
 
266
+ require 'pp'
250
267
  def detect_columns
251
268
  results, found_likely_money_column = evaluate_columns(columns)
269
+ self.money_column_indices = [ results.sort { |a, b| b[:money_score] <=> a[:money_score] }.first[:index] ]
252
270
 
253
- if found_likely_money_column
254
- self.money_column_indices = [ results.sort { |a, b| b[:money_score] <=> a[:money_score] }.first[:index] ]
255
- else
271
+ if !found_likely_money_column
272
+ found_likely_double_money_columns = false
256
273
  0.upto(columns.length - 2) do |i|
257
- _, found_likely_money_column = evaluate_columns(merge_columns(i, i+1))
274
+ _, found_likely_double_money_columns = evaluate_columns(merge_columns(i, i+1))
258
275
 
259
- if found_likely_money_column
276
+ if found_likely_double_money_columns
260
277
  self.money_column_indices = [ i, i+1 ]
278
+ unless settings[:testing]
279
+ puts "It looks like this CSV has two seperate columns for money, one of which shows positive"
280
+ puts "changes and one of which shows negative changes. If this is true, great. Otherwise,"
281
+ puts "please report this issue to us so we can take a look!\n"
282
+ end
261
283
  break
262
284
  end
263
285
  end
286
+
287
+ if !found_likely_double_money_columns && !settings[:testing]
288
+ puts "I didn't find a high-likelyhood money column, but I'm taking my best guess with column #{money_column_indices.first + 1}."
289
+ end
264
290
  end
265
291
 
266
- if money_column_indices
267
- results.reject! {|i| money_column_indices.include?(i[:index]) }
268
- self.date_column_index = results.sort { |a, b| b[:date_score] <=> a[:date_score] }.first[:index]
269
- results.reject! {|i| i[:index] == date_column_index }
292
+ results.reject! {|i| money_column_indices.include?(i[:index]) }
293
+ self.date_column_index = results.sort { |a, b| b[:date_score] <=> a[:date_score] }.first[:index]
294
+ results.reject! {|i| i[:index] == date_column_index }
270
295
 
271
- self.description_column_indices = results.map { |i| i[:index] }
272
- end
296
+ self.description_column_indices = results.map { |i| i[:index] }
273
297
  end
274
298
 
275
299
  def each_row_backwards
@@ -288,7 +312,7 @@ module Reckon
288
312
  @columns ||= begin
289
313
  last_row_length = nil
290
314
  csv_data.inject([]) do |memo, row|
291
- fail "Input CSV must have consistent row lengths." if last_row_length && row.length != last_row_length
315
+ # fail "Input CSV must have consistent row lengths." if last_row_length && row.length != last_row_length
292
316
  unless row.all? { |i| i.nil? || i.length == 0 }
293
317
  row.each_with_index do |entry, index|
294
318
  memo[index] ||= []
@@ -303,7 +327,7 @@ module Reckon
303
327
 
304
328
  def parse
305
329
  data = options[:string] || File.read(options[:file])
306
- self.csv_data = FasterCSV.parse(data.strip)
330
+ self.csv_data = FasterCSV.parse(data.strip, :col_sep => options[:csv_separator] || ',')
307
331
  end
308
332
 
309
333
  def self.parse_opts(args = ARGV)
@@ -336,6 +360,14 @@ module Reckon
336
360
  options[:ignore_columns] = ignore.split(",").map { |i| i.to_i }
337
361
  end
338
362
 
363
+ opts.on("", "--csv-separator ';'", "Separator for parsing the CSV - default is comma.") do |csv_separator|
364
+ options[:csv_separator] = csv_separator
365
+ end
366
+
367
+ opts.on("", "--comma-separates-cents", "Use comma instead of period to deliminate dollars from cents when parsing ($100,50 instead of $100.50)") do |c|
368
+ options[:comma_separates_cents] = c
369
+ end
370
+
339
371
  opts.on_tail("-h", "--help", "Show this message") do
340
372
  puts opts
341
373
  exit
@@ -367,5 +399,16 @@ module Reckon
367
399
 
368
400
  options
369
401
  end
402
+
403
+ @settings = { :testing => false }
404
+
405
+ def self.settings
406
+ @settings
407
+ end
408
+
409
+ def settings
410
+ self.class.settings
411
+ end
370
412
  end
371
413
  end
414
+
data/reckon.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{reckon}
8
- s.version = "0.2.3"
8
+ s.version = "0.3.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Andrew Cantino"]
12
- s.date = %q{2010-11-10}
12
+ s.date = %q{2010-11-12}
13
13
  s.default_executable = %q{reckon}
14
14
  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.}
15
15
  s.email = %q{andrew@iterationlabs.com}
@@ -4,6 +4,8 @@ require File.dirname(__FILE__) + '/../spec_helper'
4
4
  require 'rubygems'
5
5
  require 'reckon'
6
6
 
7
+ Reckon::App.settings[:testing] = true
8
+
7
9
  describe Reckon::App do
8
10
  before do
9
11
  @chase = Reckon::App.new(:string => CHASE_CSV)
@@ -11,6 +13,15 @@ describe Reckon::App do
11
13
  @two_money_columns = Reckon::App.new(:string => TWO_MONEY_COLUMNS_BANK)
12
14
  @simple_csv = Reckon::App.new(:string => SIMPLE_CSV)
13
15
  end
16
+
17
+ it "should be in testing mode" do
18
+ @chase.settings[:testing].should be_true
19
+ Reckon::App.settings[:testing].should be_true
20
+ end
21
+
22
+ it "should work with other separators" do
23
+ Reckon::App.new(:string => "one;two\nthree;four", :csv_separator => ';').columns.should == [['one', 'three'], ['two', 'four']]
24
+ end
14
25
 
15
26
  describe "columns" do
16
27
  it "should return the csv transposed" do
@@ -74,6 +85,12 @@ describe Reckon::App do
74
85
  @two_money_columns.money_for(3).should == -88.55
75
86
  @two_money_columns.money_for(4).should == 88.55
76
87
  end
88
+
89
+ it "should handle the comma_separates_cents option correctly" do
90
+ european_csv = Reckon::App.new(:string => "$2,00;something\n1.025,67;something else", :csv_separator => ';', :comma_separates_cents => true)
91
+ european_csv.money_for(0).should == 2.00
92
+ european_csv.money_for(1).should == 1025.67
93
+ end
77
94
  end
78
95
 
79
96
  describe "date_for" do
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reckon
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 2
9
8
  - 3
10
- version: 0.2.3
9
+ - 0
10
+ version: 0.3.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Andrew Cantino
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-11-10 00:00:00 -08:00
18
+ date: 2010-11-12 00:00:00 -08:00
19
19
  default_executable: reckon
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency