reckon 0.7.1 → 0.8.1
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 +4 -4
- data/.github/workflows/ruby.yml +7 -13
- data/.gitignore +3 -0
- data/CHANGELOG.md +43 -1
- data/Gemfile.lock +3 -1
- data/README.md +8 -5
- data/Rakefile +2 -2
- data/bin/reckon +3 -0
- data/lib/reckon/app.rb +6 -6
- data/lib/reckon/cosine_similarity.rb +67 -62
- data/lib/reckon/date_column.rb +3 -2
- data/lib/reckon/ledger_parser.rb +1 -1
- data/lib/reckon/money.rb +12 -5
- data/lib/reckon/options.rb +9 -1
- data/lib/reckon/version.rb +1 -1
- data/reckon.gemspec +1 -0
- data/spec/cosine_training_and_test.rb +52 -0
- data/spec/integration/another_bank_example/output.ledger +3 -3
- data/spec/integration/ask_for_account/cli_input.exp +33 -0
- data/spec/integration/ask_for_account/expected_output +11 -0
- data/spec/integration/ask_for_account/input.csv +9 -0
- data/spec/integration/ask_for_account/test_args +1 -0
- data/spec/integration/broker_canada_example/output.ledger +2 -2
- data/spec/integration/chase/account_tokens_and_regex/output.ledger +3 -3
- data/spec/integration/chase/default_account_names/output.ledger +3 -3
- data/spec/integration/chase/learn_from_existing/output.ledger +3 -3
- data/spec/integration/chase/simple/output.ledger +3 -3
- data/spec/integration/danish_kroner_nordea_example/output.ledger +1 -1
- data/spec/integration/extratofake/output.ledger +1 -1
- data/spec/integration/harder_date_example/output.ledger +2 -2
- data/spec/integration/ledger_date_format/compare_cmds +1 -0
- data/spec/integration/ledger_date_format/input.csv +3 -0
- data/spec/integration/ledger_date_format/output.ledger +12 -0
- data/spec/integration/ledger_date_format/test_args +1 -0
- data/spec/integration/test.sh +79 -27
- data/spec/reckon/app_spec.rb +1 -1
- data/spec/reckon/csv_parser_spec.rb +3 -3
- data/spec/reckon/date_column_spec.rb +12 -0
- data/spec/reckon/money_spec.rb +3 -3
- metadata +160 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '08cdfad096b04db11f671775c0119fcecefa7db46134f1c43677e949628498db'
|
|
4
|
+
data.tar.gz: 5ed2722b2843a5147faabdd82b0027fe590ff9dd66000100e756fad6de13a116
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7ebe28c482f091424d405efa1dc4b94cdc76022b114ce6f0db22a96e3ec6104a6e1efecec833af981f9c97f4c705cbbbd7f791403b5b669452abccd2caf295c8
|
|
7
|
+
data.tar.gz: 7af0ef7c71bcca9b189d23939ebeb93eedbe0875373818419d878461c33035a5e92359204c939ac3d8933935ad29b8a9cec319ddb84ea13626e22f51897f2fea
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -21,23 +21,17 @@ jobs:
|
|
|
21
21
|
matrix:
|
|
22
22
|
ruby-version:
|
|
23
23
|
# Current ruby stable version
|
|
24
|
+
- 3.1.2
|
|
25
|
+
# Ubuntu 22.04
|
|
24
26
|
- 3.0
|
|
25
|
-
# Ubuntu 20.
|
|
27
|
+
# Ubuntu 20.04
|
|
26
28
|
- 2.7
|
|
27
|
-
# Ubuntu 19.10
|
|
28
|
-
- 2.5
|
|
29
|
-
# Mac v11 Big Sur
|
|
30
|
-
# - 2.6?
|
|
31
|
-
# Mac v10.15 Catalina
|
|
32
|
-
- 2.6
|
|
33
|
-
# Mac v10.14 Mojave
|
|
34
|
-
- 2.3.7
|
|
35
29
|
steps:
|
|
36
30
|
- uses: actions/checkout@v2
|
|
31
|
+
- name: Update package
|
|
32
|
+
run: sudo apt-get update
|
|
37
33
|
- name: Install packages
|
|
38
|
-
run: sudo apt-get -y install ledger hledger
|
|
39
|
-
- name: Install bundler
|
|
40
|
-
run: sudo gem install -v 1.17.3 bundler
|
|
34
|
+
run: sudo apt-get -y install ledger hledger expect
|
|
41
35
|
- name: Set up Ruby
|
|
42
36
|
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
|
43
37
|
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
|
@@ -45,6 +39,6 @@ jobs:
|
|
|
45
39
|
# uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
|
|
46
40
|
with:
|
|
47
41
|
ruby-version: ${{ matrix.ruby-version }}
|
|
48
|
-
bundler-cache: true # runs 'bundle install' and caches installed gems
|
|
42
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems
|
|
49
43
|
- name: Run tests
|
|
50
44
|
run: bundle exec rake test_all
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [v0.
|
|
3
|
+
## [v0.8.1](https://github.com/cantino/reckon/tree/v0.8.1) (2022-07-02)
|
|
4
|
+
|
|
5
|
+
[Full Changelog](https://github.com/cantino/reckon/compare/v0.8.0...v0.8.1)
|
|
6
|
+
|
|
7
|
+
**Closed issues:**
|
|
8
|
+
|
|
9
|
+
- Reckon files every transaction \(incoming and outgoing\) as Income:Unknown [\#116](https://github.com/cantino/reckon/issues/116)
|
|
10
|
+
|
|
11
|
+
**Merged pull requests:**
|
|
12
|
+
|
|
13
|
+
- fix bugs with test.sh [\#117](https://github.com/cantino/reckon/pull/117) ([benprew](https://github.com/benprew))
|
|
14
|
+
|
|
15
|
+
## [v0.8.0](https://github.com/cantino/reckon/tree/v0.8.0) (2021-08-08)
|
|
16
|
+
|
|
17
|
+
[Full Changelog](https://github.com/cantino/reckon/compare/v0.7.2...v0.8.0)
|
|
18
|
+
|
|
19
|
+
**Closed issues:**
|
|
20
|
+
|
|
21
|
+
- --date-format '%d/%m/%Y' not working [\#113](https://github.com/cantino/reckon/issues/113)
|
|
22
|
+
- Reckon behaviour does not match what is explained on README.md [\#112](https://github.com/cantino/reckon/issues/112)
|
|
23
|
+
- --date-format '%d/%m/%Y' not working [\#111](https://github.com/cantino/reckon/issues/111)
|
|
24
|
+
- --date-format '%d/ [\#110](https://github.com/cantino/reckon/issues/110)
|
|
25
|
+
|
|
26
|
+
**Merged pull requests:**
|
|
27
|
+
|
|
28
|
+
- Add ledger-date-format option to specify ledger file date format [\#114](https://github.com/cantino/reckon/pull/114) ([benprew](https://github.com/benprew))
|
|
29
|
+
|
|
30
|
+
## [v0.7.2](https://github.com/cantino/reckon/tree/v0.7.2) (2021-04-22)
|
|
31
|
+
|
|
32
|
+
[Full Changelog](https://github.com/cantino/reckon/compare/v0.7.1...v0.7.2)
|
|
33
|
+
|
|
34
|
+
**Closed issues:**
|
|
35
|
+
|
|
36
|
+
- \[feature request\] Better format for large transactions [\#108](https://github.com/cantino/reckon/issues/108)
|
|
37
|
+
- cosine similarity not comparing documents correctly [\#106](https://github.com/cantino/reckon/issues/106)
|
|
38
|
+
|
|
39
|
+
**Merged pull requests:**
|
|
40
|
+
|
|
41
|
+
- Add thousands separator in money output. Fixes \#108. [\#109](https://github.com/cantino/reckon/pull/109) ([benprew](https://github.com/benprew))
|
|
42
|
+
- Cosine similarity should use all docs tokens. not just matched tokens. [\#107](https://github.com/cantino/reckon/pull/107) ([benprew](https://github.com/benprew))
|
|
43
|
+
- Test getting expect working with actions [\#105](https://github.com/cantino/reckon/pull/105) ([benprew](https://github.com/benprew))
|
|
44
|
+
|
|
45
|
+
## [v0.7.1](https://github.com/cantino/reckon/tree/v0.7.1) (2021-02-07)
|
|
4
46
|
|
|
5
47
|
[Full Changelog](https://github.com/cantino/reckon/compare/v0.7.0...v0.7.1)
|
|
6
48
|
|
data/Gemfile.lock
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
reckon (0.
|
|
4
|
+
reckon (0.8.1)
|
|
5
5
|
chronic (>= 0.3.0)
|
|
6
6
|
highline (>= 1.5.2)
|
|
7
|
+
matrix (>= 0.4.2)
|
|
7
8
|
rchardet (>= 1.8.0)
|
|
8
9
|
|
|
9
10
|
GEM
|
|
@@ -13,6 +14,7 @@ GEM
|
|
|
13
14
|
coderay (1.1.2)
|
|
14
15
|
diff-lcs (1.3)
|
|
15
16
|
highline (2.0.3)
|
|
17
|
+
matrix (0.4.2)
|
|
16
18
|
method_source (0.9.2)
|
|
17
19
|
pry (0.12.2)
|
|
18
20
|
coderay (~> 1.1.0)
|
data/README.md
CHANGED
|
@@ -58,10 +58,13 @@ Learn more:
|
|
|
58
58
|
--encoding 'UTF-8'
|
|
59
59
|
Specify an encoding for the CSV file
|
|
60
60
|
-c, --currency '$' Currency symbol to use - default $ (ex £, EUR)
|
|
61
|
-
--date-format
|
|
62
|
-
|
|
61
|
+
--date-format FORMAT
|
|
62
|
+
CSV file date format (see `date` for format)
|
|
63
|
+
--ledger-date-format FORMAT
|
|
64
|
+
Ledger date format (see `date` for format)
|
|
63
65
|
-u, --unattended Don't ask questions and guess all the accounts automatically. Use with --learn-from or --account-tokens options.
|
|
64
66
|
-t, --account-tokens FILE YAML file with manually-assigned tokens for each account (see README)
|
|
67
|
+
--table-output-file FILE
|
|
65
68
|
--default-into-account NAME
|
|
66
69
|
Default into account
|
|
67
70
|
--default-outof-account NAME
|
|
@@ -80,13 +83,13 @@ If you find CSV files that it can't parse, send me examples or pull requests!
|
|
|
80
83
|
You can run reckon in a non-interactive mode.
|
|
81
84
|
To guess the accounts reckon can use an existing ledger file or a token file with keywords.
|
|
82
85
|
|
|
83
|
-
`reckon --unattended -l 2010.dat -f bank.csv -o ledger.dat`
|
|
86
|
+
`reckon --unattended -a Checking -l 2010.dat -f bank.csv -o ledger.dat`
|
|
84
87
|
|
|
85
|
-
`reckon --unattended --account-tokens tokens.yaml -f bank.csv -o ledger.dat`
|
|
88
|
+
`reckon --unattended -a Checking --account-tokens tokens.yaml -f bank.csv -o ledger.dat`
|
|
86
89
|
|
|
87
90
|
In unattended mode, you can use STDIN to read your csv data, by specifying `-` as the argument to `-f`.
|
|
88
91
|
|
|
89
|
-
`csv_file_generator | reckon --unattended -l 2010.dat -o ledger.dat -f -`
|
|
92
|
+
`csv_file_generator | reckon --unattended -a Checking -l 2010.dat -o ledger.dat -f -`
|
|
90
93
|
|
|
91
94
|
### Account Tokens
|
|
92
95
|
|
data/Rakefile
CHANGED
|
@@ -17,6 +17,6 @@ task :test_all do
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
task :integration_tests do
|
|
20
|
-
|
|
21
|
-
raise 'Integration tests failed'
|
|
20
|
+
cmd = 'prove -v ./spec/integration/test.sh'
|
|
21
|
+
raise 'Integration tests failed' unless system(cmd)
|
|
22
22
|
end
|
data/bin/reckon
CHANGED
data/lib/reckon/app.rb
CHANGED
|
@@ -20,10 +20,10 @@ module Reckon
|
|
|
20
20
|
learn!
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
def interactive_output(str)
|
|
23
|
+
def interactive_output(str, fh = $stdout)
|
|
24
24
|
return if options[:unattended]
|
|
25
25
|
|
|
26
|
-
puts str
|
|
26
|
+
fh.puts str
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def learn!
|
|
@@ -161,7 +161,7 @@ module Reckon
|
|
|
161
161
|
rows.sort_by { |n| [n[:date], -n[:money], n[:description]] }.each { |row| yield row }
|
|
162
162
|
end
|
|
163
163
|
|
|
164
|
-
def print_transaction(rows)
|
|
164
|
+
def print_transaction(rows, fh = $stdout)
|
|
165
165
|
str = "\n"
|
|
166
166
|
header = %w[Date Amount Description Note]
|
|
167
167
|
maxes = header.map(&:length)
|
|
@@ -185,7 +185,7 @@ module Reckon
|
|
|
185
185
|
str += "\n"
|
|
186
186
|
end
|
|
187
187
|
|
|
188
|
-
interactive_output str
|
|
188
|
+
interactive_output str, fh
|
|
189
189
|
end
|
|
190
190
|
|
|
191
191
|
def ask_account_question(msg, row)
|
|
@@ -280,12 +280,12 @@ module Reckon
|
|
|
280
280
|
exit
|
|
281
281
|
end
|
|
282
282
|
|
|
283
|
-
def output_table
|
|
283
|
+
def output_table(fh = $stdout)
|
|
284
284
|
rows = []
|
|
285
285
|
each_row_backwards do |row|
|
|
286
286
|
rows << row
|
|
287
287
|
end
|
|
288
|
-
print_transaction(rows)
|
|
288
|
+
print_transaction(rows, fh)
|
|
289
289
|
end
|
|
290
290
|
end
|
|
291
291
|
end
|
|
@@ -1,47 +1,52 @@
|
|
|
1
1
|
require 'matrix'
|
|
2
2
|
require 'set'
|
|
3
3
|
|
|
4
|
-
# Implementation of
|
|
5
|
-
#
|
|
4
|
+
# Implementation of cosine similarity using TF-IDF for vectorization.
|
|
5
|
+
#
|
|
6
|
+
# In information retrieval, tf–idf, short for term frequency–inverse document frequency,
|
|
7
|
+
# is a numerical statistic that is intended to reflect how important a word is to a
|
|
8
|
+
# document in a collection or corpus
|
|
9
|
+
#
|
|
10
|
+
# Cosine Similarity a measurement to determine how similar 2 documents are to each other.
|
|
11
|
+
#
|
|
12
|
+
# These weights and measures are used to suggest which account a transaction should be
|
|
13
|
+
# assigned to.
|
|
6
14
|
module Reckon
|
|
7
15
|
class CosineSimilarity
|
|
16
|
+
DocumentInfo = Struct.new(:tokens, :accounts)
|
|
17
|
+
|
|
8
18
|
def initialize(options)
|
|
19
|
+
@docs = DocumentInfo.new({}, {})
|
|
9
20
|
@options = options
|
|
10
|
-
@tokens = {}
|
|
11
|
-
@accounts = Hash.new(0)
|
|
12
21
|
end
|
|
13
22
|
|
|
14
23
|
def add_document(account, doc)
|
|
15
|
-
tokenize(doc)
|
|
24
|
+
tokens = tokenize(doc)
|
|
25
|
+
LOGGER.info "doc tokens: #{tokens}"
|
|
26
|
+
tokens.each do |n|
|
|
16
27
|
(token, count) = n
|
|
17
28
|
|
|
18
|
-
@tokens[token] ||=
|
|
19
|
-
@tokens[token][account]
|
|
20
|
-
@
|
|
21
|
-
@accounts[account] += count
|
|
29
|
+
@docs.tokens[token] ||= Hash.new(0)
|
|
30
|
+
@docs.tokens[token][account] += count
|
|
31
|
+
@docs.accounts[account] ||= Hash.new(0)
|
|
32
|
+
@docs.accounts[account][token] += count
|
|
22
33
|
end
|
|
23
34
|
end
|
|
24
35
|
|
|
25
36
|
# find most similar documents to query
|
|
26
37
|
def find_similar(query)
|
|
27
|
-
|
|
38
|
+
LOGGER.info "find_similar #{query}"
|
|
28
39
|
|
|
29
|
-
|
|
40
|
+
accounts = docs_to_check(query).map do |a|
|
|
41
|
+
[a, tfidf(@docs.accounts[a])]
|
|
42
|
+
end
|
|
30
43
|
|
|
31
|
-
|
|
32
|
-
suggestions = corpus_scores.map do |account, scores|
|
|
33
|
-
acct_vector = Vector.elements(scores, false)
|
|
44
|
+
q = tfidf(tokenize(query))
|
|
34
45
|
|
|
35
|
-
|
|
36
|
-
# similarity is a float between 1 and -1, where 1 is exactly the same and -1 is
|
|
37
|
-
# exactly opposite
|
|
38
|
-
# see https://en.wikipedia.org/wiki/Cosine_similarity
|
|
39
|
-
# cos(theta) = (A . B) / (||A|| ||B||)
|
|
40
|
-
# where A . B is the "dot product" and ||A|| is the magnitude of A
|
|
41
|
-
# ruby has the 'matrix' library we can use to do these calculations.
|
|
46
|
+
suggestions = accounts.map do |a, d|
|
|
42
47
|
{
|
|
43
|
-
similarity:
|
|
44
|
-
account:
|
|
48
|
+
similarity: calc_similarity(q, d),
|
|
49
|
+
account: a
|
|
45
50
|
}
|
|
46
51
|
end.select { |n| n[:similarity] > 0 }.sort_by { |n| -n[:similarity] }
|
|
47
52
|
|
|
@@ -52,50 +57,51 @@ module Reckon
|
|
|
52
57
|
|
|
53
58
|
private
|
|
54
59
|
|
|
55
|
-
def
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
corpus_scores = {}
|
|
59
|
-
query_scores = []
|
|
60
|
-
num_docs = @accounts.length
|
|
61
|
-
|
|
62
|
-
query_tokens.each do |n|
|
|
63
|
-
(token, _count) = n
|
|
64
|
-
next unless @tokens[token]
|
|
65
|
-
corpus = corpus.union(Set.new(@tokens[token].keys))
|
|
60
|
+
def docs_to_check(query)
|
|
61
|
+
return tokenize(query).reduce(Set.new) do |corpus, t|
|
|
62
|
+
corpus.union(Set.new(@docs.tokens[t[0]]&.keys))
|
|
66
63
|
end
|
|
64
|
+
end
|
|
67
65
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
# if no other docs have token, ignore it
|
|
72
|
-
next unless @tokens[token]
|
|
66
|
+
def tfidf(tokens)
|
|
67
|
+
scores = {}
|
|
73
68
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@tokens[
|
|
79
|
-
|
|
69
|
+
tokens.each do |t, n|
|
|
70
|
+
scores[t] = calc_tf_idf(
|
|
71
|
+
n,
|
|
72
|
+
tokens.length,
|
|
73
|
+
@docs.tokens[t]&.length&.to_f || 0,
|
|
74
|
+
@docs.accounts.length
|
|
80
75
|
)
|
|
81
|
-
|
|
82
|
-
## Next, calculate for the corpus, where our "account" is a document
|
|
83
|
-
corpus.each do |account|
|
|
84
|
-
corpus_scores[account] ||= []
|
|
85
|
-
|
|
86
|
-
corpus_scores[account] << calc_tf_idf(
|
|
87
|
-
(@tokens[token][account] || 0),
|
|
88
|
-
@accounts[account].to_f,
|
|
89
|
-
@tokens[token].length.to_f,
|
|
90
|
-
num_docs
|
|
91
|
-
)
|
|
92
|
-
end
|
|
93
76
|
end
|
|
94
|
-
|
|
77
|
+
|
|
78
|
+
return scores
|
|
95
79
|
end
|
|
96
80
|
|
|
97
|
-
|
|
81
|
+
# Cosine similarity is used to compare how similar 2 documents are. Returns a float
|
|
82
|
+
# between 1 and -1, where 1 is exactly the same and -1 is exactly opposite.
|
|
83
|
+
#
|
|
84
|
+
# see https://en.wikipedia.org/wiki/Cosine_similarity
|
|
85
|
+
# cos(theta) = (A . B) / (||A|| ||B||)
|
|
86
|
+
# where A . B is the "dot product" and ||A|| is the magnitude of A
|
|
87
|
+
#
|
|
88
|
+
# The variables A and B are the set of unique terms in q and d.
|
|
89
|
+
#
|
|
90
|
+
# For example, when q = "big red balloon" and d ="small green balloon" then the
|
|
91
|
+
# variables are (big,red,balloon,small,green) and a = (1,1,1,0,0) and b =
|
|
92
|
+
# (0,0,1,1,1).
|
|
93
|
+
#
|
|
94
|
+
# query and doc are hashes of token => tf/idf score
|
|
95
|
+
def calc_similarity(query, doc)
|
|
96
|
+
tokens = Set.new(query.keys + doc.keys)
|
|
97
|
+
|
|
98
|
+
a = Vector.elements(tokens.map { |n| query[n] || 0 }, false)
|
|
99
|
+
b = Vector.elements(tokens.map { |n| doc[n] || 0 }, false)
|
|
100
|
+
|
|
101
|
+
return a.inner_product(b) / (a.magnitude * b.magnitude)
|
|
102
|
+
end
|
|
98
103
|
|
|
104
|
+
def calc_tf_idf(token_count, num_words_in_doc, df, num_docs)
|
|
99
105
|
# tf(t,d) = count of t in d / number of words in d
|
|
100
106
|
tf = token_count / num_words_in_doc.to_f
|
|
101
107
|
|
|
@@ -109,14 +115,13 @@ module Reckon
|
|
|
109
115
|
end
|
|
110
116
|
|
|
111
117
|
def tokenize(str)
|
|
112
|
-
mk_tokens(str).
|
|
118
|
+
mk_tokens(str).each_with_object(Hash.new(0)) do |n, memo|
|
|
113
119
|
memo[n] += 1
|
|
114
|
-
memo
|
|
115
120
|
end.to_a
|
|
116
121
|
end
|
|
117
122
|
|
|
118
123
|
def mk_tokens(str)
|
|
119
|
-
str.downcase.tr(';', ' ').tr("'", '').split(/[^a-z0-9.]+/)
|
|
124
|
+
str.downcase.tr(';', ' ').tr("'", '').split(/[^a-z0-9.]+/).reject(&:empty?)
|
|
120
125
|
end
|
|
121
126
|
end
|
|
122
127
|
end
|
data/lib/reckon/date_column.rb
CHANGED
|
@@ -2,12 +2,13 @@ module Reckon
|
|
|
2
2
|
class DateColumn < Array
|
|
3
3
|
attr_accessor :endian_precedence
|
|
4
4
|
def initialize( arr = [], options = {} )
|
|
5
|
+
@options = options
|
|
5
6
|
arr.each do |value|
|
|
6
7
|
if options[:date_format]
|
|
7
8
|
begin
|
|
8
9
|
value = Date.strptime(value, options[:date_format])
|
|
9
10
|
rescue
|
|
10
|
-
puts "I'm having trouble parsing #{value} with the desired format: #{options[:date_format]}"
|
|
11
|
+
puts "I'm having trouble parsing '#{value}' with the desired format: #{options[:date_format]}"
|
|
11
12
|
exit 1
|
|
12
13
|
end
|
|
13
14
|
else
|
|
@@ -53,7 +54,7 @@ module Reckon
|
|
|
53
54
|
date = self.for(index)
|
|
54
55
|
return "" if date.nil?
|
|
55
56
|
|
|
56
|
-
date.
|
|
57
|
+
date.strftime(@options[:ledger_date_format] || '%Y-%m-%d')
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
def self.likelihood(entry)
|
data/lib/reckon/ledger_parser.rb
CHANGED
|
@@ -114,7 +114,7 @@ module Reckon
|
|
|
114
114
|
|
|
115
115
|
def initialize(ledger, options = {})
|
|
116
116
|
@options = options
|
|
117
|
-
@date_format = options[:date_format] || '%Y-%m-%d'
|
|
117
|
+
@date_format = options[:ledger_date_format] || options[:date_format] || '%Y-%m-%d'
|
|
118
118
|
parse(ledger)
|
|
119
119
|
end
|
|
120
120
|
|
data/lib/reckon/money.rb
CHANGED
|
@@ -50,11 +50,18 @@ module Reckon
|
|
|
50
50
|
return @amount_raw[0] == '-' ? @amount_raw[1..-1] : "-#{@amount_raw}"
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
amt = pretty_amount(@amount * (negate ? -1 : 1))
|
|
54
|
+
amt = if @suffixed
|
|
55
|
+
"#{amt} #{@currency}"
|
|
56
|
+
else
|
|
57
|
+
amt.gsub(/^((-)|)(?=\d)/, "\\1#{@currency}")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
return (@amount >= 0 ? " " : "") + amt
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def pretty_amount(amount)
|
|
64
|
+
sprintf("%0.2f", amount).reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
|
|
58
65
|
end
|
|
59
66
|
|
|
60
67
|
def parse(value, options = {})
|
data/lib/reckon/options.rb
CHANGED
|
@@ -73,10 +73,14 @@ module Reckon
|
|
|
73
73
|
options[:currency] = e
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
-
opts.on("", "--date-format
|
|
76
|
+
opts.on("", "--date-format FORMAT", "CSV file date format (see `date` for format)") do |d|
|
|
77
77
|
options[:date_format] = d
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
+
opts.on("", "--ledger-date-format FORMAT", "Ledger date format (see `date` for format)") do |d|
|
|
81
|
+
options[:ledger_date_format] = d
|
|
82
|
+
end
|
|
83
|
+
|
|
80
84
|
opts.on("-u", "--unattended", "Don't ask questions and guess all the accounts automatically. Use with --learn-from or --account-tokens options.") do |n|
|
|
81
85
|
options[:unattended] = n
|
|
82
86
|
end
|
|
@@ -85,6 +89,10 @@ module Reckon
|
|
|
85
89
|
options[:account_tokens_file] = a
|
|
86
90
|
end
|
|
87
91
|
|
|
92
|
+
opts.on("", "--table-output-file FILE") do |n|
|
|
93
|
+
options[:table_output_file] = n
|
|
94
|
+
end
|
|
95
|
+
|
|
88
96
|
options[:default_into_account] = 'Expenses:Unknown'
|
|
89
97
|
opts.on("", "--default-into-account NAME", "Default into account") do |a|
|
|
90
98
|
options[:default_into_account] = a
|
data/lib/reckon/version.rb
CHANGED
data/reckon.gemspec
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'pp'
|
|
4
|
+
|
|
5
|
+
require 'reckon'
|
|
6
|
+
|
|
7
|
+
ledger_file = ARGV[0]
|
|
8
|
+
account = ARGV[1]
|
|
9
|
+
seed = ARGV[2] ? ARGV[2].to_i : Random.new_seed
|
|
10
|
+
|
|
11
|
+
ledger = Reckon::LedgerParser.new(File.read(ledger_file))
|
|
12
|
+
matcher = Reckon::CosineSimilarity.new({})
|
|
13
|
+
|
|
14
|
+
train = []
|
|
15
|
+
test = []
|
|
16
|
+
|
|
17
|
+
def has_account(account, entry)
|
|
18
|
+
entry[:accounts].map { |a| a[:name] }.include?(account)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
entries = ledger.entries.select { |e| has_account(account, e) }
|
|
22
|
+
|
|
23
|
+
r = Random.new(seed)
|
|
24
|
+
entries.length.times do |i|
|
|
25
|
+
r.rand < 0.9 ? train << i : test << i
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
train.each do |i|
|
|
29
|
+
entry = entries[i]
|
|
30
|
+
entry[:accounts].each do |a|
|
|
31
|
+
matcher.add_document(
|
|
32
|
+
a[:name],
|
|
33
|
+
[entry[:desc], a[:amount]].join(" ")
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
result = [nil] * test.length
|
|
39
|
+
test.each do |i|
|
|
40
|
+
entry = entries[i]
|
|
41
|
+
matches = matcher.find_similar(
|
|
42
|
+
entry[:desc] + " " + entry[:accounts][0][:amount].to_s
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if !matches[0] || !has_account(matches[0][:account], entry)
|
|
46
|
+
result[i] = [entry, matches]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# pp result.compact
|
|
51
|
+
puts "using #{seed} as random seed"
|
|
52
|
+
puts "true: #{result.count(nil)} false: #{result.count { |v| !v.nil? }}"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
2003-12-24 CREDIT; Some Company vendorpymt PPD ID: 5KL3832735
|
|
2
|
-
Assets:Bank:Checking $
|
|
2
|
+
Assets:Bank:Checking $2,105.00
|
|
3
3
|
Income:Unknown
|
|
4
4
|
|
|
5
5
|
2004-12-24 CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
Expenses:Unknown
|
|
16
16
|
|
|
17
17
|
2007-12-24 CREDIT; Blarg BLARG REVENUE PPD ID: 00jah78563
|
|
18
|
-
Assets:Bank:Checking $
|
|
18
|
+
Assets:Bank:Checking $1,558.52
|
|
19
19
|
Income:Unknown
|
|
20
20
|
|
|
21
21
|
2008-12-24 CREDIT; Some Company vendorpymt PPD ID: 59728JSL20
|
|
22
|
-
Assets:Bank:Checking $
|
|
22
|
+
Assets:Bank:Checking $3,520.00
|
|
23
23
|
Income:Unknown
|
|
24
24
|
|
|
25
25
|
2009-12-24 DEBIT; GITHUB 041287430274 CA 12/22GITHUB 04
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/expect -f
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
set timeout 7
|
|
5
|
+
match_max 100000
|
|
6
|
+
expect "What is this account named in Ledger |Assets:Bank:Checking|?\r
|
|
7
|
+
\[1G▽\[6n"
|
|
8
|
+
send -- "\[45;2R"
|
|
9
|
+
expect -exact "\[1G\[K\[6n"
|
|
10
|
+
send -- "\[45;1R"
|
|
11
|
+
expect "\[1G\[K\[1G\[1G"
|
|
12
|
+
send -- "T"
|
|
13
|
+
expect "\[1GT\[K\[1G\[2G"
|
|
14
|
+
send -- "e"
|
|
15
|
+
expect "\[1GTe\[K\[1G\[3G"
|
|
16
|
+
send -- "s"
|
|
17
|
+
expect "\[1GTes\[K\[1G\[4G"
|
|
18
|
+
send -- "t"
|
|
19
|
+
expect "\[1GTest\[K\[1G\[5G"
|
|
20
|
+
send -- ":"
|
|
21
|
+
expect "\[1GTest:\[K\[1G\[6G"
|
|
22
|
+
send -- ":"
|
|
23
|
+
expect "\[1GTest::\[K\[1G\[7G"
|
|
24
|
+
send -- "B"
|
|
25
|
+
expect "\[1GTest::B\[K\[1G\[8G"
|
|
26
|
+
send -- "a"
|
|
27
|
+
expect "\[1GTest::Ba\[K\[1G\[9G"
|
|
28
|
+
send -- "n"
|
|
29
|
+
expect "\[1GTest::Ban\[K\[1G\[10G"
|
|
30
|
+
send -- "k"
|
|
31
|
+
expect "\[1GTest::Bank\[K\[1G\[11G"
|
|
32
|
+
send -- "\r"
|
|
33
|
+
expect eof
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
Date | Amount | Description | Note |
|
|
3
|
+
2003-12-24 | $2,105.00 | CREDIT; Some Company vendorpymt PPD ID: 5KL3832735 | |
|
|
4
|
+
2004-12-24 | -$116.22 | CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL | |
|
|
5
|
+
2005-12-24 | -$0.96 | DEBIT; WEBSITE-BALANCE-10DEC09 12 12/10WEBSITE-BAL | |
|
|
6
|
+
2006-12-24 | $0.23 | DEBIT; WEBSITE-BALANCE-17DEC09 12 12/17WEBSITE-BAL | |
|
|
7
|
+
2007-12-24 | $1,558.52 | CREDIT; Blarg BLARG REVENUE PPD ID: 00jah78563 | |
|
|
8
|
+
2008-12-24 | $3,520.00 | CREDIT; Some Company vendorpymt PPD ID: 59728JSL20 | |
|
|
9
|
+
2009-12-24 | -$7.00 | DEBIT; GITHUB 041287430274 CA 12/22GITHUB 04 | |
|
|
10
|
+
2010-12-24 | -$20.00 | CHECK; CHECK 2656 | |
|
|
11
|
+
2011-12-24 | -$85.00 | DEBIT; HOST 037196321563 MO 12/22SLICEHOST | |
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
DEBIT,2011/12/24,"HOST 037196321563 MO 12/22SLICEHOST",($85.00)
|
|
2
|
+
CHECK,2010/12/24,"CHECK 2656",($20.00)
|
|
3
|
+
DEBIT,2009/12/24,"GITHUB 041287430274 CA 12/22GITHUB 04",($7.00)
|
|
4
|
+
CREDIT,2008/12/24,"Some Company vendorpymt PPD ID: 59728JSL20",$3520.00
|
|
5
|
+
CREDIT,2007/12/24,"Blarg BLARG REVENUE PPD ID: 00jah78563",$1558.52
|
|
6
|
+
DEBIT,2006/12/24,"WEBSITE-BALANCE-17DEC09 12 12/17WEBSITE-BAL",$.23
|
|
7
|
+
DEBIT,2005/12/24,"WEBSITE-BALANCE-10DEC09 12 12/10WEBSITE-BAL",($0.96)
|
|
8
|
+
CREDIT,2004/12/24,"PAYPAL TRANSFER PPD ID: PAYPALSDSL",($116.22)
|
|
9
|
+
CREDIT,2003/12/24,"Some Company vendorpymt PPD ID: 5KL3832735",$2105.00
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
-f input.csv -p
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
2013-06-19 2013-06-24; Buy; ISHARES S&P/TSX CAPPED REIT IN; XRE; 300; 15.90; CDN; CAD
|
|
10
10
|
Income:Unknown
|
|
11
|
-
Assets:Bank:Checking -$
|
|
11
|
+
Assets:Bank:Checking -$4,779.95
|
|
12
12
|
|
|
13
13
|
2013-06-27 2013-06-27; Dividend; ICICI BK SPONSORED ADR; IBN; 100; USD
|
|
14
14
|
Assets:Bank:Checking $66.70
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
Income:Unknown
|
|
20
20
|
|
|
21
21
|
2014-01-07 2014-01-10; Sell; BMO NASDAQ 100 EQTY HEDGED TO; ZQQ; -300; 27.44; CDN; CAD
|
|
22
|
-
Assets:Bank:Checking $
|
|
22
|
+
Assets:Bank:Checking $8,222.05
|
|
23
23
|
Income:Unknown
|
|
24
24
|
|
|
25
25
|
2014-01-07 2014-01-07; Interest; BMO S&P/TSX EQUAL WEIGHT BKS I; ZEB; 250; CAD
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
2009-12-10 CREDIT; Some Company vendorpymt PPD ID: 5KL3832735
|
|
2
|
-
Assets:Bank:Checking $
|
|
2
|
+
Assets:Bank:Checking $2,105.00
|
|
3
3
|
Income:Unknown
|
|
4
4
|
|
|
5
5
|
2009-12-11 CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
Assets:Bank:Checking -$12.23
|
|
16
16
|
|
|
17
17
|
2009-12-23 CREDIT; Some Company vendorpymt PPD ID: 59728JSL20
|
|
18
|
-
Assets:Bank:Checking $
|
|
18
|
+
Assets:Bank:Checking $3,520.00
|
|
19
19
|
Income:Unknown
|
|
20
20
|
|
|
21
21
|
2009-12-23 CREDIT; Blarg BLARG REVENUE PPD ID: 00jah78563
|
|
22
|
-
Assets:Bank:Checking $
|
|
22
|
+
Assets:Bank:Checking $1,558.52
|
|
23
23
|
Income:Unknown
|
|
24
24
|
|
|
25
25
|
2009-12-24 DEBIT; GITHUB 041287430274 CA 12/22GITHUB 04
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
2009-12-10 CREDIT; Some Company vendorpymt PPD ID: 5KL3832735
|
|
2
|
-
Assets:Bank:Checking $
|
|
2
|
+
Assets:Bank:Checking $2,105.00
|
|
3
3
|
Income:Default
|
|
4
4
|
|
|
5
5
|
2009-12-11 CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
Assets:Bank:Checking -$12.23
|
|
16
16
|
|
|
17
17
|
2009-12-23 CREDIT; Some Company vendorpymt PPD ID: 59728JSL20
|
|
18
|
-
Assets:Bank:Checking $
|
|
18
|
+
Assets:Bank:Checking $3,520.00
|
|
19
19
|
Income:Default
|
|
20
20
|
|
|
21
21
|
2009-12-23 CREDIT; Blarg BLARG REVENUE PPD ID: 00jah78563
|
|
22
|
-
Assets:Bank:Checking $
|
|
22
|
+
Assets:Bank:Checking $1,558.52
|
|
23
23
|
Income:Default
|
|
24
24
|
|
|
25
25
|
2009-12-24 DEBIT; GITHUB 041287430274 CA 12/22GITHUB 04
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
2009-12-10 CREDIT; Some Company vendorpymt PPD ID: 5KL3832735
|
|
2
|
-
Assets:Bank:Checking $
|
|
2
|
+
Assets:Bank:Checking $2,105.00
|
|
3
3
|
Income:Unknown
|
|
4
4
|
|
|
5
5
|
2009-12-11 CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
Assets:Bank:Checking -$12.23
|
|
16
16
|
|
|
17
17
|
2009-12-23 CREDIT; Some Company vendorpymt PPD ID: 59728JSL20
|
|
18
|
-
Assets:Bank:Checking $
|
|
18
|
+
Assets:Bank:Checking $3,520.00
|
|
19
19
|
Income:Unknown
|
|
20
20
|
|
|
21
21
|
2009-12-23 CREDIT; Blarg BLARG REVENUE PPD ID: 00jah78563
|
|
22
|
-
Assets:Bank:Checking $
|
|
22
|
+
Assets:Bank:Checking $1,558.52
|
|
23
23
|
Income:Unknown
|
|
24
24
|
|
|
25
25
|
2009-12-24 DEBIT; GITHUB 041287430274 CA 12/22GITHUB 04
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
2009-12-10 CREDIT; Some Company vendorpymt PPD ID: 5KL3832735
|
|
2
|
-
Assets:Bank:Checking $
|
|
2
|
+
Assets:Bank:Checking $2,105.00
|
|
3
3
|
Income:Unknown
|
|
4
4
|
|
|
5
5
|
2009-12-11 CREDIT; PAYPAL TRANSFER PPD ID: PAYPALSDSL
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
Assets:Bank:Checking -$12.23
|
|
16
16
|
|
|
17
17
|
2009-12-23 CREDIT; Some Company vendorpymt PPD ID: 59728JSL20
|
|
18
|
-
Assets:Bank:Checking $
|
|
18
|
+
Assets:Bank:Checking $3,520.00
|
|
19
19
|
Income:Unknown
|
|
20
20
|
|
|
21
21
|
2009-12-23 CREDIT; Blarg BLARG REVENUE PPD ID: 00jah78563
|
|
22
|
-
Assets:Bank:Checking $
|
|
22
|
+
Assets:Bank:Checking $1,558.52
|
|
23
23
|
Income:Unknown
|
|
24
24
|
|
|
25
25
|
2009-12-24 DEBIT; GITHUB 041287430274 CA 12/22GITHUB 04
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
2009-11-04 TRANSFER CREDIT INTERNET TRANSFER; INTERNET TRANSFER; 1234.00
|
|
2
|
-
Assets:Bank:Checking $
|
|
2
|
+
Assets:Bank:Checking $1,234.00
|
|
3
3
|
Income:Unknown
|
|
4
4
|
|
|
5
5
|
2009-11-10 TRANSFER DEBIT INTERNET TRANSFER; INTERNET TRANSFER MORTGAGE; 0.00
|
|
@@ -16,5 +16,5 @@
|
|
|
16
16
|
|
|
17
17
|
2011-11-04 TRANSFER DEBIT INTERNET TRANSFER; INTERNET TRANSFER SAV TO MECU; 0.00
|
|
18
18
|
Income:Unknown
|
|
19
|
-
Assets:Bank:Checking -$
|
|
19
|
+
Assets:Bank:Checking -$1,234.00
|
|
20
20
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ledger --input-date-format '%d/%m/%Y'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
02/12/2009 BLARG R SH 456930; $1,826.06
|
|
2
|
+
Assets:Bank:Checking $327.49
|
|
3
|
+
Income:Unknown
|
|
4
|
+
|
|
5
|
+
02/12/2009 Check - 0000000122; 122; $1,750.06
|
|
6
|
+
Income:Unknown
|
|
7
|
+
Assets:Bank:Checking -$76.00
|
|
8
|
+
|
|
9
|
+
02/12/2009 Check - 0000000112; 112; $1,498.57
|
|
10
|
+
Income:Unknown
|
|
11
|
+
Assets:Bank:Checking -$800.00
|
|
12
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
-f input.csv --unattended --account Assets:Bank:Checking --date-format '%d/%m/%Y' --ledger-date-format '%d/%m/%Y'
|
data/spec/integration/test.sh
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
|
-
# set -x
|
|
4
|
-
|
|
5
3
|
set -Euo pipefail
|
|
6
4
|
|
|
5
|
+
|
|
7
6
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
|
8
7
|
TEST_DIFF=""
|
|
9
8
|
OUTPUT=""
|
|
10
|
-
RECKON_CMD="
|
|
9
|
+
RECKON_CMD="reckon -v"
|
|
10
|
+
export RUBYLIB=$SCRIPT_DIR/../../lib:${RUBYLIB:-}
|
|
11
11
|
export PATH="$SCRIPT_DIR/../../bin:$PATH"
|
|
12
12
|
|
|
13
13
|
main () {
|
|
@@ -21,28 +21,64 @@ main () {
|
|
|
21
21
|
|
|
22
22
|
echo > test.log
|
|
23
23
|
|
|
24
|
+
NUM_TESTS=$(echo "$TESTS" |wc -l |awk '{print $1}')
|
|
25
|
+
|
|
26
|
+
echo "1..$NUM_TESTS"
|
|
27
|
+
|
|
28
|
+
I=1
|
|
29
|
+
|
|
24
30
|
for t in $TESTS; do
|
|
25
|
-
OUTPUT_FILE=$(mktemp)
|
|
26
31
|
TEST_DIR=$(dirname "$t")
|
|
32
|
+
TEST_LOG=$(mktemp)
|
|
27
33
|
pushd "$TEST_DIR" >/dev/null || exit 1
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
compare_output "$OUTPUT_FILE"
|
|
34
|
+
if [[ -e "cli_input.exp" ]]; then
|
|
35
|
+
cli_test >$TEST_LOG 2>&1
|
|
36
|
+
else
|
|
37
|
+
unattended_test >$TEST_LOG 2>&1
|
|
38
|
+
fi
|
|
34
39
|
|
|
35
40
|
popd >/dev/null || exit 1
|
|
36
41
|
# have to save output after popd
|
|
37
42
|
echo -e "\n\n======>$TEST_DIR" >> test.log
|
|
38
|
-
echo -e "TEST_CMD
|
|
43
|
+
echo -e "TEST_CMD: $TEST_CMD" >> test.log
|
|
44
|
+
cat $TEST_LOG >> test.log
|
|
39
45
|
|
|
40
46
|
if [[ $ERROR -ne 0 ]]; then
|
|
47
|
+
echo -e "not ok $I - $TEST_DIR"
|
|
48
|
+
tail -n25 test.log
|
|
41
49
|
exit 1
|
|
50
|
+
else
|
|
51
|
+
echo -e "ok $I - $TEST_DIR"
|
|
42
52
|
fi
|
|
53
|
+
I=$(($I + 1))
|
|
43
54
|
done
|
|
44
55
|
}
|
|
45
56
|
|
|
57
|
+
cli_test () {
|
|
58
|
+
OUTPUT_FILE=$(mktemp)
|
|
59
|
+
CLI_CMD="$RECKON_CMD --table-output-file $OUTPUT_FILE $(cat test_args)"
|
|
60
|
+
TEST_CMD="expect -d -c 'spawn $CLI_CMD' cli_input.exp"
|
|
61
|
+
eval "$TEST_CMD" 2>&1
|
|
62
|
+
ERROR=0
|
|
63
|
+
TEST_DIFF=$(diff -u "$OUTPUT_FILE" expected_output)
|
|
64
|
+
|
|
65
|
+
# ${#} is character length, test that there was no output from diff
|
|
66
|
+
if [ ${#TEST_DIFF} -eq 0 ]; then
|
|
67
|
+
ERROR=0
|
|
68
|
+
else
|
|
69
|
+
ERROR=1
|
|
70
|
+
fi
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
unattended_test() {
|
|
74
|
+
OUTPUT_FILE=$(mktemp)
|
|
75
|
+
TEST_CMD="$RECKON_CMD -o $OUTPUT_FILE $(cat test_args)"
|
|
76
|
+
eval "$TEST_CMD" 2>&1
|
|
77
|
+
ERROR=0
|
|
78
|
+
|
|
79
|
+
compare_output "$OUTPUT_FILE"
|
|
80
|
+
}
|
|
81
|
+
|
|
46
82
|
test_fail () {
|
|
47
83
|
STATUS=$?
|
|
48
84
|
if [[ $STATUS -ne 0 ]]; then
|
|
@@ -51,11 +87,42 @@ test_fail () {
|
|
|
51
87
|
fi
|
|
52
88
|
}
|
|
53
89
|
|
|
90
|
+
compare_output () {
|
|
91
|
+
OUTPUT_FILE=$1
|
|
92
|
+
pwd
|
|
93
|
+
if [[ -e compare_cmds ]]; then
|
|
94
|
+
COMPARE_CMDS=$(cat compare_cmds)
|
|
95
|
+
else
|
|
96
|
+
COMPARE_CMDS=$'ledger\nhledger'
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
ERROR=1
|
|
100
|
+
while IFS= read -r n; do
|
|
101
|
+
if compare_output_for "$OUTPUT_FILE" "$n"; then
|
|
102
|
+
ERROR=0
|
|
103
|
+
else
|
|
104
|
+
ERROR=1
|
|
105
|
+
break
|
|
106
|
+
fi
|
|
107
|
+
done <<< "$COMPARE_CMDS"
|
|
108
|
+
}
|
|
109
|
+
|
|
54
110
|
compare_output_for () {
|
|
55
111
|
OUTPUT_FILE=$1
|
|
56
112
|
LEDGER=$2
|
|
57
113
|
|
|
58
|
-
|
|
114
|
+
EXPECTED_FILE=$(mktemp)
|
|
115
|
+
ACTUAL_FILE=$(mktemp)
|
|
116
|
+
|
|
117
|
+
EXPECTED_CMD="$LEDGER -f output.ledger r >$EXPECTED_FILE"
|
|
118
|
+
echo "$EXPECTED_CMD"
|
|
119
|
+
eval "$EXPECTED_CMD" || return 1
|
|
120
|
+
|
|
121
|
+
ACTUAL_CMD="$LEDGER -f \"$OUTPUT_FILE\" r"
|
|
122
|
+
echo "running $ACTUAL_CMD"
|
|
123
|
+
eval $ACTUAL_CMD >$ACTUAL_FILE || return 1
|
|
124
|
+
|
|
125
|
+
TEST_DIFF=$(diff -u "$EXPECTED_FILE" "$ACTUAL_FILE")
|
|
59
126
|
|
|
60
127
|
# ${#} is character length, test that there was no output from diff
|
|
61
128
|
if [ ${#TEST_DIFF} -eq 0 ]; then
|
|
@@ -65,19 +132,4 @@ compare_output_for () {
|
|
|
65
132
|
fi
|
|
66
133
|
}
|
|
67
134
|
|
|
68
|
-
compare_output () {
|
|
69
|
-
OUTPUT_FILE=$1
|
|
70
|
-
|
|
71
|
-
for n in {ledger,hledger}; do
|
|
72
|
-
echo -n " - $n..."
|
|
73
|
-
if compare_output_for "$OUTPUT_FILE" "$n"; then
|
|
74
|
-
echo "SUCCESS!"
|
|
75
|
-
else
|
|
76
|
-
echo "FAILED!"
|
|
77
|
-
ERROR=1
|
|
78
|
-
return 0
|
|
79
|
-
fi
|
|
80
|
-
done
|
|
81
|
-
}
|
|
82
|
-
|
|
83
135
|
main "$@"
|
data/spec/reckon/app_spec.rb
CHANGED
|
@@ -16,7 +16,7 @@ describe Reckon::App do
|
|
|
16
16
|
describe "each_row_backwards" do
|
|
17
17
|
it "should return rows with hashes" do
|
|
18
18
|
@rows[0][:pretty_date].should == "2009-12-10"
|
|
19
|
-
@rows[0][:pretty_money].should == " $
|
|
19
|
+
@rows[0][:pretty_money].should == " $2,105.00"
|
|
20
20
|
@rows[0][:description].should == "CREDIT; Some Company vendorpymt PPD ID: 5KL3832735"
|
|
21
21
|
@rows[1][:pretty_date].should == "2009-12-11"
|
|
22
22
|
@rows[1][:pretty_money].should == " $116.22"
|
|
@@ -242,7 +242,7 @@ describe Reckon::CSVParser do
|
|
|
242
242
|
describe "pretty_money_for" do
|
|
243
243
|
it "work with negative and positive numbers" do
|
|
244
244
|
some_other_bank.pretty_money_for(1).should == "-$20.00"
|
|
245
|
-
some_other_bank.pretty_money_for(4).should == " $
|
|
245
|
+
some_other_bank.pretty_money_for(4).should == " $1,558.52"
|
|
246
246
|
some_other_bank.pretty_money_for(7).should == "-$116.22"
|
|
247
247
|
some_other_bank.pretty_money_for(5).should == " $0.23"
|
|
248
248
|
some_other_bank.pretty_money_for(6).should == "-$0.96"
|
|
@@ -251,7 +251,7 @@ describe Reckon::CSVParser do
|
|
|
251
251
|
it "work with other currencies such as €" do
|
|
252
252
|
euro_bank = Reckon::CSVParser.new(file: fixture_path('some_other.csv'), currency: "€", suffixed: false )
|
|
253
253
|
euro_bank.pretty_money_for(1).should == "-€20.00"
|
|
254
|
-
euro_bank.pretty_money_for(4).should == " €
|
|
254
|
+
euro_bank.pretty_money_for(4).should == " €1,558.52"
|
|
255
255
|
euro_bank.pretty_money_for(7).should == "-€116.22"
|
|
256
256
|
euro_bank.pretty_money_for(5).should == " €0.23"
|
|
257
257
|
euro_bank.pretty_money_for(6).should == "-€0.96"
|
|
@@ -260,7 +260,7 @@ describe Reckon::CSVParser do
|
|
|
260
260
|
it "work with suffixed currencies such as SEK" do
|
|
261
261
|
swedish_bank = Reckon::CSVParser.new(file: fixture_path('some_other.csv'), currency: 'SEK', suffixed: true )
|
|
262
262
|
swedish_bank.pretty_money_for(1).should == "-20.00 SEK"
|
|
263
|
-
swedish_bank.pretty_money_for(4).should == "
|
|
263
|
+
swedish_bank.pretty_money_for(4).should == " 1,558.52 SEK"
|
|
264
264
|
swedish_bank.pretty_money_for(7).should == "-116.22 SEK"
|
|
265
265
|
swedish_bank.pretty_money_for(5).should == " 0.23 SEK"
|
|
266
266
|
swedish_bank.pretty_money_for(6).should == "-0.96 SEK"
|
|
@@ -38,4 +38,16 @@ describe Reckon::DateColumn do
|
|
|
38
38
|
.to eq(Date.new(2013, 2, 1))
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
|
+
|
|
42
|
+
describe "#pretty_for" do
|
|
43
|
+
it 'should use ledger_date_format' do
|
|
44
|
+
expect(Reckon::DateColumn.new(%w[13/02/2013], {ledger_date_format: '%d/%m/%Y'}).pretty_for(0))
|
|
45
|
+
.to eq('13/02/2013')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'should default to is' do
|
|
49
|
+
expect(Reckon::DateColumn.new(%w[13/12/2013]).pretty_for(0))
|
|
50
|
+
.to eq('2013-12-13')
|
|
51
|
+
end
|
|
52
|
+
end
|
|
41
53
|
end
|
data/spec/reckon/money_spec.rb
CHANGED
|
@@ -32,17 +32,17 @@ describe Reckon::Money do
|
|
|
32
32
|
describe "pretty" do
|
|
33
33
|
it "work with negative and positive numbers" do
|
|
34
34
|
expect(Reckon::Money.new(-20.00).pretty).to eq("-$20.00")
|
|
35
|
-
expect(Reckon::Money.new(1558.52).pretty).to eq(" $
|
|
35
|
+
expect(Reckon::Money.new(1558.52).pretty).to eq(" $1,558.52")
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
it "work with other currencies such as €" do
|
|
39
39
|
expect(Reckon::Money.new(-20.00, currency: "€", suffixed: false).pretty).to eq("-€20.00")
|
|
40
|
-
expect(Reckon::Money.new(1558.52, currency: "€", suffixed: false).pretty).to eq(" €
|
|
40
|
+
expect(Reckon::Money.new(1558.52, currency: "€", suffixed: false).pretty).to eq(" €1,558.52")
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
it "work with suffixed currencies such as SEK" do
|
|
44
44
|
expect(Reckon::Money.new(-20.00, currency: "SEK", suffixed: true).pretty).to eq("-20.00 SEK")
|
|
45
|
-
expect(Reckon::Money.new(1558.52, currency: "SEK", suffixed: true).pretty).to eq("
|
|
45
|
+
expect(Reckon::Money.new(1558.52, currency: "SEK", suffixed: true).pretty).to eq(" 1,558.52 SEK")
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: reckon
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Cantino
|
|
@@ -10,7 +10,7 @@ authors:
|
|
|
10
10
|
autorequire:
|
|
11
11
|
bindir: bin
|
|
12
12
|
cert_chain: []
|
|
13
|
-
date:
|
|
13
|
+
date: 2022-07-02 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: rspec
|
|
@@ -96,6 +96,20 @@ dependencies:
|
|
|
96
96
|
- - ">="
|
|
97
97
|
- !ruby/object:Gem::Version
|
|
98
98
|
version: 1.8.0
|
|
99
|
+
- !ruby/object:Gem::Dependency
|
|
100
|
+
name: matrix
|
|
101
|
+
requirement: !ruby/object:Gem::Requirement
|
|
102
|
+
requirements:
|
|
103
|
+
- - ">="
|
|
104
|
+
- !ruby/object:Gem::Version
|
|
105
|
+
version: 0.4.2
|
|
106
|
+
type: :runtime
|
|
107
|
+
prerelease: false
|
|
108
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
109
|
+
requirements:
|
|
110
|
+
- - ">="
|
|
111
|
+
- !ruby/object:Gem::Version
|
|
112
|
+
version: 0.4.2
|
|
99
113
|
description: Reckon automagically converts CSV files for use with the command-line
|
|
100
114
|
accounting tool Ledger. It also helps you to select the correct accounts associated
|
|
101
115
|
with the CSV data using Bayesian machine learning.
|
|
@@ -130,6 +144,7 @@ files:
|
|
|
130
144
|
- lib/reckon/options.rb
|
|
131
145
|
- lib/reckon/version.rb
|
|
132
146
|
- reckon.gemspec
|
|
147
|
+
- spec/cosine_training_and_test.rb
|
|
133
148
|
- spec/data_fixtures/51-sample.csv
|
|
134
149
|
- spec/data_fixtures/51-tokens.yml
|
|
135
150
|
- spec/data_fixtures/73-sample.csv
|
|
@@ -162,6 +177,10 @@ files:
|
|
|
162
177
|
- spec/integration/another_bank_example/input.csv
|
|
163
178
|
- spec/integration/another_bank_example/output.ledger
|
|
164
179
|
- spec/integration/another_bank_example/test_args
|
|
180
|
+
- spec/integration/ask_for_account/cli_input.exp
|
|
181
|
+
- spec/integration/ask_for_account/expected_output
|
|
182
|
+
- spec/integration/ask_for_account/input.csv
|
|
183
|
+
- spec/integration/ask_for_account/test_args
|
|
165
184
|
- spec/integration/austrian_example/input.csv
|
|
166
185
|
- spec/integration/austrian_example/output.ledger
|
|
167
186
|
- spec/integration/austrian_example/test_args
|
|
@@ -212,6 +231,10 @@ files:
|
|
|
212
231
|
- spec/integration/inversed_credit_card/input.csv
|
|
213
232
|
- spec/integration/inversed_credit_card/output.ledger
|
|
214
233
|
- spec/integration/inversed_credit_card/test_args
|
|
234
|
+
- spec/integration/ledger_date_format/compare_cmds
|
|
235
|
+
- spec/integration/ledger_date_format/input.csv
|
|
236
|
+
- spec/integration/ledger_date_format/output.ledger
|
|
237
|
+
- spec/integration/ledger_date_format/test_args
|
|
215
238
|
- spec/integration/nationwide/input.csv
|
|
216
239
|
- spec/integration/nationwide/output.ledger
|
|
217
240
|
- spec/integration/nationwide/test_args
|
|
@@ -273,9 +296,142 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
273
296
|
- !ruby/object:Gem::Version
|
|
274
297
|
version: '0'
|
|
275
298
|
requirements: []
|
|
276
|
-
rubygems_version: 3.
|
|
299
|
+
rubygems_version: 3.3.7
|
|
277
300
|
signing_key:
|
|
278
301
|
specification_version: 4
|
|
279
302
|
summary: Utility for interactively converting and labeling CSV files for the Ledger
|
|
280
303
|
accounting tool.
|
|
281
|
-
test_files:
|
|
304
|
+
test_files:
|
|
305
|
+
- spec/cosine_training_and_test.rb
|
|
306
|
+
- spec/data_fixtures/51-sample.csv
|
|
307
|
+
- spec/data_fixtures/51-tokens.yml
|
|
308
|
+
- spec/data_fixtures/73-sample.csv
|
|
309
|
+
- spec/data_fixtures/73-tokens.yml
|
|
310
|
+
- spec/data_fixtures/73-transactions.ledger
|
|
311
|
+
- spec/data_fixtures/85-date-example.csv
|
|
312
|
+
- spec/data_fixtures/austrian_example.csv
|
|
313
|
+
- spec/data_fixtures/bom_utf8_file.csv
|
|
314
|
+
- spec/data_fixtures/broker_canada_example.csv
|
|
315
|
+
- spec/data_fixtures/chase.csv
|
|
316
|
+
- spec/data_fixtures/danish_kroner_nordea_example.csv
|
|
317
|
+
- spec/data_fixtures/english_date_example.csv
|
|
318
|
+
- spec/data_fixtures/extratofake.csv
|
|
319
|
+
- spec/data_fixtures/french_example.csv
|
|
320
|
+
- spec/data_fixtures/german_date_example.csv
|
|
321
|
+
- spec/data_fixtures/harder_date_example.csv
|
|
322
|
+
- spec/data_fixtures/ing.csv
|
|
323
|
+
- spec/data_fixtures/intuit_mint_example.csv
|
|
324
|
+
- spec/data_fixtures/invalid_header_example.csv
|
|
325
|
+
- spec/data_fixtures/inversed_credit_card.csv
|
|
326
|
+
- spec/data_fixtures/nationwide.csv
|
|
327
|
+
- spec/data_fixtures/simple.csv
|
|
328
|
+
- spec/data_fixtures/some_other.csv
|
|
329
|
+
- spec/data_fixtures/spanish_date_example.csv
|
|
330
|
+
- spec/data_fixtures/suntrust.csv
|
|
331
|
+
- spec/data_fixtures/test_money_column.csv
|
|
332
|
+
- spec/data_fixtures/tokens.yaml
|
|
333
|
+
- spec/data_fixtures/two_money_columns.csv
|
|
334
|
+
- spec/data_fixtures/yyyymmdd_date_example.csv
|
|
335
|
+
- spec/integration/another_bank_example/input.csv
|
|
336
|
+
- spec/integration/another_bank_example/output.ledger
|
|
337
|
+
- spec/integration/another_bank_example/test_args
|
|
338
|
+
- spec/integration/ask_for_account/cli_input.exp
|
|
339
|
+
- spec/integration/ask_for_account/expected_output
|
|
340
|
+
- spec/integration/ask_for_account/input.csv
|
|
341
|
+
- spec/integration/ask_for_account/test_args
|
|
342
|
+
- spec/integration/austrian_example/input.csv
|
|
343
|
+
- spec/integration/austrian_example/output.ledger
|
|
344
|
+
- spec/integration/austrian_example/test_args
|
|
345
|
+
- spec/integration/bom_utf8_file/input.csv
|
|
346
|
+
- spec/integration/bom_utf8_file/output.ledger
|
|
347
|
+
- spec/integration/bom_utf8_file/test_args
|
|
348
|
+
- spec/integration/broker_canada_example/input.csv
|
|
349
|
+
- spec/integration/broker_canada_example/output.ledger
|
|
350
|
+
- spec/integration/broker_canada_example/test_args
|
|
351
|
+
- spec/integration/chase/account_tokens_and_regex/output.ledger
|
|
352
|
+
- spec/integration/chase/account_tokens_and_regex/test_args
|
|
353
|
+
- spec/integration/chase/account_tokens_and_regex/tokens.yml
|
|
354
|
+
- spec/integration/chase/default_account_names/output.ledger
|
|
355
|
+
- spec/integration/chase/default_account_names/test_args
|
|
356
|
+
- spec/integration/chase/input.csv
|
|
357
|
+
- spec/integration/chase/learn_from_existing/learn.ledger
|
|
358
|
+
- spec/integration/chase/learn_from_existing/output.ledger
|
|
359
|
+
- spec/integration/chase/learn_from_existing/test_args
|
|
360
|
+
- spec/integration/chase/simple/output.ledger
|
|
361
|
+
- spec/integration/chase/simple/test_args
|
|
362
|
+
- spec/integration/danish_kroner_nordea_example/input.csv
|
|
363
|
+
- spec/integration/danish_kroner_nordea_example/output.ledger
|
|
364
|
+
- spec/integration/danish_kroner_nordea_example/test_args
|
|
365
|
+
- spec/integration/english_date_example/input.csv
|
|
366
|
+
- spec/integration/english_date_example/output.ledger
|
|
367
|
+
- spec/integration/english_date_example/test_args
|
|
368
|
+
- spec/integration/extratofake/input.csv
|
|
369
|
+
- spec/integration/extratofake/output.ledger
|
|
370
|
+
- spec/integration/extratofake/test_args
|
|
371
|
+
- spec/integration/french_example/input.csv
|
|
372
|
+
- spec/integration/french_example/output.ledger
|
|
373
|
+
- spec/integration/french_example/test_args
|
|
374
|
+
- spec/integration/german_date_example/input.csv
|
|
375
|
+
- spec/integration/german_date_example/output.ledger
|
|
376
|
+
- spec/integration/german_date_example/test_args
|
|
377
|
+
- spec/integration/harder_date_example/input.csv
|
|
378
|
+
- spec/integration/harder_date_example/output.ledger
|
|
379
|
+
- spec/integration/harder_date_example/test_args
|
|
380
|
+
- spec/integration/ing/input.csv
|
|
381
|
+
- spec/integration/ing/output.ledger
|
|
382
|
+
- spec/integration/ing/test_args
|
|
383
|
+
- spec/integration/intuit_mint_example/input.csv
|
|
384
|
+
- spec/integration/intuit_mint_example/output.ledger
|
|
385
|
+
- spec/integration/intuit_mint_example/test_args
|
|
386
|
+
- spec/integration/invalid_header_example/input.csv
|
|
387
|
+
- spec/integration/invalid_header_example/output.ledger
|
|
388
|
+
- spec/integration/invalid_header_example/test_args
|
|
389
|
+
- spec/integration/inversed_credit_card/input.csv
|
|
390
|
+
- spec/integration/inversed_credit_card/output.ledger
|
|
391
|
+
- spec/integration/inversed_credit_card/test_args
|
|
392
|
+
- spec/integration/ledger_date_format/compare_cmds
|
|
393
|
+
- spec/integration/ledger_date_format/input.csv
|
|
394
|
+
- spec/integration/ledger_date_format/output.ledger
|
|
395
|
+
- spec/integration/ledger_date_format/test_args
|
|
396
|
+
- spec/integration/nationwide/input.csv
|
|
397
|
+
- spec/integration/nationwide/output.ledger
|
|
398
|
+
- spec/integration/nationwide/test_args
|
|
399
|
+
- spec/integration/regression/issue_51_account_tokens/input.csv
|
|
400
|
+
- spec/integration/regression/issue_51_account_tokens/output.ledger
|
|
401
|
+
- spec/integration/regression/issue_51_account_tokens/test_args
|
|
402
|
+
- spec/integration/regression/issue_51_account_tokens/tokens.yml
|
|
403
|
+
- spec/integration/regression/issue_64_date_column/input.csv
|
|
404
|
+
- spec/integration/regression/issue_64_date_column/output.ledger
|
|
405
|
+
- spec/integration/regression/issue_64_date_column/test_args
|
|
406
|
+
- spec/integration/regression/issue_73_account_token_matching/input.csv
|
|
407
|
+
- spec/integration/regression/issue_73_account_token_matching/output.ledger
|
|
408
|
+
- spec/integration/regression/issue_73_account_token_matching/test_args
|
|
409
|
+
- spec/integration/regression/issue_73_account_token_matching/tokens.yml
|
|
410
|
+
- spec/integration/regression/issue_85_date_example/input.csv
|
|
411
|
+
- spec/integration/regression/issue_85_date_example/output.ledger
|
|
412
|
+
- spec/integration/regression/issue_85_date_example/test_args
|
|
413
|
+
- spec/integration/spanish_date_example/input.csv
|
|
414
|
+
- spec/integration/spanish_date_example/output.ledger
|
|
415
|
+
- spec/integration/spanish_date_example/test_args
|
|
416
|
+
- spec/integration/suntrust/input.csv
|
|
417
|
+
- spec/integration/suntrust/output.ledger
|
|
418
|
+
- spec/integration/suntrust/test_args
|
|
419
|
+
- spec/integration/test.sh
|
|
420
|
+
- spec/integration/test_money_column/input.csv
|
|
421
|
+
- spec/integration/test_money_column/output.ledger
|
|
422
|
+
- spec/integration/test_money_column/test_args
|
|
423
|
+
- spec/integration/two_money_columns/input.csv
|
|
424
|
+
- spec/integration/two_money_columns/output.ledger
|
|
425
|
+
- spec/integration/two_money_columns/test_args
|
|
426
|
+
- spec/integration/yyyymmdd_date_example/input.csv
|
|
427
|
+
- spec/integration/yyyymmdd_date_example/output.ledger
|
|
428
|
+
- spec/integration/yyyymmdd_date_example/test_args
|
|
429
|
+
- spec/reckon/app_spec.rb
|
|
430
|
+
- spec/reckon/csv_parser_spec.rb
|
|
431
|
+
- spec/reckon/date_column_spec.rb
|
|
432
|
+
- spec/reckon/ledger_parser_spec.rb
|
|
433
|
+
- spec/reckon/money_column_spec.rb
|
|
434
|
+
- spec/reckon/money_spec.rb
|
|
435
|
+
- spec/reckon/options_spec.rb
|
|
436
|
+
- spec/spec.opts
|
|
437
|
+
- spec/spec_helper.rb
|