diff options
author | David Lord <davidism@gmail.com> | 2019-10-12 21:04:31 -0700 |
---|---|---|
committer | David Lord <davidism@gmail.com> | 2019-10-12 21:25:48 -0700 |
commit | 719537aeec37368e0d7fbea42a5d090bb5a1b7c9 (patch) | |
tree | 6ae8ae02ee249d7ab7af255e6523d198dbcc8fa7 | |
parent | 636c7122d98f6ace18fc4860587ac3cc1b0bfb8e (diff) | |
download | jinja2-719537aeec37368e0d7fbea42a5d090bb5a1b7c9.tar.gz |
don't finalize TemplateData nodesno-finalize-template-data
Finalize only applies to the output of expressions (constant or not).
Add tests for context, eval, and env finalize functions.
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | CHANGES.rst | 3 | ||||
-rw-r--r-- | jinja2/compiler.py | 67 | ||||
-rw-r--r-- | tests/test_api.py | 57 |
4 files changed, 88 insertions, 40 deletions
@@ -17,3 +17,4 @@ venv-*/ .coverage.* htmlcov .pytest_cache/ +/.vscode/ diff --git a/CHANGES.rst b/CHANGES.rst index 2c1e286..89844b2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -34,6 +34,9 @@ Unreleased :issue:`755`, :pr:`938` - Add new ``boolean``, ``false``, ``true``, ``integer`` and ``float`` tests. :pr:`824` +- The environment's ``finalize`` function is only applied to the + output of expressions (constant or not), not static template data. + :issue:`63` Version 2.10.3 diff --git a/jinja2/compiler.py b/jinja2/compiler.py index c0eb240..708186b 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -1224,19 +1224,28 @@ class CodeGenerator(NodeVisitor): if self.has_known_extends and frame.require_output_check: return + finalize = text_type + finalize_src = None allow_constant_finalize = True + if self.environment.finalize: - func = self.environment.finalize - if getattr(func, 'contextfunction', False) or \ - getattr(func, 'evalcontextfunction', False): + env_finalize = self.environment.finalize + finalize_src = "environment.finalize(" + + def finalize(value): + return text_type(env_finalize(value)) + + if getattr(env_finalize, "contextfunction", False): + finalize_src += "context, " allow_constant_finalize = False - elif getattr(func, 'environmentfunction', False): - finalize = lambda x: text_type( - self.environment.finalize(self.environment, x)) - else: - finalize = lambda x: text_type(self.environment.finalize(x)) - else: - finalize = text_type + elif getattr(env_finalize, "evalcontextfunction", False): + finalize_src += "context.eval_ctx, " + allow_constant_finalize = False + elif getattr(env_finalize, "environmentfunction", False): + finalize_src += "environment, " + + def finalize(value): + return text_type(env_finalize(self.environment, value)) # if we are inside a frame that requires output checking, we do so outdent_later = False @@ -1250,27 +1259,37 @@ class CodeGenerator(NodeVisitor): body = [] for child in node.nodes: try: - if not allow_constant_finalize: + # If the finalize function needs context, and this isn't + # template data, evaluate the node at render. + if not ( + allow_constant_finalize + or isinstance(child, nodes.TemplateData) + ): raise nodes.Impossible() + const = child.as_const(frame.eval_ctx) except nodes.Impossible: body.append(child) continue + # the frame can't be volatile here, becaus otherwise the # as_const() function would raise an Impossible exception # at that point. try: if frame.eval_ctx.autoescape: - if hasattr(const, '__html__'): - const = const.__html__() - else: - const = escape(const) - const = finalize(const) + const = escape(const) + + # Only call finalize on expressions, not template data. + if isinstance(child, nodes.TemplateData): + const = text_type(const) + else: + const = finalize(const) except Exception: # if something goes wrong here we evaluate the node # at runtime for easier debugging body.append(child) continue + if body and isinstance(body[-1], list): body[-1].append(const) else: @@ -1306,10 +1325,7 @@ class CodeGenerator(NodeVisitor): else: self.write('to_string(') if self.environment.finalize is not None: - self.write('environment.finalize(') - if getattr(self.environment.finalize, - "contextfunction", False): - self.write('context, ') + self.write(finalize_src) close += 1 self.visit(item, frame) self.write(')' * close) @@ -1344,16 +1360,7 @@ class CodeGenerator(NodeVisitor): self.write('escape(') close += 1 if self.environment.finalize is not None: - self.write('environment.finalize(') - if getattr(self.environment.finalize, - 'contextfunction', False): - self.write('context, ') - elif getattr(self.environment.finalize, - 'evalcontextfunction', False): - self.write('context.eval_ctx, ') - elif getattr(self.environment.finalize, - 'environmentfunction', False): - self.write('environment, ') + self.write(finalize_src) close += 1 self.visit(argument, frame) self.write(')' * close + ', ') diff --git a/tests/test_api.py b/tests/test_api.py index 1829b1d..a457602 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -19,6 +19,9 @@ from jinja2 import Environment, Undefined, ChainableUndefined, \ from jinja2.compiler import CodeGenerator from jinja2.runtime import Context from jinja2.utils import Cycler +from jinja2.utils import contextfunction +from jinja2.utils import evalcontextfunction +from jinja2.utils import environmentfunction @pytest.mark.api @@ -37,16 +40,50 @@ class TestExtendedAPI(object): tmpl = env.from_string('{{ foo["items"] }}') assert tmpl.render(foo={'items': 42}) == '42' - def test_finalizer(self, env): - def finalize_none_empty(value): - if value is None: - value = u'' - return value - env = Environment(finalize=finalize_none_empty) - tmpl = env.from_string('{% for item in seq %}|{{ item }}{% endfor %}') - assert tmpl.render(seq=(None, 1, "foo")) == '||1|foo' - tmpl = env.from_string('<{{ none }}>') - assert tmpl.render() == '<>' + def test_finalize(self): + e = Environment(finalize=lambda v: "" if v is None else v) + t = e.from_string("{% for item in seq %}|{{ item }}{% endfor %}") + assert t.render(seq=(None, 1, "foo")) == "||1|foo" + + def test_finalize_constant_expression(self): + e = Environment(finalize=lambda v: "" if v is None else v) + t = e.from_string("<{{ none }}>") + assert t.render() == "<>" + + def test_no_finalize_template_data(self): + e = Environment(finalize=lambda v: type(v).__name__) + t = e.from_string("<{{ value }}>") + # If template data was finalized, it would print "strintstr". + assert t.render(value=123) == "<int>" + + def test_context_finalize(self): + @contextfunction + def finalize(context, value): + return value * context["scale"] + + e = Environment(finalize=finalize) + t = e.from_string("{{ value }}") + assert t.render(value=5, scale=3) == "15" + + def test_eval_finalize(self): + @evalcontextfunction + def finalize(eval_ctx, value): + return str(eval_ctx.autoescape) + value + + e = Environment(finalize=finalize, autoescape=True) + t = e.from_string("{{ value }}") + assert t.render(value="<script>") == "True<script>" + + def test_env_autoescape(self): + @environmentfunction + def finalize(env, value): + return " ".join( + (env.variable_start_string, repr(value), env.variable_end_string) + ) + + e = Environment(finalize=finalize) + t = e.from_string("{{ value }}") + assert t.render(value="hello") == "{{ 'hello' }}" def test_cycler(self, env): items = 1, 2, 3 |