Runtime helpers.
- :copyright: Copyright 2008 by Armin Ronacher.
- :license: GNU GPL.
+ :copyright: (c) 2010 by the Jinja Team.
+ :license: BSD.
"""
import sys
-from types import FunctionType
from itertools import chain, imap
-from jinja2.utils import Markup, partial, soft_unicode, escape, missing, concat
-from jinja2.exceptions import UndefinedError, TemplateRuntimeError
+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', 'Context', 'TemplateReference', 'Macro', 'Markup',
+__all__ = ['LoopContext', 'TemplateReference', 'Macro', 'Markup',
'TemplateRuntimeError', 'missing', 'concat', 'escape',
- 'markup_join', 'unicode_join']
+ '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):
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)
+
+
+class TemplateReference(object):
+ """The `self` in templates."""
+
+ def __init__(self, context):
+ self.__context = context
+
+ 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 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.
method that doesn't fail with a `KeyError` but returns an
:class:`Undefined` object for missing variables.
"""
- __slots__ = ('parent', 'vars', 'environment', 'exported_vars', 'name',
- 'blocks')
+ __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
"""Render a parent block."""
try:
blocks = self.blocks[name]
- block = blocks[blocks.index(current) + 1]
+ index = blocks.index(current) + 1
+ blocks[index]
except LookupError:
return self.environment.undefined('there is no parent block '
'called %r.' % name,
name='super')
- wrap = self.environment.autoescape and Markup or (lambda x: x)
- render = lambda: wrap(concat(block(self)))
- render.__name__ = render.name = name
- return render
+ return BlockReference(name, self, blocks, index)
def get(self, key, default=None):
"""Returns an item from the template context, if it doesn't exist
"""
return dict(self.parent, **self.vars)
+ @internalcode
def call(__self, __obj, *args, **kwargs):
- """Called by the template code to inject the current context
- or environment as first arguments. Then forwards the call to
- the object with the arguments and keyword arguments.
+ """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 getattr(__obj, 'contextfunction', 0):
- args = (__self,) + args
- elif getattr(__obj, 'environmentfunction', 0):
- args = (__self.environment,) + args
+ 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__
keys = _all('keys')
values = _all('values')
items = _all('items')
- iterkeys = _all('iterkeys')
- itervalues = _all('itervalues')
- iteritems = _all('iteritems')
+
+ # not available on python 3
+ if hasattr(dict, 'iterkeys'):
+ iterkeys = _all('iterkeys')
+ itervalues = _all('itervalues')
+ iteritems = _all('iteritems')
del _all
def __contains__(self, name):
)
-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][0]
- wrap = self.__context.environment.autoescape and \
- Markup or (lambda x: x)
- render = lambda: wrap(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, recurse=None):
- 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):
"""Cycles among the arguments with the current loop index."""
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)
def __iter__(self):
return LoopContextIterator(self)
+ @internalcode
def loop(self, iterable):
if self._recurse is None:
raise TypeError('Tried to call non recursive loop. Maybe you '
# 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
+ __call__ = loop
+ del loop
@property
def length(self):
if self._length is None:
- try:
- # first try to get the length from the iterable (if the
- # iterable is a sequence)
- length = len(self._iterable)
- except TypeError:
- # if that's not possible (ie: iterating over a generator)
- # we have to convert the iterable into a sequence and
- # use the length of that.
- 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):
def next(self):
ctx = self.context
ctx.index0 += 1
- return ctx._next(), ctx
+ return next(ctx._iterator), ctx
class Macro(object):
self.catch_varargs = catch_varargs
self.caller = caller
+ @internalcode
def __call__(self, *args, **kwargs):
arguments = []
for idx, name in enumerate(self.arguments):
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:])
elif len(args) > self._argument_count:
)
-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 self._undefined_exception(hint)
-
-
class Undefined(object):
"""The default undefined type. This undefined type can be printed and
iterated over, but every other access will raise an :exc:`UndefinedError`:
>>> foo + 42
Traceback (most recent call last):
...
- jinja2.exceptions.UndefinedError: 'foo' is undefined
+ UndefinedError: 'foo' is undefined
"""
__slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name',
'_undefined_exception')
- def __init__(self, hint=None, obj=None, name=None, exc=UndefinedError):
+ 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.
>>> foo + 42
Traceback (most recent call last):
...
- jinja2.exceptions.UndefinedError: 'foo' is undefined
+ 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
>>> str(foo)
Traceback (most recent call last):
...
- jinja2.exceptions.UndefinedError: 'foo' is undefined
+ UndefinedError: 'foo' is undefined
>>> not foo
Traceback (most recent call last):
...
- jinja2.exceptions.UndefinedError: 'foo' is undefined
+ UndefinedError: 'foo' is undefined
>>> foo + 42
Traceback (most recent call last):
...
- jinja2.exceptions.UndefinedError: 'foo' is undefined
+ 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