]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
Implement with-tag with a custom node
authorArmin Ronacher <armin.ronacher@active-4.com>
Sun, 8 Jan 2017 14:35:54 +0000 (15:35 +0100)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sun, 8 Jan 2017 14:35:56 +0000 (15:35 +0100)
CHANGES
jinja2/compiler.py
jinja2/idtracking.py
jinja2/nodes.py
jinja2/parser.py
tests/test_core_tags.py
tests/test_ext.py

diff --git a/CHANGES b/CHANGES
index 8a160d79a55b888464ef13899af0ba4a2f1f1e93..436ebad92714bb1b9c583f40b6fe3f791a15824d 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -16,6 +16,9 @@ Version 2.9.3
 - Resolved an issue where `block scoped` would not take advantage of the
   new scoping rules.  In some more exotic cases a variable overriden in a
   local scope would not make it into a block.
+- Change the code generation of the `with` statement to be in line with the
+  new scoping rules.  This resolves some unlikely bugs in edge cases.  This
+  also introduces a new internal `With` node that can be used by extensions.
 
 Version 2.9.2
 -------------
index 9a6ac6a034f75df853a2602f1fb9b4278ce277c2..cdfe38ebfd230191f107b74c0715c211cef0e847 100644 (file)
@@ -19,7 +19,7 @@ from jinja2.optimizer import Optimizer
 from jinja2.exceptions import TemplateAssertionError
 from jinja2.utils import Markup, concat, escape
 from jinja2._compat import range_type, text_type, string_types, \
-     iteritems, NativeStringIO, imap
+     iteritems, NativeStringIO, imap, izip
 from jinja2.idtracking import Symbols, VAR_LOAD_PARAMETER, \
      VAR_LOAD_RESOLVE, VAR_LOAD_ALIAS, VAR_LOAD_UNDEFINED
 
@@ -1165,6 +1165,18 @@ class CodeGenerator(NodeVisitor):
         self.end_write(frame)
         self.leave_frame(filter_frame)
 
+    def visit_With(self, node, frame):
+        with_frame = frame.inner()
+        with_frame.symbols.analyze_node(node)
+        self.enter_frame(with_frame)
+        for idx, (target, expr) in enumerate(izip(node.targets, node.values)):
+            self.newline()
+            self.visit(target, with_frame)
+            self.write(' = ')
+            self.visit(expr, frame)
+        self.blockvisit(node.body, with_frame)
+        self.leave_frame(with_frame)
+
     def visit_ExprStmt(self, node, frame):
         self.newline(node)
         self.visit(node.node, frame)
index b00dab8c1c604ce1568ffb9eca37724e96e7e2f7..433b92c8f453c0400ab920ac815e8347a1253a95 100644 (file)
@@ -180,6 +180,12 @@ class RootVisitor(NodeVisitor):
         for item in branch or ():
             self.sym_visitor.visit(item)
 
+    def visit_With(self, node, **kwargs):
+        for target in node.targets:
+            self.sym_visitor.visit(target)
+        for child in node.body:
+            self.sym_visitor.visit(child)
+
     def generic_visit(self, node, *args, **kwargs):
         raise NotImplementedError('Cannot find symbols for %r' %
                                   node.__class__.__name__)
@@ -249,6 +255,10 @@ class FrameSymbolVisitor(NodeVisitor):
     def visit_FilterBlock(self, node, **kwargs):
         self.visit(node.filter, **kwargs)
 
+    def visit_With(self, node, **kwargs):
+        for target in node.values:
+            self.visit(target)
+
     def visit_AssignBlock(self, node, **kwargs):
         """Stop visiting at block assigns."""
         self.visit(node.target, **kwargs)
index d1a4c381922991a630d7af92c84db45df315b152..2c6a296a5887b29de82347db911ae528e01f24d4 100644 (file)
@@ -337,6 +337,15 @@ class FilterBlock(Stmt):
     fields = ('body', 'filter')
 
 
+class With(Stmt):
+    """Specific node for with statements.  In older versions of Jinja the
+    with statement was implemented on the base of the `Scope` node instead.
+
+    .. versionadded:: 2.9.3
+    """
+    fields = ('targets', 'values', 'body')
+
+
 class Block(Stmt):
     """A node that represents a block."""
     fields = ('name', 'body', 'scoped')
index 9742a2793ed93a8c83c7824578f52d311bd0f1d4..0bf74c9459be00c325b12be338f4925bb6bfcfe2 100644 (file)
@@ -225,19 +225,22 @@ class Parser(object):
         return result
 
     def parse_with(self):
-        node = nodes.Scope(lineno=next(self.stream).lineno)
-        assignments = []
+        node = nodes.With(lineno=next(self.stream).lineno)
+        targets = []
+        values = []
         while self.stream.current.type != 'block_end':
             lineno = self.stream.current.lineno
-            if assignments:
+            if targets:
                 self.stream.expect('comma')
             target = self.parse_assign_target()
+            target.set_ctx('param')
+            targets.append(target)
             self.stream.expect('assign')
-            expr = self.parse_expression()
-            assignments.append(nodes.Assign(target, expr, lineno=lineno))
-        node.body = assignments + \
-            list(self.parse_statements(('name:endwith',),
-                                         drop_needle=True))
+            values.append(self.parse_expression())
+        node.targets = targets
+        node.values = values
+        node.body = self.parse_statements(('name:endwith',),
+                                          drop_needle=True)
         return node
 
     def parse_autoescape(self):
index f857cc7b3ca33f5bd2167d22dee0ae6b88371e3d..f48d8b4483b947ad5269373725ee042b1a581d83 100644 (file)
@@ -378,3 +378,11 @@ class TestWith(object):
         ''')
         assert [x.strip() for x in tmpl.render(a=1, b=2).splitlines()] \
             == ['42 = 23', '1 = 2']
+
+    def test_with_argument_scoping(self, env):
+        tmpl = env.from_string('''\
+        {%- with a=1, b=2, c=b, d=e, e=5 -%}
+            {{ a }}|{{ b }}|{{ c }}|{{ d }}|{{ e }}
+        {%- endwith -%}
+        ''')
+        assert tmpl.render(b=3, e=4) == '1|2|3|4|5'
index 9ec5ac35a126e096e1ad620e2b321f4b92463187..1301d22a2a3aa38a56ea46124ada312c462812da 100644 (file)
@@ -329,9 +329,9 @@ class TestScope(object):
 
         env = Environment(extensions=[ScopeExt])
         tmpl = env.from_string('''\
-        {%- with a=1, b=2, c=b, d=e, e=5 -%}
+        {%- scope a=1, b=2, c=b, d=e, e=5 -%}
             {{ a }}|{{ b }}|{{ c }}|{{ d }}|{{ e }}
-        {%- endwith -%}
+        {%- endscope -%}
         ''')
         assert tmpl.render(b=3, e=4) == '1|2|2|4|5'