From: Armin Ronacher Date: Fri, 23 May 2008 20:18:38 +0000 (+0200) Subject: more unittests and updated documentation for extensions. Fixed bug in optimizer... X-Git-Tag: 2.0rc1~32 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=6df604ec215d56fac8ed40d1c2aab715817f2474;p=jinja2.git 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 @@ 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 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 @@ 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 %} + + {% 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 @@ 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) 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 @@ 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.""" 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 @@ 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: 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 @@ 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))) 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 @@ 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|{}' 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 @@ 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]' 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_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' 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_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' 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 @@ 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. +'''