diff --git a/jrnl/plugins/__init__.py b/jrnl/plugins/__init__.py index da6199fb..e3484927 100644 --- a/jrnl/plugins/__init__.py +++ b/jrnl/plugins/__init__.py @@ -8,7 +8,6 @@ from .json_exporter import JSONExporter from .markdown_exporter import MarkdownExporter from .tag_exporter import TagExporter from .dates_exporter import DatesExporter -from .template_exporter import __all__ as template_exporters from .text_exporter import TextExporter from .xml_exporter import XMLExporter from .yaml_exporter import YAMLExporter @@ -22,7 +21,7 @@ __exporters = [ XMLExporter, YAMLExporter, FancyExporter, -] + template_exporters +] __importers = [JRNLImporter] __exporter_types = {name: plugin for plugin in __exporters for name in plugin.names} diff --git a/jrnl/plugins/template.py b/jrnl/plugins/template.py deleted file mode 100644 index 206eaf13..00000000 --- a/jrnl/plugins/template.py +++ /dev/null @@ -1,142 +0,0 @@ -# Copyright (C) 2012-2021 jrnl contributors -# License: https://www.gnu.org/licenses/gpl-3.0.html - -import re - -from ruamel.yaml import YAML - -VAR_RE = r"[_a-zA-Z][a-zA-Z0-9_]*" -EXPRESSION_RE = r"[\[\]():.a-zA-Z0-9_]*" -PRINT_RE = r"{{ *(.+?) *}}" -START_BLOCK_RE = r"{% *(if|for) +(.+?) *%}" -END_BLOCK_RE = r"{% *end(for|if) *%}" -FOR_RE = r"{{% *for +({varname}) +in +([^%]+) *%}}".format(varname=VAR_RE) -IF_RE = r"{% *if +(.+?) *%}" -BLOCK_RE = r"{% *block +(.+?) *%}((?:.|\n)+?){% *endblock *%}" -INCLUDE_RE = r"{% *include +(.+?) *%}" - - -class Template: - def __init__(self, template): - self.template = template - self.clean_template = None - self.blocks = {} - - @classmethod - def from_file(cls, filename): - with open(filename) as f: - front_matter, body = f.read().strip("-\n").split("---", 2) - front_matter = YAML(typ="safe").load(front_matter) - template = cls(body) - template.__dict__.update(front_matter) - return template - - def render(self, **vars): - if self.clean_template is None: - self._get_blocks() - return self._expand(self.clean_template, **vars) - - def render_block(self, block, **vars): - if self.clean_template is None: - self._get_blocks() - return self._expand(self.blocks[block], **vars) - - def _eval_context(self, vars): - import asteval - - e = asteval.Interpreter(use_numpy=False, writer=None) - e.symtable.update(vars) - e.symtable["__last_iteration"] = vars.get("__last_iteration", False) - return e - - def _get_blocks(self): - def s(match): - name, contents = match.groups() - self.blocks[name] = self._strip_single_nl(contents) - return "" - - self.clean_template = re.sub(BLOCK_RE, s, self.template, flags=re.MULTILINE) - - def _expand(self, template, **vars): - stack = sorted( - [ - (m.start(), 1, m.groups()[0]) - for m in re.finditer(START_BLOCK_RE, template) - ] - + [ - (m.end(), -1, m.groups()[0]) - for m in re.finditer(END_BLOCK_RE, template) - ] - ) - - last_nesting, nesting = 0, 0 - start = 0 - result = "" - block_type = None - if not stack: - return self._expand_vars(template, **vars) - - for pos, indent, typ in stack: - nesting += indent - if nesting == 1 and last_nesting == 0: - block_type = typ - result += self._expand_vars(template[start:pos], **vars) - start = pos - elif nesting == 0 and last_nesting == 1: - if block_type == "if": - result += self._expand_cond(template[start:pos], **vars) - elif block_type == "for": - result += self._expand_loops(template[start:pos], **vars) - elif block_type == "block": - result += self._save_block(template[start:pos], **vars) - start = pos - last_nesting = nesting - - result += self._expand_vars(template[stack[-1][0] :], **vars) - return result - - def _expand_vars(self, template, **vars): - safe_eval = self._eval_context(vars) - expanded = re.sub( - INCLUDE_RE, lambda m: self.render_block(m.groups()[0], **vars), template - ) - return re.sub(PRINT_RE, lambda m: str(safe_eval(m.groups()[0])), expanded) - - def _expand_cond(self, template, **vars): - start_block = re.search(IF_RE, template, re.M) - end_block = list(re.finditer(END_BLOCK_RE, template, re.M))[-1] - expression = start_block.groups()[0] - sub_template = self._strip_single_nl( - template[start_block.end() : end_block.start()] - ) - - safe_eval = self._eval_context(vars) - if safe_eval(expression): - return self._expand(sub_template) - return "" - - def _strip_single_nl(self, template, strip_r=True): - if template[0] == "\n": - template = template[1:] - if strip_r and template[-1] == "\n": - template = template[:-1] - return template - - def _expand_loops(self, template, **vars): - start_block = re.search(FOR_RE, template, re.M) - end_block = list(re.finditer(END_BLOCK_RE, template, re.M))[-1] - var_name, iterator = start_block.groups() - sub_template = self._strip_single_nl( - template[start_block.end() : end_block.start()], strip_r=False - ) - - safe_eval = self._eval_context(vars) - - result = "" - items = safe_eval(iterator) - for idx, var in enumerate(items): - vars[var_name] = var - vars["__last_iteration"] = idx == len(items) - 1 - result += self._expand(sub_template, **vars) - del vars[var_name] - return self._strip_single_nl(result) diff --git a/jrnl/plugins/template_exporter.py b/jrnl/plugins/template_exporter.py deleted file mode 100644 index d2e5ce3e..00000000 --- a/jrnl/plugins/template_exporter.py +++ /dev/null @@ -1,43 +0,0 @@ -# encoding: utf-8 -# Copyright (C) 2012-2021 jrnl contributors -# License: https://www.gnu.org/licenses/gpl-3.0.html - -from glob import glob -import os - -from .template import Template -from .text_exporter import TextExporter - - -class GenericTemplateExporter(TextExporter): - """This Exporter can convert entries and journals into text files.""" - - @classmethod - def export_entry(cls, entry): - """Returns a string representation of a single entry.""" - vars = {"entry": entry, "tags": entry.tags} - return cls.template.render_block("entry", **vars) - - @classmethod - def export_journal(cls, journal): - """Returns a string representation of an entire journal.""" - vars = {"journal": journal, "entries": journal.entries, "tags": journal.tags} - return cls.template.render_block("journal", **vars) - - -def __exporter_from_file(template_file): - """Create a template class from a file""" - name = os.path.basename(template_file).replace(".template", "") - template = Template.from_file(template_file) - return type( - str(f"{name.title()}Exporter"), - (GenericTemplateExporter,), - {"names": [name], "extension": template.extension, "template": template}, - ) - - -__all__ = [] - -# Factory pattern to create Exporter classes for all available templates -for template_file in glob("jrnl/templates/*.template"): - __all__.append(__exporter_from_file(template_file)) diff --git a/poetry.lock b/poetry.lock index d2e8f239..a749c927 100644 --- a/poetry.lock +++ b/poetry.lock @@ -28,14 +28,6 @@ python-versions = ">=3.6" [package.extras] test = ["coverage", "flake8", "pexpect", "wheel"] -[[package]] -name = "asteval" -version = "0.9.26" -description = "Safe, minimalistic evaluator of python expression using ast module" -category = "main" -optional = false -python-versions = ">=3.6" - [[package]] name = "asttokens" version = "2.0.5" @@ -941,7 +933,7 @@ testing = ["pytest", "pytest-bdd", "toml"] [metadata] lock-version = "1.1" python-versions = ">=3.9.0, <3.12" -content-hash = "8393124bac95ba61eb1a3590f5ed97d938d7b3637ea1dc19e0baacfc1ccab093" +content-hash = "8a4b09a72f705e5265d6d1bebc61926df2ff1e0237ac1369bef6dbebb44f4d9c" [metadata.files] ansiwrap = [ @@ -956,9 +948,6 @@ argcomplete = [ {file = "argcomplete-2.0.0-py2.py3-none-any.whl", hash = "sha256:cffa11ea77999bb0dd27bb25ff6dc142a6796142f68d45b1a26b11f58724561e"}, {file = "argcomplete-2.0.0.tar.gz", hash = "sha256:6372ad78c89d662035101418ae253668445b391755cfe94ea52f1b9d22425b20"}, ] -asteval = [ - {file = "asteval-0.9.26.tar.gz", hash = "sha256:36125613ec21ed3e33e370ca8960a1f1e8a2324d78a8016bfa5ad76f1e16ef05"}, -] asttokens = [ {file = "asttokens-2.0.5-py2.py3-none-any.whl", hash = "sha256:0844691e88552595a6f4a4281a9f7f79b8dd45ca4ccea82e5e05b4bbdb76705c"}, {file = "asttokens-2.0.5.tar.gz", hash = "sha256:9a54c114f02c7a9480d56550932546a3f1fe71d8a02f1bc7ccd0ee3ee35cf4d5"}, diff --git a/pyproject.toml b/pyproject.toml index 3d04c956..88861740 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,6 @@ classifiers = [ python = ">=3.9.0, <3.12" ansiwrap = "^0.8.4" -asteval = "^0.9" colorama = ">=0.4" # https://github.com/tartley/colorama/blob/master/CHANGELOG.rst cryptography = ">=3.0" # https://cryptography.io/en/latest/api-stability.html keyring = ">=21.0" # https://github.com/jaraco/keyring#integration diff --git a/tests/bdd/features/format.feature b/tests/bdd/features/format.feature index 18426674..dc5c92ca 100644 --- a/tests/bdd/features/format.feature +++ b/tests/bdd/features/format.feature @@ -118,32 +118,6 @@ Feature: Custom formats | basic_folder.yaml | | basic_dayone.yaml | - Scenario Outline: Exporting using custom templates - Given we use the config "" - And we use the password "test" if prompted - When we run "jrnl -1 --format sample" - Then the output should be - The third entry finally after weeks without writing. - ---------------------------------------------------- - - I'm so excited about emojis. 💯 🎶 💩 - - Donec semper pellentesque iaculis. Nullam cursus et justo sit amet venenatis. - Vivamus tempus ex dictum metus vehicula gravida. Aliquam sed sem dolor. Nulla - eget ultrices purus. Quisque at nunc at quam pharetra consectetur vitae quis - dolor. Fusce ultricies purus eu est feugiat, quis scelerisque nibh malesuada. - Quisque egestas semper nibh in hendrerit. Nam finibus ex in mi mattis - vulputate. Sed mauris urna, consectetur in justo eu, volutpat accumsan justo. - Phasellus aliquam lacus placerat convallis vestibulum. Curabitur maximus at - ante eget fringilla. @tagthree and also @tagone - - Examples: configs - | config_file | - | basic_onefile.yaml | - | basic_encrypted.yaml | - | basic_folder.yaml | - | basic_dayone.yaml | - Scenario Outline: Increasing Headings on Markdown export Given we use the config "" And we use the password "test" if prompted