template: Support custom payee lines.

This commit is contained in:
Brett Smith 2017-12-18 12:23:18 -05:00
parent 668906b944
commit 474a0390e3
4 changed files with 76 additions and 21 deletions

View file

@ -51,14 +51,17 @@ Refer to the `Python documentation for INI file structure <https://docs.python.o
Template variables
~~~~~~~~~~~~~~~~~~
import2ledger templates have access to a few variables for each transaction that can be included anywhere in the entry, not just amount expressions. In other parts of the entry, they're treated as strings, and you can control their formatting using `Python's format string syntax <https://docs.python.org/3/library/string.html#formatstrings>`_. For example, this template sets different payees for each side of the transaction::
import2ledger templates have access to a few variables for each transaction that can be included anywhere in the entry, not just amount expressions. In other parts of the entry, they're treated as strings, and you can control their formatting using `Python's format string syntax <https://docs.python.org/3/library/string.html#formatstrings>`_. For example, this template customizes the payee line and sets different payees for each side of the transaction::
template patreon income =
{date} Patreon payment from {payee}
Income:Patreon -{amount}
;Payee: {payee}
Accrued:Accounts Receivable:Patreon {amount}
;Payee: Patreon
Templates automatically detect whether or not you have a custom payee line by checking if the first line begins with a date variable. If it does, it's assumed to be your payee line. Otherwise, the template uses a default payee line of ``{date} {payee}``.
Every template can use the following variables:
================== ==========================================================

View file

@ -180,6 +180,7 @@ class AccountSplitter:
class Template:
ACCOUNT_SPLIT_RE = re.compile(r'(?:\t| )\s*')
DATE_FMT = '%Y/%m/%d'
PAYEE_LINE_RE = re.compile(r'\{(\w*_)*date\}')
SIGNED_CURRENCY_FMT = '¤#,##0.###;¤-#,##0.###'
UNSIGNED_CURRENCY_FMT = '#,##0.### ¤¤'
@ -192,19 +193,19 @@ class Template:
self.splitter = AccountSplitter(
signed_currencies, signed_currency_fmt, unsigned_currency_fmt, template_name)
lines = [s.lstrip() for s in template_s.splitlines(True)]
for index, line in enumerate(lines):
if line:
break
del lines[:index]
self.format_funcs = [
'\n{date} {payee}\n'.format_map,
]
start_index = 0
for index, line in enumerate(lines):
if line[0].isalpha():
if start_index < index:
self._add_str_func(lines[start_index:index])
lines = self._template_lines(template_s)
self.format_funcs = []
try:
self.format_funcs.append(next(lines).format_map)
except StopIteration:
return
metadata = []
for line in lines:
if line.startswith(';'):
metadata.append(line)
else:
self._add_str_func(metadata)
metadata = []
line = line.strip()
match = self.ACCOUNT_SPLIT_RE.search(line)
if match is None:
@ -213,15 +214,35 @@ class Template:
amount_expr = line[match.end():]
self.splitter.add(account, amount_expr)
self.format_funcs.append(self.splitter.render_next)
start_index = index + 1
if start_index <= index:
self._add_str_func(lines[start_index:])
if not line.endswith('\n'):
if metadata:
self._add_str_func(metadata)
if not metadata[-1].endswith('\n'):
self.format_funcs.append('\n'.format_map)
def _nonblank_lines(self, s):
for line in s.splitlines(True):
line = line.lstrip()
if line:
yield line
def _template_lines(self, template_s):
lines = self._nonblank_lines(template_s)
try:
line1 = next(lines)
except StopIteration:
return
if self.PAYEE_LINE_RE.match(line1):
yield '\n' + line1
else:
yield '\n{date} {payee}\n'
yield line1
yield from lines
def _add_str_func(self, str_seq):
str_flat = ''.join(' ' + s for s in str_seq)
if self.splitter.is_empty():
if not str_flat:
pass
elif self.splitter.is_empty():
self.format_funcs.append(str_flat.format_map)
else:
self.splitter.set_metadata(str_flat)
@ -235,4 +256,7 @@ class Template:
amount=decimal.Decimal(amount),
currency=currency,
)
for key, value in template_vars.items():
if key.endswith('_date'):
template_vars[key] = value.strftime(self.date_fmt)
return ''.join(f(template_vars) for f in self.format_funcs)

View file

@ -1,6 +1,5 @@
[Simplest]
template =
Accrued:Accounts Receivable {amount}
template = Accrued:Accounts Receivable {amount}
Income:Donations -{amount}
[FiftyFifty]
@ -28,3 +27,8 @@ template =
Accrued:Accounts Receivable {amount} - {tax}
Income:RBI -.1*{amount}
Income:Donations -.9*{amount}
[Custom Payee]
template = {custom_date} {payee} - Custom
Accrued:Accounts Receivable {amount}
Income:Donations -{amount}

View file

@ -102,6 +102,30 @@ def test_zeroed_account_skipped():
" Income:Donations -99.00 USD",
]
def test_custom_payee_line():
tmpl = template_from('Custom Payee')
rendered = tmpl.render('ZZ', decimal.Decimal('10.00'), 'USD', DATE,
custom_date=datetime.date(2014, 2, 13))
lines = [normalize_whitespace(s) for s in rendered.splitlines()]
assert lines == [
"",
"2014/02/13 ZZ - Custom",
" Accrued:Accounts Receivable 10.00 USD",
" Income:Donations -10.00 USD",
]
def test_line1_not_custom_payee():
tmpl = template_from('Simplest')
rendered = tmpl.render('VV', decimal.Decimal('15.00'), 'USD', DATE,
custom_date=datetime.date(2014, 2, 12))
lines = [normalize_whitespace(s) for s in rendered.splitlines()]
assert lines == [
"",
"2015/03/14 VV",
" Accrued:Accounts Receivable 15.00 USD",
" Income:Donations -15.00 USD",
]
@pytest.mark.parametrize('amount_expr', [
'',
'name',