rtutil: Add RT.iter_urls() method.
This commit is contained in:
parent
9fef177d2d
commit
5a1f7122bd
2 changed files with 132 additions and 43 deletions
|
@ -271,55 +271,40 @@ class RT:
|
|||
)
|
||||
return self._extend_url(path_tail)
|
||||
|
||||
def _urls(self, links: Iterable[str]) -> Iterator[str]:
|
||||
def exists(self, ticket_id: RTId, attachment_id: Optional[RTId]=None) -> bool:
|
||||
return self.url(ticket_id, attachment_id) is not None
|
||||
|
||||
def iter_urls(self,
|
||||
links: Iterable[str],
|
||||
rt_fmt: str='{}',
|
||||
nonrt_fmt: str='{}',
|
||||
missing_fmt: str='{}',
|
||||
) -> Iterator[str]:
|
||||
"""Iterate over metadata links, replacing RT references with web URLs
|
||||
|
||||
This method iterates over metadata link strings (e.g., from
|
||||
Metadata.get_links()) and transforms them for web presentation.
|
||||
|
||||
If the string is a valid RT reference, the corresponding web URL
|
||||
will be formatted with ``rt_fmt``.
|
||||
|
||||
If the string is a well-formed RT reference but the object doesn't
|
||||
exist, it will be formatted with ``missing_fmt``.
|
||||
|
||||
All other link strings will be formatted with ``nonrt_fmt``.
|
||||
|
||||
"""
|
||||
for link in links:
|
||||
parsed = self.parse(link)
|
||||
if parsed is None:
|
||||
yield link
|
||||
yield nonrt_fmt.format(link)
|
||||
else:
|
||||
ticket_id, attachment_id = parsed
|
||||
url = self.url(ticket_id, attachment_id)
|
||||
yield f'<{url}>'
|
||||
|
||||
@overload
|
||||
def _meta_with_urls(self, meta: None) -> None: ...
|
||||
|
||||
@overload
|
||||
def _meta_with_urls(self, meta: bc_data.Meta) -> bc_data.Meta: ...
|
||||
|
||||
def _meta_with_urls(self, meta: Optional[bc_data.Meta]) -> Optional[bc_data.Meta]:
|
||||
if meta is None:
|
||||
return None
|
||||
link_meta = data.Metadata(meta)
|
||||
retval = meta.copy()
|
||||
for key in data.LINK_METADATA:
|
||||
try:
|
||||
links = link_meta.get_links(key)
|
||||
except TypeError:
|
||||
continue
|
||||
if links:
|
||||
retval[key] = ' '.join(self._urls(links))
|
||||
return retval
|
||||
|
||||
def txn_with_urls(self, txn: Transaction) -> Transaction:
|
||||
"""Copy a transaction with RT references replaced with web URLs
|
||||
|
||||
Given a Beancount Transaction, this method returns a Transaction
|
||||
that's identical, except any references to RT in the metadata for
|
||||
the Transaction and its Postings are replaced with web URLs.
|
||||
This is useful for reporting tools that want to format the
|
||||
transaction with URLs that are recognizable by other tools.
|
||||
"""
|
||||
# mypy doesn't recognize that postings is a valid argument, probably a
|
||||
# bug in the NamedTuple→Directive→Transaction hierarchy.
|
||||
return txn._replace( # type:ignore[call-arg]
|
||||
meta=self._meta_with_urls(txn.meta),
|
||||
postings=[post._replace(meta=self._meta_with_urls(post.meta))
|
||||
for post in txn.postings],
|
||||
)
|
||||
|
||||
def exists(self, ticket_id: RTId, attachment_id: Optional[RTId]=None) -> bool:
|
||||
return self.url(ticket_id, attachment_id) is not None
|
||||
if url is None:
|
||||
yield missing_fmt.format(link)
|
||||
else:
|
||||
yield rt_fmt.format(url)
|
||||
|
||||
@classmethod
|
||||
def metadata_regexp(self,
|
||||
|
@ -365,6 +350,68 @@ class RT:
|
|||
return None
|
||||
return self._ticket_url(ticket_id)
|
||||
|
||||
@overload
|
||||
def _meta_with_urls(self,
|
||||
meta: None,
|
||||
rt_fmt: str,
|
||||
nonrt_fmt: str,
|
||||
missing_fmt: str,
|
||||
) -> None: ...
|
||||
|
||||
@overload
|
||||
def _meta_with_urls(self,
|
||||
meta: bc_data.Meta,
|
||||
rt_fmt: str,
|
||||
nonrt_fmt: str,
|
||||
missing_fmt: str,
|
||||
) -> bc_data.Meta: ...
|
||||
|
||||
def _meta_with_urls(self,
|
||||
meta: Optional[bc_data.Meta],
|
||||
rt_fmt: str,
|
||||
nonrt_fmt: str,
|
||||
missing_fmt: str,
|
||||
) -> Optional[bc_data.Meta]:
|
||||
if meta is None:
|
||||
return None
|
||||
link_meta = data.Metadata(meta)
|
||||
retval = meta.copy()
|
||||
for key in data.LINK_METADATA:
|
||||
try:
|
||||
links = link_meta.get_links(key)
|
||||
except TypeError:
|
||||
links = ()
|
||||
if links:
|
||||
retval[key] = ' '.join(self.iter_urls(
|
||||
links, rt_fmt, nonrt_fmt, missing_fmt,
|
||||
))
|
||||
return retval
|
||||
|
||||
def txn_with_urls(self, txn: Transaction,
|
||||
rt_fmt: str='<{}>',
|
||||
nonrt_fmt: str='{}',
|
||||
missing_fmt: str='{}',
|
||||
) -> Transaction:
|
||||
"""Copy a transaction with RT references replaced with web URLs
|
||||
|
||||
Given a Beancount Transaction, this method returns a Transaction
|
||||
that's identical, except any references to RT in the metadata for
|
||||
the Transaction and its Postings are replaced with web URLs.
|
||||
This is useful for reporting tools that want to format the
|
||||
transaction with URLs that are recognizable by other tools.
|
||||
|
||||
The format string arguments have the same meaning as RT.iter_urls().
|
||||
See that docstring for details.
|
||||
"""
|
||||
# mypy doesn't recognize that postings is a valid argument, probably a
|
||||
# bug in the NamedTuple→Directive→Transaction hierarchy.
|
||||
return txn._replace( # type:ignore[call-arg]
|
||||
meta=self._meta_with_urls(txn.meta, rt_fmt, nonrt_fmt, missing_fmt),
|
||||
postings=[post._replace(meta=self._meta_with_urls(
|
||||
post.meta, rt_fmt, nonrt_fmt, missing_fmt,
|
||||
)) for post in txn.postings],
|
||||
)
|
||||
|
||||
def url(self, ticket_id: RTId, attachment_id: Optional[RTId]=None) -> Optional[str]:
|
||||
if attachment_id is None:
|
||||
return self.ticket_url(ticket_id)
|
||||
|
|
|
@ -118,6 +118,30 @@ def test_url_default_filename(new_client, mimetype, extension):
|
|||
expected = '{}Ticket/Attachment/9/9/RT1%20attachment%209.{}'.format(DEFAULT_RT_URL, extension)
|
||||
assert rt.url(1, 9) == expected
|
||||
|
||||
@pytest.mark.parametrize('rt_fmt,nonrt_fmt,missing_fmt', [
|
||||
('{}', '{}', '{}',),
|
||||
('<{}>', '[{}]', '({})'),
|
||||
])
|
||||
def test_iter_urls(rt, rt_fmt, nonrt_fmt, missing_fmt):
|
||||
expected_map = {
|
||||
'rt:{}{}'.format(tid, '' if aid is None else f'/{aid}'): url
|
||||
for tid, aid, url in EXPECTED_URLS
|
||||
}
|
||||
expected_map['https://example.com'] = None
|
||||
expected_map['invoice.pdf'] = None
|
||||
keys = list(expected_map)
|
||||
urls = rt.iter_urls(keys, rt_fmt, nonrt_fmt, missing_fmt)
|
||||
for key, actual in itertools.zip_longest(keys, urls):
|
||||
expected = expected_map[key]
|
||||
if expected is None:
|
||||
if key.startswith('rt:'):
|
||||
expected = missing_fmt.format(key)
|
||||
else:
|
||||
expected = nonrt_fmt.format(key)
|
||||
else:
|
||||
expected = rt_fmt.format(DEFAULT_RT_URL + expected)
|
||||
assert actual == expected
|
||||
|
||||
@pytest.mark.parametrize('ticket_id,attachment_id,expected', EXPECTED_URLS)
|
||||
def test_exists(rt, ticket_id, attachment_id, expected):
|
||||
expected = False if expected is None else True
|
||||
|
@ -239,3 +263,21 @@ def test_txn_with_urls(rt):
|
|||
assert txn.meta[key] == expected
|
||||
assert txn.postings[0].meta['receipt'] == 'rt:2/13 donation.txt'
|
||||
assert txn.postings[1].meta['receipt'] == 'cash.png rt:2/14'
|
||||
|
||||
def test_txn_with_urls_with_fmts(rt):
|
||||
txn_meta = {
|
||||
'rt-id': 'rt:1',
|
||||
'contract': 'RepoLink.pdf',
|
||||
'statement': 'rt:1/99 rt:1/4 stmt.txt',
|
||||
}
|
||||
txn = testutil.Transaction(**txn_meta)
|
||||
actual = rt.txn_with_urls(txn, '<{}>', '[{}]', '({})')
|
||||
rt_id_path = EXPECTED_URLS_MAP[(1, None)]
|
||||
assert actual.meta['rt-id'] == f'<{DEFAULT_RT_URL}{rt_id_path}>'
|
||||
assert actual.meta['contract'] == '[RepoLink.pdf]'
|
||||
statement_path = EXPECTED_URLS_MAP[(1, 4)]
|
||||
assert actual.meta['statement'] == ' '.join([
|
||||
'(rt:1/99)',
|
||||
f'<{DEFAULT_RT_URL}{statement_path}>',
|
||||
'[stmt.txt]',
|
||||
])
|
||||
|
|
Loading…
Reference in a new issue