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):
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:
srcname = 'transaction'
else:
srcname = post.account
if value is None:
msg = "{} missing {}".format(srcname, key)
else:
elif isinstance(value, need_type):
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)

View file

@ -23,11 +23,13 @@ from .. import errors as errormod
from ..beancount_types import (
MetaKey,
MetaValue,
Posting,
Transaction,
)
from typing import (
Mapping,
MutableMapping,
Optional,
)
class MetaRepoLinks(core.TransactionHook):
@ -41,19 +43,26 @@ class MetaRepoLinks(core.TransactionHook):
self.repo_path = repo_path
def _check_links(self,
meta: MutableMapping[MetaKey, MetaValue],
txn: Transaction,
meta: Mapping[MetaKey, MetaValue],
post: Optional[Posting]=None,
) -> errormod.Iter:
for key in data.LINK_METADATA.intersection(meta):
for link in str(meta[key]).split():
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)
metadata = data.Metadata(meta)
for key in data.LINK_METADATA:
try:
links = metadata.get_links(key)
except TypeError:
yield errormod.InvalidMetadataError(txn, key, meta[key], post)
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:
yield from self._check_links(txn, txn.meta)
yield from self._check_links(txn.meta, txn)
for post in txn.postings:
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 (
MetaKey,
MetaValue,
Posting,
Transaction,
)
from typing import (
Mapping,
MutableMapping,
Optional,
)
class MetaRTLinks(core.TransactionHook):
@ -39,19 +41,26 @@ class MetaRTLinks(core.TransactionHook):
self.rt = rt_wrapper
def _check_links(self,
meta: MutableMapping[MetaKey, MetaValue],
txn: Transaction,
meta: Mapping[MetaKey, MetaValue],
post: Optional[Posting]=None,
) -> errormod.Iter:
for key in self.LINK_METADATA.intersection(meta):
for link in str(meta[key]).split():
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)
metadata = data.Metadata(meta)
for key in self.LINK_METADATA:
try:
links = metadata.get_links(key)
except TypeError:
yield errormod.InvalidMetadataError(txn, key, meta[key], post)
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:
yield from self._check_links(txn, txn.meta)
yield from self._check_links(txn.meta, txn)
for post in txn.postings:
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)}
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', [
'rt:123',
'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)}
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', [
'statement.txt',
'https://example.org/',