plugin: Link checkers use Metadata class.

This commit is contained in:
Brett Smith 2020-03-28 13:36:56 -04:00
parent 9b63d898af
commit 46cfc558ec
5 changed files with 70 additions and 24 deletions

View file

@ -67,13 +67,17 @@ class ConfigurationError(Error):
class InvalidMetadataError(Error): class InvalidMetadataError(Error):
def __init__(self, txn, key, value=None, post=None, source=None): def __init__(self, txn, key, value=None, post=None, need_type=str, source=None):
if post is None: if post is None:
srcname = 'transaction' srcname = 'transaction'
else: else:
srcname = post.account srcname = post.account
if value is None: if value is None:
msg = "{} missing {}".format(srcname, key) msg = "{} missing {}".format(srcname, key)
else: elif isinstance(value, need_type):
msg = "{} has invalid {}: {}".format(srcname, key, value) msg = "{} has invalid {}: {}".format(srcname, key, value)
else:
msg = "{} has wrong type of {}: expected {} but is a {}".format(
srcname, key, need_type.__name__, type(value).__name__,
)
super().__init__(msg, txn, source) super().__init__(msg, txn, source)

View file

@ -23,11 +23,13 @@ from .. import errors as errormod
from ..beancount_types import ( from ..beancount_types import (
MetaKey, MetaKey,
MetaValue, MetaValue,
Posting,
Transaction, Transaction,
) )
from typing import ( from typing import (
Mapping, MutableMapping,
Optional,
) )
class MetaRepoLinks(core.TransactionHook): class MetaRepoLinks(core.TransactionHook):
@ -41,19 +43,26 @@ class MetaRepoLinks(core.TransactionHook):
self.repo_path = repo_path self.repo_path = repo_path
def _check_links(self, def _check_links(self,
meta: MutableMapping[MetaKey, MetaValue],
txn: Transaction, txn: Transaction,
meta: Mapping[MetaKey, MetaValue], post: Optional[Posting]=None,
) -> errormod.Iter: ) -> errormod.Iter:
for key in data.LINK_METADATA.intersection(meta): metadata = data.Metadata(meta)
for link in str(meta[key]).split(): for key in data.LINK_METADATA:
match = self.PATH_PUNCT_RE.search(link) try:
if match and match.group(0) == ':': links = metadata.get_links(key)
pass except TypeError:
elif not (self.repo_path / link).exists(): yield errormod.InvalidMetadataError(txn, key, meta[key], post)
yield errormod.BrokenLinkError(txn, key, link) else:
for link in links:
match = self.PATH_PUNCT_RE.search(link)
if match and match.group(0) == ':':
pass
elif not (self.repo_path / link).exists():
yield errormod.BrokenLinkError(txn, key, link)
def run(self, txn: Transaction) -> errormod.Iter: def run(self, txn: Transaction) -> errormod.Iter:
yield from self._check_links(txn, txn.meta) yield from self._check_links(txn.meta, txn)
for post in txn.postings: for post in txn.postings:
if post.meta is not None: if post.meta is not None:
yield from self._check_links(txn, post.meta) yield from self._check_links(post.meta, txn, post)

View file

@ -21,11 +21,13 @@ from .. import errors as errormod
from ..beancount_types import ( from ..beancount_types import (
MetaKey, MetaKey,
MetaValue, MetaValue,
Posting,
Transaction, Transaction,
) )
from typing import ( from typing import (
Mapping, MutableMapping,
Optional,
) )
class MetaRTLinks(core.TransactionHook): class MetaRTLinks(core.TransactionHook):
@ -39,19 +41,26 @@ class MetaRTLinks(core.TransactionHook):
self.rt = rt_wrapper self.rt = rt_wrapper
def _check_links(self, def _check_links(self,
meta: MutableMapping[MetaKey, MetaValue],
txn: Transaction, txn: Transaction,
meta: Mapping[MetaKey, MetaValue], post: Optional[Posting]=None,
) -> errormod.Iter: ) -> errormod.Iter:
for key in self.LINK_METADATA.intersection(meta): metadata = data.Metadata(meta)
for link in str(meta[key]).split(): for key in self.LINK_METADATA:
if not link.startswith('rt:'): try:
continue links = metadata.get_links(key)
parsed = self.rt.parse(link) except TypeError:
if parsed is None or not self.rt.exists(*parsed): yield errormod.InvalidMetadataError(txn, key, meta[key], post)
yield errormod.BrokenRTLinkError(txn, key, link, parsed) else:
for link in links:
if not link.startswith('rt:'):
continue
parsed = self.rt.parse(link)
if parsed is None or not self.rt.exists(*parsed):
yield errormod.BrokenRTLinkError(txn, key, link, parsed)
def run(self, txn: Transaction) -> errormod.Iter: def run(self, txn: Transaction) -> errormod.Iter:
yield from self._check_links(txn, txn.meta) yield from self._check_links(txn.meta, txn)
for post in txn.postings: for post in txn.postings:
if post.meta is not None: if post.meta is not None:
yield from self._check_links(txn, post.meta) yield from self._check_links(post.meta, txn, post)

View file

@ -100,6 +100,18 @@ def test_bad_post_links(hook):
actual = {error.message for error in hook.run(txn)} actual = {error.message for error in hook.run(txn)}
assert expected == actual assert expected == actual
@pytest.mark.parametrize('value', testutil.NON_STRING_METADATA_VALUES)
def test_bad_metadata_type(hook, value):
txn = testutil.Transaction(**{'check': value}, postings=[
('Income:Donations', -5),
('Assets:Cash', 5),
])
expected = {'transaction has wrong type of check: expected str but is a {}'.format(
type(value).__name__,
)}
actual = {error.message for error in hook.run(txn)}
assert expected == actual
@pytest.mark.parametrize('ext_doc', [ @pytest.mark.parametrize('ext_doc', [
'rt:123', 'rt:123',
'rt:456/789', 'rt:456/789',

View file

@ -119,6 +119,18 @@ def test_bad_post_links(hook, link_source, format_error):
actual = {error.message for error in hook.run(txn)} actual = {error.message for error in hook.run(txn)}
assert expected == actual assert expected == actual
@pytest.mark.parametrize('value', testutil.NON_STRING_METADATA_VALUES)
def test_bad_metadata_type(hook, value):
txn = testutil.Transaction(**{'rt-id': value}, postings=[
('Income:Donations', -5),
('Assets:Cash', 5),
])
expected = {'transaction has wrong type of rt-id: expected str but is a {}'.format(
type(value).__name__,
)}
actual = {error.message for error in hook.run(txn)}
assert expected == actual
@pytest.mark.parametrize('ext_doc', [ @pytest.mark.parametrize('ext_doc', [
'statement.txt', 'statement.txt',
'https://example.org/', 'https://example.org/',