ledger-rest 2.0.3 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +2 -0
- data/Gemfile +1 -1
- data/Guardfile +4 -22
- data/{LICENSE.txt → LICENSE} +1 -1
- data/README.md +62 -11
- data/config.ru +1 -1
- data/ledger-rest.gemspec +17 -17
- data/ledger-rest.yml.example +3 -0
- data/lib/ledger-rest/app.rb +21 -34
- data/lib/ledger-rest/core_ext.rb +4 -3
- data/lib/ledger-rest/git.rb +8 -7
- data/lib/ledger-rest/ledger.rb +29 -24
- data/lib/ledger-rest/ledger/balance.rb +48 -15
- data/lib/ledger-rest/ledger/budget.rb +35 -16
- data/lib/ledger-rest/ledger/entry.rb +1 -4
- data/lib/ledger-rest/ledger/parser.rb +41 -37
- data/lib/ledger-rest/ledger/register.rb +31 -24
- data/lib/ledger-rest/ledger/transaction.rb +33 -28
- data/lib/ledger-rest/transactions.py +47 -0
- data/lib/ledger-rest/version.rb +2 -1
- data/spec/files/append.ledger +13 -0
- data/spec/files/budget.ledger +4 -0
- data/spec/files/main.ledger +4 -0
- data/spec/files/opening_balance.ledger +4 -0
- data/spec/files/transactions.ledger +11 -0
- data/spec/ledger-rest.yml +3 -0
- data/spec/ledger-rest/api/accounts_spec.rb +19 -0
- data/spec/ledger-rest/api/balance_spec.rb +117 -0
- data/spec/ledger-rest/api/budget_spec.rb +110 -0
- data/spec/ledger-rest/api/payees_spec.rb +19 -0
- data/spec/ledger-rest/api/register_spec.rb +233 -0
- data/spec/ledger-rest/api/transactions_spec.rb +201 -0
- data/spec/ledger-rest/api/version_spec.rb +13 -0
- data/spec/ledger-rest/ledger/balance_spec.rb +124 -0
- data/spec/ledger-rest/ledger/parser_spec.rb +184 -117
- data/spec/ledger-rest/ledger/transaction_spec.rb +38 -31
- data/spec/spec_helper.rb +22 -0
- data/spec/support/deep_eq_matcher.rb +149 -0
- metadata +52 -31
- data/ledger-rest.org +0 -22
@@ -0,0 +1,201 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe '/transactions' do
|
5
|
+
describe 'GET' do
|
6
|
+
let(:valid_json) do
|
7
|
+
[
|
8
|
+
{
|
9
|
+
'date' => '2013/01/01',
|
10
|
+
'payee' => 'Opening Balance',
|
11
|
+
'cleared' => true,
|
12
|
+
'posts' =>
|
13
|
+
[
|
14
|
+
{
|
15
|
+
'account' => 'Assets:Giro',
|
16
|
+
'amount' => 2000.0,
|
17
|
+
'commodity' => 'EUR'
|
18
|
+
}, {
|
19
|
+
'account' => 'Assets:Reimbursements:Hans Maulwurf',
|
20
|
+
'amount' => 19.0,
|
21
|
+
'commodity' => 'EUR'
|
22
|
+
}, {
|
23
|
+
'account' => 'Equity:Opening Balances',
|
24
|
+
'amount' => -2019.0,
|
25
|
+
'commodity' => 'EUR'
|
26
|
+
}
|
27
|
+
],
|
28
|
+
'pending' => false
|
29
|
+
}, {
|
30
|
+
'date' => '2013/12/03',
|
31
|
+
'payee' => 'NaveenaPath',
|
32
|
+
'cleared' => false,
|
33
|
+
'posts' =>
|
34
|
+
[
|
35
|
+
{
|
36
|
+
'account' => 'Expenses:Restaurants',
|
37
|
+
'amount' => 9.0,
|
38
|
+
'commodity' => 'EUR'
|
39
|
+
}, {
|
40
|
+
'account' => 'Assets:Cash',
|
41
|
+
'amount' => -9.0,
|
42
|
+
'commodity' => 'EUR'
|
43
|
+
}
|
44
|
+
],
|
45
|
+
'pending' => false
|
46
|
+
}, {
|
47
|
+
'date' => '2013/12/05',
|
48
|
+
'payee' => 'Shikgoo',
|
49
|
+
'cleared' => false,
|
50
|
+
'posts' =>
|
51
|
+
[
|
52
|
+
{
|
53
|
+
'account' => 'Expenses:Restaurants',
|
54
|
+
'amount' => 12.0,
|
55
|
+
'commodity' => 'EUR'
|
56
|
+
}, {
|
57
|
+
'account' => 'Liabilities:Max Mustermann',
|
58
|
+
'amount' => -12.0,
|
59
|
+
'commodity' => 'EUR'
|
60
|
+
}
|
61
|
+
],
|
62
|
+
'pending' => false
|
63
|
+
}, {
|
64
|
+
'posts' =>
|
65
|
+
[
|
66
|
+
{
|
67
|
+
'account' => 'Assets:Reimbursements:Hans Maulwurf',
|
68
|
+
'amount' => 3.1,
|
69
|
+
'commodity' => 'EUR'
|
70
|
+
}, {
|
71
|
+
'account' => 'Assets:Cash',
|
72
|
+
'amount' => -3.1,
|
73
|
+
'commodity' => 'EUR'
|
74
|
+
}
|
75
|
+
],
|
76
|
+
'note' => 'Sonnenblumenkernbrot',
|
77
|
+
'payee' => 'Bioladen Tegeler Straße',
|
78
|
+
'date' => '2013/12/10',
|
79
|
+
'cleared' => false,
|
80
|
+
'pending' => false
|
81
|
+
}, {
|
82
|
+
'effective_date' => '2013/12/02',
|
83
|
+
'posts' =>
|
84
|
+
[
|
85
|
+
{
|
86
|
+
'account' => 'Assets:Cash',
|
87
|
+
'amount' => 50.0,
|
88
|
+
'commodity' => 'EUR'
|
89
|
+
}, {
|
90
|
+
'account' => 'Assets:Giro',
|
91
|
+
'amount' => -50.0,
|
92
|
+
'commodity' => 'EUR'
|
93
|
+
}
|
94
|
+
],
|
95
|
+
'payee' => 'Sparkasse',
|
96
|
+
'date' => '2013/12/01',
|
97
|
+
'cleared' => true,
|
98
|
+
'pending' => false
|
99
|
+
}, {
|
100
|
+
'date' => '2013/12/04',
|
101
|
+
'payee' => 'Trattoria La Vialla',
|
102
|
+
'cleared' => false,
|
103
|
+
'posts' =>
|
104
|
+
[
|
105
|
+
{
|
106
|
+
'account' => 'Expenses:Restaurants',
|
107
|
+
'amount' => 32.2,
|
108
|
+
'commodity' => 'EUR'
|
109
|
+
}, {
|
110
|
+
'account' => 'Assets:Cash',
|
111
|
+
'amount' => -32.2,
|
112
|
+
'commodity' => 'EUR'
|
113
|
+
}
|
114
|
+
],
|
115
|
+
'pending' => false
|
116
|
+
}, {
|
117
|
+
'date' => '2013/12/06',
|
118
|
+
'payee' => 'Customer X',
|
119
|
+
'cleared' => false,
|
120
|
+
'posts' =>
|
121
|
+
[
|
122
|
+
{
|
123
|
+
'account' => 'Assets:Giro',
|
124
|
+
'amount' => 1200.0,
|
125
|
+
'commodity' => 'EUR'
|
126
|
+
}, {
|
127
|
+
'account' => 'Income:Invoice',
|
128
|
+
'amount' => -1200.0,
|
129
|
+
'commodity' => 'EUR'
|
130
|
+
}
|
131
|
+
],
|
132
|
+
'pending' => false
|
133
|
+
}
|
134
|
+
]
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'returns all transactions' do
|
138
|
+
get '/transactions'
|
139
|
+
JSON.parse(last_response.body).should deep_eq valid_json
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe 'POST' do
|
144
|
+
let(:transaction) do
|
145
|
+
{
|
146
|
+
date: '2013/12/12',
|
147
|
+
cleared: true,
|
148
|
+
payee: 'New Payee',
|
149
|
+
postings:
|
150
|
+
[
|
151
|
+
{
|
152
|
+
account: 'Expenses:Restaurants',
|
153
|
+
amount: 11.0,
|
154
|
+
commodity: 'EUR'
|
155
|
+
},
|
156
|
+
{
|
157
|
+
account: 'Assets:Cash',
|
158
|
+
amount: -11.0,
|
159
|
+
commodity: 'EUR'
|
160
|
+
}
|
161
|
+
]
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
let(:correct_response) do
|
166
|
+
{ transaction: transaction }
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'adds a new transaction to the append file' do
|
170
|
+
restore_file('spec/files/append.ledger') do
|
171
|
+
post '/transactions', transaction.to_json
|
172
|
+
|
173
|
+
last_response.status.should == 201
|
174
|
+
JSON.parse(last_response.body, symbolize_names: true).should deep_eq correct_response
|
175
|
+
puts File.read('spec/files/append.ledger').should == <<RESULT
|
176
|
+
2013/12/03 NaveenaPath
|
177
|
+
Expenses:Restaurants 9.00EUR
|
178
|
+
Assets:Cash
|
179
|
+
|
180
|
+
2013/12/05 Shikgoo
|
181
|
+
Expenses:Restaurants 12.00EUR
|
182
|
+
Liabilities:Max Mustermann
|
183
|
+
; meta_info: Some interesting meta information
|
184
|
+
|
185
|
+
2013/12/10 Bioladen Tegeler Straße
|
186
|
+
; Sonnenblumenkernbrot
|
187
|
+
Assets:Reimbursements:Hans Maulwurf 3.10EUR
|
188
|
+
Assets:Cash
|
189
|
+
|
190
|
+
2013/12/12 * New Payee
|
191
|
+
Expenses:Restaurants 11.00EUR
|
192
|
+
Assets:Cash -11.00EUR
|
193
|
+
RESULT
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe 'PUT' do
|
199
|
+
it 'updates a transaction'
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe '/version' do
|
5
|
+
it 'returns the ledger-rest version' do
|
6
|
+
get '/version'
|
7
|
+
|
8
|
+
JSON.parse(last_response.body).should == {
|
9
|
+
'version' => LedgerRest::VERSION,
|
10
|
+
'ledger-version' => LedgerRest::Ledger.version
|
11
|
+
}
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe LedgerRest::Ledger::Balance do
|
4
|
+
describe '::expand_accounts' do
|
5
|
+
let(:accounts) do
|
6
|
+
[
|
7
|
+
{
|
8
|
+
fullname: 'Expenses',
|
9
|
+
name: 'Expenses',
|
10
|
+
depth: 1,
|
11
|
+
total: '453.10EUR'
|
12
|
+
},
|
13
|
+
{
|
14
|
+
fullname: 'Expenses:Groceries:Bread',
|
15
|
+
name: 'Groceries:Bread',
|
16
|
+
depth: 3,
|
17
|
+
total: '3.10EUR'
|
18
|
+
},
|
19
|
+
{
|
20
|
+
fullname: 'Assets:Cash',
|
21
|
+
name: 'Assets:Cash',
|
22
|
+
depth: 1,
|
23
|
+
total: '200.00EUR'
|
24
|
+
}
|
25
|
+
]
|
26
|
+
end
|
27
|
+
|
28
|
+
let(:expanded) do
|
29
|
+
[
|
30
|
+
{ fullname: 'Expenses', name: 'Expenses', depth: 1, total: '453.10EUR' },
|
31
|
+
{ fullname: 'Expenses:Groceries', name: 'Groceries', depth: 2, total: '3.10EUR' },
|
32
|
+
{ fullname: 'Expenses:Groceries:Bread', name: 'Bread', depth: 3, total: '3.10EUR' },
|
33
|
+
{ fullname: 'Assets', name: 'Assets', depth: 1, total: '200.00EUR' },
|
34
|
+
{ fullname: 'Assets:Cash', name: 'Cash', depth: 2, total: '200.00EUR' }
|
35
|
+
]
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'expands accounts correctly' do
|
39
|
+
LedgerRest::Ledger::Balance.expand_accounts(accounts).should deep_eq expanded
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '::wrap_accounts' do
|
44
|
+
let(:accounts) do
|
45
|
+
[
|
46
|
+
{
|
47
|
+
fullname: 'Expenses',
|
48
|
+
name: 'Expenses',
|
49
|
+
depth: 1,
|
50
|
+
total: '453.10EUR'
|
51
|
+
},
|
52
|
+
{
|
53
|
+
fullname: 'Expenses:Groceries',
|
54
|
+
name: 'Groceries',
|
55
|
+
depth: 2,
|
56
|
+
total: '3.10EUR'
|
57
|
+
},
|
58
|
+
{
|
59
|
+
fullname: 'Expenses:Groceries:Bread',
|
60
|
+
name: 'Bread',
|
61
|
+
depth: 3,
|
62
|
+
total: '3.10EUR'
|
63
|
+
},
|
64
|
+
{
|
65
|
+
fullname: 'Expenses:Flat',
|
66
|
+
name: 'Flat',
|
67
|
+
depth: 2,
|
68
|
+
total: '450.00EUR'
|
69
|
+
},
|
70
|
+
{
|
71
|
+
fullname: 'Assets',
|
72
|
+
name: 'Assets',
|
73
|
+
depth: 1,
|
74
|
+
total: '200.00EUR'
|
75
|
+
}
|
76
|
+
]
|
77
|
+
end
|
78
|
+
|
79
|
+
let(:wrapped) do
|
80
|
+
[
|
81
|
+
{
|
82
|
+
fullname: 'Expenses',
|
83
|
+
name: 'Expenses',
|
84
|
+
depth: 1,
|
85
|
+
total: '453.10EUR',
|
86
|
+
accounts:
|
87
|
+
[
|
88
|
+
{
|
89
|
+
fullname: 'Expenses:Groceries',
|
90
|
+
name: 'Groceries',
|
91
|
+
depth: 2,
|
92
|
+
total: '3.10EUR',
|
93
|
+
accounts:
|
94
|
+
[
|
95
|
+
{
|
96
|
+
fullname: 'Expenses:Groceries:Bread',
|
97
|
+
name: 'Bread',
|
98
|
+
depth: 3,
|
99
|
+
total: '3.10EUR'
|
100
|
+
}
|
101
|
+
]
|
102
|
+
},
|
103
|
+
{
|
104
|
+
fullname: 'Expenses:Flat',
|
105
|
+
name: 'Flat',
|
106
|
+
depth: 2,
|
107
|
+
total: '450.00EUR'
|
108
|
+
}
|
109
|
+
]
|
110
|
+
},
|
111
|
+
{
|
112
|
+
fullname: 'Assets',
|
113
|
+
name: 'Assets',
|
114
|
+
depth: 1,
|
115
|
+
total: '200.00EUR'
|
116
|
+
}
|
117
|
+
]
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'wraps accounts correctly' do
|
121
|
+
LedgerRest::Ledger::Balance.wrap_accounts(accounts).should deep_eq wrapped
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -6,79 +6,83 @@ describe LedgerRest::Ledger::Parser do
|
|
6
6
|
@parser = LedgerRest::Ledger::Parser.new
|
7
7
|
end
|
8
8
|
|
9
|
-
|
10
|
-
subject
|
11
|
-
LedgerRest::Ledger::Transaction.new(:
|
12
|
-
:
|
13
|
-
:
|
14
|
-
:
|
15
|
-
:
|
16
|
-
:
|
17
|
-
:
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
9
|
+
describe '#parse' do
|
10
|
+
subject do
|
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
|
+
{
|
20
|
+
account: 'Expenses:Imaginary',
|
21
|
+
amount: 23.0,
|
22
|
+
commodity: 'EUR',
|
23
|
+
per_unit_cost: 2300.0,
|
24
|
+
per_unit_commodity: 'USD',
|
25
|
+
actual_date: '2012/03/24',
|
26
|
+
effective_date: '2012/03/25'
|
27
|
+
}, {
|
28
|
+
account: 'Expenses:Magical',
|
29
|
+
amount: 42.0,
|
30
|
+
commodity: 'EUR',
|
31
|
+
posting_cost: 23000000.0,
|
32
|
+
posting_cost_commodity: 'USD',
|
33
|
+
virtual: true
|
34
|
+
}, {
|
35
|
+
account: 'Assets:Mighty'
|
36
|
+
}, {
|
37
|
+
comment: 'This is a freeform comment'
|
38
|
+
},
|
39
|
+
])
|
40
|
+
end
|
36
41
|
|
37
42
|
it 'should parse a to_ledger converted transaction into the same original hash' do
|
38
43
|
@parser.parse(subject.to_ledger).should == subject
|
39
44
|
end
|
40
|
-
|
41
45
|
end
|
42
46
|
|
43
|
-
|
47
|
+
describe '#parse_date' do
|
44
48
|
before :all do
|
45
49
|
@ret = @parser.parse_date("2012/11/23 * Rest with\nAnd Stuff")
|
46
50
|
end
|
47
51
|
|
48
|
-
it '
|
52
|
+
it 'returns the parsed date' do
|
49
53
|
@ret[0].should == '2012/11/23'
|
50
54
|
end
|
51
55
|
|
52
|
-
it '
|
56
|
+
it 'returns the rest of the input' do
|
53
57
|
@ret[1].should == " * Rest with\nAnd Stuff"
|
54
58
|
end
|
55
59
|
end
|
56
60
|
|
57
|
-
|
61
|
+
describe '#parse_effective_date' do
|
58
62
|
before :all do
|
59
63
|
@ret = @parser.parse_effective_date("=2012/11/24 * Rest with\nAnd Stuff")
|
60
64
|
end
|
61
65
|
|
62
|
-
it '
|
63
|
-
@ret[0].should ==
|
66
|
+
it 'returns the parsed date' do
|
67
|
+
@ret[0].should == '2012/11/24'
|
64
68
|
end
|
65
69
|
|
66
|
-
it '
|
70
|
+
it 'returns the rest of the input' do
|
67
71
|
@ret[1].should == " * Rest with\nAnd Stuff"
|
68
72
|
end
|
69
73
|
end
|
70
74
|
|
71
|
-
|
75
|
+
describe '#parse_state' do
|
72
76
|
context 'given cleared transaction input' do
|
73
77
|
before :all do
|
74
78
|
@ret = @parser.parse_cleared(" * Rest with\nAnd Stuff")
|
75
79
|
end
|
76
80
|
|
77
|
-
it '
|
81
|
+
it 'returns true' do
|
78
82
|
@ret[0].should == true
|
79
83
|
end
|
80
84
|
|
81
|
-
it '
|
85
|
+
it 'returns the rest of the input' do
|
82
86
|
@ret[1].should == "Rest with\nAnd Stuff"
|
83
87
|
end
|
84
88
|
end
|
@@ -88,27 +92,27 @@ describe LedgerRest::Ledger::Parser do
|
|
88
92
|
@ret = @parser.parse_cleared("Rest with\nAnd Stuff")
|
89
93
|
end
|
90
94
|
|
91
|
-
it '
|
95
|
+
it 'returns false' do
|
92
96
|
@ret[0].should == false
|
93
97
|
end
|
94
98
|
|
95
|
-
it '
|
99
|
+
it 'returns the rest of the input' do
|
96
100
|
@ret[1].should == "Rest with\nAnd Stuff"
|
97
101
|
end
|
98
102
|
end
|
99
103
|
end
|
100
104
|
|
101
|
-
|
105
|
+
describe '#parse_pending' do
|
102
106
|
context 'given pending transaction input' do
|
103
107
|
before :all do
|
104
108
|
@ret = @parser.parse_pending(" ! Rest with\nAnd Stuff")
|
105
109
|
end
|
106
110
|
|
107
|
-
it '
|
111
|
+
it 'returns true' do
|
108
112
|
@ret[0].should == true
|
109
113
|
end
|
110
114
|
|
111
|
-
it '
|
115
|
+
it 'returns the rest of the input' do
|
112
116
|
@ret[1].should == "Rest with\nAnd Stuff"
|
113
117
|
end
|
114
118
|
end
|
@@ -118,22 +122,22 @@ describe LedgerRest::Ledger::Parser do
|
|
118
122
|
@ret = @parser.parse_pending("Rest with\nAnd Stuff")
|
119
123
|
end
|
120
124
|
|
121
|
-
it '
|
125
|
+
it 'returns false' do
|
122
126
|
@ret[0].should == false
|
123
127
|
end
|
124
128
|
|
125
|
-
it '
|
129
|
+
it 'returns the rest of the input' do
|
126
130
|
@ret[1].should == "Rest with\nAnd Stuff"
|
127
131
|
end
|
128
132
|
end
|
129
133
|
end
|
130
134
|
|
131
|
-
|
135
|
+
describe '#parse_code' do
|
132
136
|
context 'given a transaction with white-spaced code' do
|
133
137
|
subject { @parser.parse_code(" (#123) Rest with\nAnd Stuff") }
|
134
138
|
|
135
139
|
its(:first) { should == '#123' }
|
136
|
-
its(:last) { should == "Rest with\nAnd Stuff"}
|
140
|
+
its(:last) { should == "Rest with\nAnd Stuff" }
|
137
141
|
end
|
138
142
|
|
139
143
|
context 'given a transaction with code' do
|
@@ -146,124 +150,124 @@ describe LedgerRest::Ledger::Parser do
|
|
146
150
|
context 'given a transaction without code' do
|
147
151
|
subject { @parser.parse_code("Rest with\nAnd Stuff") }
|
148
152
|
|
149
|
-
its(:first) { should
|
153
|
+
its(:first) { should be_nil }
|
150
154
|
its(:last) { should == "Rest with\nAnd Stuff" }
|
151
155
|
end
|
152
156
|
end
|
153
157
|
|
154
|
-
|
158
|
+
describe '#parse_payee' do
|
155
159
|
context 'given an unstripped line' do
|
156
160
|
subject { @parser.parse_payee(" Monsieur Le Payee\n Some:Account 123EUR\n Some:Other")}
|
157
161
|
|
158
|
-
its(:first) { should ==
|
159
|
-
its(:last) { should == " Some:Account 123EUR\n Some:Other"}
|
162
|
+
its(:first) { should == 'Monsieur Le Payee' }
|
163
|
+
its(:last) { should == " Some:Account 123EUR\n Some:Other" }
|
160
164
|
end
|
161
165
|
|
162
166
|
context 'given a stripped line' do
|
163
167
|
context 'given an unstripped line' do
|
164
168
|
subject { @parser.parse_payee("Monsieur Le Payee\n Some:Account 123EUR\n Some:Other")}
|
165
169
|
|
166
|
-
its(:first) { should ==
|
167
|
-
its(:last) { should == " Some:Account 123EUR\n Some:Other"}
|
170
|
+
its(:first) { should == 'Monsieur Le Payee' }
|
171
|
+
its(:last) { should == " Some:Account 123EUR\n Some:Other" }
|
168
172
|
end
|
169
173
|
end
|
170
174
|
end
|
171
175
|
|
172
|
-
|
176
|
+
describe '#parse_comments' do
|
173
177
|
context 'given no comments' do
|
174
|
-
subject { @parser.parse_comments(
|
178
|
+
subject { @parser.parse_comments(' Assets:Some:Stuff 23EUR')}
|
175
179
|
|
176
|
-
it '
|
177
|
-
subject[0].should
|
180
|
+
it 'returns all comments' do
|
181
|
+
subject[0].should be_nil
|
178
182
|
end
|
179
183
|
|
180
|
-
it '
|
181
|
-
subject[1].should ==
|
184
|
+
it 'returns the rest of the input' do
|
185
|
+
subject[1].should == ' Assets:Some:Stuff 23EUR'
|
182
186
|
end
|
183
187
|
end
|
184
188
|
|
185
189
|
context 'given one line of transaction comments' do
|
186
190
|
subject { @parser.parse_comments(" ; ABC\n Assets:Some:Stuff 23EUR")}
|
187
191
|
|
188
|
-
it '
|
192
|
+
it 'returns all comments' do
|
189
193
|
subject[0].should == "ABC\n"
|
190
194
|
end
|
191
195
|
|
192
|
-
it '
|
193
|
-
subject[1].should ==
|
196
|
+
it 'returns the rest of the input' do
|
197
|
+
subject[1].should == ' Assets:Some:Stuff 23EUR'
|
194
198
|
end
|
195
199
|
end
|
196
200
|
|
197
201
|
context 'given multiple lines of transaction comments' do
|
198
202
|
subject { @parser.parse_comments(" ; ABC\n ;DEF\n Assets:Some:Stuff 23EUR")}
|
199
203
|
|
200
|
-
it '
|
204
|
+
it 'returns all comments' do
|
201
205
|
subject[0].should == "ABC\nDEF\n"
|
202
206
|
end
|
203
207
|
|
204
|
-
it '
|
205
|
-
subject[1].should ==
|
208
|
+
it 'returns the rest of the input' do
|
209
|
+
subject[1].should == ' Assets:Some:Stuff 23EUR'
|
206
210
|
end
|
207
211
|
end
|
208
212
|
end
|
209
213
|
|
210
|
-
|
214
|
+
describe '#parse_account' do
|
211
215
|
context 'given normal' do
|
212
216
|
subject { @parser.parse_account(" Assets:Some:Nice 200EUR\n Assets:Account")}
|
213
217
|
|
214
|
-
it '
|
215
|
-
subject[0].should ==
|
218
|
+
it 'returns the account' do
|
219
|
+
subject[0].should == 'Assets:Some:Nice'
|
216
220
|
end
|
217
221
|
|
218
|
-
it '
|
222
|
+
it 'is not virtual' do
|
219
223
|
subject[1].should == false
|
220
224
|
end
|
221
225
|
|
222
|
-
it '
|
226
|
+
it 'is not balanced virtual' do
|
223
227
|
subject[2].should == false
|
224
228
|
end
|
225
229
|
|
226
|
-
it '
|
230
|
+
it 'returns the rest of the input' do
|
227
231
|
subject[3].should == "200EUR\n Assets:Account"
|
228
232
|
end
|
229
233
|
end
|
230
234
|
|
231
235
|
context 'given input without amount' do
|
232
|
-
subject { @parser.parse_account(
|
236
|
+
subject { @parser.parse_account(' Assets:Some:Nice') }
|
233
237
|
|
234
|
-
it '
|
235
|
-
subject[0].should ==
|
238
|
+
it 'returns the account' do
|
239
|
+
subject[0].should == 'Assets:Some:Nice'
|
236
240
|
end
|
237
241
|
|
238
|
-
it '
|
242
|
+
it 'is not virtual' do
|
239
243
|
subject[1].should == false
|
240
244
|
end
|
241
245
|
|
242
|
-
it '
|
246
|
+
it 'is not balanced virtual' do
|
243
247
|
subject[2].should == false
|
244
248
|
end
|
245
249
|
|
246
|
-
it '
|
247
|
-
subject[3].should ==
|
250
|
+
it 'returns the rest of the input' do
|
251
|
+
subject[3].should == ''
|
248
252
|
end
|
249
253
|
end
|
250
254
|
|
251
255
|
context 'given virtual' do
|
252
256
|
subject { @parser.parse_account(" (Assets:Some:Nice) 200EUR\n Assets:Account")}
|
253
257
|
|
254
|
-
it '
|
255
|
-
subject[0].should ==
|
258
|
+
it 'returns the account' do
|
259
|
+
subject[0].should == 'Assets:Some:Nice'
|
256
260
|
end
|
257
261
|
|
258
|
-
it '
|
262
|
+
it 'is not virtual' do
|
259
263
|
subject[1].should == true
|
260
264
|
end
|
261
265
|
|
262
|
-
it '
|
266
|
+
it 'is not balanced virtual' do
|
263
267
|
subject[2].should == false
|
264
268
|
end
|
265
269
|
|
266
|
-
it '
|
270
|
+
it 'returns the rest of the input' do
|
267
271
|
subject[3].should == "200EUR\n Assets:Account"
|
268
272
|
end
|
269
273
|
end
|
@@ -271,92 +275,155 @@ describe LedgerRest::Ledger::Parser do
|
|
271
275
|
context 'given balanced virtual' do
|
272
276
|
subject { @parser.parse_account(" [Assets:Some:Nice] 200EUR\n Assets:Account")}
|
273
277
|
|
274
|
-
it '
|
275
|
-
subject[0].should ==
|
278
|
+
it 'returns the account' do
|
279
|
+
subject[0].should == 'Assets:Some:Nice'
|
276
280
|
end
|
277
281
|
|
278
|
-
it '
|
282
|
+
it 'is not virtual' do
|
279
283
|
subject[1].should == true
|
280
284
|
end
|
281
285
|
|
282
|
-
it '
|
286
|
+
it 'is not balanced virtual' do
|
283
287
|
subject[2].should == true
|
284
288
|
end
|
285
289
|
|
286
|
-
it '
|
290
|
+
it 'returns the rest of the input' do
|
287
291
|
subject[3].should == "200EUR\n Assets:Account"
|
288
292
|
end
|
289
293
|
end
|
290
294
|
end
|
291
295
|
|
292
|
-
|
296
|
+
describe '#parse_amount_parts' do
|
297
|
+
context 'given "23.00EUR"' do
|
298
|
+
subject { @parser.parse_amount_parts('23.00EUR') }
|
299
|
+
|
300
|
+
it 'returns the value' do
|
301
|
+
subject[0].should == 23.0
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'returns the commodity' do
|
305
|
+
subject[1].should == 'EUR'
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
context 'given "23EUR"' do
|
310
|
+
subject { @parser.parse_amount_parts('23EUR') }
|
311
|
+
|
312
|
+
it 'returns the value' do
|
313
|
+
subject[0].should == 23.0
|
314
|
+
end
|
315
|
+
|
316
|
+
it 'returns the commodity' do
|
317
|
+
subject[1].should == 'EUR'
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
context 'given "USD23"' do
|
322
|
+
subject { @parser.parse_amount_parts('USD23') }
|
323
|
+
|
324
|
+
it 'returns the value' do
|
325
|
+
subject[0].should == 23.0
|
326
|
+
end
|
327
|
+
|
328
|
+
it 'returns the commodity' do
|
329
|
+
subject[1].should == 'USD'
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
context 'given "USD23.00"' do
|
334
|
+
subject { @parser.parse_amount_parts('USD23') }
|
335
|
+
|
336
|
+
it 'returns the value' do
|
337
|
+
subject[0].should == 23.0
|
338
|
+
end
|
339
|
+
|
340
|
+
it 'returns the commodity' do
|
341
|
+
subject[1].should == 'USD'
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
context 'given "€ 23.00"' do
|
346
|
+
subject { @parser.parse_amount_parts('€ 23.00') }
|
347
|
+
|
348
|
+
it 'returns the value' do
|
349
|
+
subject[0].should == 23.0
|
350
|
+
end
|
351
|
+
|
352
|
+
it 'returns the commodity' do
|
353
|
+
subject[1].should == '€'
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
describe '#parse_amount' do
|
293
359
|
context 'given "23.00EUR"' do
|
294
|
-
subject { @parser.parse_amount(
|
360
|
+
subject { @parser.parse_amount('23.00EUR') }
|
295
361
|
|
296
|
-
it '
|
297
|
-
subject[0].should ==
|
362
|
+
it 'returns amount and commodity' do
|
363
|
+
subject[0].should == '23.00EUR'
|
298
364
|
end
|
299
365
|
|
300
|
-
it '
|
301
|
-
subject[1].should
|
366
|
+
it 'returns no posting_cost' do
|
367
|
+
subject[1].should be_nil
|
302
368
|
end
|
303
369
|
|
304
|
-
it '
|
305
|
-
subject[2].should
|
370
|
+
it 'returns no per_unit_cost' do
|
371
|
+
subject[2].should be_nil
|
306
372
|
end
|
307
373
|
end
|
308
374
|
|
309
375
|
context 'given "25 AAPL @ 10.00EUR"' do
|
310
|
-
subject { @parser.parse_amount(
|
376
|
+
subject { @parser.parse_amount('25 AAPL @ 10.00EUR') }
|
311
377
|
|
312
|
-
it '
|
313
|
-
subject[0].should ==
|
378
|
+
it 'returns amount and commodity' do
|
379
|
+
subject[0].should == '25 AAPL'
|
314
380
|
end
|
315
381
|
|
316
|
-
it '
|
317
|
-
subject[1].should ==
|
382
|
+
it 'returns correct posting_cost' do
|
383
|
+
subject[1].should == '10.00EUR'
|
318
384
|
end
|
319
385
|
|
320
|
-
it '
|
321
|
-
subject[2].should
|
386
|
+
it 'returns no per_unit_cost' do
|
387
|
+
subject[2].should be_nil
|
322
388
|
end
|
323
389
|
end
|
324
390
|
|
325
391
|
context 'given "30Liters @@ 1.64EUR"' do
|
326
|
-
subject { @parser.parse_amount(
|
392
|
+
subject { @parser.parse_amount('30Liters @@ 1.64EUR') }
|
327
393
|
|
328
|
-
it '
|
329
|
-
subject[0].should ==
|
394
|
+
it 'returns amount and commodity' do
|
395
|
+
subject[0].should == '30Liters'
|
330
396
|
end
|
331
397
|
|
332
|
-
it '
|
333
|
-
subject[1].should
|
398
|
+
it 'returns no posting_cost' do
|
399
|
+
subject[1].should be_nil
|
334
400
|
end
|
335
401
|
|
336
|
-
it '
|
337
|
-
subject[2].should ==
|
402
|
+
it 'returns correct per_unit_cost' do
|
403
|
+
subject[2].should == '1.64EUR'
|
338
404
|
end
|
339
405
|
end
|
340
406
|
end
|
341
407
|
|
342
|
-
|
408
|
+
describe '#parse_posting' do
|
343
409
|
context 'given posting with comment' do
|
344
410
|
subject { @parser.parse_posting(" Assets:Test:Account 123EUR\n ; Some comment") }
|
345
411
|
|
346
|
-
it '
|
412
|
+
it 'has parsed correctly' do
|
347
413
|
subject.should == {
|
348
|
-
:
|
349
|
-
:
|
414
|
+
account: 'Assets:Test:Account',
|
415
|
+
amount: 123.0,
|
416
|
+
commodity: 'EUR'
|
350
417
|
}
|
351
418
|
end
|
352
419
|
end
|
353
420
|
|
354
421
|
context 'given source posting' do
|
355
|
-
subject { @parser.parse_posting(
|
422
|
+
subject { @parser.parse_posting(' Assets:Test:Account') }
|
356
423
|
|
357
|
-
it '
|
424
|
+
it 'has parsed correctly' do
|
358
425
|
subject.should == {
|
359
|
-
:
|
426
|
+
account: 'Assets:Test:Account'
|
360
427
|
}
|
361
428
|
end
|
362
429
|
end
|