template: Improve error detection and reporting in amount expressions.
This commit is contained in:
parent
e3335aa6ec
commit
a1f815c60b
2 changed files with 37 additions and 4 deletions
|
@ -52,7 +52,15 @@ class TokenTransformer:
|
||||||
class AmountTokenTransformer(TokenTransformer):
|
class AmountTokenTransformer(TokenTransformer):
|
||||||
SUPPORTED_OPS = frozenset('+-*/()')
|
SUPPORTED_OPS = frozenset('+-*/()')
|
||||||
|
|
||||||
transform_NAME = TokenTransformer._noop_transformer
|
def __iter__(self):
|
||||||
|
tokens = super().__iter__()
|
||||||
|
for token in tokens:
|
||||||
|
yield token
|
||||||
|
if token[0] == tokenize.NAME:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise ValueError("no amount in expression")
|
||||||
|
yield from tokens
|
||||||
|
|
||||||
def transform_NUMBER(self, ttype, tvalue):
|
def transform_NUMBER(self, ttype, tvalue):
|
||||||
yield (tokenize.NAME, 'Decimal')
|
yield (tokenize.NAME, 'Decimal')
|
||||||
|
@ -66,6 +74,7 @@ class AmountTokenTransformer(TokenTransformer):
|
||||||
name_type, name_value, _, _, _ = next(self.in_tokens)
|
name_type, name_value, _, _, _ = next(self.in_tokens)
|
||||||
close_type, close_value, _, _, _ = next(self.in_tokens)
|
close_type, close_value, _, _, _ = next(self.in_tokens)
|
||||||
if (name_type != tokenize.NAME
|
if (name_type != tokenize.NAME
|
||||||
|
or name_value != name_value.lower()
|
||||||
or close_type != tokenize.OP
|
or close_type != tokenize.OP
|
||||||
or close_value != '}'):
|
or close_value != '}'):
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
|
@ -91,9 +100,11 @@ class AccountSplitter:
|
||||||
def add(self, account, amount_expr):
|
def add(self, account, amount_expr):
|
||||||
try:
|
try:
|
||||||
clean_expr = AmountTokenTransformer.from_str(amount_expr).transform()
|
clean_expr = AmountTokenTransformer.from_str(amount_expr).transform()
|
||||||
except ValueError as error:
|
compiled_expr = compile(clean_expr, self.template_name, 'eval')
|
||||||
|
except (SyntaxError, tokenize.TokenError, ValueError) as error:
|
||||||
raise errors.UserInputConfigurationError(error.args[0], amount_expr)
|
raise errors.UserInputConfigurationError(error.args[0], amount_expr)
|
||||||
self.splits[account] = compile(clean_expr, self.template_name, 'eval')
|
else:
|
||||||
|
self.splits[account] = compiled_expr
|
||||||
|
|
||||||
def _currency_decimal(self, amount, currency):
|
def _currency_decimal(self, amount, currency):
|
||||||
return decimal.Decimal(babel.numbers.format_currency(amount, currency, '###0.###'))
|
return decimal.Decimal(babel.numbers.format_currency(amount, currency, '###0.###'))
|
||||||
|
|
|
@ -4,7 +4,7 @@ import decimal
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from import2ledger import template
|
from import2ledger import errors, template
|
||||||
|
|
||||||
from . import DATA_DIR, normalize_whitespace
|
from . import DATA_DIR, normalize_whitespace
|
||||||
|
|
||||||
|
@ -87,3 +87,25 @@ def test_multivalue():
|
||||||
" Income:RBI -15.00 USD",
|
" Income:RBI -15.00 USD",
|
||||||
" Income:Donations -135.00 USD",
|
" Income:Donations -135.00 USD",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('amount_expr', [
|
||||||
|
'',
|
||||||
|
'name',
|
||||||
|
'-',
|
||||||
|
'()',
|
||||||
|
'+()',
|
||||||
|
'{}',
|
||||||
|
'{{}}',
|
||||||
|
'{()}',
|
||||||
|
'{name',
|
||||||
|
'name}',
|
||||||
|
'{42}',
|
||||||
|
'(5).real',
|
||||||
|
'{amount.real}',
|
||||||
|
'{amount.is_nan()}',
|
||||||
|
'{Decimal}',
|
||||||
|
'{FOO}',
|
||||||
|
])
|
||||||
|
def test_bad_amount_expression(amount_expr):
|
||||||
|
with pytest.raises(errors.UserInputError):
|
||||||
|
template.Template(" Income " + amount_expr)
|
||||||
|
|
Loading…
Reference in a new issue