more unittests and updated documentation for extensions.  Fixed bug in optimizer that caused blocks to be optimized away under some circumstances.

--HG--
branch : trunk
diff --git a/docs/api.rst b/docs/api.rst
index 5b9e5ad..d520b40 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -109,7 +109,7 @@
     .. automethod:: stream([context])
 
 
-.. autoclass:: jinja2.environment.TemplateStream
+.. autoclass:: jinja2.environment.TemplateStream()
     :members: disable_buffering, enable_buffering
 
 
@@ -154,7 +154,7 @@
 The Context
 -----------
 
-.. autoclass:: jinja2.runtime.Context
+.. autoclass:: jinja2.runtime.Context()
     :members: resolve, get_exported, get_all
 
     .. attribute:: parent
diff --git a/docs/extensions.rst b/docs/extensions.rst
index d499192..8ea554a 100644
--- a/docs/extensions.rst
+++ b/docs/extensions.rst
@@ -92,19 +92,19 @@
 .. _Babel: http://babel.edgewall.org/
 
 
-do
-~~
+Expression Statement
+--------------------
 
 **Import name:** `jinja2.ext.do`
 
-The do aka expression-statement extension adds a simple `do` tag to the
+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
-~~~~~~~~~~~~
+Loop Controls
+-------------
 
 **Import name:** `jinja2.ext.loopcontrols`
 
@@ -149,6 +149,17 @@
     env = Environment(extensions=[FragmentCacheExtension])
     env.fragment_cache = SimpleCache()
 
+Inside the template it's then possible to mark blocks as cacheable.  The
+following example caches a sidebar for 300 seconds:
+
+.. sourcecode:: html+jinja
+
+    {% cache 'sidebar', 300 %}
+    <div class="sidebar">
+        ...
+    </div>
+    {% endcache %}
+
 .. _Werkzeug: http://werkzeug.pocoo.org/
 
 Extension API
diff --git a/jinja2/compiler.py b/jinja2/compiler.py
index 3b5f8c0..6dcaf08 100644
--- a/jinja2/compiler.py
+++ b/jinja2/compiler.py
@@ -678,11 +678,11 @@
         self.indent()
         if have_extends:
             self.writeline('parent_template = None')
-        self.pull_locals(frame)
-        self.pull_dependencies(node.body)
         if 'self' in find_undeclared(node.body, ('self',)):
             frame.identifiers.add_special('self')
             self.writeline('l_self = TemplateReference(context)')
+        self.pull_locals(frame)
+        self.pull_dependencies(node.body)
         self.blockvisit(node.body, frame)
         self.outdent()
 
@@ -1364,7 +1364,7 @@
         self.write('environment.' + node.name)
 
     def visit_ExtensionAttribute(self, node, frame):
-        self.write('environment.extensions[%r].%s' % (node.identifier, node.attr))
+        self.write('environment.extensions[%r].%s' % (node.identifier, node.name))
 
     def visit_ImportedName(self, node, frame):
         self.write(self.import_aliases[node.importname])
@@ -1372,6 +1372,9 @@
     def visit_InternalName(self, node, frame):
         self.write(node.name)
 
+    def visit_ContextReference(self, node, frame):
+        self.write('context')
+
     def visit_Continue(self, node, frame):
         self.writeline('continue', node)
 
diff --git a/jinja2/nodes.py b/jinja2/nodes.py
index ad39903..12fdc34 100644
--- a/jinja2/nodes.py
+++ b/jinja2/nodes.py
@@ -767,7 +767,7 @@
     This node is usually constructed by calling the
     :meth:`~jinja2.ext.Extension.attr` method on an extension.
     """
-    fields = ('identifier', 'attr')
+    fields = ('identifier', 'name')
 
 
 class ImportedName(Expr):
@@ -801,6 +801,10 @@
         return Markup(self.expr.as_const())
 
 
+class ContextReference(Expr):
+    """Returns the current template context."""
+
+
 class Continue(Stmt):
     """Continue a loop."""
 
diff --git a/jinja2/optimizer.py b/jinja2/optimizer.py
index 5b95c99..8f92e38 100644
--- a/jinja2/optimizer.py
+++ b/jinja2/optimizer.py
@@ -34,6 +34,10 @@
 
     def visit_If(self, node):
         """Eliminate dead code."""
+        # do not optimize ifs that have a block inside so that it doesn't
+        # break super().
+        if node.find(nodes.Block) is not None:
+            return self.generic_visit(node)
         try:
             val = self.visit(node.test).as_const()
         except nodes.Impossible:
diff --git a/jinja2/runtime.py b/jinja2/runtime.py
index 76eae80..fb72ed4 100644
--- a/jinja2/runtime.py
+++ b/jinja2/runtime.py
@@ -163,7 +163,7 @@
         self.__context = context
 
     def __getitem__(self, name):
-        func = self.__context.blocks[name][-1]
+        func = self.__context.blocks[name][0]
         wrap = self.__context.environment.autoescape and \
                Markup or (lambda x: x)
         render = lambda: wrap(concat(func(self.__context)))
diff --git a/tests/test_ext.py b/tests/test_ext.py
index 5a487cf..f91b0f7 100644
--- a/tests/test_ext.py
+++ b/tests/test_ext.py
@@ -6,7 +6,32 @@
     :copyright: 2008 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
-from jinja2 import Environment
+from jinja2 import Environment, nodes
+from jinja2.ext import Extension
+
+
+importable_object = 23
+
+
+class TestExtension(Extension):
+    tags = set(['test'])
+    ext_attr = 42
+
+    def parse(self, parser):
+        return nodes.Output([self.call_method('_dump', [
+            nodes.EnvironmentAttribute('sandboxed'),
+            self.attr('ext_attr'),
+            nodes.ImportedName(__name__ + '.importable_object'),
+            nodes.ContextReference()
+        ])]).set_lineno(parser.stream.next().lineno)
+
+    def _dump(self, sandboxed, ext_attr, imported_object, context):
+        return '%s|%s|%s|%s' % (
+            sandboxed,
+            ext_attr,
+            imported_object,
+            context.blocks
+        )
 
 
 def test_loop_controls():
@@ -35,3 +60,9 @@
             {%- do items.append(loop.index0 ~ char) %}
         {%- endfor %}{{ items|join(', ') }}''')
     assert tmpl.render() == '0f, 1o, 2o'
+
+
+def test_extension_nodes():
+    env = Environment(extensions=[TestExtension])
+    tmpl = env.from_string('{% test %}')
+    assert tmpl.render() == 'False|42|23|{}'
diff --git a/tests/test_forloop.py b/tests/test_forloop.py
index 5c0288d..67fb39e 100644
--- a/tests/test_forloop.py
+++ b/tests/test_forloop.py
@@ -35,6 +35,11 @@
 {% for item in [1] if loop.index == 0 %}...{% endfor %}'''
 LOOPERROR2 = '''\
 {% for item in [] %}...{% else %}{{ loop }}{% endfor %}'''
+LOOPFILTER = '''\
+{% for item in range(10) if item is even %}[{{ item }}]{% endfor %}'''
+EXTENDEDLOOPFILTER = '''\
+{% for item in range(10) if item is even %}[{{ loop.index
+}}:{{ item }}]{% endfor %}'''
 
 
 def test_simple(env):
@@ -114,3 +119,10 @@
     raises(UndefinedError, tmpl.render)
     tmpl = env.from_string(LOOPERROR2)
     assert tmpl.render() == ''
+
+
+def test_loop_filter(env):
+    tmpl = env.from_string(LOOPFILTER)
+    assert tmpl.render() == '[0][2][4][6][8]'
+    tmpl = env.from_string(EXTENDEDLOOPFILTER)
+    assert tmpl.render() == '[1:0][2:2][3:4][4:6][5:8]'
diff --git a/tests/test_ifcondition.py b/tests/test_ifcondition.py
index 12add62..ec1fff4 100644
--- a/tests/test_ifcondition.py
+++ b/tests/test_ifcondition.py
@@ -31,3 +31,16 @@
 def test_empty(env):
     tmpl = env.from_string(EMPTY)
     assert tmpl.render() == '[]'
+
+
+def test_complete(env):
+    tmpl = env.from_string('{% if a %}A{% elif b %}B{% elif c == d %}'
+                           'C{% else %}D{% endif %}')
+    assert tmpl.render(a=0, b=False, c=42, d=42.0) == 'C'
+
+
+def test_no_scope(env):
+    tmpl = env.from_string('{% if a %}{% set foo = 1 %}{% endif %}{{ foo }}')
+    assert tmpl.render(a=True) == '1'
+    tmpl = env.from_string('{% if true %}{% set foo = 1 %}{% endif %}{{ foo }}')
+    assert tmpl.render() == '1'
diff --git a/tests/test_inheritance.py b/tests/test_inheritance.py
index 114ec9c..ee8296a 100644
--- a/tests/test_inheritance.py
+++ b/tests/test_inheritance.py
@@ -98,3 +98,12 @@
 def test_reuse_blocks(env):
     tmpl = env.from_string('{{ self.foo() }}|{% block foo %}42{% endblock %}|{{ self.foo() }}')
     assert tmpl.render() == '42|42|42'
+
+
+def test_preserve_blocks():
+    env = Environment(loader=DictLoader({
+        'a': '{% if false %}{% block x %}A{% endblock %}{% endif %}{{ self.x() }}',
+        'b': '{% extends "a" %}{% block x %}B{{ super() }}{% endblock %}'
+    }))
+    tmpl = env.get_template('b')
+    assert tmpl.render() == 'BA'
diff --git a/tests/test_security.py b/tests/test_security.py
index 6813656..5974e1f 100644
--- a/tests/test_security.py
+++ b/tests/test_security.py
@@ -6,7 +6,8 @@
     :copyright: 2007 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
-from jinja2.sandbox import SandboxedEnvironment, unsafe
+from jinja2.sandbox import SandboxedEnvironment, \
+     ImmutableSandboxedEnvironment, unsafe
 
 
 class PrivateStuff(object):
@@ -68,3 +69,16 @@
     ...
 TemplateSyntaxError: expected token 'in', got '.' (line 1)
 '''
+
+
+test_immutable_environment = '''
+>>> env = MODULE.ImmutableSandboxedEnvironment()
+>>> env.from_string('{{ [].append(23) }}').render()
+Traceback (most recent call last):
+    ...
+SecurityError: access to attribute 'append' of 'list' object is unsafe.
+>>> env.from_string('{{ {1:2}.clear() }}').render()
+Traceback (most recent call last):
+    ...
+SecurityError: access to attribute 'clear' of 'dict' object is unsafe.
+'''