from jinja2 import nodes
from jinja2.visitor import NodeVisitor, NodeTransformer
from jinja2.exceptions import TemplateAssertionError
+from jinja2.runtime import StaticLoopContext
operators = {
return generator.stream.getvalue()
+def has_safe_repr(value):
+ """Does the node have a safe representation?"""
+ if value is None:
+ return True
+ if isinstance(value, (int, long, float, basestring, StaticLoopContext)):
+ return True
+ if isinstance(value, (tuple, list)):
+ for item in value:
+ if not has_safe_repr(item):
+ return False
+ return True
+ elif isinstance(value, dict):
+ for key, value in value.iteritems():
+ if not has_safe_repr(key):
+ return False
+ if not has_safe_repr(value):
+ return False
+ return True
+ return False
+
+
class Identifiers(object):
"""Tracks the status of identifiers in frames."""
self.writeline('from jinja2.runtime import *')
self.writeline('filename = %r' % self.filename)
self.writeline('template_context = TemplateContext(global_context, '
- 'make_undefined, filename)')
+ 'filename)')
# generate the root render function.
self.writeline('def root(context=template_context):', extra=1)
for arg in node.defaults:
self.visit(arg)
self.write(', ')
- self.write('), %r, make_undefined)' % accesses_arguments)
+ self.write('), %r)' % accesses_arguments)
def visit_ExprStmt(self, node, frame):
self.newline(node)
self.write(repr(const))
else:
self.visit(node.arg, frame)
- self.write(', make_undefined)')
+ self.write(')')
def visit_Slice(self, node, frame):
if node.start is not None:
"""
from jinja2.lexer import Lexer
from jinja2.parser import Parser
+from jinja2.runtime import Undefined
from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
self.tests = DEFAULT_TESTS.copy()
self.globals = DEFAULT_NAMESPACE.copy()
+ # the factory that creates the undefined object
+ self.undefined_factory = Undefined
+
# create lexer
self.lexer = Lexer(self)
from itertools import chain, izip
from collections import deque
from copy import copy
+from jinja2.runtime import Undefined, subscribe
_binop_to_func = {
def as_const(self):
return self.value
+ @classmethod
+ def from_untrusted(cls, value, lineno=None, silent=False):
+ """Return a const object if the value is representable as
+ constant value in the generated code, otherwise it will raise
+ an `Impossible` exception."""
+ from compiler import has_safe_repr
+ if not has_safe_repr(value):
+ if silent:
+ return
+ raise Impossible()
+ return cls(value, lineno=lineno)
+
class Tuple(Literal):
"""For loop unpacking and some other things like multiple arguments
"""{{ foo(bar) }}"""
fields = ('node', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs')
+ def as_const(self):
+ obj = self.node.as_const()
+ args = [x.as_const() for x in self.args]
+ kwargs = dict(x.as_const() for x in self.kwargs)
+ if self.dyn_args is not None:
+ try:
+ args.extend(self.dyn_args.as_const())
+ except:
+ raise Impossible()
+ if self.dyn_kwargs is not None:
+ try:
+ dyn_kwargs.update(self.dyn_kwargs.as_const())
+ except:
+ raise Impossible()
+ try:
+ return obj(*args, **kwargs)
+ except:
+ raise nodes.Impossible()
+
class Subscript(Expr):
"""{{ foo.bar }} and {{ foo['bar'] }} etc."""
fields = ('node', 'arg', 'ctx')
def as_const(self):
+ if self.ctx != 'load':
+ raise Impossible()
try:
- return self.node.as_const()[self.node.as_const()]
+ return subscribe(self.node.as_const(), self.arg.as_const())
except:
raise Impossible()
"""1:2:3 etc."""
fields = ('start', 'stop', 'step')
+ def as_const(self):
+ def const(obj):
+ if obj is None:
+ return obj
+ return obj.as_const()
+ return slice(const(self.start), const(self.stop), const(self.step))
+
class Concat(Expr):
"""For {{ foo ~ bar }}. Concatenates strings."""
# XXX: call filters with arguments
x = self.environment.filters[filter.name](self.environment, x)
# XXX: don't optimize context dependent filters
- return nodes.Const(x)
+ try:
+ return nodes.Const.from_untrusted(x, lineno=node.lineno)
+ except nodes.Impossible:
+ return self.generic_visit(node)
def visit_For(self, node, context):
"""Loop unrolling for iterable constant values."""
def visit_Name(self, node, context):
if node.ctx == 'load':
try:
- return nodes.Const(context[node.name], lineno=node.lineno)
- except KeyError:
+ return nodes.Const.from_untrusted(context[node.name],
+ lineno=node.lineno)
+ except (KeyError, nodes.Impossible):
pass
return node
lineno = node.lineno
def walk(target, value):
if isinstance(target, nodes.Name):
- const_value = nodes.Const(value, lineno=lineno)
- result.append(nodes.Assign(target, const_value, lineno=lineno))
+ const = nodes.Const.from_untrusted(value, lineno=lineno)
+ result.append(nodes.Assign(target, const, lineno=lineno))
context[target.name] = value
elif isinstance(target, nodes.Tuple):
try:
"""Do constant folding."""
node = self.generic_visit(node, context)
try:
- return nodes.Const(node.as_const(), lineno=node.lineno)
+ return nodes.Const.from_untrusted(node.as_const(),
+ lineno=node.lineno)
except nodes.Impossible:
return node
visit_Add = visit_Sub = visit_Mul = visit_Div = visit_FloorDiv = \
visit_Pow = visit_Mod = visit_And = visit_Or = visit_Pos = visit_Neg = \
- visit_Not = visit_Compare = fold
-
- def visit_Subscript(self, node, context):
- if node.ctx == 'load':
- try:
- item = self.visit(node.node, context).as_const()
- arg = self.visit(node.arg, context).as_const()
- except nodes.Impossible:
- return self.generic_visit(node, context)
- return nodes.Const(subscribe(item, arg, 'load'))
- return self.generic_visit(node, context)
+ visit_Not = visit_Compare = visit_Subscribt = visit_Call = fold
+ del fold
def optimize(node, environment, context_hint=None):
defaultdict = None
-# contains only the variables the template will import automatically, not the
-# objects injected by the evaluation loop (such as undefined objects)
__all__ = ['extends', 'subscribe', 'LoopContext', 'StaticLoopContext',
- 'TemplateContext', 'Macro']
+ 'TemplateContext', 'Macro', 'Undefined']
def extends(template, namespace):
"""This loads a template (and evaluates it) and replaces the blocks."""
-def subscribe(obj, argument, undefined_factory):
+def subscribe(obj, argument):
"""Get an item or attribute of an object."""
try:
return getattr(obj, str(argument))
try:
return obj[argument]
except LookupError:
- return undefined_factory(attr=argument)
+ return Undefined(obj, argument)
class TemplateContext(dict):
the exported variables for example).
"""
- def __init__(self, globals, undefined_factory, filename):
+ def __init__(self, globals, filename):
dict.__init__(self, globals)
self.exported = set()
- self.undefined_factory = undefined_factory
self.filename = filename
self.filters = {}
self.tests = {}
def __getitem__(self, name):
if name in self:
return self[name]
- return self.undefined_factory(name)
+ return Undefined(name)
else:
def __missing__(self, key):
- return self.undefined_factory(key)
+ return Undefined(key)
class LoopContextBase(object):
class StaticLoopContext(LoopContextBase):
+ """The static loop context is used in the optimizer to "freeze" the
+ status of an iteration. The only reason for this object is if the
+ loop object is accessed in a non static way (eg: becomes part of a
+ function call)."""
def __init__(self, index0, length, parent):
self.index0 = index0
self._length = length
def __repr__(self):
+ """The repr is used by the optimizer to dump the object."""
return 'StaticLoopContext(%r, %r, %r)' % (
self.index0,
self._length,
Wraps a macor
"""
- def __init__(self, func, name, arguments, defaults, catch_all, \
- undefined_factory):
+ def __init__(self, func, name, arguments, defaults, catch_all):
self.func = func
self.name = name
self.arguments = arguments
self.defaults = defaults
self.catch_all = catch_all
- self.undefined_factory = undefined_factory
def __call__(self, *args, **kwargs):
arg_count = len(self.arguments)
try:
value = self.defaults[idx - arg_count]
except IndexError:
- value = self.undefined_factory(name)
+ value = Undefined(name)
arguments['l_' + name] = arg
if self.catch_all:
arguments['l_arguments'] = kwargs
class Undefined(object):
- """The default undefined behavior."""
+ """The object for undefined values."""
def __init__(self, name=None, attr=None):
if attr is None:
{% for key, value in navigation %}
<li>{{ test(loop) }}: <a href="{{ key|e }}">{{ value|e }}</a></li>
{% endfor %}
+ {{ "Hello World".upper() }}
</ul>
""")
print ast