reckon 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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