From f4e8fb7892c74dbc344de0e0d75fc0a51a7f5bb8 Mon Sep 17 00:00:00 2001 From: sam schick Date: Sat, 15 Apr 2023 15:07:47 -0400 Subject: [PATCH] added parsing of balance assertions --- grammar/ledger.ne | 21 +++++++++++++-------- tests/nearly.test.ts | 34 ++++++++++++++++++++++++++++++++++ tests/parser.test.ts | 2 ++ 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/grammar/ledger.ne b/grammar/ledger.ne index 6d264d7..6107b6e 100644 --- a/grammar/ledger.ne +++ b/grammar/ledger.ne @@ -1,12 +1,15 @@ @preprocessor typescript +### Partially supported ### +# Transactions +# - Balance assertions (https://www.ledger-cli.org/3.0/doc/ledger3.html#Balance-assertions) +# - parsed but ignored in output ### Not supported ### # # Transactions: # - Metadata (https://www.ledger-cli.org/3.0/doc/ledger3.html#Metadata) # - Virtual postings (https://www.ledger-cli.org/3.0/doc/ledger3.html#Virtual-postings) # - Expression amounts (https://www.ledger-cli.org/3.0/doc/ledger3.html#Expression-amounts) -# - Balance assertions (https://www.ledger-cli.org/3.0/doc/ledger3.html#Balance-assertions) # - Balance assignments (https://www.ledger-cli.org/3.0/doc/ledger3.html#Balance-assignments) # - Commodities (https://www.ledger-cli.org/3.0/doc/ledger3.html#Commodity-prices) # @@ -41,6 +44,7 @@ currency: /[$£₤€₿₹¥¥₩Р]/, // Note: Р != P reconciled: /[!*]/, comment: { match: /[;#|][^\n]+/, value: (s:string) => s.slice(1).trim() }, + assertion: {match: /==?\*?/}, account: { match: /[^$£₤€₿₹¥¥₩Р;#|\n]+/, value: (s:string) => s.trim() }, }, alias: { @@ -81,20 +85,21 @@ expenselines -> | expenselines %newline expenseline {% ([rest,,l]) => { return [rest,l].flat(1) } %} expenseline -> - %ws:+ reconciled:? %account amount:? %ws:* %comment:? + %ws:+ reconciled:? %account amount:? balance:? %ws:* %comment:? {% - function(d) { + function([,r,acct,amt,_ba,,cmt]) { return { - reconcile: d[1] || '', - account: d[2].value, - currency: d[3]?.currency, - amount: d[3]?.amount, - comment: d[5]?.value, + reconcile: r || '', + account: acct.value, + currency: amt?.currency, + amount: amt?.amount, + comment: cmt?.value, } } %} | %ws:+ %comment {% ([,c]) => { return {comment: c.value} } %} +balance -> %ws:* %assertion %ws:+ amount {% (d) => {return {}} %} reconciled -> %reconciled %ws:+ {% ([r,]) => r.value %} alias -> "alias" %account %equal %account {% ([,l,,r]) => { return { blockLine: l.line, left: l.value, right: r.value } } %} amount -> %currency %number {% ([c,a]) => { return {currency: c.value, amount: parseFloat(a.value)} } %} diff --git a/tests/nearly.test.ts b/tests/nearly.test.ts index 919152e..2629bde 100644 --- a/tests/nearly.test.ts +++ b/tests/nearly.test.ts @@ -6,6 +6,8 @@ beforeEach(() => { parser = new Parser(Grammar.fromCompiled(grammar)); }); +const ASSERTIONS = ['=', '=*', '==', '==*'] + describe('parsing multiple blocks', () => { test('when there are not newlines separating blocks', () => { parser.feed('; This is a comment\n'); @@ -299,4 +301,36 @@ describe('parsing a transaction', () => { ], ]); }); + test.each(ASSERTIONS)('when there is a "%s" balance assertion', (assertion) => { + parser.feed('2018-04-03 Half Price Books\n'); + parser.feed(' Expenses:Books $300\n'); + parser.feed(` Assets:Checking $300 ${assertion} $300`); + + expect(parser.results).toEqual([ + [ + { + type: 'tx', + blockLine: 1, + value: { + date: '2018-04-03', + payee: 'Half Price Books', + expenselines: [ + { + amount: 300, + currency: '$', + account: 'Expenses:Books', + reconcile: '', + }, + { + amount: 300, + currency: '$', + account: 'Assets:Checking', + reconcile: '', + }, + ], + }, + }, + ], + ]); + }); }); diff --git a/tests/parser.test.ts b/tests/parser.test.ts index 645fa56..6417f39 100644 --- a/tests/parser.test.ts +++ b/tests/parser.test.ts @@ -875,6 +875,8 @@ alias c=Credit ], }, }; + + expect(txCache.parsingErrors).toHaveLength(0); expect(txCache.transactions).toHaveLength(1); expect(txCache.transactions[0]).toEqual(expected); expect(txCache.payees).toEqual(['Costco']);