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
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
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:
|
||||
|
||||
================== ==========================================================
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in a new issue