From: Armin Ronacher Date: Sun, 8 Jan 2017 14:35:54 +0000 (+0100) Subject: Implement with-tag with a custom node X-Git-Tag: 2.9.3~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=90ac7644d0fcdddf4a20dd0ae51e7189202d133c;p=thirdparty%2Fjinja.git Implement with-tag with a custom node --- diff --git a/CHANGES b/CHANGES index 8a160d79..436ebad9 100644 --- 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 ------------- diff --git a/jinja2/compiler.py b/jinja2/compiler.py index 9a6ac6a0..cdfe38eb 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -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) diff --git a/jinja2/idtracking.py b/jinja2/idtracking.py index b00dab8c..433b92c8 100644 --- a/jinja2/idtracking.py +++ b/jinja2/idtracking.py @@ -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) diff --git a/jinja2/nodes.py b/jinja2/nodes.py index d1a4c381..2c6a296a 100644 --- a/jinja2/nodes.py +++ b/jinja2/nodes.py @@ -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') diff --git a/jinja2/parser.py b/jinja2/parser.py index 9742a279..0bf74c94 100644 --- a/jinja2/parser.py +++ b/jinja2/parser.py @@ -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): diff --git a/tests/test_core_tags.py b/tests/test_core_tags.py index f857cc7b..f48d8b44 100644 --- a/tests/test_core_tags.py +++ b/tests/test_core_tags.py @@ -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' diff --git a/tests/test_ext.py b/tests/test_ext.py index 9ec5ac35..1301d22a 100644 --- a/tests/test_ext.py +++ b/tests/test_ext.py @@ -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'