ledger-rest 2.0.3 → 3.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.
- 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
|