total_recall 0.2.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +3 -0
- data/Gemfile +0 -2
- data/README.md +86 -24
- data/lib/total_recall.rb +111 -272
- data/lib/total_recall/templates/sample.yml.tt +79 -0
- data/lib/total_recall/templates/simple.yml.tt +35 -0
- data/lib/total_recall/version.rb +1 -1
- data/spec/spec_helper.rb +1 -9
- data/spec/total_recall/total_recall_spec.rb +176 -0
- data/total_recall.gemspec +21 -16
- metadata +97 -63
- data/spec/fixtures/.keep +0 -0
- data/spec/fixtures/abn.csv +0 -2
- data/spec/fixtures/abncc.csv +0 -3
- data/spec/fixtures/ing.csv +0 -3
- data/spec/total_recall/bank_parser_spec.rb +0 -57
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 162939627043b1aea142c3c8a6dedc9c0557801a
|
4
|
+
data.tar.gz: fd307f5b53406336a46225d36b9138d11d228bfd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a78dcb6fa9e0d5d533dcc8bef4d34769ced4f8809c8f074158c4a3c3de2cd45a53cacd2364d0e0b941330cc7fccf35c8f443106eada6f6dfa7c2b48cc555db3f
|
7
|
+
data.tar.gz: 1ac9db0b80f7c7e08be72ccadb399d1eb209b56bbfa40bafcc0fa3c7037c691e3e329c6a9e4147bcda57b6893058a8b2f8a1b10459ebb31ca7fb428ec701a80f
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,39 +1,100 @@
|
|
1
|
-
TotalRecall
|
2
|
-
============
|
1
|
+
# TotalRecall [![travis](https://secure.travis-ci.org/eval/total_recall.png?branch=master)](https://secure.travis-ci.org/#!/eval/total_recall)
|
3
2
|
|
4
|
-
Turn
|
3
|
+
Turn **any** csv-file into a [Ledger](http://ledger-cli.org/) journal.
|
5
4
|
|
6
|
-
|
7
|
-
------------
|
5
|
+
## Quickstart
|
8
6
|
|
9
|
-
|
10
|
-
Tasks:
|
11
|
-
total_recall help [TASK] # Describe available tasks or one specific task
|
12
|
-
total_recall ledger -i, --input=INPUT -p, --parser=PARSER # Convert input to ledger-transactions
|
13
|
-
total_recall parse -i, --input=INPUT -p, --parser=PARSER # Parses input-file and prints it
|
7
|
+
### Generate a sample config
|
14
8
|
|
15
|
-
|
16
|
-
|
9
|
+
```bash
|
10
|
+
total_recall sample
|
11
|
+
# An annotated config 'sample.yml' will be written to CWD.
|
12
|
+
```
|
17
13
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
14
|
+
### Generate a ledger
|
15
|
+
|
16
|
+
The sample config contains 2 transactions.
|
17
|
+
Let's get 'em in a journal:
|
18
|
+
|
19
|
+
```bash
|
20
|
+
total_recall ledger -c sample.yml > sample.dat
|
21
|
+
# What account provides these transactions? |Assets:Checking|
|
22
|
+
#
|
23
|
+
# +------------+-----+----------+
|
24
|
+
# | 03.11.2013 | Foo | 1.638,00 |
|
25
|
+
# +------------+-----+----------+
|
26
|
+
# To what account did the money go?
|
27
|
+
# Expenses:Foo
|
28
|
+
# +------------+-----+---------+
|
29
|
+
# | 03.11.2013 | Bar | -492,93 |
|
30
|
+
# +------------+-----+---------+
|
31
|
+
# To what account did the money go?
|
32
|
+
# Expenses:Bar
|
33
|
+
```
|
34
|
+
Now verify the ledger file:
|
35
|
+
|
36
|
+
```bash
|
37
|
+
ledger -f sample.dat balance
|
38
|
+
|
39
|
+
# $ -1.145,07 Assets:Checking
|
40
|
+
# $ 1.145,07 Expenses
|
41
|
+
# $ -492,93 Bar
|
42
|
+
# $ 1.638,00 Foo
|
43
|
+
#--------------------
|
44
|
+
# 0
|
45
|
+
```
|
46
|
+
|
47
|
+
### Now what?
|
48
|
+
|
49
|
+
May I suggest:
|
50
|
+
* read `sample.yml`
|
51
|
+
|
52
|
+
It explains what options are available.
|
53
|
+
* put your own csv in `sample.yml` and adjust the context
|
54
|
+
|
55
|
+
## Usage
|
25
56
|
|
26
|
-
|
27
|
-
|
57
|
+
```bash
|
58
|
+
total_recall
|
59
|
+
|
60
|
+
# Commands:
|
61
|
+
# total_recall help [COMMAND] # Describe available commands or one specific command
|
62
|
+
# total_recall init NAME # Generate a minimal config NAME.yml
|
63
|
+
# total_recall ledger -c, --config=CONFIG # Convert the config to a ledger
|
64
|
+
# total_recall sample # Generate an annotated config
|
65
|
+
|
66
|
+
# typically you would do:
|
67
|
+
total_recall init my-bank
|
68
|
+
|
69
|
+
# fiddle with the settings in 'my-bank.yml' and test-run it:
|
70
|
+
total_recal ledger -c my-bank.yml
|
71
|
+
# to skip prompts just provide dummy-data:
|
72
|
+
yes 'Dummy' | total_recal ledger -c my-bank.yml
|
73
|
+
|
74
|
+
# export it to a journal:
|
75
|
+
total_recall ledger -c my-bank.yml > my-bank.dat
|
76
|
+
|
77
|
+
# verify correctness with ledger:
|
78
|
+
ledger -f my-bank.dat bal
|
79
|
+
```
|
80
|
+
|
81
|
+
## Develop
|
82
|
+
|
83
|
+
```bash
|
84
|
+
git clone git://github.com/eval/total_recall.git
|
85
|
+
cd total_recall
|
86
|
+
bundle
|
87
|
+
bundle exec rake spec
|
88
|
+
```
|
89
|
+
## Author
|
28
90
|
|
29
91
|
Gert Goet (eval) :: gert@thinkcreate.nl :: @gertgoet
|
30
92
|
|
31
|
-
License
|
32
|
-
------
|
93
|
+
## License
|
33
94
|
|
34
95
|
(The MIT license)
|
35
96
|
|
36
|
-
Copyright (c)
|
97
|
+
Copyright (c) 2014 Gert Goet, ThinkCreate
|
37
98
|
|
38
99
|
Permission is hereby granted, free of charge, to any person obtaining
|
39
100
|
a copy of this software and associated documentation files (the
|
@@ -53,3 +114,4 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
53
114
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
54
115
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
55
116
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
117
|
+
|
data/lib/total_recall.rb
CHANGED
@@ -1,323 +1,162 @@
|
|
1
|
-
require
|
1
|
+
require 'yaml'
|
2
2
|
require "thor"
|
3
3
|
require "mustache"
|
4
|
+
require 'csv'
|
4
5
|
|
5
6
|
module TotalRecall
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
# Expected: Hash with:
|
11
|
-
#:amount => Float,
|
12
|
-
#:currency => String,
|
13
|
-
#:description => String,
|
14
|
-
#:date => Date
|
15
|
-
def parse_row(row)
|
16
|
-
amount = row[6].sub(/,/,'.').to_f
|
17
|
-
{
|
18
|
-
:amount => (row[5] == 'Bij' ? amount : -amount),
|
19
|
-
:date => Date.parse(row[0]),
|
20
|
-
:description => [row[1], row[8]].map{|i| i.strip.gsub(/\s+/, ' ')}.join(' '),
|
21
|
-
:currency => 'EUR'
|
22
|
-
}
|
23
|
-
end
|
24
|
-
|
25
|
-
|
26
|
-
def self.options
|
27
|
-
{:col_sep => ",", :headers => true}
|
28
|
-
end
|
29
|
-
|
30
|
-
def options
|
31
|
-
self.class.options
|
32
|
-
end
|
33
|
-
end # /Ing
|
34
|
-
|
35
|
-
class Abn
|
36
|
-
require 'time'
|
37
|
-
|
38
|
-
# Expected: Hash with:
|
39
|
-
#:amount => Float,
|
40
|
-
#:currency => String,
|
41
|
-
#:description => String,
|
42
|
-
#:date => Date
|
43
|
-
def parse_row(row)
|
44
|
-
{
|
45
|
-
:amount => row[6].sub(/,/,'.').to_f,
|
46
|
-
:date => Date.parse(row[2]),
|
47
|
-
:description => row[7].strip.gsub(/\s+/,' '),
|
48
|
-
:currency => row[1]
|
49
|
-
}
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.options
|
53
|
-
{:col_sep => "\t"}
|
54
|
-
end
|
55
|
-
|
56
|
-
def options
|
57
|
-
self.class.options
|
58
|
-
end
|
59
|
-
end # /Abn
|
60
|
-
|
61
|
-
class AbnCC
|
62
|
-
require 'time'
|
7
|
+
class Helper
|
8
|
+
require 'highline/import'
|
9
|
+
require "terminal-table"
|
63
10
|
|
64
|
-
|
65
|
-
|
66
|
-
#:currency => String,
|
67
|
-
#:description => String,
|
68
|
-
#:date => Date
|
69
|
-
def parse_row(row)
|
70
|
-
amount = row[2].sub(/,/,'.').to_f
|
71
|
-
{
|
72
|
-
:amount => (row[8] == 'D' ? -amount : amount),
|
73
|
-
:date => Date.parse(row[0]),
|
74
|
-
:description => row[3],
|
75
|
-
:currency => 'EUR'
|
76
|
-
}
|
77
|
-
end
|
11
|
+
attr_reader :config
|
12
|
+
attr_accessor :row
|
78
13
|
|
79
|
-
|
80
|
-
|
81
|
-
|
14
|
+
def initialize(config = {})
|
15
|
+
@config = config
|
16
|
+
end
|
82
17
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
18
|
+
def with_row(row, &block)
|
19
|
+
@row = row
|
20
|
+
instance_eval(&block)
|
21
|
+
ensure
|
22
|
+
@row = nil
|
23
|
+
end
|
88
24
|
|
89
|
-
|
90
|
-
|
25
|
+
def highline
|
26
|
+
@highline ||= HighLine.new($stdin, $stderr)
|
27
|
+
end
|
91
28
|
|
92
|
-
def
|
93
|
-
|
94
|
-
@default_account = default_account
|
95
|
-
@_transactions = transactions
|
29
|
+
def ask(question, &block)
|
30
|
+
highline.ask(question, &block)
|
96
31
|
end
|
97
32
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
33
|
+
# Prompts the user for an account-name.
|
34
|
+
#
|
35
|
+
# @param question [String] the question
|
36
|
+
# @param options [Hash]
|
37
|
+
# @option options [String] :default (nil) account-name that will be used
|
38
|
+
# if no input is provided.
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# ask_account("What account did this money come from?",
|
42
|
+
# default: 'Expenses:Various')
|
43
|
+
# What account did this money come from? |Expenses:Various|
|
44
|
+
# _
|
45
|
+
#
|
46
|
+
# @return [String] the account-name
|
47
|
+
def ask_account(question, options = {})
|
48
|
+
options = { default: nil }.merge(options)
|
49
|
+
highline.ask(question) do |q|
|
50
|
+
q.default = options[:default] if options[:default]
|
113
51
|
end
|
114
52
|
end
|
115
53
|
|
116
|
-
|
117
|
-
|
118
|
-
{
|
119
|
-
|
120
|
-
{{to}} {{spacer}}{{currency}} {{amount}}{{to_tags}}
|
121
|
-
{{from}}{{from_tags}}
|
122
|
-
|
123
|
-
{{/ transactions}}
|
124
|
-
TEMPLATE
|
54
|
+
def render_row(options = {})
|
55
|
+
options = { columns: [] }.merge(options)
|
56
|
+
_row = options[:columns].map{|i| row[i] }
|
57
|
+
$stderr.puts Terminal::Table.new(rows: [ _row ])
|
125
58
|
end
|
126
|
-
|
127
|
-
protected
|
128
|
-
def fmt_amount(amount)
|
129
|
-
("%10.2f" % amount).split('.') * options[:decimal_mark]
|
130
|
-
end
|
131
59
|
end
|
132
60
|
|
133
|
-
class
|
134
|
-
|
61
|
+
class Config
|
62
|
+
YAML::add_builtin_type('proc') {|_, val| eval("proc{ #{val} }") }
|
135
63
|
|
136
|
-
def initialize
|
137
|
-
|
138
|
-
@
|
64
|
+
def initialize(options = {})
|
65
|
+
options = {file: 'total_recall.yml'}.merge(options)
|
66
|
+
@config_file = File.expand_path(options[:file])
|
139
67
|
end
|
140
68
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
search_vector = []
|
146
|
-
account_vectors = {}
|
147
|
-
|
148
|
-
query_tokens.each do |token|
|
149
|
-
idf = Math.log((accounts.keys.length + 1) / ((tokens[token] || {}).keys.length.to_f + 1))
|
150
|
-
tf = 1.0 / query_tokens.length.to_f
|
151
|
-
search_vector << tf*idf
|
69
|
+
def config
|
70
|
+
@config ||= YAML.load_file(@config_file)
|
71
|
+
end
|
152
72
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
end
|
158
|
-
end
|
73
|
+
def csv_file
|
74
|
+
config[:csv][:file] &&
|
75
|
+
File.expand_path(config[:csv][:file], File.dirname(@config_file))
|
76
|
+
end
|
159
77
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
78
|
+
def csv
|
79
|
+
@csv ||= begin
|
80
|
+
csv_raw = csv_file ? File.read(csv_file) : config[:csv][:raw]
|
81
|
+
CSV.parse(csv_raw, config[:csv][:options] || {})
|
164
82
|
end
|
165
|
-
|
166
|
-
account_vectors.sort! {|a, b| b[:cosine] <=> a[:cosine] }
|
167
|
-
account_vectors.first && account_vectors.first[:account]
|
168
83
|
end
|
169
84
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
tokenize(data).each do |token|
|
174
|
-
tokens[token] ||= {}
|
175
|
-
tokens[token][account] ||= 0
|
176
|
-
tokens[token][account] += 1
|
177
|
-
accounts[account] += 1
|
178
|
-
end
|
85
|
+
def template_file
|
86
|
+
config[:template][:file] &&
|
87
|
+
File.expand_path(config[:template][:file], File.dirname(@config_file))
|
179
88
|
end
|
180
89
|
|
181
|
-
|
182
|
-
|
183
|
-
|
90
|
+
def template
|
91
|
+
@template ||= begin
|
92
|
+
template_file ? File.read(template_file) : config[:template][:raw]
|
184
93
|
end
|
185
|
-
end
|
186
|
-
|
187
|
-
class BankParser
|
188
|
-
require 'csv'
|
189
|
-
attr_reader :strategy
|
190
|
-
|
191
|
-
def initialize(options={})
|
192
|
-
@strategy = options[:strategy]
|
193
94
|
end
|
194
95
|
|
195
|
-
|
196
|
-
|
197
|
-
# @example
|
198
|
-
# parser = TotalRecall::BankParser.new(:strategy => TotalRecall::ParseStrategy::Some.new)
|
199
|
-
# parser.parse("12.1\n1.99") #=> [{:amount => 12.1}, {:amount => 1.99}]
|
200
|
-
#
|
201
|
-
# @param [String] str content of csv to parse.
|
202
|
-
# @return [Array<Hash>]
|
203
|
-
def parse(str, options={})
|
204
|
-
options = strategy.options.merge(options)
|
205
|
-
|
206
|
-
result = []
|
207
|
-
CSV.parse(str, options){|row| result << strategy.parse_row(row)}
|
208
|
-
result
|
96
|
+
def session
|
97
|
+
@session ||= Helper.new(config)
|
209
98
|
end
|
210
|
-
end #/BankParser
|
211
|
-
|
212
99
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
require "bayes_motel"
|
217
|
-
|
218
|
-
no_tasks do
|
219
|
-
def self.strategies
|
220
|
-
{
|
221
|
-
'abn' => TotalRecall::ParseStrategy::Abn,
|
222
|
-
'ing' => TotalRecall::ParseStrategy::Ing,
|
223
|
-
'abncc' => TotalRecall::ParseStrategy::AbnCC
|
224
|
-
}
|
225
|
-
end
|
100
|
+
def context
|
101
|
+
@context ||= config[:context].merge(transactions: transactions)
|
102
|
+
end
|
226
103
|
|
227
|
-
|
228
|
-
|
104
|
+
def transactions
|
105
|
+
@transactions ||= begin
|
106
|
+
csv.each_with_object([]) do |row, result|
|
107
|
+
result << transaction_defaults.merge(transactions_config).each_with_object({}) do |(k,v), cfg|
|
108
|
+
next if k[/^__/]
|
109
|
+
cfg[k] = v.respond_to?(:call) ? session.with_row(row, &v) : v
|
110
|
+
end
|
111
|
+
end
|
229
112
|
end
|
113
|
+
end
|
230
114
|
|
231
|
-
|
232
|
-
|
115
|
+
def transaction_defaults
|
116
|
+
@transaction_defaults ||= begin
|
117
|
+
defaults = transactions_config[:__defaults__] || {}
|
118
|
+
defaults.each_with_object({}) do |(k,v), result|
|
119
|
+
result[k] = v.respond_to?(:call) ? session.with_row(nil, &v) : v
|
120
|
+
end
|
233
121
|
end
|
122
|
+
end
|
234
123
|
|
235
|
-
|
236
|
-
|
237
|
-
end
|
124
|
+
def transactions_config
|
125
|
+
config[:context][:transactions]
|
238
126
|
end
|
239
127
|
|
240
|
-
desc "ledger", "Convert input to ledger-transactions"
|
241
|
-
method_option :input, :aliases => "-i", :desc => "CSV file to use for input", :required => true
|
242
|
-
method_option :parser, :aliases => "-p", :desc => "Parser to use (one of #{strategies.keys.inspect})", :required => true
|
243
128
|
def ledger
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
guesser = AccountGuesser.new
|
249
|
-
|
250
|
-
default_account = highline.ask("What is the account name of this bank account in Ledger?\n> ")
|
251
|
-
transactions = [] # [{<row>, :account => 'Expenses:Car'}, ...]
|
252
|
-
tags = []
|
253
|
-
start_from = 0
|
254
|
-
begin
|
255
|
-
rows.each_with_index do |row, ix|
|
256
|
-
if start_from && start_from != ix
|
257
|
-
next
|
258
|
-
elsif start_from
|
259
|
-
start_from = nil
|
260
|
-
end
|
261
|
-
|
262
|
-
guessed = guesser.guess(row[:description])
|
263
|
-
question = row[:amount] > 0 ? "What account provided this income?" : "To which account did this money go?"
|
129
|
+
Mustache.render(template, context)
|
130
|
+
end
|
131
|
+
end
|
264
132
|
|
265
|
-
|
266
|
-
|
267
|
-
account = account.empty? ? guessed : account # defaults not working
|
268
|
-
case account
|
269
|
-
when 'd'
|
270
|
-
break
|
271
|
-
when 's'
|
272
|
-
next
|
273
|
-
when 'p'
|
274
|
-
transactions.pop
|
275
|
-
start_from = [0, (ix - 1)].max
|
276
|
-
raise
|
277
|
-
end
|
133
|
+
class Cli < Thor
|
134
|
+
require 'total_recall/version'
|
278
135
|
|
279
|
-
|
280
|
-
|
281
|
-
tag = highline.choose do |menu|
|
282
|
-
menu.shell = true
|
283
|
-
menu.prompt = "Add tags? "
|
284
|
-
menu.prompt << "(#{transaction_tags.join(',')})" if transaction_tags.any?
|
136
|
+
include Thor::Actions
|
137
|
+
source_root File.expand_path('../total_recall/templates', __FILE__)
|
285
138
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
transaction_tags << tag
|
292
|
-
end
|
139
|
+
desc "ledger", "Convert the config to a ledger"
|
140
|
+
method_option :config, :aliases => "-c", :desc => "Config file", :required => true
|
141
|
+
def ledger
|
142
|
+
puts TotalRecall::Config.new(file: File.expand_path(options[:config])).ledger
|
143
|
+
end
|
293
144
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
$stderr.puts "Let's do that again"
|
299
|
-
retry
|
300
|
-
end
|
145
|
+
desc "sample", "Generate an annotated config"
|
146
|
+
def sample
|
147
|
+
@version = TotalRecall::VERSION
|
148
|
+
template("sample.yml.tt")
|
301
149
|
|
302
|
-
|
150
|
+
say "Now run '#{$0} ledger -c sample.yml' to generate the ledger"
|
303
151
|
end
|
304
152
|
|
305
|
-
desc "
|
306
|
-
|
307
|
-
|
308
|
-
def parse
|
309
|
-
strategy = strategies[options[:parser]]
|
310
|
-
file_contents = File.read(options[:input])
|
311
|
-
data = parser(strategy.new).parse(file_contents)
|
153
|
+
desc "init NAME", "Generate a minimal config NAME.yml"
|
154
|
+
def init(name = "total_recall")
|
155
|
+
destination = name + ".yml"
|
312
156
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
t << [ row[:date], row[:amount], row[:description] ]
|
317
|
-
end
|
318
|
-
end
|
319
|
-
puts table
|
157
|
+
@version = TotalRecall::VERSION
|
158
|
+
@name = name
|
159
|
+
template("simple.yml.tt", destination)
|
320
160
|
end
|
321
|
-
|
322
161
|
end
|
323
162
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
---
|
2
|
+
:total_recall:
|
3
|
+
# The version of total_recall that generated this file
|
4
|
+
:version: <%= @version %>
|
5
|
+
:template:
|
6
|
+
# 1). Provide the [mustache-template](https://github.com/defunkt/mustache)
|
7
|
+
# for your ledger.
|
8
|
+
#
|
9
|
+
# Either assign a template to :raw: or point to a file via :file:.
|
10
|
+
# The location of :file: is relative to this file.
|
11
|
+
# :file: takes precedence over :raw:
|
12
|
+
#
|
13
|
+
# The template MUST contain a section `transactions`.
|
14
|
+
#
|
15
|
+
#:file: ledger.mustache
|
16
|
+
:raw: |-
|
17
|
+
; -*- ledger -*-¬
|
18
|
+
; Generated by total_recall at {{generated_at}}
|
19
|
+
--decimal-comma
|
20
|
+
|
21
|
+
{{# transactions}}
|
22
|
+
{{date}} {{{description}}}
|
23
|
+
{{to}} {{currency}} {{amount}}
|
24
|
+
{{from}}
|
25
|
+
|
26
|
+
{{/ transactions}}
|
27
|
+
|
28
|
+
:csv:
|
29
|
+
# 2). Provide the csv
|
30
|
+
#
|
31
|
+
# Either assign csv to :raw: or point to a csv-file via :file:.
|
32
|
+
# :raw: is ideal for experimenting with the csv-format.
|
33
|
+
#
|
34
|
+
# The location of :file: is relative to this file.
|
35
|
+
# :file: takes precedence over :raw:
|
36
|
+
#
|
37
|
+
#:file: sample.csv
|
38
|
+
:raw: |-
|
39
|
+
"03.11.2013";"Foo";"04.11.2013";"1.638,00";""
|
40
|
+
"03.11.2013";"Bar";"04.11.2013";"-492,93";""
|
41
|
+
:options:
|
42
|
+
# Any option accepted by CSV#new (http://www.ruby-doc.org/stdlib-2.1.1/libdoc/csv/rdoc/CSV.html#method-c-new).
|
43
|
+
:col_sep: ";"
|
44
|
+
:headers: false
|
45
|
+
|
46
|
+
:context:
|
47
|
+
# 3). Define the context
|
48
|
+
#
|
49
|
+
# The context is in essence the dictionary that will be applied to the template.
|
50
|
+
# The value for transactions will be expanded to an array, one for every line in the csv.
|
51
|
+
:transactions:
|
52
|
+
# transactions have defaults...
|
53
|
+
:__defaults__:
|
54
|
+
:from: !!proc |
|
55
|
+
ask_account("What account provides these transactions?",
|
56
|
+
default: 'Assets:Checking')
|
57
|
+
:currency: $
|
58
|
+
# ...and fields that vary per transaction.
|
59
|
+
# Helper method exist such as `row`, `ask_account` (see `TotalRecall::Helper` for more).
|
60
|
+
:date: !!proc |
|
61
|
+
# For weird date formats consider using Date#strptime (http://www.ruby-doc.org/stdlib-2.1.1/libdoc/date/rdoc/Date.html#method-c-strptime)
|
62
|
+
Date.parse(row[0])
|
63
|
+
:description: !!proc row[1]
|
64
|
+
:amount: !!proc row[3]
|
65
|
+
:to: !!proc |
|
66
|
+
# show the row and let the user decide
|
67
|
+
render_row(columns: [0, 1, 3])
|
68
|
+
ask_account("To what account did the money go?")
|
69
|
+
:generated_at: !!proc Time.now
|
70
|
+
|
71
|
+
# You can add any other data needed:
|
72
|
+
# :ticker_sybols:
|
73
|
+
# "Apple": AAPL
|
74
|
+
# "Google Inc.": GOOG
|
75
|
+
#
|
76
|
+
# Example usage:
|
77
|
+
# :context:
|
78
|
+
# :transactions:
|
79
|
+
# :symbol: !!proc config[:ticker_symbols][row[1]]
|
@@ -0,0 +1,35 @@
|
|
1
|
+
---
|
2
|
+
:total_recall:
|
3
|
+
:version: <%= @version %>
|
4
|
+
:template:
|
5
|
+
:raw: |-
|
6
|
+
; -*- ledger -*-¬
|
7
|
+
|
8
|
+
{{# transactions}}
|
9
|
+
{{date}} {{{description}}}
|
10
|
+
{{to}} {{currency}} {{amount}}
|
11
|
+
{{from}}
|
12
|
+
|
13
|
+
{{/ transactions}}
|
14
|
+
|
15
|
+
:csv:
|
16
|
+
#:file: <%= @name %>.csv
|
17
|
+
:raw: |-
|
18
|
+
"03.11.2013";"Foo";"04.11.2013";"1.638,00";""
|
19
|
+
"03.11.2013";"Bar";"04.11.2013";"-492,93";""
|
20
|
+
:options:
|
21
|
+
:col_sep: ";"
|
22
|
+
:headers: false
|
23
|
+
|
24
|
+
:context:
|
25
|
+
:transactions:
|
26
|
+
:__defaults__:
|
27
|
+
:from: !!proc |
|
28
|
+
ask_account("What account provides these transactions?", default: 'Assets:Checking')
|
29
|
+
:currency: $
|
30
|
+
:date: !!proc Date.parse(row[0])
|
31
|
+
:description: !!proc row[1]
|
32
|
+
:amount: !!proc row[3]
|
33
|
+
:to: !!proc |
|
34
|
+
render_row(columns: [0, 1, 3, 4])
|
35
|
+
ask_account("To what account did the money go?")
|
data/lib/total_recall/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -4,12 +4,4 @@ Bundler.setup
|
|
4
4
|
|
5
5
|
$:.unshift File.expand_path("../../lib", __FILE__)
|
6
6
|
require "total_recall"
|
7
|
-
|
8
|
-
# fullpath to csv-file
|
9
|
-
def fixture_pathname(str)
|
10
|
-
Pathname(%W(spec fixtures #{str}.csv).join(File::SEPARATOR)).realpath
|
11
|
-
end
|
12
|
-
|
13
|
-
def fixture_contents(str)
|
14
|
-
File.read(fixture_pathname(str))
|
15
|
-
end
|
7
|
+
require 'fakefs/spec_helpers'
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe TotalRecall::Config do
|
4
|
+
include FakeFS::SpecHelpers
|
5
|
+
|
6
|
+
def stubbed_file(path, content)
|
7
|
+
# SOURCE http://edgeapi.rubyonrails.org/classes/String.html#method-i-strip_heredoc
|
8
|
+
indent = content.scan(/^[ \t]*(?=\S)/).min.size rescue 0
|
9
|
+
content = content.gsub(/^[ \t]{#{indent}}/, '')
|
10
|
+
|
11
|
+
FakeFS do
|
12
|
+
File.open(path, 'w'){|f| f.print content }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def instance_with_config(config, options = {})
|
17
|
+
options = {file: 'config.yml'}.merge(options)
|
18
|
+
stubbed_file(options[:file], config)
|
19
|
+
described_class.new(options)
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#config' do
|
23
|
+
it 'yields the config as hash' do
|
24
|
+
instance = instance_with_config(<<-CONFIG)
|
25
|
+
:csv:
|
26
|
+
:raw: Some csv
|
27
|
+
:a: 1
|
28
|
+
CONFIG
|
29
|
+
|
30
|
+
expect(instance.config).to eql({csv: { raw: 'Some csv'}, a: 1})
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#csv' do
|
35
|
+
it 'yields csv assigned to :raw' do
|
36
|
+
instance = instance_with_config(<<-CONFIG)
|
37
|
+
:csv:
|
38
|
+
:raw: Some csv
|
39
|
+
CONFIG
|
40
|
+
|
41
|
+
expect(instance.csv).to eql(CSV.parse('Some csv'))
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'yields csv from file :file' do
|
45
|
+
csv_file = stubbed_file('some.csv', 'Some csv')
|
46
|
+
instance = instance_with_config(<<-CONFIG)
|
47
|
+
:csv:
|
48
|
+
:file: some.csv
|
49
|
+
CONFIG
|
50
|
+
|
51
|
+
expect(instance.csv).to eql(CSV.parse('Some csv'))
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'yields csv from :file when both :raw and :file are configured' do
|
55
|
+
csv_file = stubbed_file('some.csv', 'Some csv')
|
56
|
+
instance = instance_with_config(<<-CONFIG)
|
57
|
+
:csv:
|
58
|
+
:file: some.csv
|
59
|
+
:raw: Some raw csv
|
60
|
+
CONFIG
|
61
|
+
|
62
|
+
expect(instance.csv).to eql(CSV.parse('Some csv'))
|
63
|
+
end
|
64
|
+
|
65
|
+
specify 'csv-options are passed on to CSV#read' do
|
66
|
+
instance = instance_with_config(<<-CONFIG)
|
67
|
+
:csv:
|
68
|
+
:options:
|
69
|
+
:option1: true
|
70
|
+
CONFIG
|
71
|
+
|
72
|
+
expect(CSV).to receive(:parse).with(anything(), { option1: true })
|
73
|
+
instance.csv
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#template' do
|
78
|
+
it 'yields template assigned to :raw' do
|
79
|
+
instance = instance_with_config(<<-CONFIG)
|
80
|
+
:template:
|
81
|
+
:raw: |-
|
82
|
+
Raw template
|
83
|
+
here
|
84
|
+
CONFIG
|
85
|
+
|
86
|
+
expect(instance.template).to eql("Raw template\nhere")
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'yields template from file :file' do
|
90
|
+
template_file = stubbed_file('template.mustache', 'File template')
|
91
|
+
instance = instance_with_config(<<-CONFIG)
|
92
|
+
:template:
|
93
|
+
:file: template.mustache
|
94
|
+
CONFIG
|
95
|
+
|
96
|
+
expect(instance.template).to eql('File template')
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'yields template from :file when both :raw and :file are configured' do
|
100
|
+
template_file = stubbed_file('template.mustache', 'File template')
|
101
|
+
instance = instance_with_config(<<-CONFIG)
|
102
|
+
:template:
|
103
|
+
:file: template.mustache
|
104
|
+
:raw: Raw template
|
105
|
+
CONFIG
|
106
|
+
|
107
|
+
expect(instance.template).to eql('File template')
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe 'YAML types' do
|
112
|
+
it 'allows proc-types' do
|
113
|
+
instance = instance_with_config(<<-CONFIG)
|
114
|
+
:a: !!proc 1 + 1
|
115
|
+
:b: !!proc |
|
116
|
+
1 + 1
|
117
|
+
CONFIG
|
118
|
+
|
119
|
+
expect(instance.config[:a].call).to eq 2
|
120
|
+
expect(instance.config[:b].call).to eq 2
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe '#context' do
|
125
|
+
it 'has a transaction per line of csv' do
|
126
|
+
instance = instance_with_config(<<-CONFIG)
|
127
|
+
:csv:
|
128
|
+
:raw: |-
|
129
|
+
1
|
130
|
+
1
|
131
|
+
:context:
|
132
|
+
:transactions:
|
133
|
+
:from: From
|
134
|
+
:to: !!proc 1 + 1
|
135
|
+
:amount: !!proc row[0]
|
136
|
+
CONFIG
|
137
|
+
|
138
|
+
transactions = instance.context[:transactions]
|
139
|
+
expect(transactions.size).to eq 2
|
140
|
+
|
141
|
+
transaction = transactions.first
|
142
|
+
expect(transaction).to match({from: 'From', to: 2, amount: "1"})
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'adds defaults to every transaction' do
|
146
|
+
instance = instance_with_config(<<-CONFIG)
|
147
|
+
:csv:
|
148
|
+
:raw: |-
|
149
|
+
line 1
|
150
|
+
line 2
|
151
|
+
:context:
|
152
|
+
:transactions:
|
153
|
+
:__defaults__:
|
154
|
+
:default: !!proc 1
|
155
|
+
:from: From
|
156
|
+
CONFIG
|
157
|
+
|
158
|
+
transaction = instance.context[:transactions].first
|
159
|
+
|
160
|
+
expect(transaction).to match({from: 'From', default: 1})
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'may contain any other settings' do
|
164
|
+
instance = instance_with_config(<<-CONFIG)
|
165
|
+
:csv:
|
166
|
+
:raw: some csv
|
167
|
+
:context:
|
168
|
+
:transactions:
|
169
|
+
:from: From
|
170
|
+
:a: 1
|
171
|
+
CONFIG
|
172
|
+
|
173
|
+
expect(instance.context).to match(transactions: [{from: 'From'}], a: 1)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
data/total_recall.gemspec
CHANGED
@@ -1,24 +1,29 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'total_recall/version'
|
3
5
|
|
4
6
|
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "total_recall"
|
8
|
+
gem.version = TotalRecall::VERSION
|
5
9
|
gem.authors = ["Gert Goet"]
|
6
10
|
gem.email = ["gert@thinkcreate.nl"]
|
7
|
-
gem.description = %q{Turn
|
8
|
-
gem.summary = %q{Turn
|
9
|
-
gem.homepage = ""
|
11
|
+
gem.description = %q{Turn any csv into a Ledger journal}
|
12
|
+
gem.summary = %q{Turn any csv into a Ledger journal}
|
13
|
+
gem.homepage = "https://github.com/eval/total_recall"
|
14
|
+
gem.license = "MIT"
|
10
15
|
|
11
|
-
gem.
|
12
|
-
gem.
|
13
|
-
gem.test_files =
|
14
|
-
gem.name = "total_recall"
|
16
|
+
gem.files = `git ls-files -z`.split("\x0")
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
15
19
|
gem.require_paths = ["lib"]
|
16
|
-
gem.version = TotalRecall::VERSION
|
17
20
|
|
18
|
-
gem.add_dependency
|
19
|
-
gem.add_dependency
|
20
|
-
gem.add_dependency
|
21
|
-
gem.add_dependency
|
22
|
-
gem.
|
23
|
-
gem.add_development_dependency
|
21
|
+
gem.add_dependency 'thor', '~> 0.19'
|
22
|
+
gem.add_dependency 'terminal-table', '~> 1.4'
|
23
|
+
gem.add_dependency 'highline', '~> 1.6'
|
24
|
+
gem.add_dependency 'mustache', '~> 0.99'
|
25
|
+
gem.add_development_dependency "bundler", "~> 1.6"
|
26
|
+
gem.add_development_dependency "rake"
|
27
|
+
gem.add_development_dependency 'rspec', '~> 3.0.0'
|
28
|
+
gem.add_development_dependency 'fakefs'
|
24
29
|
end
|
metadata
CHANGED
@@ -1,83 +1,128 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: total_recall
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.4.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Gert Goet
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2014-06-04 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: thor
|
16
|
-
requirement:
|
17
|
-
none: false
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
18
16
|
requirements:
|
19
|
-
- - ~>
|
17
|
+
- - "~>"
|
20
18
|
- !ruby/object:Gem::Version
|
21
|
-
version: 0.
|
19
|
+
version: '0.19'
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
|
-
version_requirements:
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.19'
|
25
27
|
- !ruby/object:Gem::Dependency
|
26
28
|
name: terminal-table
|
27
|
-
requirement:
|
28
|
-
none: false
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
29
30
|
requirements:
|
30
|
-
- - ~>
|
31
|
+
- - "~>"
|
31
32
|
- !ruby/object:Gem::Version
|
32
|
-
version: 1.4
|
33
|
+
version: '1.4'
|
33
34
|
type: :runtime
|
34
35
|
prerelease: false
|
35
|
-
version_requirements:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.4'
|
36
41
|
- !ruby/object:Gem::Dependency
|
37
42
|
name: highline
|
38
|
-
requirement:
|
39
|
-
none: false
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
40
44
|
requirements:
|
41
|
-
- - ~>
|
45
|
+
- - "~>"
|
42
46
|
- !ruby/object:Gem::Version
|
43
|
-
version: 1.6
|
47
|
+
version: '1.6'
|
44
48
|
type: :runtime
|
45
49
|
prerelease: false
|
46
|
-
version_requirements:
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.6'
|
47
55
|
- !ruby/object:Gem::Dependency
|
48
|
-
name:
|
49
|
-
requirement:
|
50
|
-
none: false
|
56
|
+
name: mustache
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
51
58
|
requirements:
|
52
|
-
- - ~>
|
59
|
+
- - "~>"
|
53
60
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0.
|
61
|
+
version: '0.99'
|
55
62
|
type: :runtime
|
56
63
|
prerelease: false
|
57
|
-
version_requirements:
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.99'
|
58
69
|
- !ruby/object:Gem::Dependency
|
59
|
-
name:
|
60
|
-
requirement:
|
61
|
-
none: false
|
70
|
+
name: bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
62
72
|
requirements:
|
63
|
-
- - ~>
|
73
|
+
- - "~>"
|
64
74
|
- !ruby/object:Gem::Version
|
65
|
-
version:
|
66
|
-
type: :
|
75
|
+
version: '1.6'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.6'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
67
91
|
prerelease: false
|
68
|
-
version_requirements:
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
69
97
|
- !ruby/object:Gem::Dependency
|
70
98
|
name: rspec
|
71
|
-
requirement:
|
72
|
-
none: false
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
73
100
|
requirements:
|
74
|
-
- - ~>
|
101
|
+
- - "~>"
|
75
102
|
- !ruby/object:Gem::Version
|
76
|
-
version:
|
103
|
+
version: 3.0.0
|
77
104
|
type: :development
|
78
105
|
prerelease: false
|
79
|
-
version_requirements:
|
80
|
-
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 3.0.0
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: fakefs
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: Turn any csv into a Ledger journal
|
81
126
|
email:
|
82
127
|
- gert@thinkcreate.nl
|
83
128
|
executables:
|
@@ -86,57 +131,46 @@ executables:
|
|
86
131
|
extensions: []
|
87
132
|
extra_rdoc_files: []
|
88
133
|
files:
|
89
|
-
- .gitignore
|
134
|
+
- ".gitignore"
|
135
|
+
- ".travis.yml"
|
90
136
|
- Gemfile
|
91
137
|
- README.md
|
92
138
|
- Rakefile
|
93
139
|
- bin/total_recall
|
94
140
|
- bin/total_recall.rb
|
95
141
|
- lib/total_recall.rb
|
142
|
+
- lib/total_recall/templates/sample.yml.tt
|
143
|
+
- lib/total_recall/templates/simple.yml.tt
|
96
144
|
- lib/total_recall/version.rb
|
97
|
-
- spec/fixtures/.keep
|
98
|
-
- spec/fixtures/abn.csv
|
99
|
-
- spec/fixtures/abncc.csv
|
100
|
-
- spec/fixtures/ing.csv
|
101
145
|
- spec/spec.opts
|
102
146
|
- spec/spec_helper.rb
|
103
|
-
- spec/total_recall/
|
147
|
+
- spec/total_recall/total_recall_spec.rb
|
104
148
|
- total_recall.gemspec
|
105
|
-
homepage:
|
106
|
-
licenses:
|
149
|
+
homepage: https://github.com/eval/total_recall
|
150
|
+
licenses:
|
151
|
+
- MIT
|
152
|
+
metadata: {}
|
107
153
|
post_install_message:
|
108
154
|
rdoc_options: []
|
109
155
|
require_paths:
|
110
156
|
- lib
|
111
157
|
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
-
none: false
|
113
158
|
requirements:
|
114
|
-
- -
|
159
|
+
- - ">="
|
115
160
|
- !ruby/object:Gem::Version
|
116
161
|
version: '0'
|
117
|
-
segments:
|
118
|
-
- 0
|
119
|
-
hash: -1362278848564129285
|
120
162
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
-
none: false
|
122
163
|
requirements:
|
123
|
-
- -
|
164
|
+
- - ">="
|
124
165
|
- !ruby/object:Gem::Version
|
125
166
|
version: '0'
|
126
|
-
segments:
|
127
|
-
- 0
|
128
|
-
hash: -1362278848564129285
|
129
167
|
requirements: []
|
130
168
|
rubyforge_project:
|
131
|
-
rubygems_version:
|
169
|
+
rubygems_version: 2.2.2
|
132
170
|
signing_key:
|
133
|
-
specification_version:
|
134
|
-
summary: Turn
|
171
|
+
specification_version: 4
|
172
|
+
summary: Turn any csv into a Ledger journal
|
135
173
|
test_files:
|
136
|
-
- spec/fixtures/.keep
|
137
|
-
- spec/fixtures/abn.csv
|
138
|
-
- spec/fixtures/abncc.csv
|
139
|
-
- spec/fixtures/ing.csv
|
140
174
|
- spec/spec.opts
|
141
175
|
- spec/spec_helper.rb
|
142
|
-
- spec/total_recall/
|
176
|
+
- spec/total_recall/total_recall_spec.rb
|
data/spec/fixtures/.keep
DELETED
File without changes
|
data/spec/fixtures/abn.csv
DELETED
data/spec/fixtures/abncc.csv
DELETED
data/spec/fixtures/ing.csv
DELETED
@@ -1,3 +0,0 @@
|
|
1
|
-
"Datum","Naam / Omschrijving","Rekening","Tegenrekening","Code","Af Bij","Bedrag (EUR)","MutatieSoort","Mededelingen"
|
2
|
-
"20101231","Omschrijving ","123456789","012345678","GT","Af","100,00","Internetbankieren","mededeling eèéêë"
|
3
|
-
"20101230","Omschrijving ","123456789","012345678","GT","Af","200,00","Internetbankieren","mededeling "
|
@@ -1,57 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
-
|
3
|
-
describe TotalRecall::BankParser do
|
4
|
-
context '#parse' do
|
5
|
-
subject{ described_class.new(:strategy => TotalRecall::ParseStrategy::Abn.new) }
|
6
|
-
|
7
|
-
it "should return an Array" do
|
8
|
-
subject.parse(fixture_contents('abn')).class.should == Array
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
|
14
|
-
shared_examples "a parser" do
|
15
|
-
context '#parse_row' do
|
16
|
-
it "should return a hash" do
|
17
|
-
CSV.parse(fixture_contents(@fixture), subject.options).each do |i|
|
18
|
-
parsed_row = subject.parse_row(i)
|
19
|
-
|
20
|
-
parsed_row.class.should == Hash
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
it "should return the correct keys and classes" do
|
25
|
-
expected_keys_and_classes = {
|
26
|
-
:amount => Float,
|
27
|
-
:currency => String,
|
28
|
-
:description => String,
|
29
|
-
:date => Date
|
30
|
-
}
|
31
|
-
|
32
|
-
CSV.parse(fixture_contents(@fixture), subject.options).each do |i|
|
33
|
-
parsed_row = subject.parse_row(i)
|
34
|
-
|
35
|
-
parsed_row.keys.should =~ expected_keys_and_classes.keys
|
36
|
-
expected_keys_and_classes.each do |key, klass|
|
37
|
-
parsed_row.send(:[], key).class.should == klass
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
describe TotalRecall::ParseStrategy::Abn do
|
45
|
-
before{ @fixture = 'abn' }
|
46
|
-
it_behaves_like "a parser"
|
47
|
-
end
|
48
|
-
|
49
|
-
describe TotalRecall::ParseStrategy::Ing do
|
50
|
-
before{ @fixture = 'ing' }
|
51
|
-
it_behaves_like "a parser"
|
52
|
-
end
|
53
|
-
|
54
|
-
describe TotalRecall::ParseStrategy::AbnCC do
|
55
|
-
before{ @fixture = 'abncc' }
|
56
|
-
it_behaves_like "a parser"
|
57
|
-
end
|