data: Add Account.is_account and Account.load_options_map.
These work in concert to distinguish account names from other colon-separated strings.
This commit is contained in:
parent
6a7815090c
commit
fff9e37bf8
3 changed files with 67 additions and 0 deletions
|
@ -30,6 +30,7 @@ from beancount.core import amount as bc_amount
|
||||||
from beancount.core import convert as bc_convert
|
from beancount.core import convert as bc_convert
|
||||||
from beancount.core import data as bc_data
|
from beancount.core import data as bc_data
|
||||||
from beancount.core import position as bc_position
|
from beancount.core import position as bc_position
|
||||||
|
from beancount.parser import options as bc_options
|
||||||
|
|
||||||
from typing import (
|
from typing import (
|
||||||
cast,
|
cast,
|
||||||
|
@ -40,6 +41,7 @@ from typing import (
|
||||||
Iterator,
|
Iterator,
|
||||||
MutableMapping,
|
MutableMapping,
|
||||||
Optional,
|
Optional,
|
||||||
|
Pattern,
|
||||||
Sequence,
|
Sequence,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Union,
|
Union,
|
||||||
|
@ -53,6 +55,7 @@ from .beancount_types import (
|
||||||
MetaKey,
|
MetaKey,
|
||||||
MetaValue,
|
MetaValue,
|
||||||
Open,
|
Open,
|
||||||
|
OptionsMap,
|
||||||
Posting as BasePosting,
|
Posting as BasePosting,
|
||||||
Transaction,
|
Transaction,
|
||||||
)
|
)
|
||||||
|
@ -153,8 +156,19 @@ class Account(str):
|
||||||
"""
|
"""
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
|
ACCOUNT_RE: Pattern
|
||||||
SEP = bc_account.sep
|
SEP = bc_account.sep
|
||||||
_meta_map: MutableMapping[str, AccountMeta] = {}
|
_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
|
@classmethod
|
||||||
def load_opening(cls, opening: Open) -> None:
|
def load_opening(cls, opening: Open) -> None:
|
||||||
|
@ -178,6 +192,10 @@ class Account(str):
|
||||||
elif isinstance(entry, bc_data.Close):
|
elif isinstance(entry, bc_data.Close):
|
||||||
cls.load_closing(entry) # type:ignore[arg-type]
|
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
|
@property
|
||||||
def meta(self) -> AccountMeta:
|
def meta(self) -> AccountMeta:
|
||||||
return self._meta_map[self]
|
return self._meta_map[self]
|
||||||
|
@ -286,6 +304,7 @@ class Account(str):
|
||||||
return self
|
return self
|
||||||
else:
|
else:
|
||||||
return self[:stop]
|
return self[:stop]
|
||||||
|
Account.load_options_map(bc_options.OPTIONS_DEFAULTS)
|
||||||
|
|
||||||
|
|
||||||
class Amount(bc_amount.Amount):
|
class Amount(bc_amount.Amount):
|
||||||
|
|
|
@ -21,6 +21,7 @@ from . import testutil
|
||||||
from datetime import date as Date
|
from datetime import date as Date
|
||||||
|
|
||||||
from beancount.core.data import Open, Close, Booking
|
from beancount.core.data import Open, Close, Booking
|
||||||
|
from beancount.parser import options as bc_options
|
||||||
|
|
||||||
from conservancy_beancount import data
|
from conservancy_beancount import data
|
||||||
|
|
||||||
|
@ -267,3 +268,48 @@ def test_load_openings_and_closings(clean_account_meta):
|
||||||
check_account_meta('Income:Donations', entries[0])
|
check_account_meta('Income:Donations', entries[0])
|
||||||
check_account_meta('Income:Other', entries[1])
|
check_account_meta('Income:Other', entries[1])
|
||||||
check_account_meta('Assets:Checking', entries[2], entries[-1])
|
check_account_meta('Assets:Checking', entries[2], entries[-1])
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('account_s', [
|
||||||
|
'Assets:Bank:Checking',
|
||||||
|
'Equity:Funds:Restricted',
|
||||||
|
'Expenses:Other',
|
||||||
|
'Income:Donations',
|
||||||
|
'Liabilities:CreditCard:Visa',
|
||||||
|
])
|
||||||
|
def test_is_account(account_s):
|
||||||
|
assert data.Account.is_account(account_s)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('account_s', [
|
||||||
|
'Assets:Bank:12-345',
|
||||||
|
'Equity:Funds:Restricted',
|
||||||
|
'Expenses:Other',
|
||||||
|
'Income:Donations',
|
||||||
|
'Liabilities:CreditCard:Visa0123',
|
||||||
|
])
|
||||||
|
def test_is_account(clean_account_meta, account_s):
|
||||||
|
assert data.Account.is_account(account_s)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('account_s', [
|
||||||
|
'Assets:checking',
|
||||||
|
'Assets::Cash',
|
||||||
|
'Equity',
|
||||||
|
'Liabilities:Credit Card',
|
||||||
|
'income:Donations',
|
||||||
|
'Expenses:Banking_Fees',
|
||||||
|
'Revenue:Grants',
|
||||||
|
])
|
||||||
|
def test_is_not_account(clean_account_meta, account_s):
|
||||||
|
assert not data.Account.is_account(account_s)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('account_s,expected', [
|
||||||
|
('Revenue:Donations', True),
|
||||||
|
('Costs:Other', True),
|
||||||
|
('Income:Donations', False),
|
||||||
|
('Expenses:Other', False),
|
||||||
|
])
|
||||||
|
def test_is_account_respects_configured_roots(clean_account_meta, account_s, expected):
|
||||||
|
config = bc_options.OPTIONS_DEFAULTS.copy()
|
||||||
|
config['name_expenses'] = 'Costs'
|
||||||
|
config['name_income'] = 'Revenue'
|
||||||
|
data.Account.load_options_map(config)
|
||||||
|
assert data.Account.is_account(account_s) == expected
|
||||||
|
|
|
@ -21,6 +21,7 @@ import re
|
||||||
import beancount.core.amount as bc_amount
|
import beancount.core.amount as bc_amount
|
||||||
import beancount.core.data as bc_data
|
import beancount.core.data as bc_data
|
||||||
import beancount.loader as bc_loader
|
import beancount.loader as bc_loader
|
||||||
|
import beancount.parser.options as bc_options
|
||||||
|
|
||||||
import odf.element
|
import odf.element
|
||||||
import odf.opendocument
|
import odf.opendocument
|
||||||
|
@ -43,6 +44,7 @@ TESTS_DIR = Path(__file__).parent
|
||||||
# it with different scopes. Typical usage looks like:
|
# it with different scopes. Typical usage looks like:
|
||||||
# clean_account_meta = pytest.fixture([options])(testutil.clean_account_meta)
|
# clean_account_meta = pytest.fixture([options])(testutil.clean_account_meta)
|
||||||
def clean_account_meta():
|
def clean_account_meta():
|
||||||
|
data.Account.load_options_map(bc_options.OPTIONS_DEFAULTS)
|
||||||
data.Account._meta_map.clear()
|
data.Account._meta_map.clear()
|
||||||
|
|
||||||
def _ods_cell_value_type(cell):
|
def _ods_cell_value_type(cell):
|
||||||
|
|
Loading…
Reference in a new issue