more unittests and updated documentation for extensions. Fixed bug in optimizer...
authorArmin Ronacher <armin.ronacher@active-4.com>
Fri, 23 May 2008 20:18:38 +0000 (22:18 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Fri, 23 May 2008 20:18:38 +0000 (22:18 +0200)
--HG--
branch : trunk

docs/api.rst
docs/extensions.rst
jinja2/compiler.py
jinja2/nodes.py
jinja2/optimizer.py
jinja2/runtime.py
tests/test_ext.py
tests/test_forloop.py
tests/test_ifcondition.py
tests/test_inheritance.py
tests/test_security.py

index 5b9e5aded74a3b07df797a304ffd0844d9580a7b..d520b40027384f51ea23fddc5a7fb2169916f54e 100644 (file)
@@ -109,7 +109,7 @@ High Level API
     .. automethod:: stream([context])
 
 
-.. autoclass:: jinja2.environment.TemplateStream
+.. autoclass:: jinja2.environment.TemplateStream()
     :members: disable_buffering, enable_buffering
 
 
@@ -154,7 +154,7 @@ disallows all operations beside testing if it's an undefined object.
 The Context
 -----------
 
-.. autoclass:: jinja2.runtime.Context
+.. autoclass:: jinja2.runtime.Context()
     :members: resolve, get_exported, get_all
 
     .. attribute:: parent
index d499192bdfd0e784912c8b02f4b06207313cda23..8ea554a6e559546c169c3a4c20ac0e282e01a47a 100644 (file)
@@ -92,19 +92,19 @@ The usage of the `i18n` extension for template designers is covered as part
 .. _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 @@ And here is how you use it in an environment::
     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
index 3b5f8c03c0315b0b59ee39be97016f6fd910f803..6dcaf083d1772f23ac6ee93caa9d3a60c5d31ba0 100644 (file)
@@ -678,11 +678,11 @@ class CodeGenerator(NodeVisitor):
         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 @@ class CodeGenerator(NodeVisitor):
         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 @@ class CodeGenerator(NodeVisitor):
     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)
 
index ad39903acec5b21da17c59ad6033b08084725bad..12fdc348ad581b07409067a5aa5f76f1ebe8ce21 100644 (file)
@@ -767,7 +767,7 @@ class ExtensionAttribute(Expr):
     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 @@ class MarkSafe(Expr):
         return Markup(self.expr.as_const())
 
 
+class ContextReference(Expr):
+    """Returns the current template context."""
+
+
 class Continue(Stmt):
     """Continue a loop."""
 
index 5b95c99a4027db427ec40df499a0ac0395c73993..8f92e38952a6ff4e8f271927b0509ddcd87c1c56 100644 (file)
@@ -34,6 +34,10 @@ class Optimizer(NodeTransformer):
 
     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:
index 76eae80267598aaae37d35f840a7eb490a12db81..fb72ed4ec38fbad67df360857e6878983d2c6e9c 100644 (file)
@@ -163,7 +163,7 @@ class TemplateReference(object):
         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)))
index 5a487cf6b09295deec6dfd805a74ac616f3251d9..f91b0f700b2843062237c394cd088bd84a02fd6c 100644 (file)
@@ -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 @@ def test_do():
             {%- 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|{}'
index 5c0288d0ac4b06a13328919846f721b7016f9b37..67fb39e5809a4d62b67e00cba149a645d8445b91 100644 (file)
@@ -35,6 +35,11 @@ LOOPERROR1 = '''\
 {% 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 @@ def test_loop_errors(env):
     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]'
index 12add62d0022dfd6539d834f80a8b1c4fd196520..ec1fff4b2c2b945d8463752d8f506473367b8496 100644 (file)
@@ -31,3 +31,16 @@ def test_else(env):
 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'
index 114ec9c431c19231b74e9c730a5e596ef1dd36b9..ee8296ab0046c3f9734b3bce2b50fba452955fd2 100644 (file)
@@ -98,3 +98,12 @@ def test_working(env):
 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'
index 6813656fd856c15d5725e5ceb5cdc09642de134b..5974e1f6813108972ed499828e6012ba8d6086a0 100644 (file)
@@ -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 @@ Traceback (most recent call last):
     ...
 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.
+'''