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.
@@ -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,3 @@
1
+ module LedgerRest
2
+ VERSION = "2.0.0"
3
+ 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