Added support for optional `scoped` modifier to blocks.

--HG--
branch : trunk
diff --git a/CHANGES b/CHANGES
index c49797b..213cd72 100644
--- a/CHANGES
+++ b/CHANGES
@@ -18,6 +18,7 @@
 - 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
 -------------
diff --git a/docs/templates.rst b/docs/templates.rst
index fe21eef..9fd9692 100644
--- a/docs/templates.rst
+++ b/docs/templates.rst
@@ -378,6 +378,32 @@
 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
 -------------
 
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index 20ac03b..6b9c786 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -783,8 +783,12 @@
                 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)
diff --git a/jinja2/environment.py b/jinja2/environment.py
index 3c53fbd..fcc11d2 100644
--- a/jinja2/environment.py
+++ b/jinja2/environment.py
@@ -15,7 +15,7 @@
 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 @@
 
         `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
diff --git a/jinja2/nodes.py b/jinja2/nodes.py
index 6383372..c7858b6 100644
--- a/jinja2/nodes.py
+++ b/jinja2/nodes.py
@@ -269,7 +269,7 @@
 
 class Block(Stmt):
     """A node that represents a block."""
-    fields = ('name', 'body')
+    fields = ('name', 'body', 'scoped')
 
 
 class Include(Stmt):
diff --git a/jinja2/parser.py b/jinja2/parser.py
index d3eb8c4..f3de6e7 100644
--- a/jinja2/parser.py
+++ b/jinja2/parser.py
@@ -149,6 +149,7 @@
     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
diff --git a/jinja2/runtime.py b/jinja2/runtime.py
index 60a9035..013d987 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -17,7 +17,7 @@
 
 
 # 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 @@
     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 @@
                 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 @@
     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."""
 
diff --git a/tests/test_inheritance.py b/tests/test_inheritance.py
index ed7b31a..6a45f59 100644
--- a/tests/test_inheritance.py
+++ b/tests/test_inheritance.py
@@ -166,3 +166,12 @@
     {% 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]'