added loopcontrols extension and added unittests for it
authorArmin Ronacher <armin.ronacher@active-4.com>
Fri, 23 May 2008 14:37:28 +0000 (16:37 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Fri, 23 May 2008 14:37:28 +0000 (16:37 +0200)
--HG--
branch : trunk

docs/extensions.rst
docs/templates.rst
jinja2/compiler.py
jinja2/ext.py
jinja2/nodes.py
tests/test_ext.py [new file with mode: 0644]

index 65f0082b8a19134591f7446898f8ef17b9524fc4..d499192bdfd0e784912c8b02f4b06207313cda23 100644 (file)
@@ -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:
 
index 1c1b8a7bf846236737a93906a99966a9d67dbe20..a95b87472801ea8e37c5ebdd0696cedc753008a1 100644 (file)
@@ -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 %}
index 9bf00445b94290a13097d5f58eb897ab22af9849..3b5f8c03c0315b0b59ee39be97016f6fd910f803 100644 (file)
@@ -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(')')
 
index a489f97cc6ce0761385670b0ccb54cbf9a797008..63e8f5b0cbca543f1f10010d7c35b0b8d30611c3 100644 (file)
@@ -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
index 27c9ddbcd2e6a6862bc9fe3812b58a75d1be18a6..ad39903acec5b21da17c59ad6033b08084725bad 100644 (file)
@@ -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 (file)
index 0000000..5a487cf
--- /dev/null
@@ -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'