total_recall 0.2.0 → 0.4.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.
- 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 [](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
|