From b3e0bcb8da7bea6c0b0636fdf68d0aac6ab74754 Mon Sep 17 00:00:00 2001 From: Elena ``of Valhalla'' Grandi Date: Thu, 16 Sep 2021 12:23:54 +0200 Subject: Add a method to create an entry from a rendered template --- lesana/collection.py | 20 ++++++ lesana/command.py | 21 +++---- .../data/simple/templates/new_entry_from_data.yaml | 12 ++++ .../templates/new_entry_from_data_broken.yaml | 14 +++++ .../new_entry_from_data_invalid_yaml.yaml | 13 ++++ .../templates/new_entry_from_multiple_data.yaml | 12 ++++ tests/test_collection.py | 71 ++++++++++++++++++++++ 7 files changed, 150 insertions(+), 13 deletions(-) create mode 100644 tests/data/simple/templates/new_entry_from_data.yaml create mode 100644 tests/data/simple/templates/new_entry_from_data_broken.yaml create mode 100644 tests/data/simple/templates/new_entry_from_data_invalid_yaml.yaml create mode 100644 tests/data/simple/templates/new_entry_from_multiple_data.yaml diff --git a/lesana/collection.py b/lesana/collection.py index 41dd23f..20f8a0c 100644 --- a/lesana/collection.py +++ b/lesana/collection.py @@ -523,6 +523,26 @@ class Collection(object): raise TemplatingError('Could not find template ' + str(e)) return template + def entry_from_rendered_template(self, template, data): + try: + template = self.get_template(template) + rendered = template.render(**data) + except jinja2.exceptions.TemplateSyntaxError as e: + raise TemplatingError(e) + try: + data = ruamel.yaml.load(rendered, ruamel.yaml.RoundTripLoader) + except ruamel.yaml.YAMLError as e: + logger.warning( + "The following data failed to load as YAML: \n{}".format( + rendered + ) + + ) + raise TemplatingError(e) + entry = self.entry_class(self, data=data) + self.save_entries([entry]) + return entry + @classmethod def init( cls, directory=None, git_enabled=True, edit_file=None, settings={} diff --git a/lesana/command.py b/lesana/command.py index 0521234..4c723af 100644 --- a/lesana/command.py +++ b/lesana/command.py @@ -4,8 +4,6 @@ import os import subprocess import sys -import ruamel.yaml - from . import Collection, Entry, TemplatingError logger = logging.getLogger(__name__) @@ -420,22 +418,19 @@ class Export(Command): collection.start_search(' '.join(self.args.query)) results = collection.get_all_search_results() for entry in results: + data = { + "entry": entry + } + data.update(entry.data) try: - template = collection.get_template(self.args.template) - rendered = template.render(entry=entry, **entry.data) + destination.entry_from_rendered_template( + self.args.template, + data + ) except TemplatingError as e: logger.error("Error converting entry: {}".format(entry)) logger.error("{}".format(e)) sys.exit(1) - try: - data = ruamel.yaml.load(rendered, ruamel.yaml.RoundTripLoader) - except ruamel.yaml.YAMLError as e: - logger.error("Error loading exported entry: {}".format(entry)) - logger.error("exported data was\n{}".format(rendered)) - logger.error("{}".format(e)) - sys.exit(1) - e = self.entry_class(destination, data=data) - destination.save_entries([e]) class Init(Command): diff --git a/tests/data/simple/templates/new_entry_from_data.yaml b/tests/data/simple/templates/new_entry_from_data.yaml new file mode 100644 index 0000000..1f9e9a5 --- /dev/null +++ b/tests/data/simple/templates/new_entry_from_data.yaml @@ -0,0 +1,12 @@ +name: '{{ name }}' +description: | + {{ description if description else "." | indent(width=2, first=False) }} +position: '{{ position }}' +# # quantity (integer): how many items are there +quantity: {{ quantity if quantity else "0" }} +# # value (float): how much each item is +value: 0.0 +# # cost (decimal): how much this costs +cost: '0' +# # other (yaml): +other: diff --git a/tests/data/simple/templates/new_entry_from_data_broken.yaml b/tests/data/simple/templates/new_entry_from_data_broken.yaml new file mode 100644 index 0000000..73798ce --- /dev/null +++ b/tests/data/simple/templates/new_entry_from_data_broken.yaml @@ -0,0 +1,14 @@ +name: '{{ name }}' +description: | + {{ description if description else "." indent(width=2, first=False) }} + # Note that the lack of | between "." and indent is wanted, to get a proper + # TemplatingError. +position: '{{ position }}' +# quantity (integer): how many items are there +quantity: {{ quantity if quantity else "0" }} +# value (float): how much each item is +value: 0.0 +# cost (decimal): how much this costs +cost: '0' +# other (yaml): +other: diff --git a/tests/data/simple/templates/new_entry_from_data_invalid_yaml.yaml b/tests/data/simple/templates/new_entry_from_data_invalid_yaml.yaml new file mode 100644 index 0000000..7493947 --- /dev/null +++ b/tests/data/simple/templates/new_entry_from_data_invalid_yaml.yaml @@ -0,0 +1,13 @@ +# A : in that position should result in invalid yaml +name: '{{ name }}': some text +description: | + {{ description if description else "." | indent(width=2, first=False) }} +position: '{{ position }}' +# # quantity (integer): how many items are there +quantity: {{ quantity if quantity else "0" }} +# # value (float): how much each item is +value: 0.0 +# # cost (decimal): how much this costs +cost: '0' +# # other (yaml): +other: diff --git a/tests/data/simple/templates/new_entry_from_multiple_data.yaml b/tests/data/simple/templates/new_entry_from_multiple_data.yaml new file mode 100644 index 0000000..cfa4296 --- /dev/null +++ b/tests/data/simple/templates/new_entry_from_multiple_data.yaml @@ -0,0 +1,12 @@ +name: '{{ data.name }}' +description: | + {{ description if description else "." | indent(width=2, first=False) }} +position: '{{ data.position }}' +# # quantity (integer): how many items are there +quantity: {{ values.quantity if values.quantity else "0" }} +# # value (float): how much each item is +value: 0.0 +# # cost (decimal): how much this costs +cost: '{{ values.cost if values.cost else "0.0" }}' +# # other (yaml): +other: diff --git a/tests/test_collection.py b/tests/test_collection.py index d05415f..2308633 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -1,4 +1,5 @@ import datetime +import decimal import logging import os.path import shutil @@ -294,6 +295,76 @@ class testSimpleCollection(unittest.TestCase): {'value': None, 'frequency': 1}, ]) + def test_entry_from_template(self): + # TODO: make finding the templates less prone to breaking and + # then remove the cwd change from here + old_cwd = os.getcwd() + os.chdir(self.tmpdir) + data = { + "name": "This is a name", + } + entry = self.collection.entry_from_rendered_template( + "templates/new_entry_from_data.yaml", + data + ) + os.chdir(old_cwd) + self.assertIsInstance(entry, lesana.Entry) + self.assertEqual(entry.data["name"], "This is a name") + + def test_entry_from_template_multiple_data_sources(self): + # TODO: make finding the templates less prone to breaking and + # then remove the cwd change from here + old_cwd = os.getcwd() + os.chdir(self.tmpdir) + data = { + "name": "This is a name", + } + values = { + "quantity": 5, + "cost": decimal.Decimal("3.5"), + } + entry = self.collection.entry_from_rendered_template( + "templates/new_entry_from_multiple_data.yaml", + { + "data": data, + "values": values + } + ) + os.chdir(old_cwd) + self.assertIsInstance(entry, lesana.Entry) + self.assertEqual(entry.data["name"], "This is a name") + self.assertEqual(entry.data["quantity"], 5) + + def test_entry_from_bad_template(self): + # TODO: make finding the templates less prone to breaking and + # then remove the cwd change from here + old_cwd = os.getcwd() + os.chdir(self.tmpdir) + data = { + "name": "This is a name", + } + with self.assertRaises(lesana.collection.TemplatingError): + self.collection.entry_from_rendered_template( + "templates/new_entry_from_data_broken.yaml", + data + ) + os.chdir(old_cwd) + + def test_entry_from_bad_yaml(self): + # TODO: make finding the templates less prone to breaking and + # then remove the cwd change from here + old_cwd = os.getcwd() + os.chdir(self.tmpdir) + data = { + "name": "This is a name", + } + with self.assertRaises(lesana.collection.TemplatingError): + self.collection.entry_from_rendered_template( + "templates/new_entry_from_data_invalid_yaml.yaml", + data + ) + os.chdir(old_cwd) + class testComplexCollection(unittest.TestCase): def setUp(self): -- cgit v1.2.3