diff --git a/conservancy_beancount/reports/core.py b/conservancy_beancount/reports/core.py index 2767645393bf8bb4df258125c82eb19f0726d7cc..c583c4297b2742e2192c0fb38945626136af9752 100644 --- a/conservancy_beancount/reports/core.py +++ b/conservancy_beancount/reports/core.py @@ -29,6 +29,7 @@ import babel.numbers # type:ignore[import] import odf.config # type:ignore[import] import odf.element # type:ignore[import] +import odf.meta # type:ignore[import] import odf.number # type:ignore[import] import odf.opendocument # type:ignore[import] import odf.style # type:ignore[import] @@ -39,7 +40,9 @@ from decimal import Decimal from pathlib import Path from beancount.core import amount as bc_amount +from odf.namespaces import TOOLSVERSION # type:ignore[import] +from ..cliutil import VERSION from .. import data from .. import filters from .. import rtutil @@ -561,6 +564,7 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta): self.document = odf.opendocument.OpenDocumentSpreadsheet() self.init_settings() self.init_styles() + self.set_properties() self.sheet = self.use_sheet("Report") ### Low-level document tree manipulation @@ -1046,6 +1050,21 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta): aligned_style.addElement(odf.style.ParagraphProperties(textalign=textalign)) setattr(self, f'style_{textalign}text', aligned_style) + ### Properties + + def set_properties(self, *, + created: Optional[datetime.datetime]=None, + generator: str='conservancy_beancount', + ) -> None: + if created is None: + created = datetime.datetime.now() + created_elem = self.ensure_child(self.document.meta, odf.meta.CreationDate) + created_elem.childNodes.clear() + created_elem.addText(created.isoformat()) + generator_elem = self.ensure_child(self.document.meta, odf.meta.Generator) + generator_elem.childNodes.clear() + generator_elem.addText(f'{generator}/{VERSION} {TOOLSVERSION}') + ### Rows and cells def add_row(self, *cells: odf.table.TableCell, **attrs: Any) -> odf.table.TableRow: diff --git a/tests/test_reports_spreadsheet.py b/tests/test_reports_spreadsheet.py index a771c08d7cee5f5b3a59de771b07a41c989daa12..133b5e60e326a0ba99fa816154bfb9109c0b6b3c 100644 --- a/tests/test_reports_spreadsheet.py +++ b/tests/test_reports_spreadsheet.py @@ -17,12 +17,14 @@ import datetime import io import itertools +import re import pytest import babel.core import babel.numbers import odf.config +import odf.meta import odf.number import odf.style import odf.table @@ -704,3 +706,45 @@ def test_ods_writer_copy_element(ods_writer): assert actual_a.getAttribute('href') == 'linkhref' assert actual_a.text == 'linktext' assert actual2.text == 'para2' + +def test_ods_writer_default_properties(ods_writer): + meta = ods_writer.document.meta + yesterday = datetime.datetime.now() - datetime.timedelta(days=1) + creation_date_elem = get_child(meta, odf.meta.CreationDate) + creation_date = datetime.datetime.strptime( + creation_date_elem.text, '%Y-%m-%dT%H:%M:%S.%f', + ) + assert creation_date > yesterday + generator = get_child(meta, odf.meta.Generator) + assert re.match(r'conservancy_beancount/\d+\.\d+', generator.text) + +def test_ods_writer_set_properties(ods_writer): + ctime = datetime.datetime(2009, 9, 19, 9, 49, 59) + ods_writer.set_properties(created=ctime, generator='testgen') + meta = ods_writer.document.meta + creation_date_elem = get_child(meta, odf.meta.CreationDate) + assert creation_date_elem.text == ctime.isoformat() + generator = get_child(meta, odf.meta.Generator) + assert re.match(r'testgen/\d+\.\d+', generator.text) + +@pytest.mark.parametrize('value,exptype', [ + (1, 'float'), + (12.34, 'float'), + (Decimal('5.99'), 'float'), + (datetime.date(2009, 8, 17), 'date'), + (datetime.datetime(2009, 8, 17, 18, 38, 58), 'date'), + (True, 'boolean'), + (False, 'boolean'), + ('foo', None) +]) +def test_ods_writer_set_custom_property(ods_writer, value, exptype): + cprop = ods_writer.set_custom_property('cprop', value) + assert cprop.getAttribute('name') == 'cprop' + assert cprop.getAttribute('valuetype') == exptype + if exptype == 'boolean': + expected = str(value).lower() + elif exptype == 'date': + expected = value.isoformat() + else: + expected = str(value) + assert cprop.text == expected