ledger-rest 2.0.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 +17 -0
- data/.rspec +2 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +57 -0
- data/Guardfile +24 -0
- data/LICENSE.txt +22 -0
- data/README.md +17 -0
- data/Rakefile +1 -0
- data/config.ru +5 -0
- data/ledger-rest.gemspec +19 -0
- data/ledger-rest.org +14 -0
- data/lib/ledger-rest.rb +11 -0
- data/lib/ledger-rest/app.rb +124 -0
- data/lib/ledger-rest/core_ext.rb +23 -0
- data/lib/ledger-rest/git.rb +68 -0
- data/lib/ledger-rest/ledger.rb +87 -0
- data/lib/ledger-rest/ledger/balance.rb +44 -0
- data/lib/ledger-rest/ledger/budget.rb +33 -0
- data/lib/ledger-rest/ledger/entry.rb +27 -0
- data/lib/ledger-rest/ledger/parser.rb +178 -0
- data/lib/ledger-rest/ledger/register.rb +39 -0
- data/lib/ledger-rest/ledger/transaction.rb +99 -0
- data/lib/ledger-rest/version.rb +3 -0
- data/spec/ledger-rest/ledger/parser_spec.rb +364 -0
- data/spec/ledger-rest/ledger/transaction_spec.rb +41 -0
- data/spec/spec_helper.rb +9 -0
- metadata +78 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
module LedgerRest
|
2
|
+
class Ledger
|
3
|
+
class Balance
|
4
|
+
|
5
|
+
FORMAT = [
|
6
|
+
'{',
|
7
|
+
' "total": %(quoted(display_total)),',
|
8
|
+
' "name": %(quoted(partial_account)),',
|
9
|
+
' "depth": %(depth), "fullname": %(quoted(account))',
|
10
|
+
'},%/',
|
11
|
+
'{ "total": %(quoted(display_total)) }'
|
12
|
+
].join('\\n')
|
13
|
+
|
14
|
+
class << self
|
15
|
+
|
16
|
+
def get query = nil, params = {}
|
17
|
+
JSON.parse(json(query, params), :symbolize_names => true)
|
18
|
+
end
|
19
|
+
|
20
|
+
def json query = nil, params = {}
|
21
|
+
params = { '--format' => FORMAT }.merge(params)
|
22
|
+
result = Ledger.exec("bal #{query}", params)
|
23
|
+
|
24
|
+
total = nil
|
25
|
+
if result.end_with?(',')
|
26
|
+
result = result[0..-2]
|
27
|
+
else
|
28
|
+
match_total = result.match(/,\n.*?{ +"total": +("[0-9\.A-Za-z ]+") +}\z/)
|
29
|
+
if match_total
|
30
|
+
total = match_total[1]
|
31
|
+
result = result[0,match_total.offset(0)[0]]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
json_str = "{"
|
36
|
+
json_str << " \"accounts\": [ #{result} ]"
|
37
|
+
json_str << ", \"total\": #{total}" if total
|
38
|
+
json_str << " }"
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module LedgerRest
|
2
|
+
class Ledger
|
3
|
+
class Budget
|
4
|
+
FORMAT = [
|
5
|
+
'{',
|
6
|
+
' "name": %(quoted(account)),',
|
7
|
+
' "amount": %(quoted(get_at(T, 0))),',
|
8
|
+
' "budget": %(quoted(-get_at(T, 1)))',
|
9
|
+
' },%/',
|
10
|
+
' ],',
|
11
|
+
' "total_amount": %(quoted(get_at(T, 0))),',
|
12
|
+
' "total_budget": %(quoted(-get_at(T, 1)))'
|
13
|
+
].join('\\n')
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
def get(query = nil, params = {})
|
18
|
+
JSON.parse(json(query, params), :symbolize_names => true)
|
19
|
+
end
|
20
|
+
|
21
|
+
def json(query = nil, params = {})
|
22
|
+
params = { '--format' => FORMAT }.merge(params)
|
23
|
+
result = Ledger.exec("budget #{query}", params)
|
24
|
+
result.gsub!(/\},\n *?\]/, "}\n ]")
|
25
|
+
|
26
|
+
"{\n \"budget\": {\n \"accounts\": [\n #{result}\n }\n}"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module LedgerRest
|
2
|
+
class Ledger
|
3
|
+
|
4
|
+
# Ledger offers a simple command to create a new entry based on
|
5
|
+
# previous entries in your ledger files. This class abstracts
|
6
|
+
# mentioned functionality for easy integration into ledger-rest.
|
7
|
+
class Entry
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
# Return a new transaction object based on previous transactions.
|
12
|
+
def get(desc, options = {})
|
13
|
+
result = Ledger.exec("entry #{desc}", options)
|
14
|
+
Transaction.parse(result)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Appends a new transaction
|
18
|
+
def append(desc, options = {})
|
19
|
+
transaction = get(desc, options)
|
20
|
+
transaction.append_to(Ledger.append_file)
|
21
|
+
transaction
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module LedgerRest
|
3
|
+
class Ledger
|
4
|
+
|
5
|
+
# A very simple parser for single legder format transactions.
|
6
|
+
# There has to be a better way ... This does not implement the
|
7
|
+
# whole ledger format. Tragically ... someone told me it´s the
|
8
|
+
# essence of evil to do parser duplication. We need to use the
|
9
|
+
# ledger parser. Maybe we can use the ledger code base and
|
10
|
+
# integrate it into a ruby gem ... I don´t know.
|
11
|
+
#
|
12
|
+
# This works for `ledger entry` with my transactions ...
|
13
|
+
class Parser
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
def parse(str)
|
18
|
+
parser = Parser.new
|
19
|
+
parser.parse(str)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@transaction = Transaction.new
|
26
|
+
end
|
27
|
+
|
28
|
+
# Begins to parse a whole
|
29
|
+
def parse(str)
|
30
|
+
@str = str
|
31
|
+
@transaction[:postings] = []
|
32
|
+
|
33
|
+
@transaction[:date],str = parse_date(str)
|
34
|
+
|
35
|
+
effective_date, str = parse_effective_date(str)
|
36
|
+
@transaction[:effective_date] = effective_date if effective_date
|
37
|
+
|
38
|
+
@transaction[:cleared],str = parse_cleared(str)
|
39
|
+
@transaction[:pending],str = parse_pending(str)
|
40
|
+
|
41
|
+
code, str = parse_code(str)
|
42
|
+
@transaction[:code] = code if code
|
43
|
+
|
44
|
+
@transaction[:payee],str = parse_payee(str)
|
45
|
+
|
46
|
+
comments,str = parse_comments(str)
|
47
|
+
@transaction[:comments] = comments if comments
|
48
|
+
|
49
|
+
str.split("\n").each do |line|
|
50
|
+
posting = parse_posting(line)
|
51
|
+
@transaction[:postings] << posting
|
52
|
+
end
|
53
|
+
|
54
|
+
@transaction
|
55
|
+
rescue Exception => e
|
56
|
+
puts e
|
57
|
+
puts "In: \n#{@str}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_date(str)
|
61
|
+
if match = str.match(/\A(\d{4}\/\d{1,2}\/\d{1,2})(.*)/m)
|
62
|
+
[ match[1], match[2] ]
|
63
|
+
else
|
64
|
+
raise "Date was expected."
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def parse_effective_date(str)
|
69
|
+
if match = str.match(/\A=(\d{4}\/\d{1,2}\/\d{1,2})(.*)/m)
|
70
|
+
[ match[1], match[2] ]
|
71
|
+
else
|
72
|
+
[ nil, str ]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_pending(str)
|
77
|
+
if match = str.match(/\A ! (.*)/m)
|
78
|
+
[ true, match[1] ]
|
79
|
+
else
|
80
|
+
[ false, str]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def parse_cleared(str)
|
85
|
+
if match = str.match(/\A \* (.*)/m)
|
86
|
+
[ true, match[1] ]
|
87
|
+
else
|
88
|
+
[ false, str]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def parse_code(str)
|
93
|
+
if match = str.match(/\A *?\(([^\(\)]+)\) (.*)/m)
|
94
|
+
[ match[1], match[2] ]
|
95
|
+
else
|
96
|
+
[ nil, str ]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def parse_payee(str)
|
101
|
+
if match = str.match(/\A *?([^ ][^\n]+)\n(.*)/m)
|
102
|
+
[ match[1], match[2] ]
|
103
|
+
else
|
104
|
+
raise "No payee given."
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def parse_comments(str)
|
109
|
+
comments = ""
|
110
|
+
while str && match = str.match(/\A +;(.*?)(\n|$)(.*)/m)
|
111
|
+
comments << match[1].strip << "\n"
|
112
|
+
str = match[3]
|
113
|
+
end
|
114
|
+
[ comments.empty? ? nil : comments, str ]
|
115
|
+
end
|
116
|
+
|
117
|
+
# parses a ledger posting line
|
118
|
+
def parse_posting(str)
|
119
|
+
posting = {}
|
120
|
+
|
121
|
+
account, virtual, balanced, str = parse_account(str)
|
122
|
+
posting[:account] = account if account
|
123
|
+
posting[:virtual] = virtual if virtual
|
124
|
+
posting[:balanced] = balanced if balanced
|
125
|
+
|
126
|
+
amount, posting_cost, per_unit_cost, str = parse_amount(str)
|
127
|
+
posting[:amount] = amount if amount
|
128
|
+
posting[:posting_cost] = posting_cost if posting_cost
|
129
|
+
posting[:per_unit_cost] = per_unit_cost if per_unit_cost
|
130
|
+
|
131
|
+
comment, actual_date, effective_date = parse_posting_comment(str)
|
132
|
+
posting[:comment] = comment if comment
|
133
|
+
posting[:actual_date] = actual_date if actual_date
|
134
|
+
posting[:effective_date] = effective_date if effective_date
|
135
|
+
|
136
|
+
posting
|
137
|
+
end
|
138
|
+
|
139
|
+
def parse_account(str)
|
140
|
+
return [] if str.nil? or str.empty?
|
141
|
+
if match = str.match(/\A +([\w:]+)(\n|$| )(.*)/m)
|
142
|
+
[ match[1], false, false , match[3] ]
|
143
|
+
elsif match = str.match(/\A +\(([\w:]+)\)(\n|$| )(.*)/m)
|
144
|
+
[ match[1], true, false , match[3] ]
|
145
|
+
elsif match = str.match(/\A +\[([\w:]+)\](\n|$| )(.*)/m)
|
146
|
+
[ match[1], true, true , match[3] ]
|
147
|
+
else
|
148
|
+
[ nil, false, false, str ]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def parse_amount(str)
|
153
|
+
if match = str.match(/\A(.*?)@@([^;]*?)(;(.*)|\n(.*)|$(.*))/m)
|
154
|
+
amount = match[1].strip
|
155
|
+
[ amount.empty? ? nil : amount, nil, match[2].strip, match[3] ]
|
156
|
+
elsif match = str.match(/\A(.*?)@([^;]*)(;(.*)|\n(.*)|$(.*))/m)
|
157
|
+
amount = match[1].strip
|
158
|
+
[ amount.empty? ? nil : amount, match[2].strip, nil, match[3] ]
|
159
|
+
elsif match = str.match(/\A([^;]*?)(;(.*)|\n(.*)|$(.*))/m)
|
160
|
+
amount = match[1].strip
|
161
|
+
[ amount.empty? ? nil : amount, nil, nil, match[2] ]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def parse_posting_comment(str)
|
166
|
+
comment, actual_date, effective_date = nil, nil, nil
|
167
|
+
|
168
|
+
if match = str.match(/\A *?; \[(.*)\]/)
|
169
|
+
actual_date, effective_date = match[1].split('=')
|
170
|
+
elsif match = str.match(/\A *?; (.*)/)
|
171
|
+
comment = match[1]
|
172
|
+
end
|
173
|
+
|
174
|
+
[ comment, actual_date, effective_date ]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module LedgerRest
|
2
|
+
class Ledger
|
3
|
+
class Register
|
4
|
+
|
5
|
+
FORMAT = [
|
6
|
+
"{",
|
7
|
+
' "date": %(quoted(date)),',
|
8
|
+
' "effective_date": %(effective_date ? quoted(effective_date) : "null"),',
|
9
|
+
' "code": %(code ? quoted(code) : "null"),',
|
10
|
+
' "cleared": %(cleared ? "true" : "false"),',
|
11
|
+
' "pending": %(pending ? "true" : "false"),',
|
12
|
+
' "payee": %(quoted(payee)),',
|
13
|
+
' "postings":',
|
14
|
+
' [',
|
15
|
+
' { "account": %(quoted(display_account)), "amount": %(quoted(amount)) },%/',
|
16
|
+
' { "account": %(quoted(display_account)), "amount": %(quoted(amount)) },%/',
|
17
|
+
' ]',
|
18
|
+
'},'
|
19
|
+
].join('\\n')
|
20
|
+
|
21
|
+
class << self
|
22
|
+
|
23
|
+
def get(query = nil, params = {})
|
24
|
+
JSON.parse(json(query, params), :symbolize_names => true)
|
25
|
+
end
|
26
|
+
|
27
|
+
def json(query = nil, params = {})
|
28
|
+
params = { '--format' => FORMAT }.merge(params)
|
29
|
+
result = Ledger.exec("reg #{query}", params)
|
30
|
+
result << "\n]\n}"
|
31
|
+
result.gsub! /\},\n *?\]/m, "}\n \]"
|
32
|
+
|
33
|
+
"{\"transactions\":[#{result}]}"
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module LedgerRest
|
3
|
+
class Ledger
|
4
|
+
class Transaction < Hash
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
# Parse a ledger transaction string into a `Transaction` object.
|
9
|
+
def parse(str)
|
10
|
+
LedgerRest::Ledger::Parser.parse(str)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(params = {})
|
16
|
+
self.merge!(params)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Return true if the `Transaction#to_ledger` is a valid ledger string.
|
20
|
+
def valid?
|
21
|
+
result = IO.popen("#{settings.ledger_bin} -f - stats 2>&1", "r+") do |f|
|
22
|
+
f.write self.to_ledger
|
23
|
+
f.close_write
|
24
|
+
f.readlines
|
25
|
+
end
|
26
|
+
|
27
|
+
$?.success? and not result.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_ledger
|
31
|
+
if self[:date].nil? or
|
32
|
+
self[:payee].nil? or
|
33
|
+
self[:postings].nil?
|
34
|
+
return nil
|
35
|
+
end
|
36
|
+
|
37
|
+
result = ""
|
38
|
+
|
39
|
+
result << self[:date]
|
40
|
+
result << "=#{self[:effective_date]}" if self[:effective_date]
|
41
|
+
|
42
|
+
if self[:cleared]
|
43
|
+
result << " *"
|
44
|
+
elsif self[:pending]
|
45
|
+
result << " !"
|
46
|
+
end
|
47
|
+
|
48
|
+
result << " (#{self[:code]})" if self[:code]
|
49
|
+
result << " #{self[:payee]}"
|
50
|
+
result << "\n"
|
51
|
+
|
52
|
+
self[:postings].each do |posting|
|
53
|
+
if posting[:comment]
|
54
|
+
result << " ; #{posting[:comment]}\n"
|
55
|
+
next
|
56
|
+
end
|
57
|
+
|
58
|
+
next unless posting[:account]
|
59
|
+
|
60
|
+
if posting[:virtual]
|
61
|
+
if posting[:balance]
|
62
|
+
result << " [#{posting[:account]}]"
|
63
|
+
else
|
64
|
+
result << " (#{posting[:account]})"
|
65
|
+
end
|
66
|
+
else
|
67
|
+
result << " #{posting[:account]}"
|
68
|
+
end
|
69
|
+
|
70
|
+
if posting[:amount].nil?
|
71
|
+
result << "\n"
|
72
|
+
next
|
73
|
+
end
|
74
|
+
|
75
|
+
result << " #{posting[:amount]}"
|
76
|
+
|
77
|
+
if(posting[:per_unit_cost])
|
78
|
+
result << " @@ #{posting[:per_unit_cost]}"
|
79
|
+
elsif(posting[:posting_cost])
|
80
|
+
result << " @ #{posting[:posting_cost]}"
|
81
|
+
end
|
82
|
+
|
83
|
+
if posting[:actual_date] or posting[:effective_date]
|
84
|
+
result << " ; ["
|
85
|
+
result << posting[:actual_date] if posting[:actual_date]
|
86
|
+
result << "=#{posting[:effective_date]}" if posting[:effective_date]
|
87
|
+
result << "]"
|
88
|
+
end
|
89
|
+
|
90
|
+
result << "\n"
|
91
|
+
end
|
92
|
+
|
93
|
+
result << "\n"
|
94
|
+
|
95
|
+
result
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,364 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe LedgerRest::Ledger::Parser do
|
5
|
+
before :all do
|
6
|
+
@parser = LedgerRest::Ledger::Parser.new
|
7
|
+
end
|
8
|
+
|
9
|
+
context '#parse' do
|
10
|
+
subject {
|
11
|
+
LedgerRest::Ledger::Transaction.new(:date => "2012/03/01",
|
12
|
+
:effective_date => "2012/03/23",
|
13
|
+
:cleared => true,
|
14
|
+
:pending => false,
|
15
|
+
:code => "INV#23",
|
16
|
+
:payee => "me, myself and I",
|
17
|
+
:postings => [
|
18
|
+
{
|
19
|
+
:account => "Expenses:Imaginary",
|
20
|
+
:amount => "€ 23",
|
21
|
+
:per_unit_cost => "USD 2300",
|
22
|
+
:actual_date => "2012/03/24",
|
23
|
+
:effective_date => "2012/03/25"
|
24
|
+
}, {
|
25
|
+
:account => "Expenses:Magical",
|
26
|
+
:amount => "€ 42",
|
27
|
+
:posting_cost => "USD 23000000",
|
28
|
+
:virtual => true
|
29
|
+
}, {
|
30
|
+
:account => "Assets:Mighty"
|
31
|
+
}, {
|
32
|
+
:comment => "This is a freeform comment"
|
33
|
+
},
|
34
|
+
])
|
35
|
+
}
|
36
|
+
|
37
|
+
it 'should parse a to_ledger converted transaction into the same original hash' do
|
38
|
+
@parser.parse(subject.to_ledger).should == subject
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
context '#parse_date' do
|
44
|
+
before :all do
|
45
|
+
@ret = @parser.parse_date("2012/11/23 * Rest with\nAnd Stuff")
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should return the parsed date' do
|
49
|
+
@ret[0].should == '2012/11/23'
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should return the rest of the input' do
|
53
|
+
@ret[1].should == " * Rest with\nAnd Stuff"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context '#parse_effective_date' do
|
58
|
+
before :all do
|
59
|
+
@ret = @parser.parse_effective_date("=2012/11/24 * Rest with\nAnd Stuff")
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should return the parsed date' do
|
63
|
+
@ret[0].should == "2012/11/24"
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should return the rest of the input' do
|
67
|
+
@ret[1].should == " * Rest with\nAnd Stuff"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context '#parse_state' do
|
72
|
+
context 'given cleared transaction input' do
|
73
|
+
before :all do
|
74
|
+
@ret = @parser.parse_cleared(" * Rest with\nAnd Stuff")
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should return true' do
|
78
|
+
@ret[0].should == true
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should return the rest of the input' do
|
82
|
+
@ret[1].should == "Rest with\nAnd Stuff"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'unspecified transaction input' do
|
87
|
+
before :all do
|
88
|
+
@ret = @parser.parse_cleared("Rest with\nAnd Stuff")
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should return false' do
|
92
|
+
@ret[0].should == false
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should return the rest of the input' do
|
96
|
+
@ret[1].should == "Rest with\nAnd Stuff"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context '#parse_pending' do
|
102
|
+
context 'given pending transaction input' do
|
103
|
+
before :all do
|
104
|
+
@ret = @parser.parse_pending(" ! Rest with\nAnd Stuff")
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'should return true' do
|
108
|
+
@ret[0].should == true
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should return the rest of the input' do
|
112
|
+
@ret[1].should == "Rest with\nAnd Stuff"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'unspecified transaction input' do
|
117
|
+
before :all do
|
118
|
+
@ret = @parser.parse_pending("Rest with\nAnd Stuff")
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should return false' do
|
122
|
+
@ret[0].should == false
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'should return the rest of the input' do
|
126
|
+
@ret[1].should == "Rest with\nAnd Stuff"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context '#parse_code' do
|
132
|
+
context 'given a transaction with white-spaced code' do
|
133
|
+
subject { @parser.parse_code(" (#123) Rest with\nAnd Stuff") }
|
134
|
+
|
135
|
+
its(:first) { should == '#123' }
|
136
|
+
its(:last) { should == "Rest with\nAnd Stuff"}
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'given a transaction with code' do
|
140
|
+
subject { @parser.parse_code("(#123) Rest with\nAnd Stuff") }
|
141
|
+
|
142
|
+
its(:first) { should == '#123' }
|
143
|
+
its(:last) { should == "Rest with\nAnd Stuff" }
|
144
|
+
end
|
145
|
+
|
146
|
+
context 'given a transaction without code' do
|
147
|
+
subject { @parser.parse_code("Rest with\nAnd Stuff") }
|
148
|
+
|
149
|
+
its(:first) { should == nil }
|
150
|
+
its(:last) { should == "Rest with\nAnd Stuff" }
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context '#parse_payee' do
|
155
|
+
context 'given an unstripped line' do
|
156
|
+
subject { @parser.parse_payee(" Monsieur Le Payee\n Some:Account 123EUR\n Some:Other")}
|
157
|
+
|
158
|
+
its(:first) { should == "Monsieur Le Payee" }
|
159
|
+
its(:last) { should == " Some:Account 123EUR\n Some:Other"}
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'given a stripped line' do
|
163
|
+
context 'given an unstripped line' do
|
164
|
+
subject { @parser.parse_payee("Monsieur Le Payee\n Some:Account 123EUR\n Some:Other")}
|
165
|
+
|
166
|
+
its(:first) { should == "Monsieur Le Payee" }
|
167
|
+
its(:last) { should == " Some:Account 123EUR\n Some:Other"}
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context '#parse_comments' do
|
173
|
+
context 'given no comments' do
|
174
|
+
subject { @parser.parse_comments(" Assets:Some:Stuff 23EUR")}
|
175
|
+
|
176
|
+
it 'should return all comments' do
|
177
|
+
subject[0].should == nil
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'should return the rest of the input' do
|
181
|
+
subject[1].should == " Assets:Some:Stuff 23EUR"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context 'given one line of transaction comments' do
|
186
|
+
subject { @parser.parse_comments(" ; ABC\n Assets:Some:Stuff 23EUR")}
|
187
|
+
|
188
|
+
it 'should return all comments' do
|
189
|
+
subject[0].should == "ABC\n"
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'should return the rest of the input' do
|
193
|
+
subject[1].should == " Assets:Some:Stuff 23EUR"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context 'given multiple lines of transaction comments' do
|
198
|
+
subject { @parser.parse_comments(" ; ABC\n ;DEF\n Assets:Some:Stuff 23EUR")}
|
199
|
+
|
200
|
+
it 'should return all comments' do
|
201
|
+
subject[0].should == "ABC\nDEF\n"
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'should return the rest of the input' do
|
205
|
+
subject[1].should == " Assets:Some:Stuff 23EUR"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context '#parse_account' do
|
211
|
+
context 'given normal' do
|
212
|
+
subject { @parser.parse_account(" Assets:Some:Nice 200EUR\n Assets:Account")}
|
213
|
+
|
214
|
+
it 'should return the account' do
|
215
|
+
subject[0].should == "Assets:Some:Nice"
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'should not be virtual' do
|
219
|
+
subject[1].should == false
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'should not be balanced virtual' do
|
223
|
+
subject[2].should == false
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'should return the rest of the input' do
|
227
|
+
subject[3].should == "200EUR\n Assets:Account"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
context 'given input without amount' do
|
232
|
+
subject { @parser.parse_account(" Assets:Some:Nice")}
|
233
|
+
|
234
|
+
it 'should return the account' do
|
235
|
+
subject[0].should == "Assets:Some:Nice"
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'should not be virtual' do
|
239
|
+
subject[1].should == false
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'should not be balanced virtual' do
|
243
|
+
subject[2].should == false
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'should return the rest of the input' do
|
247
|
+
subject[3].should == ""
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
context 'given virtual' do
|
252
|
+
subject { @parser.parse_account(" (Assets:Some:Nice) 200EUR\n Assets:Account")}
|
253
|
+
|
254
|
+
it 'should return the account' do
|
255
|
+
subject[0].should == "Assets:Some:Nice"
|
256
|
+
end
|
257
|
+
|
258
|
+
it 'should not be virtual' do
|
259
|
+
subject[1].should == true
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'should not be balanced virtual' do
|
263
|
+
subject[2].should == false
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'should return the rest of the input' do
|
267
|
+
subject[3].should == "200EUR\n Assets:Account"
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
context 'given balanced virtual' do
|
272
|
+
subject { @parser.parse_account(" [Assets:Some:Nice] 200EUR\n Assets:Account")}
|
273
|
+
|
274
|
+
it 'should return the account' do
|
275
|
+
subject[0].should == "Assets:Some:Nice"
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'should not be virtual' do
|
279
|
+
subject[1].should == true
|
280
|
+
end
|
281
|
+
|
282
|
+
it 'should not be balanced virtual' do
|
283
|
+
subject[2].should == true
|
284
|
+
end
|
285
|
+
|
286
|
+
it 'should return the rest of the input' do
|
287
|
+
subject[3].should == "200EUR\n Assets:Account"
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
context '#parse_amount' do
|
293
|
+
context 'given "23.00EUR"' do
|
294
|
+
subject { @parser.parse_amount("23.00EUR") }
|
295
|
+
|
296
|
+
it 'should return amount and commodity' do
|
297
|
+
subject[0].should == "23.00EUR"
|
298
|
+
end
|
299
|
+
|
300
|
+
it 'should return no posting_cost' do
|
301
|
+
subject[1].should == nil
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'should return no per_unit_cost' do
|
305
|
+
subject[2].should == nil
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
context 'given "25 AAPL @ 10.00EUR"' do
|
310
|
+
subject { @parser.parse_amount("25 AAPL @ 10.00EUR") }
|
311
|
+
|
312
|
+
it 'should return amount and commodity' do
|
313
|
+
subject[0].should == "25 AAPL"
|
314
|
+
end
|
315
|
+
|
316
|
+
it 'should return correct posting_cost' do
|
317
|
+
subject[1].should == "10.00EUR"
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'should return no per_unit_cost' do
|
321
|
+
subject[2].should == nil
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
context 'given "30Liters @@ 1.64EUR"' do
|
326
|
+
subject { @parser.parse_amount("30Liters @@ 1.64EUR") }
|
327
|
+
|
328
|
+
it 'should return amount and commodity' do
|
329
|
+
subject[0].should == "30Liters"
|
330
|
+
end
|
331
|
+
|
332
|
+
it 'should return no posting_cost' do
|
333
|
+
subject[1].should == nil
|
334
|
+
end
|
335
|
+
|
336
|
+
it 'should return correct per_unit_cost' do
|
337
|
+
subject[2].should == "1.64EUR"
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
context '#parse_posting' do
|
343
|
+
context 'given posting with comment' do
|
344
|
+
subject { @parser.parse_posting(" Assets:Test:Account 123EUR\n ; Some comment") }
|
345
|
+
|
346
|
+
it 'should have parsed correctly' do
|
347
|
+
subject.should == {
|
348
|
+
:account => "Assets:Test:Account",
|
349
|
+
:amount => "123EUR"
|
350
|
+
}
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
context 'given source posting' do
|
355
|
+
subject { @parser.parse_posting(" Assets:Test:Account") }
|
356
|
+
|
357
|
+
it 'should have parsed correctly' do
|
358
|
+
subject.should == {
|
359
|
+
:account => "Assets:Test:Account"
|
360
|
+
}
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|