]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
Added support for optional `scoped` modifier to blocks.
authorArmin Ronacher <armin.ronacher@active-4.com>
Thu, 19 Feb 2009 14:56:53 +0000 (15:56 +0100)
committerArmin Ronacher <armin.ronacher@active-4.com>
Thu, 19 Feb 2009 14:56:53 +0000 (15:56 +0100)
--HG--
branch : trunk

CHANGES
docs/templates.rst
jinja2/compiler.py
jinja2/environment.py
jinja2/nodes.py
jinja2/parser.py
jinja2/runtime.py
tests/test_inheritance.py

diff --git a/CHANGES b/CHANGES
index c49797baaf669e968419a876e90848a5b08c9a5b..213cd725b6a34d0596263f4644d63f6936b77ccf 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -18,6 +18,7 @@ Version 2.2
 - Fixed a bug that caused internal errors if names where used as iteration
   variable and regular variable *after* the loop if that variable was unused
   *before* the loop.  (#331)
+- Added support for optional `scoped` modifier to blocks.
 
 Version 2.1.1
 -------------
index fe21eefe89179fe3edaa6eae03c88712ac14953e..9fd9692633a24698c9073c980a680f03cb87b9fe 100644 (file)
@@ -378,6 +378,32 @@ readability::
 However the name after the `endblock` word must match the block name.
 
 
+Block Nesting and Scope
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Blocks can be nested for more complex layouts.  However per default blocks
+may not access variables from outer scopes::
+
+    {% for item in seq %}
+        <li>{% block loop_item %}{{ item }}{% endblock %}</li>
+    {% endfor %}
+
+This example would output empty ``<li>`` items because `item` is unavailable
+inside the block.  The reason for this is that if the block is replaced by
+a child template a variable would appear that was not defined in the block or
+passed to the context.
+
+Starting with Jinja 2.2 you can explicitly specify that variables are
+available in a block by setting the block to "scoped" by adding the `scoped`
+modifier to a block declaration::
+
+    {% for item in seq %}
+        <li>{% block loop_item scoped %}{{ item }}{% endblock %}</li>
+    {% endfor %}
+
+    When overriding a block the `scoped` modifier does not have to be provided.
+
+
 HTML Escaping
 -------------
 
index 20ac03b35df5428052ef19f1581e28758c0f0308..6b9c786fa880a898ba956aa62bd1903e77a63ba2 100644 (file)
@@ -783,8 +783,12 @@ class CodeGenerator(NodeVisitor):
                 self.writeline('if parent_template is None:')
                 self.indent()
                 level += 1
-        self.writeline('for event in context.blocks[%r][0](context):' %
-                       node.name, node)
+        if node.scoped:
+            context = 'context.derived(locals())'
+        else:
+            context = 'context'
+        self.writeline('for event in context.blocks[%r][0](%s):' % (
+                       node.name, context), node)
         self.indent()
         self.simple_write('event', frame)
         self.outdent(level)
index 3c53fbd5ae7f36cfcc025eb36d865be7c6d552e2..fcc11d2fba6c7e225f7d448ee5564d7f5572611a 100644 (file)
@@ -15,7 +15,7 @@ from jinja2.lexer import get_lexer, TokenStream
 from jinja2.parser import Parser
 from jinja2.optimizer import optimize
 from jinja2.compiler import generate
-from jinja2.runtime import Undefined, Context
+from jinja2.runtime import Undefined, new_context
 from jinja2.exceptions import TemplateSyntaxError
 from jinja2.utils import import_string, LRUCache, Markup, missing, \
      concat, consume
@@ -646,21 +646,8 @@ class Template(object):
 
         `locals` can be a dict of local variables for internal usage.
         """
-        if vars is None:
-            vars = {}
-        if shared:
-            parent = vars
-        else:
-            parent = dict(self.globals, **vars)
-        if locals:
-            # if the parent is shared a copy should be created because
-            # we don't want to modify the dict passed
-            if shared:
-                parent = dict(parent)
-            for key, value in locals.iteritems():
-                if key[:2] == 'l_' and value is not missing:
-                    parent[key[2:]] = value
-        return Context(self.environment, parent, self.name, self.blocks)
+        return new_context(self.environment, self.name, self.blocks,
+                           vars, shared, self.globals, locals)
 
     def make_module(self, vars=None, shared=False, locals=None):
         """This method works like the :attr:`module` attribute when called
index 6383372bfdb1e3c701ec2b38709baa274707ad43..c7858b6e5b1a5e7ba90d2c251a66de78bbb594d8 100644 (file)
@@ -269,7 +269,7 @@ class FilterBlock(Stmt):
 
 class Block(Stmt):
     """A node that represents a block."""
-    fields = ('name', 'body')
+    fields = ('name', 'body', 'scoped')
 
 
 class Include(Stmt):
index d3eb8c4b8b0e70fbd85bd2041311dd8a47627525..f3de6e708b6bc35a1c2a768c82d83cbad8c8c180 100644 (file)
@@ -149,6 +149,7 @@ class Parser(object):
     def parse_block(self):
         node = nodes.Block(lineno=self.stream.next().lineno)
         node.name = self.stream.expect('name').value
+        node.scoped = self.stream.skip_if('name:scoped')
         node.body = self.parse_statements(('name:endblock',), drop_needle=True)
         self.stream.skip_if('name:' + node.name)
         return node
index 60a9035f2e7cc3df70dcec5f18a884056370e67e..013d98721dcfafadc0c2cfac6c3550de27e15529 100644 (file)
@@ -17,7 +17,7 @@ from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
 
 
 # these variables are exported to the template runtime
-__all__ = ['LoopContext', 'Context', 'TemplateReference', 'Macro', 'Markup',
+__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
            'TemplateRuntimeError', 'missing', 'concat', 'escape',
            'markup_join', 'unicode_join', 'TemplateNotFound']
 
@@ -42,6 +42,45 @@ def unicode_join(seq):
     return concat(imap(unicode, seq))
 
 
+def new_context(environment, template_name, blocks, vars=None,
+                shared=None, globals=None, locals=None):
+    """Internal helper to for context creation."""
+    if vars is None:
+        vars = {}
+    if shared:
+        parent = vars
+    else:
+        parent = dict(globals or (), **vars)
+    if locals:
+        # if the parent is shared a copy should be created because
+        # we don't want to modify the dict passed
+        if shared:
+            parent = dict(parent)
+        for key, value in locals.iteritems():
+            if key[:2] == 'l_' and value is not missing:
+                parent[key[2:]] = value
+    return Context(environment, parent, template_name, blocks)
+
+
+class TemplateReference(object):
+    """The `self` in templates."""
+
+    def __init__(self, context):
+        self.__context = context
+
+    def __getitem__(self, name):
+        blocks = self.__context.blocks[name]
+        wrap = self.__context.environment.autoescape and \
+               Markup or (lambda x: x)
+        return BlockReference(name, self.__context, blocks, 0)
+
+    def __repr__(self):
+        return '<%s %r>' % (
+            self.__class__.__name__,
+            self.__context.name
+        )
+
+
 class Context(object):
     """The template context holds the variables of a template.  It stores the
     values passed to the template and also the names the template exports.
@@ -132,6 +171,11 @@ class Context(object):
                 args = (__self.environment,) + args
         return __obj(*args, **kwargs)
 
+    def derived(self, locals=None):
+        """Internal helper function to create a derived context."""
+        return new_context(self.environment, self.name, self.blocks,
+                           self.parent, True, None, locals)
+
     def _all(meth):
         proxy = lambda self: getattr(self.get_all(), meth)()
         proxy.__doc__ = getattr(dict, meth).__doc__
@@ -174,25 +218,6 @@ except ImportError:
     pass
 
 
-class TemplateReference(object):
-    """The `self` in templates."""
-
-    def __init__(self, context):
-        self.__context = context
-
-    def __getitem__(self, name):
-        blocks = self.__context.blocks[name]
-        wrap = self.__context.environment.autoescape and \
-               Markup or (lambda x: x)
-        return BlockReference(name, self.__context, blocks, 0)
-
-    def __repr__(self):
-        return '<%s %r>' % (
-            self.__class__.__name__,
-            self.__context.name
-        )
-
-
 class BlockReference(object):
     """One block on a template reference."""
 
index ed7b31a2c028dabcb55ae31da94e39958711feab..6a45f5926f24f7747ca0bda57251a056c1f2035b 100644 (file)
@@ -166,3 +166,12 @@ def test_fixed_macro_scoping_bug():
     {% block content %}&nbsp;{% endblock %}
     '''
     })).get_template("test.html").render().split() == [u'outer_box', u'my_macro']
+
+
+def test_scoped_block():
+    env = Environment(loader=DictLoader({
+        'master.html': '{% for item in seq %}[{% block item scoped %}'
+                       '{% endblock %}]{% endfor %}'
+    }))
+    t = env.from_string('{% extends "master.html" %}{% block item %}{{ item }}{% endblock %}')
+    assert t.render(seq=range(5)) == '[0][1][2][3][4]'