]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
don't finalize TemplateData nodes 1080/head
authorDavid Lord <davidism@gmail.com>
Sun, 13 Oct 2019 04:04:31 +0000 (21:04 -0700)
committerDavid Lord <davidism@gmail.com>
Sun, 13 Oct 2019 04:25:48 +0000 (21:25 -0700)
Finalize only applies to the output of expressions (constant or not).
Add tests for context, eval, and env finalize functions.

.gitignore
CHANGES.rst
jinja2/compiler.py
tests/test_api.py

index 8402c1d3ef064b18f3a781b114b9395ae8585f2b..81752e0e9eb797e860ae41074c9a8ff1f1b58881 100644 (file)
@@ -17,3 +17,4 @@ venv-*/
 .coverage.*
 htmlcov
 .pytest_cache/
+/.vscode/
index 2c1e2863291583e5e017c1bb9eb50b364e241f57..89844b238c7a3a7b8a0187713314b1d474cfa09e 100644 (file)
@@ -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
index c0eb24001e5b6c5921e8fc745cdfcb1dc4da4f9c..708186b1475b73aef7f66a7effe2a4e65e56495e 100644 (file)
@@ -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 + ', ')
index 1829b1d0069483f47fb8e1712aeaae653f08f734..a457602976a2a613b83392be12ef4f3d5729b43b 100644 (file)
@@ -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&lt;script&gt;"
+
+    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