From aff1fc537d29c4b667e51c071050a192f938117a 2020-06-12 21:10:25 From: Brett Smith Date: 2020-06-12 21:10:25 Subject: [PATCH] reports: Add BaseODS.meta_links_cell() method. --- diff --git a/conservancy_beancount/reports/accrual.py b/conservancy_beancount/reports/accrual.py index 0f825ebd13b2a0803b0492288ce625e0dca623e1..544d2948d15afafd71fb8b783eb4874695bb1d1e 100644 --- a/conservancy_beancount/reports/accrual.py +++ b/conservancy_beancount/reports/accrual.py @@ -71,7 +71,6 @@ import datetime import enum import logging import sys -import urllib.parse as urlparse from pathlib import Path @@ -293,8 +292,7 @@ class AgingODS(core.BaseODS[AccrualPostings, Optional[data.Account]]): date: datetime.date, logger: logging.Logger, ) -> None: - super().__init__() - self.rt_wrapper = rt_wrapper + super().__init__(rt_wrapper) self.date = date self.logger = logger @@ -388,20 +386,6 @@ class AgingODS(core.BaseODS[AccrualPostings, Optional[data.Account]]): self.balance_cell(total_balance), ) - def _link_seq(self, row: AccrualPostings, key: MetaKey) -> Iterator[Tuple[str, str]]: - for href in row.all_meta_links(key): - rt_ids = self.rt_wrapper.parse(href) - rt_href = rt_ids and self.rt_wrapper.url(*rt_ids) - if rt_ids is None or rt_href is None: - # '..' pops the ODS filename off the link path. In other words, - # make the link relative to the directory the ODS is in. - href_path = Path('..', urlparse.urlparse(href).path) - href = str(href_path) - text = urlparse.unquote(href_path.name) - else: - text = self.rt_wrapper.unparse(*rt_ids) - yield (href, text) - def write_row(self, row: AccrualPostings) -> None: age = (self.date - row[0].meta.date).days if row.end_balance.ge_zero(): @@ -426,11 +410,11 @@ class AgingODS(core.BaseODS[AccrualPostings, Optional[data.Account]]): amount_cell, self.balance_cell(row.end_balance), self.multiline_cell(sorted(projects)), - self.multilink_cell(self._link_seq(row, 'rt-id')), - self.multilink_cell(self._link_seq(row, 'invoice')), - self.multilink_cell(self._link_seq(row, 'approval')), - self.multilink_cell(self._link_seq(row, 'contract')), - self.multilink_cell(self._link_seq(row, 'purchase-order')), + self.meta_links_cell(row.all_meta_links('rt-id')), + self.meta_links_cell(row.all_meta_links('invoice')), + self.meta_links_cell(row.all_meta_links('approval')), + self.meta_links_cell(row.all_meta_links('contract')), + self.meta_links_cell(row.all_meta_links('purchase-order')), ) diff --git a/conservancy_beancount/reports/core.py b/conservancy_beancount/reports/core.py index e9fc923b482d50fb9e0bbf506f5736c356aa9cc5..551003b7b5ed85a847280defe6742a20d15eee13 100644 --- a/conservancy_beancount/reports/core.py +++ b/conservancy_beancount/reports/core.py @@ -20,6 +20,7 @@ import datetime import itertools import operator import re +import urllib.parse as urlparse import babel.core # type:ignore[import] import babel.numbers # type:ignore[import] @@ -39,6 +40,7 @@ from beancount.core import amount as bc_amount from .. import data from .. import filters +from .. import rtutil from typing import ( cast, @@ -457,7 +459,8 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta): See also the BaseSpreadsheet base class for additional documentation about methods you must and can define, the definition of RT and ST, etc. """ - def __init__(self) -> None: + def __init__(self, rt_wrapper: Optional[rtutil.RT]=None) -> None: + self.rt_wrapper = rt_wrapper self.locale = babel.core.Locale.default('LC_MONETARY') self.currency_fmt_key = 'accounting' self._name_counter = itertools.count(1) @@ -912,6 +915,34 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta): cell.addElement(odf.text.P(text=str(value))) return cell + def _meta_link_pairs(self, links: Iterable[Optional[str]]) -> Iterator[Tuple[str, str]]: + for href in links: + if href is None: + continue + elif self.rt_wrapper is not None: + rt_ids = self.rt_wrapper.parse(href) + rt_href = rt_ids and self.rt_wrapper.url(*rt_ids) + else: + rt_ids = None + rt_href = None + if rt_ids is None or rt_href is None: + # '..' pops the ODS filename off the link path. In other words, + # make the link relative to the directory the ODS is in. + href_path = Path('..', href) + href = str(href_path) + text = href_path.name + else: + rt_path = urlparse.urlparse(rt_href).path + if rt_path.endswith('/Ticket/Display.html'): + text = rtutil.RT.unparse(*rt_ids) + else: + text = urlparse.unquote(Path(rt_path).name) + href = rt_href + yield (href, text) + + def meta_links_cell(self, links: Iterable[Optional[str]], **attrs: Any) -> odf.table.TableCell: + return self.multilink_cell(self._meta_link_pairs(links), **attrs) + def multiline_cell(self, lines: Iterable[Any], **attrs: Any) -> odf.table.TableCell: cell = odf.table.TableCell(valuetype='string', **attrs) for line in lines: diff --git a/setup.py b/setup.py index acbc6e836bca681e9194471c5bda4d16564df92e..1a8b240f265599e8b43122a12f2aea11050cd474 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ from setuptools import setup setup( name='conservancy_beancount', description="Plugin, library, and reports for reading Conservancy's books", - version='1.1.12', + version='1.1.13', author='Software Freedom Conservancy', author_email='info@sfconservancy.org', license='GNU AGPLv3+', diff --git a/tests/test_reports_spreadsheet.py b/tests/test_reports_spreadsheet.py index fb697555d1ec6d394f37c19625e2567497a1ac83..8333c71a48fbcd96b3da5f1b0561b84c5825c2a2 100644 --- a/tests/test_reports_spreadsheet.py +++ b/tests/test_reports_spreadsheet.py @@ -32,6 +32,7 @@ from decimal import Decimal from . import testutil +from conservancy_beancount import rtutil from conservancy_beancount.reports import core EN_US = babel.core.Locale('en', 'US') @@ -493,6 +494,41 @@ def test_ods_writer_float_cell(ods_writer, cell_source, style_name): assert cell.getAttribute('value') == expected assert get_text(cell) == expected +def test_ods_writer_meta_links_cell(ods_writer): + rt_client = testutil.RTClient() + ods_writer.rt_wrapper = rtutil.RT(rt_client) + rt_url = rt_client.DEFAULT_URL[:-10] + meta_links = [ + 'rt://ticket/1', + 'rt://ticket/2/attachments/9', + 'rt:1/5', + 'Invoices/0123.pdf', + ] + cell = ods_writer.meta_links_cell(meta_links, stylename='meta1') + assert cell.getAttribute('valuetype') == 'string' + assert cell.getAttribute('stylename') == 'meta1' + children = iter(get_children(cell, odf.text.A)) + child = next(children) + assert child.getAttribute('type') == 'simple' + expect_url = f'{rt_url}/Ticket/Display.html?id=1' + assert child.getAttribute('href') == expect_url + assert get_text(child) == 'rt:1' + child = next(children) + assert child.getAttribute('type') == 'simple' + expect_url = f'{rt_url}/Ticket/Display.html?id=2#txn-7' + assert child.getAttribute('href') == expect_url + assert get_text(child) == 'rt:2/9' + child = next(children) + assert child.getAttribute('type') == 'simple' + expect_url = f'{rt_url}/Ticket/Attachment/1/5/photo.jpg' + assert child.getAttribute('href') == expect_url + assert get_text(child) == 'photo.jpg' + child = next(children) + assert child.getAttribute('type') == 'simple' + expect_url = f'../{meta_links[3]}' + assert child.getAttribute('href') == expect_url + assert get_text(child) == '0123.pdf' + def test_ods_writer_multiline_cell(ods_writer): cell = ods_writer.multiline_cell(iter(STRING_CELL_DATA)) assert cell.getAttribute('valuetype') == 'string'