diff --git a/conservancy_beancount/data.py b/conservancy_beancount/data.py index 614034ef7ae897814fd7af26a4ef13c9e9285609..28c587f14ce2ff9b323e5c45e892deb71894e5b6 100644 --- a/conservancy_beancount/data.py +++ b/conservancy_beancount/data.py @@ -30,6 +30,7 @@ from beancount.core import amount as bc_amount from beancount.core import convert as bc_convert from beancount.core import data as bc_data from beancount.core import position as bc_position +from beancount.parser import options as bc_options from typing import ( cast, @@ -40,6 +41,7 @@ from typing import ( Iterator, MutableMapping, Optional, + Pattern, Sequence, TypeVar, Union, @@ -53,6 +55,7 @@ from .beancount_types import ( MetaKey, MetaValue, Open, + OptionsMap, Posting as BasePosting, Transaction, ) @@ -153,8 +156,19 @@ class Account(str): """ __slots__ = () + ACCOUNT_RE: Pattern SEP = bc_account.sep _meta_map: MutableMapping[str, AccountMeta] = {} + _options_map: OptionsMap + + @classmethod + def load_options_map(cls, options_map: OptionsMap) -> None: + cls._options_map = options_map + roots: Sequence[str] = bc_options.get_account_types(options_map) + cls.ACCOUNT_RE = re.compile( + r'^(?:{})(?:{}[A-Z0-9][-A-Za-z0-9]*)+$'.format( + '|'.join(roots), cls.SEP, + )) @classmethod def load_opening(cls, opening: Open) -> None: @@ -178,6 +192,10 @@ class Account(str): elif isinstance(entry, bc_data.Close): cls.load_closing(entry) # type:ignore[arg-type] + @classmethod + def is_account(cls, s: str) -> bool: + return cls.ACCOUNT_RE.fullmatch(s) is not None + @property def meta(self) -> AccountMeta: return self._meta_map[self] @@ -286,6 +304,7 @@ class Account(str): return self else: return self[:stop] +Account.load_options_map(bc_options.OPTIONS_DEFAULTS) class Amount(bc_amount.Amount):