hooks.ledger_entry: Clean up whitespace in strings.

The hook now ensures it does not output whitespace that could be
significant to Ledger, either because it's a newline or an
account-amount separator.
This commit is contained in:
Brett Smith 2020-01-10 13:26:45 -05:00
parent cc2b397801
commit 89378cbf90
3 changed files with 44 additions and 6 deletions

View file

@ -222,9 +222,10 @@ class AccountSplitter:
class Template: class Template:
ACCOUNT_SPLIT_RE = re.compile(r'(?:\t| )\s*') ACCOUNT_SPLIT_RE = re.compile(r'(?: ?\t| )[ \t]*')
DATE_FMT = '%Y/%m/%d' DATE_FMT = '%Y/%m/%d'
PAYEE_LINE_RE = re.compile(r'^\{(\w*_)*date\}\s') PAYEE_LINE_RE = re.compile(r'^\{(\w*_)*date\}\s')
NEWLINE_RE = re.compile(r'[\f\n\r\v\u0085\u2028\u2029]')
SIGNED_CURRENCY_FMT = '¤#,##0.###;¤-#,##0.###' SIGNED_CURRENCY_FMT = '¤#,##0.###;¤-#,##0.###'
UNSIGNED_CURRENCY_FMT = '#,##0.### ¤¤' UNSIGNED_CURRENCY_FMT = '#,##0.### ¤¤'
@ -301,12 +302,17 @@ class Template:
"entry needs {} field but that's not set by the importer".format( "entry needs {} field but that's not set by the importer".format(
self.date_field, self.date_field,
), self.splitter.template_name) ), self.splitter.template_name)
render_vars = { render_vars = {}
'amount': strparse.currency_decimal(template_vars['amount']),
}
for key, value in template_vars.items(): for key, value in template_vars.items():
if value is not None and (key == 'date' or key.endswith('_date')): if value is None:
pass
elif key == 'date' or key.endswith('_date'):
render_vars[key] = value.strftime(self.date_fmt) render_vars[key] = value.strftime(self.date_fmt)
elif isinstance(value, str):
value = self.NEWLINE_RE.sub(' ', value)
value = self.ACCOUNT_SPLIT_RE.sub(' ', value)
render_vars[key] = value
render_vars['amount'] = strparse.currency_decimal(template_vars['amount'])
all_vars = collections.ChainMap(render_vars, template_vars) all_vars = collections.ChainMap(render_vars, template_vars)
return ''.join(f(all_vars) for f in self.format_funcs) return ''.join(f(all_vars) for f in self.format_funcs)

View file

@ -30,7 +30,7 @@ REQUIREMENTS['tests_require'] = [
setup( setup(
name='import2ledger', name='import2ledger',
description="Import different sources of financial data to Ledger", description="Import different sources of financial data to Ledger",
version='0.11.0', version='0.11.1',
author='Brett Smith', author='Brett Smith',
author_email='brettcsmith@brettcsmith.org', author_email='brettcsmith@brettcsmith.org',
license='GNU AGPLv3+', license='GNU AGPLv3+',

View file

@ -92,6 +92,38 @@ def test_complex_template():
" ;Entity: T-T", " ;Entity: T-T",
] ]
def test_variable_whitespace_cleaned():
# There are two critical parts of this to avoid making malformed Ledger
# entries:
# * Ensure newlines become non-newline whitespace so we don't write
# malformed lines.
# * Collapse multiple spaces into one so variables in account names
# can't make malformed account lines by having a premature split
# between the account name and amount.
render_vars = template_vars('W\t\tS', '125.50', other_vars={
'entity': 'W\fS',
'program': 'Spectrum\r\nDefense',
'txid': 'ABC\v\tDEF',
})
lines = render_lines(
render_vars, 'Complex',
date_fmt='%Y-%m-%d',
signed_currencies=['USD'],
)
assert lines == [
"",
"2015-03-14 W S",
" ;Tag: Value",
" ;TransactionID: ABC DEF",
" Accrued:Accounts Receivable $125.50",
" ;Entity: Supplier",
" Income:Donations:Spectrum Defense $-119.85",
" ;Program: Spectrum Defense",
" ;Entity: W S",
" Income:Donations:General $-5.65",
" ;Entity: W S",
]
def test_balancing(): def test_balancing():
lines = render_lines(template_vars('FF', '1.01'), 'FiftyFifty') lines = render_lines(template_vars('FF', '1.01'), 'FiftyFifty')
assert lines == [ assert lines == [