rewrite: Support `metakey in value
` conditions.
For now, works exactly like ``.account in value``.
This commit is contained in:
parent
ea69d58957
commit
69f597a47c
2 changed files with 49 additions and 2 deletions
|
@ -82,6 +82,13 @@ Conditions can always use Python's basic comparison operators:
|
|||
account names. The condition matches when the posting's
|
||||
account is any of those named accounts, or any of their
|
||||
respective subaccounts.
|
||||
---------------- -------------------------------------------------------
|
||||
``metakey in`` Works analogously for metadata values that are
|
||||
structured as a colon-separated hierarchy like account
|
||||
names. The operand is parsed as a space-separated list
|
||||
of parts of the hierarchy. The condition matches when
|
||||
the posting's ``metakey`` metadata is under any of
|
||||
those parts of the hierarchy.
|
||||
================ =======================================================
|
||||
|
||||
Action Operators
|
||||
|
@ -141,9 +148,11 @@ accounting equation, Equity = Assets - Liabilities.
|
|||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import abc
|
||||
import collections
|
||||
import datetime
|
||||
import decimal
|
||||
import enum
|
||||
import functools
|
||||
import logging
|
||||
import operator as opmod
|
||||
import re
|
||||
|
@ -290,9 +299,18 @@ class DateTest(Tester[datetime.date]):
|
|||
|
||||
|
||||
class MetadataTest(Tester[Optional[MetaValue]]):
|
||||
_GETTERS: Mapping[MetaKey, Callable[[str], data.Account]] = \
|
||||
collections.defaultdict(lambda: functools.lru_cache()(data.Account))
|
||||
# We use this class variable to share one cache per metadata key being
|
||||
# tested.
|
||||
|
||||
def __init__(self, key: MetaKey, operator: str, operand: str) -> None:
|
||||
super().__init__(operator, operand)
|
||||
self.key = key
|
||||
if operator == 'in':
|
||||
self._get_hierarchy = self._GETTERS[key]
|
||||
self.under_args = operand.split()
|
||||
else:
|
||||
super().__init__(operator, operand)
|
||||
|
||||
@staticmethod
|
||||
def parse_operand(operand: str) -> str:
|
||||
|
@ -301,6 +319,18 @@ class MetadataTest(Tester[Optional[MetaValue]]):
|
|||
def post_get(self, post: data.Posting) -> Optional[MetaValue]:
|
||||
return post.meta.get(self.key)
|
||||
|
||||
def __call__(self, post: data.Posting) -> bool:
|
||||
try:
|
||||
self.under_args
|
||||
except AttributeError:
|
||||
return super().__call__(post)
|
||||
else:
|
||||
meta_value = self.post_get(post)
|
||||
if isinstance(meta_value, str):
|
||||
return self._get_hierarchy(meta_value).is_under(*self.under_args) is not None
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class NumberTest(Tester[Decimal]):
|
||||
@staticmethod
|
||||
|
|
|
@ -76,6 +76,24 @@ def test_metadata_condition(value, operator):
|
|||
tester = rewrite.MetadataTest(key, operator, operand)
|
||||
assert tester(post) == eval(f'value {operator} operand')
|
||||
|
||||
@pytest.mark.parametrize('value,expected', [
|
||||
('Root', False),
|
||||
('Root:Branch', True),
|
||||
('Root:Branch:Leaf', True),
|
||||
('Branch', False),
|
||||
('RootBranch:Leaf', False),
|
||||
(None, False),
|
||||
])
|
||||
def test_metadata_in_condition(value, expected):
|
||||
key = 'testkey'
|
||||
meta = {} if value is None else {key: value}
|
||||
txn = testutil.Transaction(postings=[
|
||||
('Income:Other', -5, meta),
|
||||
])
|
||||
post, = data.Posting.from_txn(txn)
|
||||
tester = rewrite.MetadataTest(key, 'in', 'Root:Branch')
|
||||
assert tester(post) == expected
|
||||
|
||||
@pytest.mark.parametrize('value', ['4.5', '4.75', '5'])
|
||||
@pytest.mark.parametrize('operator', CMP_OPS)
|
||||
def test_number_condition(value, operator):
|
||||
|
@ -118,7 +136,6 @@ def test_parse_good_condition(subject, operator, operand):
|
|||
'.date in 1990-9-9', # Bad operator
|
||||
'.number > 0xff', # Bad operand
|
||||
'.number in 16', # Bad operator
|
||||
'testkey in foo', # Bad operator
|
||||
'units.number == 5', # Bad subject (syntax)
|
||||
'.units == 5', # Bad subject (unknown)
|
||||
])
|
||||
|
|
Loading…
Reference in a new issue