Skip to content

Commit

Permalink
Merge branch '2.9-maintenance'
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Jan 9, 2017
2 parents b088619 + ef71801 commit 9949832
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 33 deletions.
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Version 2.9.4
- Solved some warnings for string literals. (#646)
- Increment the bytecode cache version which was not done due to an
oversight before.
- Corrected bad code generation and scoping for filtered loops. (#649)

Version 2.9.3
-------------
Expand Down
59 changes: 28 additions & 31 deletions jinja2/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,7 @@ def visit_FromImport(self, node, frame):

def visit_For(self, node, frame):
loop_frame = frame.inner()
test_frame = frame.inner()
else_frame = frame.inner()

# try to figure out if we have an extended loop. An extended loop
Expand All @@ -1035,11 +1036,32 @@ def visit_For(self, node, frame):
loop_ref = None
if extended_loop:
loop_ref = loop_frame.symbols.declare_parameter('loop')
loop_frame.symbols.analyze_node(node)

loop_frame.symbols.analyze_node(node, for_branch='body')
if node.else_:
else_frame.symbols.analyze_node(node, for_branch='else')

if node.test:
loop_filter_func = self.temporary_identifier()
test_frame.symbols.analyze_node(node, for_branch='test')
self.writeline('%s(fiter):' % self.func(loop_filter_func), node.test)
self.indent()
self.enter_frame(test_frame)
self.writeline(self.environment.is_async and 'async for ' or 'for ')
self.visit(node.target, loop_frame)
self.write(' in ')
self.write(self.environment.is_async and 'auto_aiter(fiter)' or 'fiter')
self.write(':')
self.indent()
self.writeline('if ', node.test)
self.visit(node.test, test_frame)
self.write(':')
self.indent()
self.writeline('yield ')
self.visit(node.target, loop_frame)
self.outdent(3)
self.leave_frame(test_frame, with_python_scope=True)

# if we don't have an recursive loop we have to find the shadowed
# variables at that point. Because loops can be nested but the loop
# variable is a special one we have to enforce aliasing for it.
Expand Down Expand Up @@ -1073,34 +1095,18 @@ def visit_For(self, node, frame):
else:
self.write(' in ')

# if we have an extened loop and a node test, we filter in the
# "outer frame".
if extended_loop and node.test is not None:
self.write('(')
self.visit(node.target, loop_frame)
self.write(self.environment.is_async and ' async for ' or ' for ')
self.visit(node.target, loop_frame)
self.write(' in ')
if node.recursive:
self.write('reciter')
else:
if self.environment.is_async:
self.write('auto_aiter(')
self.visit(node.iter, frame)
if self.environment.is_async:
self.write(')')
self.write(' if (')
self.visit(node.test, loop_frame)
self.write('))')

elif node.recursive:
if node.test:
self.write('%s(' % loop_filter_func)
if node.recursive:
self.write('reciter')
else:
if self.environment.is_async and not extended_loop:
self.write('auto_aiter(')
self.visit(node.iter, frame)
if self.environment.is_async and not extended_loop:
self.write(')')
if node.test:
self.write(')')

if node.recursive:
self.write(', loop_render_func, depth):')
Expand All @@ -1110,15 +1116,6 @@ def visit_For(self, node, frame):
self.indent()
self.enter_frame(loop_frame)

# tests in not extended loops become a continue
if not extended_loop and node.test is not None:
self.writeline('if not ')
self.visit(node.test, loop_frame)
self.write(':')
self.indent()
self.writeline('continue')
self.outdent()

self.blockvisit(node.body, loop_frame)
if node.else_:
self.writeline('%s = 0' % iteration_indicator)
Expand Down
7 changes: 5 additions & 2 deletions jinja2/idtracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,16 @@ def visit_OverlayScope(self, node, **kwargs):
self.sym_visitor.visit(child)

def visit_For(self, node, for_branch='body', **kwargs):
if node.test is not None:
self.sym_visitor.visit(node.test)
if for_branch == 'body':
self.sym_visitor.visit(node.target, store_as_param=True)
branch = node.body
elif for_branch == 'else':
branch = node.else_
elif for_branch == 'test':
self.sym_visitor.visit(node.target, store_as_param=True)
if node.test is not None:
self.sym_visitor.visit(node.test)
return
else:
raise RuntimeError('Unknown for branch')
for item in branch or ():
Expand Down
49 changes: 49 additions & 0 deletions tests/test_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,3 +415,52 @@ def test_unpacking(self, test_env_async):
tmpl = test_env_async.from_string('{% for a, b, c in [[1, 2, 3]] %}'
'{{ a }}|{{ b }}|{{ c }}{% endfor %}')
assert tmpl.render() == '1|2|3'

def test_recursive_loop_filter(self, test_env_async):
t = test_env_async.from_string('''
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{%- for page in [site.root] if page.url != this recursive %}
<url><loc>{{ page.url }}</loc></url>
{{- loop(page.children) }}
{%- endfor %}
</urlset>
''')
sm =t.render(this='/foo', site={'root': {
'url': '/',
'children': [
{'url': '/foo'},
{'url': '/bar'},
]
}})
lines = [x.strip() for x in sm.splitlines() if x.strip()]
assert lines == [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
'<url><loc>/</loc></url>',
'<url><loc>/bar</loc></url>',
'</urlset>',
]

def test_nonrecursive_loop_filter(self, test_env_async):
t = test_env_async.from_string('''
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{%- for page in items if page.url != this %}
<url><loc>{{ page.url }}</loc></url>
{%- endfor %}
</urlset>
''')
sm =t.render(this='/foo', items=[
{'url': '/'},
{'url': '/foo'},
{'url': '/bar'},
])
lines = [x.strip() for x in sm.splitlines() if x.strip()]
assert lines == [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
'<url><loc>/</loc></url>',
'<url><loc>/bar</loc></url>',
'</urlset>',
]
27 changes: 27 additions & 0 deletions tests/test_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,3 +452,30 @@ def test_scoped_block(self, env):
t = env.from_string('{% set x = 1 %}{% with x = 2 %}{% block y scoped %}'
'{{ x }}{% endblock %}{% endwith %}')
assert t.render() == '2'

def test_recursive_loop_filter(self, env):
t = env.from_string('''
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{%- for page in [site.root] if page.url != this recursive %}
<url><loc>{{ page.url }}</loc></url>
{{- loop(page.children) }}
{%- endfor %}
</urlset>
''')
sm =t.render(this='/foo', site={'root': {
'url': '/',
'children': [
{'url': '/foo'},
{'url': '/bar'},
]
}})
lines = [x.strip() for x in sm.splitlines() if x.strip()]
print(lines)
assert lines == [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
'<url><loc>/</loc></url>',
'<url><loc>/bar</loc></url>',
'</urlset>',
]

0 comments on commit 9949832

Please sign in to comment.