Runtime helpers.
- :copyright: Copyright 2008 by Armin Ronacher.
- :license: GNU GPL.
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD.
"""
-from types import FunctionType
-from jinja2.utils import Markup, partial
-from jinja2.exceptions import UndefinedError
+import sys
+from itertools import chain, imap
+from jinja2.nodes import EvalContext, _context_function_types
+from jinja2.utils import Markup, partial, soft_unicode, escape, missing, \
+ concat, internalcode, next, object_type_repr
+from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
+ TemplateNotFound
# these variables are exported to the template runtime
-__all__ = ['LoopContext', 'TemplateContext', 'TemplateReference', 'Macro',
- 'Markup', 'missing', 'concat']
+__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
+ 'TemplateRuntimeError', 'missing', 'concat', 'escape',
+ 'markup_join', 'unicode_join', 'to_string', 'identity',
+ 'TemplateNotFound']
+
+#: the name of the function that is used to convert something into
+#: a string. 2to3 will adopt that automatically and the generated
+#: code can take advantage of it.
+to_string = unicode
+
+#: the identity function. Useful for certain things in the environment
+identity = lambda x: x
+
+
+def markup_join(seq):
+ """Concatenation that escapes if necessary and converts to unicode."""
+ buf = []
+ iterator = imap(soft_unicode, seq)
+ for arg in iterator:
+ buf.append(arg)
+ if hasattr(arg, '__html__'):
+ return Markup(u'').join(chain(buf, iterator))
+ return concat(buf)
+
+
+def unicode_join(seq):
+ """Simple args to unicode conversion and concatenation."""
+ return concat(imap(unicode, seq))
+
+
+def new_context(environment, template_name, blocks, vars=None,
+ shared=None, globals=None, locals=None):
+ """Internal helper to for context creation."""
+ if vars is None:
+ vars = {}
+ if shared:
+ parent = vars
+ else:
+ parent = dict(globals or (), **vars)
+ if locals:
+ # if the parent is shared a copy should be created because
+ # we don't want to modify the dict passed
+ if shared:
+ parent = dict(parent)
+ for key, value in locals.iteritems():
+ if key[:2] == 'l_' and value is not missing:
+ parent[key[2:]] = value
+ return Context(environment, parent, template_name, blocks)
-# special singleton representing missing values for the runtime
-missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
+class TemplateReference(object):
+ """The `self` in templates."""
+ def __init__(self, context):
+ self.__context = context
-# concatenate a list of strings and convert them to unicode.
-concat = u''.join
+ def __getitem__(self, name):
+ blocks = self.__context.blocks[name]
+ wrap = self.__context.eval_ctx.autoescape and \
+ Markup or (lambda x: x)
+ return BlockReference(name, self.__context, blocks, 0)
+ def __repr__(self):
+ return '<%s %r>' % (
+ self.__class__.__name__,
+ self.__context.name
+ )
-class TemplateContext(object):
- """Holds the variables of the local template or of the global one. It's
- not save to use this class outside of the compiled code. For example
- update and other methods will not work as they seem (they don't update
- the exported variables for example).
- The context is immutable. Modifications on `parent` must not happen and
- modifications on `vars` are allowed from generated template code. However
- functions that are passed the template context may not modify the context
- in any way.
+class Context(object):
+ """The template context holds the variables of a template. It stores the
+ values passed to the template and also the names the template exports.
+ Creating instances is neither supported nor useful as it's created
+ automatically at various stages of the template evaluation and should not
+ be created by hand.
+
+ The context is immutable. Modifications on :attr:`parent` **must not**
+ happen and modifications on :attr:`vars` are allowed from generated
+ template code only. Template filters and global functions marked as
+ :func:`contextfunction`\s get the active context passed as first argument
+ and are allowed to access the context read-only.
+
+ The template context supports read only dict operations (`get`,
+ `keys`, `values`, `items`, `iterkeys`, `itervalues`, `iteritems`,
+ `__getitem__`, `__contains__`). Additionally there is a :meth:`resolve`
+ method that doesn't fail with a `KeyError` but returns an
+ :class:`Undefined` object for missing variables.
"""
+ __slots__ = ('parent', 'vars', 'environment', 'eval_ctx', 'exported_vars',
+ 'name', 'blocks', '__weakref__')
def __init__(self, environment, parent, name, blocks):
self.parent = parent
- self.vars = vars = {}
+ self.vars = {}
self.environment = environment
+ self.eval_ctx = EvalContext(self.environment, name)
self.exported_vars = set()
self.name = name
- # bind functions to the context of environment if required
- for name, obj in parent.iteritems():
- if type(obj) is FunctionType:
- if getattr(obj, 'contextfunction', 0):
- vars[name] = partial(obj, self)
- elif getattr(obj, 'environmentfunction', 0):
- vars[name] = partial(obj, environment)
-
# create the initial mapping of blocks. Whenever template inheritance
# takes place the runtime will update this mapping with the new blocks
# from the template.
"""Render a parent block."""
try:
blocks = self.blocks[name]
- pos = blocks.index(current) - 1
- if pos < 0:
- raise IndexError()
+ index = blocks.index(current) + 1
+ blocks[index]
except LookupError:
return self.environment.undefined('there is no parent block '
- 'called %r.' % name)
- render = lambda: Markup(concat(blocks[pos](self)))
- render.__name__ = render.name = name
- return render
+ 'called %r.' % name,
+ name='super')
+ return BlockReference(name, self, blocks, index)
def get(self, key, default=None):
- """For dict compatibility"""
+ """Returns an item from the template context, if it doesn't exist
+ `default` is returned.
+ """
+ try:
+ return self[key]
+ except KeyError:
+ return default
+
+ def resolve(self, key):
+ """Looks up a variable like `__getitem__` or `get` but returns an
+ :class:`Undefined` object with the name of the name looked up.
+ """
if key in self.vars:
return self.vars[key]
if key in self.parent:
return self.parent[key]
- return default
+ return self.environment.undefined(name=key)
def get_exported(self):
"""Get a new dict with the exported variables."""
return dict((k, self.vars[k]) for k in self.exported_vars)
- def get_root(self):
- """Return a new dict with all the non local variables."""
- return dict(self.parent)
-
def get_all(self):
- """Return a copy of the complete context as dict."""
+ """Return a copy of the complete context as dict including the
+ exported variables.
+ """
return dict(self.parent, **self.vars)
- def clone(self):
- """Return a copy of the context without the locals."""
- return self.__class__(self.environment, self.parent,
- self.name, self.blocks)
+ @internalcode
+ def call(__self, __obj, *args, **kwargs):
+ """Call the callable with the arguments and keyword arguments
+ provided but inject the active context or environment as first
+ argument if the callable is a :func:`contextfunction` or
+ :func:`environmentfunction`.
+ """
+ if __debug__:
+ __traceback_hide__ = True
+ if isinstance(__obj, _context_function_types):
+ if getattr(__obj, 'contextfunction', 0):
+ args = (__self,) + args
+ elif getattr(__obj, 'evalcontextfunction', 0):
+ args = (__self.eval_ctx,) + args
+ elif getattr(__obj, 'environmentfunction', 0):
+ args = (__self.environment,) + args
+ return __obj(*args, **kwargs)
+
+ def derived(self, locals=None):
+ """Internal helper function to create a derived context."""
+ context = new_context(self.environment, self.name, {},
+ self.parent, True, None, locals)
+ context.eval_ctx = self.eval_ctx
+ context.blocks.update((k, list(v)) for k, v in self.blocks.iteritems())
+ return context
+
+ def _all(meth):
+ proxy = lambda self: getattr(self.get_all(), meth)()
+ proxy.__doc__ = getattr(dict, meth).__doc__
+ proxy.__name__ = meth
+ return proxy
+
+ keys = _all('keys')
+ values = _all('values')
+ items = _all('items')
+
+ # not available on python 3
+ if hasattr(dict, 'iterkeys'):
+ iterkeys = _all('iterkeys')
+ itervalues = _all('itervalues')
+ iteritems = _all('iteritems')
+ del _all
def __contains__(self, name):
return name in self.vars or name in self.parent
def __getitem__(self, key):
- if key in self.vars:
- return self.vars[key]
- if key in self.parent:
- return self.parent[key]
- return self.environment.undefined(name=key)
+ """Lookup a variable or raise `KeyError` if the variable is
+ undefined.
+ """
+ item = self.resolve(key)
+ if isinstance(item, Undefined):
+ raise KeyError(key)
+ return item
def __repr__(self):
return '<%s %s of %r>' % (
)
-class TemplateReference(object):
- """The `self` in templates."""
+# register the context as mapping if possible
+try:
+ from collections import Mapping
+ Mapping.register(Context)
+except ImportError:
+ pass
- def __init__(self, context):
- self.__context = context
- def __getitem__(self, name):
- func = self.__context.blocks[name][-1]
- render = lambda: Markup(concat(func(self.__context)))
- render.__name__ = render.name = name
- return render
+class BlockReference(object):
+ """One block on a template reference."""
- def __repr__(self):
- return '<%s %r>' % (
- self.__class__.__name__,
- self._context.name
- )
+ def __init__(self, name, context, stack, depth):
+ self.name = name
+ self._context = context
+ self._stack = stack
+ self._depth = depth
+
+ @property
+ def super(self):
+ """Super the block."""
+ if self._depth + 1 >= len(self._stack):
+ return self._context.environment. \
+ undefined('there is no parent block called %r.' %
+ self.name, name='super')
+ return BlockReference(self.name, self._context, self._stack,
+ self._depth + 1)
+
+ @internalcode
+ def __call__(self):
+ rv = concat(self._stack[self._depth](self._context))
+ if self._context.eval_ctx.autoescape:
+ rv = Markup(rv)
+ return rv
class LoopContext(object):
"""A loop context for dynamic iteration."""
- def __init__(self, iterable, enforce_length=False):
- self._iterable = iterable
- self._next = iter(iterable).next
- self._length = None
+ def __init__(self, iterable, recurse=None):
+ self._iterator = iter(iterable)
+ self._recurse = recurse
self.index0 = -1
- if enforce_length:
- len(self)
+
+ # try to get the length of the iterable early. This must be done
+ # here because there are some broken iterators around where there
+ # __len__ is the number of iterations left (i'm looking at your
+ # listreverseiterator!).
+ try:
+ self._length = len(iterable)
+ except (TypeError, AttributeError):
+ self._length = None
def cycle(self, *args):
- """A replacement for the old ``{% cycle %}`` tag."""
+ """Cycles among the arguments with the current loop index."""
if not args:
raise TypeError('no items for cycling given')
return args[self.index0 % len(args)]
first = property(lambda x: x.index0 == 0)
- last = property(lambda x: x.revindex0 == 0)
+ last = property(lambda x: x.index0 + 1 == x.length)
index = property(lambda x: x.index0 + 1)
revindex = property(lambda x: x.length - x.index0)
revindex0 = property(lambda x: x.length - x.index)
return self.length
def __iter__(self):
- return self
+ return LoopContextIterator(self)
- def next(self):
- self.index0 += 1
- return self._next(), self
+ @internalcode
+ def loop(self, iterable):
+ if self._recurse is None:
+ raise TypeError('Tried to call non recursive loop. Maybe you '
+ "forgot the 'recursive' modifier.")
+ return self._recurse(iterable, self._recurse)
+
+ # a nifty trick to enhance the error message if someone tried to call
+ # the the loop without or with too many arguments.
+ __call__ = loop
+ del loop
@property
def length(self):
if self._length is None:
- try:
- length = len(self._iterable)
- except TypeError:
- self._iterable = tuple(self._iterable)
- self._next = iter(self._iterable).next
- length = len(tuple(self._iterable)) + self.index0 + 1
- self._length = length
+ # if was not possible to get the length of the iterator when
+ # the loop context was created (ie: iterating over a generator)
+ # we have to convert the iterable into a sequence and use the
+ # length of that.
+ iterable = tuple(self._iterator)
+ self._iterator = iter(iterable)
+ self._length = len(iterable) + self.index0 + 1
return self._length
def __repr__(self):
)
+class LoopContextIterator(object):
+ """The iterator for a loop context."""
+ __slots__ = ('context',)
+
+ def __init__(self, context):
+ self.context = context
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ ctx = self.context
+ ctx.index0 += 1
+ return next(ctx._iterator), ctx
+
+
class Macro(object):
"""Wraps a macro."""
catch_kwargs, catch_varargs, caller):
self._environment = environment
self._func = func
+ self._argument_count = len(arguments)
self.name = name
self.arguments = arguments
- self.argument_count = len(arguments)
self.defaults = defaults
self.catch_kwargs = catch_kwargs
self.catch_varargs = catch_varargs
self.caller = caller
+ @internalcode
def __call__(self, *args, **kwargs):
- self.argument_count = len(self.arguments)
- if not self.catch_varargs and len(args) > self.argument_count:
- raise TypeError('macro %r takes not more than %d argument(s)' %
- (self.name, len(self.arguments)))
arguments = []
for idx, name in enumerate(self.arguments):
try:
value = args[idx]
- except IndexError:
+ except:
try:
value = kwargs.pop(name)
- except KeyError:
+ except:
try:
- value = self.defaults[idx - self.argument_count]
- except IndexError:
+ value = self.defaults[idx - self._argument_count]
+ except:
value = self._environment.undefined(
- 'parameter %r was not provided' % name)
+ 'parameter %r was not provided' % name, name=name)
arguments.append(value)
# it's important that the order of these arguments does not change
if self.caller:
caller = kwargs.pop('caller', None)
if caller is None:
- caller = self._environment.undefined('No caller defined')
+ caller = self._environment.undefined('No caller defined',
+ name='caller')
arguments.append(caller)
if self.catch_kwargs:
arguments.append(kwargs)
elif kwargs:
raise TypeError('macro %r takes no keyword argument %r' %
- (self.name, iter(kwargs).next()))
+ (self.name, next(iter(kwargs))))
if self.catch_varargs:
- arguments.append(args[self.argument_count:])
+ arguments.append(args[self._argument_count:])
+ elif len(args) > self._argument_count:
+ raise TypeError('macro %r takes not more than %d argument(s)' %
+ (self.name, len(self.arguments)))
return self._func(*arguments)
def __repr__(self):
)
-def fail_with_undefined_error(self, *args, **kwargs):
- """Regular callback function for undefined objects that raises an
- `UndefinedError` on call.
- """
- if self._undefined_hint is None:
- if self._undefined_obj is None:
- hint = '%r is undefined' % self._undefined_name
- elif not isinstance(self._undefined_name, basestring):
- hint = '%r object has no element %r' % (
- self._undefined_obj.__class__.__name__,
- self._undefined_name
- )
- else:
- hint = '%r object has no attribute %r' % (
- self._undefined_obj.__class__.__name__,
- self._undefined_name
- )
- else:
- hint = self._undefined_hint
- raise UndefinedError(hint)
-
-
class Undefined(object):
- """The default undefined implementation. This undefined implementation
- can be printed and iterated over, but every other access will raise a
- `NameError`. Custom undefined classes must subclass this.
+ """The default undefined type. This undefined type can be printed and
+ iterated over, but every other access will raise an :exc:`UndefinedError`:
+
+ >>> foo = Undefined(name='foo')
+ >>> str(foo)
+ ''
+ >>> not foo
+ True
+ >>> foo + 42
+ Traceback (most recent call last):
+ ...
+ UndefinedError: 'foo' is undefined
"""
- __slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name')
+ __slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name',
+ '_undefined_exception')
- def __init__(self, hint=None, obj=None, name=None):
+ def __init__(self, hint=None, obj=missing, name=None, exc=UndefinedError):
self._undefined_hint = hint
self._undefined_obj = obj
self._undefined_name = name
+ self._undefined_exception = exc
+
+ @internalcode
+ def _fail_with_undefined_error(self, *args, **kwargs):
+ """Regular callback function for undefined objects that raises an
+ `UndefinedError` on call.
+ """
+ if self._undefined_hint is None:
+ if self._undefined_obj is missing:
+ hint = '%r is undefined' % self._undefined_name
+ elif not isinstance(self._undefined_name, basestring):
+ hint = '%s has no element %r' % (
+ object_type_repr(self._undefined_obj),
+ self._undefined_name
+ )
+ else:
+ hint = '%r has no attribute %r' % (
+ object_type_repr(self._undefined_obj),
+ self._undefined_name
+ )
+ else:
+ hint = self._undefined_hint
+ raise self._undefined_exception(hint)
__add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
- __realdiv__ = __rrealdiv__ = __floordiv__ = __rfloordiv__ = \
+ __truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \
__mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
__getattr__ = __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = \
- fail_with_undefined_error
+ __int__ = __float__ = __complex__ = __pow__ = __rpow__ = \
+ _fail_with_undefined_error
def __str__(self):
- return self.__unicode__().encode('utf-8')
-
- def __repr__(self):
- return 'Undefined'
+ return unicode(self).encode('utf-8')
+ # unicode goes after __str__ because we configured 2to3 to rename
+ # __unicode__ to __str__. because the 2to3 tree is not designed to
+ # remove nodes from it, we leave the above __str__ around and let
+ # it override at runtime.
def __unicode__(self):
return u''
def __nonzero__(self):
return False
+ def __repr__(self):
+ return 'Undefined'
+
class DebugUndefined(Undefined):
- """An undefined that returns the debug info when printed."""
+ """An undefined that returns the debug info when printed.
+
+ >>> foo = DebugUndefined(name='foo')
+ >>> str(foo)
+ '{{ foo }}'
+ >>> not foo
+ True
+ >>> foo + 42
+ Traceback (most recent call last):
+ ...
+ UndefinedError: 'foo' is undefined
+ """
__slots__ = ()
def __unicode__(self):
if self._undefined_hint is None:
- if self._undefined_obj is None:
+ if self._undefined_obj is missing:
return u'{{ %s }}' % self._undefined_name
return '{{ no such element: %s[%r] }}' % (
- self._undefined_obj.__class__.__name__,
+ object_type_repr(self._undefined_obj),
self._undefined_name
)
return u'{{ undefined value printed: %s }}' % self._undefined_hint
"""An undefined that barks on print and iteration as well as boolean
tests and all kinds of comparisons. In other words: you can do nothing
with it except checking if it's defined using the `defined` test.
+
+ >>> foo = StrictUndefined(name='foo')
+ >>> str(foo)
+ Traceback (most recent call last):
+ ...
+ UndefinedError: 'foo' is undefined
+ >>> not foo
+ Traceback (most recent call last):
+ ...
+ UndefinedError: 'foo' is undefined
+ >>> foo + 42
+ Traceback (most recent call last):
+ ...
+ UndefinedError: 'foo' is undefined
"""
__slots__ = ()
- __iter__ = __unicode__ = __len__ = __nonzero__ = __eq__ = __ne__ = \
- fail_with_undefined_error
+ __iter__ = __unicode__ = __str__ = __len__ = __nonzero__ = __eq__ = \
+ __ne__ = __bool__ = Undefined._fail_with_undefined_error
# remove remaining slots attributes, after the metaclass did the magic they