From 3da9031b99b12ad829e8307b893279347ad04c2c Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 23 May 2008 16:37:28 +0200 Subject: [PATCH] added loopcontrols extension and added unittests for it --HG-- branch : trunk --- docs/extensions.rst | 11 +++++++++++ docs/templates.rst | 22 ++++++++++++++++++++++ jinja2/compiler.py | 16 +++++++--------- jinja2/ext.py | 12 ++++++++++++ jinja2/nodes.py | 6 ++++-- tests/test_ext.py | 37 +++++++++++++++++++++++++++++++++++++ 6 files changed, 93 insertions(+), 11 deletions(-) create mode 100644 tests/test_ext.py diff --git a/docs/extensions.rst b/docs/extensions.rst index 65f0082..d499192 100644 --- a/docs/extensions.rst +++ b/docs/extensions.rst @@ -101,6 +101,17 @@ The do aka expression-statement extension adds a simple `do` tag to the template engine that works like a variable expression but ignores the return value. +.. _loopcontrols-extension: + +loopcontrols +~~~~~~~~~~~~ + +**Import name:** `jinja2.ext.loopcontrols` + +This extension adds support for `break` and `continue` in loops. After +enabling Jinja2 provides those two keywords which work exactly like in +Python. + .. _writing-extensions: diff --git a/docs/templates.rst b/docs/templates.rst index 1c1b8a7..a95b874 100644 --- a/docs/templates.rst +++ b/docs/templates.rst @@ -1025,3 +1025,25 @@ that works exactly like the regular variable expression (``{{ ... }}``) just that it doesn't print anything. This can be used to modify lists:: {% do navigation.append('a string') %} + + +Loop Controls +~~~~~~~~~~~~~ + +If the application enables the :ref:`loopcontrols-extension` it's possible to +use `break` and `continue` in loops. When `break` is reached, the loop is +terminated, if `continue` is eached the processing is stopped and continues +with the next iteration. + +Here a loop that skips every second item:: + + {% for user in users %} + {%- if loop.index is even %}{% continue %}{% endif %} + ... + {% endfor %} + +Likewise a look that stops processing after the 10th iteration:: + + {% for user in users %} + {%- if loop.index >= 10 %}{% break %}{% endif %} + {%- endfor %} diff --git a/jinja2/compiler.py b/jinja2/compiler.py index 9bf0044..3b5f8c0 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -1018,8 +1018,8 @@ class CodeGenerator(NodeVisitor): self.macro_def(node, macro_frame) def visit_CallBlock(self, node, frame): - call_frame = self.macro_body(node, frame, node.iter_child_nodes - (exclude=('call',))) + children = node.iter_child_nodes(exclude=('call',)) + call_frame = self.macro_body(node, frame, children) self.writeline('caller = ') self.macro_def(node, call_frame) self.start_write(frame, node) @@ -1305,14 +1305,12 @@ class CodeGenerator(NodeVisitor): # if the filter node is None we are inside a filter block # and want to write to the current buffer - if node.node is None: - if self.environment.autoescape: - tmpl = 'Markup(concat(%s))' - else: - tmpl = 'concat(%s)' - self.write(tmpl % frame.buffer) - else: + if node.node is not None: self.visit(node.node, frame) + elif self.environment.autoescape: + self.write('Markup(concat(%s))' % frame.buffer) + else: + self.write('concat(%s)' % frame.buffer) self.signature(node, frame) self.write(')') diff --git a/jinja2/ext.py b/jinja2/ext.py index a489f97..63e8f5b 100644 --- a/jinja2/ext.py +++ b/jinja2/ext.py @@ -283,6 +283,17 @@ class ExprStmtExtension(Extension): return node +class LoopControlExtension(Extension): + """Adds break and continue to the template engine.""" + tags = set(['break', 'continue']) + + def parse(self, parser): + token = parser.stream.next() + if token.value == 'break': + return nodes.Break(lineno=token.lineno) + return nodes.Continue(lineno=token.lineno) + + def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS): """Extract localizable strings from the given template node. @@ -367,3 +378,4 @@ def babel_extract(fileobj, keywords, comment_tags, options): #: nicer import names i18n = InternationalizationExtension do = ExprStmtExtension +loopcontrols = LoopControlExtension diff --git a/jinja2/nodes.py b/jinja2/nodes.py index 27c9ddb..ad39903 100644 --- a/jinja2/nodes.py +++ b/jinja2/nodes.py @@ -118,8 +118,10 @@ class Node(object): def iter_fields(self, exclude=None, only=None): """This method iterates over all fields that are defined and yields - ``(key, value)`` tuples. Optionally a parameter of ignored fields - can be provided. + ``(key, value)`` tuples. Per default all fields are returned, but + it's possible to limit that to some fields by providing the `only` + parameter or to exclude some using the `exclude` parameter. Both + should be sets or tuples of field names. """ for name in self.fields: if (exclude is only is None) or \ diff --git a/tests/test_ext.py b/tests/test_ext.py new file mode 100644 index 0000000..5a487cf --- /dev/null +++ b/tests/test_ext.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +""" + unit test for some extensions + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + :copyright: 2008 by Armin Ronacher. + :license: BSD, see LICENSE for more details. +""" +from jinja2 import Environment + + +def test_loop_controls(): + env = Environment(extensions=['jinja2.ext.loopcontrols']) + + tmpl = env.from_string(''' + {%- for item in [1, 2, 3, 4] %} + {%- if item % 2 == 0 %}{% continue %}{% endif -%} + {{ item }} + {%- endfor %}''') + assert tmpl.render() == '13' + + tmpl = env.from_string(''' + {%- for item in [1, 2, 3, 4] %} + {%- if item > 2 %}{% break %}{% endif -%} + {{ item }} + {%- endfor %}''') + assert tmpl.render() == '12' + + +def test_do(): + env = Environment(extensions=['jinja2.ext.do']) + tmpl = env.from_string(''' + {%- set items = [] %} + {%- for char in "foo" %} + {%- do items.append(loop.index0 ~ char) %} + {%- endfor %}{{ items|join(', ') }}''') + assert tmpl.render() == '0f, 1o, 2o' -- 2.26.2