plugin: Link checkers use Metadata class.
This commit is contained in:
parent
9b63d898af
commit
46cfc558ec
5 changed files with 70 additions and 24 deletions
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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/',
|
||||||
|
|
Loading…
Reference in a new issue