From 4c111f5c0c20592d1337a2898df43fd812f7a144 Mon Sep 17 00:00:00 2001 From: Pascal Ernst Date: Sun, 31 Mar 2024 03:16:44 +0200 Subject: [PATCH 1/2] Handle negative signs in front of currencies e.g. `-$20.00` instead of `$-20.00` --- .gitignore | 3 ++- grammar/ledger.ne | 9 +++++--- tests/parser.test.ts | 50 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index f31544d..8f7c997 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,5 @@ data.json # Generated Files -grammar/ledger.ts \ No newline at end of file +grammar/ledger.ts +grammar/ledger.ts-e diff --git a/grammar/ledger.ne b/grammar/ledger.ne index f280371..1bbccd8 100644 --- a/grammar/ledger.ne +++ b/grammar/ledger.ne @@ -40,12 +40,13 @@ expenseLine: { newline: { match: '\n', lineBreaks: true }, ws: /[ \t]+/, - number: { match: /-?[0-9.,]+/, value: (s:string) => s.replace(/,/g, '') }, + absoluteNumber: { match: /[0-9.,]+/, value: (s:string) => s.replace(/,/g, '') }, + numberSign: /-/, currency: /[$£₤€₿₹¥¥₩Р₽₴₫]/, // Note: Р != P reconciled: /[!*]/, comment: { match: /[;#|][^\n]+/, value: (s:string) => s.slice(1).trim() }, assertion: {match: /==?\*?/}, - account: { match: /[^$£₤€₿₹¥¥₩Р₽₴₫;#|\n]+/, value: (s:string) => s.trim() }, + account: { match: /[^$£₤€₿₹¥¥₩Р₽₴₫;#|\n\-]+/, value: (s:string) => s.trim() }, }, alias: { account: { match: /[a-zA-Z0-9: ]+/, value: (s:string) => s.trim() }, @@ -102,5 +103,7 @@ expenseline -> 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)} } %} +amount -> %currency %absoluteNumber {% ([c,a]) => { return {currency: c.value, amount: parseFloat(a.value)} } %} +amount -> %currency %numberSign %absoluteNumber {% ([c,ns,a]) => { return {currency: c.value, amount: parseFloat(ns.value + a.value)} } %} +amount -> %numberSign %currency %absoluteNumber {% ([ns,c,a]) => { return {currency: c.value, amount: parseFloat(ns.value + a.value)} } %} check -> %check {% ([c]) => parseFloat(c.value) %} diff --git a/tests/parser.test.ts b/tests/parser.test.ts index 6417f39..bcd07a6 100644 --- a/tests/parser.test.ts +++ b/tests/parser.test.ts @@ -197,6 +197,54 @@ describe('parsing a ledger file', () => { expect(txCache.transactions).toHaveLength(1); expect(txCache.transactions[0]).toEqual(expected); }); + test('when the sign is in front of the currency', () => { + const contents = `2021/04/20 Obsidian + e:Spending Money + b:CreditUnion -$20.00`; + const txCache = parse(contents, settings); + const expected: EnhancedTransaction = { + type: 'tx', + blockLine: 1, + block: { + firstLine: 0, + lastLine: 2, + block: contents, + }, + value: { + date: '2021/04/20', + payee: 'Obsidian', + expenselines: [ + { + account: 'e:Spending Money', + dealiasedAccount: 'e:Spending Money', + amount: 20, + currency: '$', + reconcile: '', + }, + { + account: 'b:CreditUnion', + dealiasedAccount: 'b:CreditUnion', + amount: -20, + currency: '$', + reconcile: '', + }, + ], + }, + }; + expect(txCache.transactions).toHaveLength(1); + expect(txCache.transactions[0]).toEqual(expected); + }); + test('when the sign is in front of the currency as well as in the amount', () => { + // ledger-cli combines the signs in front of the currency and in front of the amount to make it positive again + // (result for `-$-20.00` is positive, e.g. 20$). + // We would have previously parsed this as a negative amount (account name would be 'b:CreditUnion -' though). + // For now lets just not allow double signs to surface this inconsistency hoping that this is an edge case. + const contents = `2021/04/20 Obsidian + e:Spending Money + b:CreditUnion -$-20.00`; + const txCache = parse(contents, settings); + expect(txCache.transactions).toHaveLength(0); + }); test('when the middle expense has no amount', () => { const contents = `2021/04/20 Obsidian e:Spending Money $20.00 @@ -875,7 +923,7 @@ alias c=Credit ], }, }; - + expect(txCache.parsingErrors).toHaveLength(0); expect(txCache.transactions).toHaveLength(1); expect(txCache.transactions[0]).toEqual(expected); From 90833f7cc3d89519a149f1b877ec825eb944c078 Mon Sep 17 00:00:00 2001 From: Pascal Ernst Date: Thu, 4 Apr 2024 00:43:10 +0200 Subject: [PATCH 2/2] feat: Support Stars in Payee string --- grammar/ledger.ne | 2 +- tests/parser.test.ts | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/grammar/ledger.ne b/grammar/ledger.ne index 1bbccd8..71f5f6e 100644 --- a/grammar/ledger.ne +++ b/grammar/ledger.ne @@ -33,7 +33,7 @@ check: { match: /\([0-9]+\)[ \t]+/, value: (s:string) => s.trim().slice(1, -1) }, ws: /[ \t]+/, reconciled: /[!*]/, - payee: { match: /[^!*;#|\n]+/, value: (s:string) => s.trim() }, + payee: { match: /[^!*;#|\n][^!;#|\n]+/, value: (s:string) => s.trim() }, comment: { match: /[;#|][^\n]+/, value: (s:string) => s.slice(1).trim() }, newline: { match: '\n', lineBreaks: true, next: 'expenseLine'}, }, diff --git a/tests/parser.test.ts b/tests/parser.test.ts index bcd07a6..af335db 100644 --- a/tests/parser.test.ts +++ b/tests/parser.test.ts @@ -546,6 +546,43 @@ describe('parsing a ledger file', () => { expect(txCache.transactions).toHaveLength(1); expect(txCache.transactions[0]).toEqual(expected); }); + test('Stars in payee are supported', () => { + const contents = `2021/04/21 CardNr.*******1234 + e:Spending Money + b:CreditUnion -$20.00`; + const txCache = parse(contents, settings); + const expected: EnhancedTransaction = { + type: 'tx', + blockLine: 1, + block: { + firstLine: 0, + lastLine: 2, + block: contents, + }, + value: { + date: '2021/04/21', + payee: 'CardNr.*******1234', + expenselines: [ + { + account: 'e:Spending Money', + dealiasedAccount: 'e:Spending Money', + amount: 20, + currency: '$', + reconcile: '', + }, + { + account: 'b:CreditUnion', + dealiasedAccount: 'b:CreditUnion', + amount: -20, + currency: '$', + reconcile: '', + }, + ], + }, + }; + expect(txCache.transactions).toHaveLength(1); + expect(txCache.transactions[0]).toEqual(expected); + }); test('A parsing error tags the result accordingly', () => { const contents = `2021/04/20 Obsidian e:Spending Money $20.00