- 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
-------------
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
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)
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__)
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)
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')
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):
''')
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'
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'