ledger-rest 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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