total_recall 0.1.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 +18 -0
- data/Gemfile +4 -0
- data/README.md +55 -0
- data/Rakefile +15 -0
- data/bin/total_recall +4 -0
- data/bin/total_recall.rb +4 -0
- data/lib/total_recall.rb +295 -0
- data/lib/total_recall/version.rb +3 -0
- data/spec/fixtures/.keep +0 -0
- data/spec/fixtures/abn.csv +2 -0
- data/spec/fixtures/ing.csv +3 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/total_recall/bank_parser_spec.rb +52 -0
- data/total_recall.gemspec +24 -0
- metadata +135 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
TotalRecall
|
2
|
+
============
|
3
|
+
|
4
|
+
Turn your bank record csv's into a [Ledger](http://ledger-cli.org/3.0/doc/ledger3.html) journal.
|
5
|
+
|
6
|
+
Usage
|
7
|
+
------------
|
8
|
+
|
9
|
+
$ total_recall
|
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
|
14
|
+
|
15
|
+
# typically you would do:
|
16
|
+
$ total_recall ledger -i abn.csv -p abn > abn.dat
|
17
|
+
|
18
|
+
Develop
|
19
|
+
------------
|
20
|
+
|
21
|
+
$ git clone git://github.com/eval/total_recall.git
|
22
|
+
$ cd total_recall
|
23
|
+
$ bundle
|
24
|
+
$ rake spec
|
25
|
+
|
26
|
+
Author
|
27
|
+
------
|
28
|
+
|
29
|
+
Gert Goet (eval) :: gert@thinkcreate.nl :: @gertgoet
|
30
|
+
|
31
|
+
License
|
32
|
+
------
|
33
|
+
|
34
|
+
(The MIT license)
|
35
|
+
|
36
|
+
Copyright (c) 2011 Gert Goet, ThinkCreate
|
37
|
+
|
38
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
39
|
+
a copy of this software and associated documentation files (the
|
40
|
+
"Software"), to deal in the Software without restriction, including
|
41
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
42
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
43
|
+
permit persons to whom the Software is furnished to do so, subject to
|
44
|
+
the following conditions:
|
45
|
+
|
46
|
+
The above copyright notice and this permission notice shall be
|
47
|
+
included in all copies or substantial portions of the Software.
|
48
|
+
|
49
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
50
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
51
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
52
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
53
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
54
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
55
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new do |t|
|
6
|
+
t.rspec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "Run the specs"
|
10
|
+
task :default => :spec
|
11
|
+
|
12
|
+
desc 'Removes trailing whitespace'
|
13
|
+
task :whitespace do
|
14
|
+
sh %{find . -name '*.rb' -exec sed -i '' 's/ *$//g' {} \\;}
|
15
|
+
end
|
data/bin/total_recall
ADDED
data/bin/total_recall.rb
ADDED
data/lib/total_recall.rb
ADDED
@@ -0,0 +1,295 @@
|
|
1
|
+
require "total_recall/version"
|
2
|
+
require "thor"
|
3
|
+
require "mustache"
|
4
|
+
|
5
|
+
module TotalRecall
|
6
|
+
module ParseStrategy
|
7
|
+
class Ing
|
8
|
+
require 'time'
|
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
|
+
end # /ParseStrategy
|
61
|
+
|
62
|
+
class Ledger < Mustache
|
63
|
+
attr_reader :default_account, :options
|
64
|
+
|
65
|
+
def initialize(default_account, transactions, options={})
|
66
|
+
@options = {:max_account_width => 80, :decimal_mark => ','}.merge(options)
|
67
|
+
@default_account = default_account
|
68
|
+
@_transactions = transactions
|
69
|
+
end
|
70
|
+
|
71
|
+
def transactions
|
72
|
+
@transactions ||= begin
|
73
|
+
@_transactions.map do |i|
|
74
|
+
res = {}
|
75
|
+
res[:to], res[:from] = (i[:amount] > 0 ? [default_account, i[:account]] : [i[:account], default_account])
|
76
|
+
if i[:tags].any?
|
77
|
+
tagsline = "\n ; :%s:" % (i[:tags] * ':')
|
78
|
+
tagskey = i[:amount] > 0 ? :from_tags : :to_tags
|
79
|
+
res[tagskey] = tagsline
|
80
|
+
end
|
81
|
+
res[:amount] = fmt_amount(i[:amount].abs)
|
82
|
+
res[:description], res[:date], res[:currency] = i[:description], i[:date], i[:currency]
|
83
|
+
res[:spacer] = " " * (options[:max_account_width] - res[:to].size)
|
84
|
+
res
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
def self.template;<<-TEMPLATE
|
91
|
+
{{# transactions}}
|
92
|
+
{{date}} {{description}}
|
93
|
+
{{to}} {{spacer}}{{currency}} {{amount}}{{to_tags}}
|
94
|
+
{{from}}{{from_tags}}
|
95
|
+
|
96
|
+
{{/ transactions}}
|
97
|
+
TEMPLATE
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
def fmt_amount(amount)
|
102
|
+
("%10.2f" % amount).split('.') * options[:decimal_mark]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class AccountGuesser
|
107
|
+
attr_reader :accounts, :tokens
|
108
|
+
|
109
|
+
def initialize
|
110
|
+
@accounts = {}
|
111
|
+
@tokens = {}
|
112
|
+
end
|
113
|
+
|
114
|
+
# copied from reckon(https://github.com/iterationlabs/reckon)
|
115
|
+
def guess(data)
|
116
|
+
query_tokens = tokenize(data)
|
117
|
+
|
118
|
+
search_vector = []
|
119
|
+
account_vectors = {}
|
120
|
+
|
121
|
+
query_tokens.each do |token|
|
122
|
+
idf = Math.log((accounts.keys.length + 1) / ((tokens[token] || {}).keys.length.to_f + 1))
|
123
|
+
tf = 1.0 / query_tokens.length.to_f
|
124
|
+
search_vector << tf*idf
|
125
|
+
|
126
|
+
accounts.each do |account, total_terms|
|
127
|
+
tf = (tokens[token] && tokens[token][account]) ? tokens[token][account] / total_terms.to_f : 0
|
128
|
+
account_vectors[account] ||= []
|
129
|
+
account_vectors[account] << tf*idf
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Should I normalize the vectors? Probably unnecessary due to tf-idf and short documents.
|
134
|
+
account_vectors = account_vectors.to_a.map do |account, account_vector|
|
135
|
+
{ :cosine => (0...account_vector.length).to_a.inject(0) { |m, i| m + search_vector[i] * account_vector[i] },
|
136
|
+
:account => account }
|
137
|
+
end
|
138
|
+
|
139
|
+
account_vectors.sort! {|a, b| b[:cosine] <=> a[:cosine] }
|
140
|
+
account_vectors.first && account_vectors.first[:account]
|
141
|
+
end
|
142
|
+
|
143
|
+
# copied from reckon(https://github.com/iterationlabs/reckon)
|
144
|
+
def learn(account, data)
|
145
|
+
accounts[account] ||= 0
|
146
|
+
tokenize(data).each do |token|
|
147
|
+
tokens[token] ||= {}
|
148
|
+
tokens[token][account] ||= 0
|
149
|
+
tokens[token][account] += 1
|
150
|
+
accounts[account] += 1
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
protected
|
155
|
+
def tokenize(str)
|
156
|
+
str.downcase.split(/[\s\-]/)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
class BankParser
|
161
|
+
require 'csv'
|
162
|
+
attr_reader :strategy
|
163
|
+
|
164
|
+
def initialize(options={})
|
165
|
+
@strategy = options[:strategy]
|
166
|
+
end
|
167
|
+
|
168
|
+
# Parses csv content and returns array of hashes.
|
169
|
+
#
|
170
|
+
# @example
|
171
|
+
# parser = TotalRecall::BankParser.new(:strategy => TotalRecall::ParseStrategy::Some.new)
|
172
|
+
# parser.parse("12.1\n1.99") #=> [{:amount => 12.1}, {:amount => 1.99}]
|
173
|
+
#
|
174
|
+
# @param [String] str content of csv to parse.
|
175
|
+
# @return [Array<Hash>]
|
176
|
+
def parse(str, options={})
|
177
|
+
options = strategy.options.merge(options)
|
178
|
+
|
179
|
+
result = []
|
180
|
+
CSV.parse(str, options){|row| result << strategy.parse_row(row)}
|
181
|
+
result
|
182
|
+
end
|
183
|
+
end #/BankParser
|
184
|
+
|
185
|
+
|
186
|
+
class Cli < Thor
|
187
|
+
require "terminal-table"
|
188
|
+
require "highline/import"
|
189
|
+
require "bayes_motel"
|
190
|
+
|
191
|
+
no_tasks do
|
192
|
+
def self.strategies
|
193
|
+
{
|
194
|
+
'abn' => TotalRecall::ParseStrategy::Abn,
|
195
|
+
'ing' => TotalRecall::ParseStrategy::Ing
|
196
|
+
}
|
197
|
+
end
|
198
|
+
|
199
|
+
def highline
|
200
|
+
@highline ||= HighLine.new($stdin, $stderr)
|
201
|
+
end
|
202
|
+
|
203
|
+
def strategies
|
204
|
+
self.class.strategies
|
205
|
+
end
|
206
|
+
|
207
|
+
def parser(strategy)
|
208
|
+
TotalRecall::BankParser.new(:strategy => strategy)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
desc "ledger", "Convert input to ledger-transactions"
|
213
|
+
method_option :input, :aliases => "-i", :desc => "CSV file to use for input", :required => true
|
214
|
+
method_option :parser, :aliases => "-p", :desc => "Parser to use (one of #{strategies.keys.inspect})", :required => true
|
215
|
+
def ledger
|
216
|
+
strategy = strategies[options[:parser]]
|
217
|
+
file_contents = File.read(options[:input])
|
218
|
+
rows = parser(strategy.new).parse(file_contents)
|
219
|
+
|
220
|
+
guesser = AccountGuesser.new
|
221
|
+
|
222
|
+
default_account = highline.ask("What is the account name of this bank account in Ledger?\n> ")
|
223
|
+
transactions = [] # [{<row>, :account => 'Expenses:Car'}, ...]
|
224
|
+
tags = []
|
225
|
+
start_from = 0
|
226
|
+
begin
|
227
|
+
rows.each_with_index do |row, ix|
|
228
|
+
if start_from && start_from != ix
|
229
|
+
next
|
230
|
+
elsif start_from
|
231
|
+
start_from = nil
|
232
|
+
end
|
233
|
+
|
234
|
+
guessed = guesser.guess(row[:description])
|
235
|
+
question = row[:amount] > 0 ? "What account provided this income?" : "To which account did this money go?"
|
236
|
+
|
237
|
+
$stderr.puts Terminal::Table.new(:rows => [ [ row[:date], row[:amount], row[:description] ] ])
|
238
|
+
account = highline.ask("#{question} (#{guessed}) or [d]one, [s]kip, [p]revious\n> ")
|
239
|
+
account = account.empty? ? guessed : account # defaults not working
|
240
|
+
case account
|
241
|
+
when 'd'
|
242
|
+
break
|
243
|
+
when 's'
|
244
|
+
next
|
245
|
+
when 'p'
|
246
|
+
transactions.pop
|
247
|
+
start_from = [0, (ix - 1)].max
|
248
|
+
raise
|
249
|
+
end
|
250
|
+
|
251
|
+
transaction_tags = []
|
252
|
+
loop do
|
253
|
+
tag = highline.choose do |menu|
|
254
|
+
menu.shell = true
|
255
|
+
menu.prompt = "Add tags? "
|
256
|
+
menu.prompt << "(#{transaction_tags.join(',')})" if transaction_tags.any?
|
257
|
+
|
258
|
+
menu.choices('Done'){ nil }
|
259
|
+
menu.choices(*tags - transaction_tags)
|
260
|
+
menu.choice('New...'){ newtag = highline.ask('Tag: ');tags << newtag; newtag }
|
261
|
+
end
|
262
|
+
break unless tag
|
263
|
+
transaction_tags << tag
|
264
|
+
end
|
265
|
+
|
266
|
+
guesser.learn(account, row[:description])
|
267
|
+
transactions << row.merge(:account => account, :tags => transaction_tags)
|
268
|
+
end
|
269
|
+
rescue
|
270
|
+
$stderr.puts "Let's do that again"
|
271
|
+
retry
|
272
|
+
end
|
273
|
+
|
274
|
+
puts Ledger.new(default_account, transactions).render
|
275
|
+
end
|
276
|
+
|
277
|
+
desc "parse", "Parses input-file and prints it"
|
278
|
+
method_option :input, :aliases => "-i", :desc => "CSV file to use for input", :required => true
|
279
|
+
method_option :parser, :aliases => "-p", :desc => "Parser to use (one of #{strategies.keys.join(',')})", :required => true
|
280
|
+
def parse
|
281
|
+
strategy = strategies[options[:parser]]
|
282
|
+
file_contents = File.read(options[:input])
|
283
|
+
data = parser(strategy.new).parse(file_contents)
|
284
|
+
|
285
|
+
table = Terminal::Table.new do |t|
|
286
|
+
t.headings = 'Date', 'Amount', 'Description'
|
287
|
+
data.each do |row|
|
288
|
+
t << [ row[:date], row[:amount], row[:description] ]
|
289
|
+
end
|
290
|
+
end
|
291
|
+
puts table
|
292
|
+
end
|
293
|
+
|
294
|
+
end
|
295
|
+
end
|
data/spec/fixtures/.keep
ADDED
File without changes
|
@@ -0,0 +1,3 @@
|
|
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 "
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler"
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
$:.unshift File.expand_path("../../lib", __FILE__)
|
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
|
@@ -0,0 +1,52 @@
|
|
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
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/total_recall/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Gert Goet"]
|
6
|
+
gem.email = ["gert@thinkcreate.nl"]
|
7
|
+
gem.description = %q{Turn your bank records csv's into Ledger journals}
|
8
|
+
gem.summary = %q{Turn your bank records csv's into Ledger journals}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "total_recall"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = TotalRecall::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency('thor', '~> 0.14.6')
|
19
|
+
gem.add_dependency('terminal-table', '~> 1.4.4')
|
20
|
+
gem.add_dependency('highline', '~> 1.6.1')
|
21
|
+
gem.add_dependency('bayes_motel', '~> 0.1.0')
|
22
|
+
gem.add_dependency('mustache', '~> 0.99.4')
|
23
|
+
gem.add_development_dependency('rspec', '~> 2.7.0')
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: total_recall
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Gert Goet
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-01-09 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: thor
|
16
|
+
requirement: &70281655447440 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.14.6
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70281655447440
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: terminal-table
|
27
|
+
requirement: &70281655446380 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.4.4
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70281655446380
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: highline
|
38
|
+
requirement: &70281655445700 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.6.1
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70281655445700
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: bayes_motel
|
49
|
+
requirement: &70281655444800 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.1.0
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70281655444800
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: mustache
|
60
|
+
requirement: &70281655444120 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 0.99.4
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70281655444120
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: &70281655443400 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 2.7.0
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70281655443400
|
80
|
+
description: Turn your bank records csv's into Ledger journals
|
81
|
+
email:
|
82
|
+
- gert@thinkcreate.nl
|
83
|
+
executables:
|
84
|
+
- total_recall
|
85
|
+
- total_recall.rb
|
86
|
+
extensions: []
|
87
|
+
extra_rdoc_files: []
|
88
|
+
files:
|
89
|
+
- .gitignore
|
90
|
+
- Gemfile
|
91
|
+
- README.md
|
92
|
+
- Rakefile
|
93
|
+
- bin/total_recall
|
94
|
+
- bin/total_recall.rb
|
95
|
+
- lib/total_recall.rb
|
96
|
+
- lib/total_recall/version.rb
|
97
|
+
- spec/fixtures/.keep
|
98
|
+
- spec/fixtures/abn.csv
|
99
|
+
- spec/fixtures/ing.csv
|
100
|
+
- spec/spec.opts
|
101
|
+
- spec/spec_helper.rb
|
102
|
+
- spec/total_recall/bank_parser_spec.rb
|
103
|
+
- total_recall.gemspec
|
104
|
+
homepage: ''
|
105
|
+
licenses: []
|
106
|
+
post_install_message:
|
107
|
+
rdoc_options: []
|
108
|
+
require_paths:
|
109
|
+
- lib
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ! '>='
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
requirements: []
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 1.8.10
|
125
|
+
signing_key:
|
126
|
+
specification_version: 3
|
127
|
+
summary: Turn your bank records csv's into Ledger journals
|
128
|
+
test_files:
|
129
|
+
- spec/fixtures/.keep
|
130
|
+
- spec/fixtures/abn.csv
|
131
|
+
- spec/fixtures/ing.csv
|
132
|
+
- spec/spec.opts
|
133
|
+
- spec/spec_helper.rb
|
134
|
+
- spec/total_recall/bank_parser_spec.rb
|
135
|
+
has_rdoc:
|