mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-10 08:38:32 +02:00
Remove sample format and its asteval dependency (#1436)
This commit is contained in:
parent
a969f9b3fb
commit
d405e92292
6 changed files with 2 additions and 226 deletions
|
@ -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}
|
||||
|
|
|
@ -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)
|
|
@ -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))
|
13
poetry.lock
generated
13
poetry.lock
generated
|
@ -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"},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -118,32 +118,6 @@ Feature: Custom formats
|
|||
| basic_folder.yaml |
|
||||
| basic_dayone.yaml |
|
||||
|
||||
Scenario Outline: Exporting using custom templates
|
||||
Given we use the config "<config_file>"
|
||||
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 "<config_file>"
|
||||
And we use the password "test" if prompted
|
||||
|
|
Loading…
Add table
Reference in a new issue