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 +1 -0
- data/VERSION +1 -1
- data/lib/reckon/app.rb +63 -20
- data/reckon.gemspec +2 -2
- data/spec/reckon/app_spec.rb +17 -0
- metadata +4 -4
data/.gitignore
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
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
|
-
|
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 +=
|
208
|
-
money_score += entry
|
209
|
-
money_score
|
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
|
-
|
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
|
-
_,
|
274
|
+
_, found_likely_double_money_columns = evaluate_columns(merge_columns(i, i+1))
|
258
275
|
|
259
|
-
if
|
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
|
-
|
267
|
-
|
268
|
-
|
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
|
-
|
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.
|
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-
|
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}
|
data/spec/reckon/app_spec.rb
CHANGED
@@ -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:
|
4
|
+
hash: 19
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
- 2
|
9
8
|
- 3
|
10
|
-
|
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-
|
18
|
+
date: 2010-11-12 00:00:00 -08:00
|
19
19
|
default_executable: reckon
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|