template: Support custom payee lines.
This commit is contained in:
parent
668906b944
commit
474a0390e3
4 changed files with 76 additions and 21 deletions
|
@ -51,14 +51,17 @@ Refer to the `Python documentation for INI file structure <https://docs.python.o
|
||||||
Template variables
|
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 =
|
template patreon income =
|
||||||
|
{date} Patreon payment from {payee}
|
||||||
Income:Patreon -{amount}
|
Income:Patreon -{amount}
|
||||||
;Payee: {payee}
|
;Payee: {payee}
|
||||||
Accrued:Accounts Receivable:Patreon {amount}
|
Accrued:Accounts Receivable:Patreon {amount}
|
||||||
;Payee: Patreon
|
;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:
|
Every template can use the following variables:
|
||||||
|
|
||||||
================== ==========================================================
|
================== ==========================================================
|
||||||
|
|
|
@ -180,6 +180,7 @@ class AccountSplitter:
|
||||||
class Template:
|
class Template:
|
||||||
ACCOUNT_SPLIT_RE = re.compile(r'(?:\t| )\s*')
|
ACCOUNT_SPLIT_RE = re.compile(r'(?:\t| )\s*')
|
||||||
DATE_FMT = '%Y/%m/%d'
|
DATE_FMT = '%Y/%m/%d'
|
||||||
|
PAYEE_LINE_RE = re.compile(r'\{(\w*_)*date\}')
|
||||||
SIGNED_CURRENCY_FMT = '¤#,##0.###;¤-#,##0.###'
|
SIGNED_CURRENCY_FMT = '¤#,##0.###;¤-#,##0.###'
|
||||||
UNSIGNED_CURRENCY_FMT = '#,##0.### ¤¤'
|
UNSIGNED_CURRENCY_FMT = '#,##0.### ¤¤'
|
||||||
|
|
||||||
|
@ -192,19 +193,19 @@ class Template:
|
||||||
self.splitter = AccountSplitter(
|
self.splitter = AccountSplitter(
|
||||||
signed_currencies, signed_currency_fmt, unsigned_currency_fmt, template_name)
|
signed_currencies, signed_currency_fmt, unsigned_currency_fmt, template_name)
|
||||||
|
|
||||||
lines = [s.lstrip() for s in template_s.splitlines(True)]
|
lines = self._template_lines(template_s)
|
||||||
for index, line in enumerate(lines):
|
self.format_funcs = []
|
||||||
if line:
|
try:
|
||||||
break
|
self.format_funcs.append(next(lines).format_map)
|
||||||
del lines[:index]
|
except StopIteration:
|
||||||
self.format_funcs = [
|
return
|
||||||
'\n{date} {payee}\n'.format_map,
|
metadata = []
|
||||||
]
|
for line in lines:
|
||||||
start_index = 0
|
if line.startswith(';'):
|
||||||
for index, line in enumerate(lines):
|
metadata.append(line)
|
||||||
if line[0].isalpha():
|
else:
|
||||||
if start_index < index:
|
self._add_str_func(metadata)
|
||||||
self._add_str_func(lines[start_index:index])
|
metadata = []
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
match = self.ACCOUNT_SPLIT_RE.search(line)
|
match = self.ACCOUNT_SPLIT_RE.search(line)
|
||||||
if match is None:
|
if match is None:
|
||||||
|
@ -213,15 +214,35 @@ class Template:
|
||||||
amount_expr = line[match.end():]
|
amount_expr = line[match.end():]
|
||||||
self.splitter.add(account, amount_expr)
|
self.splitter.add(account, amount_expr)
|
||||||
self.format_funcs.append(self.splitter.render_next)
|
self.format_funcs.append(self.splitter.render_next)
|
||||||
start_index = index + 1
|
if metadata:
|
||||||
if start_index <= index:
|
self._add_str_func(metadata)
|
||||||
self._add_str_func(lines[start_index:])
|
if not metadata[-1].endswith('\n'):
|
||||||
if not line.endswith('\n'):
|
|
||||||
self.format_funcs.append('\n'.format_map)
|
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):
|
def _add_str_func(self, str_seq):
|
||||||
str_flat = ''.join(' ' + s for s in 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)
|
self.format_funcs.append(str_flat.format_map)
|
||||||
else:
|
else:
|
||||||
self.splitter.set_metadata(str_flat)
|
self.splitter.set_metadata(str_flat)
|
||||||
|
@ -235,4 +256,7 @@ class Template:
|
||||||
amount=decimal.Decimal(amount),
|
amount=decimal.Decimal(amount),
|
||||||
currency=currency,
|
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)
|
return ''.join(f(template_vars) for f in self.format_funcs)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
[Simplest]
|
[Simplest]
|
||||||
template =
|
template = Accrued:Accounts Receivable {amount}
|
||||||
Accrued:Accounts Receivable {amount}
|
|
||||||
Income:Donations -{amount}
|
Income:Donations -{amount}
|
||||||
|
|
||||||
[FiftyFifty]
|
[FiftyFifty]
|
||||||
|
@ -28,3 +27,8 @@ template =
|
||||||
Accrued:Accounts Receivable {amount} - {tax}
|
Accrued:Accounts Receivable {amount} - {tax}
|
||||||
Income:RBI -.1*{amount}
|
Income:RBI -.1*{amount}
|
||||||
Income:Donations -.9*{amount}
|
Income:Donations -.9*{amount}
|
||||||
|
|
||||||
|
[Custom Payee]
|
||||||
|
template = {custom_date} {payee} - Custom
|
||||||
|
Accrued:Accounts Receivable {amount}
|
||||||
|
Income:Donations -{amount}
|
||||||
|
|
|
@ -102,6 +102,30 @@ def test_zeroed_account_skipped():
|
||||||
" Income:Donations -99.00 USD",
|
" 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', [
|
@pytest.mark.parametrize('amount_expr', [
|
||||||
'',
|
'',
|
||||||
'name',
|
'name',
|
||||||
|
|
Loading…
Reference in a new issue