Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

more unittests and updated documentation for extensions. Fixed bug in…

… optimizer that caused blocks to be optimized away under some circumstances.

--HG--
branch : trunk
  • Loading branch information...
commit 6df604ec215d56fac8ed40d1c2aab715817f2474 1 parent 3da9031
@mitsuhiko authored
View
4 docs/api.rst
@@ -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
View
21 docs/extensions.rst
@@ -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
View
9 jinja2/compiler.py
@@ -678,11 +678,11 @@ def visit_Template(self, node, frame=None):
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 @@ def visit_EnvironmentAttribute(self, node, frame):
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_ImportedName(self, node, frame):
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)
View
6 jinja2/nodes.py
@@ -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 @@ def as_const(self):
return Markup(self.expr.as_const())
+class ContextReference(Expr):
+ """Returns the current template context."""
+
+
class Continue(Stmt):
"""Continue a loop."""
View
4 jinja2/optimizer.py
@@ -34,6 +34,10 @@ def __init__(self, environment):
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:
View
2  jinja2/runtime.py
@@ -163,7 +163,7 @@ def __init__(self, context):
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)))
View
33 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 @@ 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|{}'
View
12 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 @@ 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]'
View
13 tests/test_ifcondition.py
@@ -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'
View
9 tests/test_inheritance.py
@@ -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'
View
16 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 @@ def __repr__(self):
...
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.
+'''
Please sign in to comment.
Something went wrong with that request. Please try again.