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 +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
|