blob: b28950050e5421ed646d6fada4ae0632bb3922ac [file] [log] [blame]
# -*- coding: utf-8 -*-
"""
jinja2.runtime
~~~~~~~~~~~~~~
Runtime helpers.
:copyright: Copyright 2008 by Armin Ronacher.
:license: GNU GPL.
"""
from types import FunctionType
from itertools import chain, imap
from jinja2.utils import Markup, partial, soft_unicode, escape
from jinja2.exceptions import UndefinedError, TemplateRuntimeError
# these variables are exported to the template runtime
__all__ = ['LoopContext', 'TemplateContext', 'TemplateReference', 'Macro',
'TemplateRuntimeError', 'Markup', 'missing', 'concat', 'escape',
'markup_join', 'unicode_join']
# special singleton representing missing values for the runtime
missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
# concatenate a list of strings and convert them to unicode.
concat = u''.join
def markup_join(*args):
"""Concatenation that escapes if necessary and converts to unicode."""
buf = []
iterator = imap(soft_unicode, args)
for arg in iterator:
buf.append(arg)
if hasattr(arg, '__html__'):
return Markup(u'').join(chain(buf, iterator))
return concat(buf)
def unicode_join(*args):
"""Simple args to unicode conversion and concatenation."""
return concat(imap(unicode, args))
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.
"""
def __init__(self, environment, parent, name, blocks):
self.parent = parent
self.vars = vars = {}
self.environment = environment
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.
self.blocks = dict((k, [v]) for k, v in blocks.iteritems())
def super(self, name, current):
"""Render a parent block."""
try:
blocks = self.blocks[name]
pos = blocks.index(current) - 1
if pos < 0:
raise IndexError()
except LookupError:
return self.environment.undefined('there is no parent block '
'called %r.' % name)
wrap = self.environment.autoescape and Markup or (lambda x: x)
render = lambda: wrap(concat(blocks[pos](self)))
render.__name__ = render.name = name
return render
def get(self, key, default=None):
"""For dict compatibility"""
if key in self.vars:
return self.vars[key]
if key in self.parent:
return self.parent[key]
return default
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 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)
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)
def __repr__(self):
return '<%s %s of %r>' % (
self.__class__.__name__,
repr(self.get_all()),
self.name
)
class TemplateReference(object):
"""The `self` in templates."""
def __init__(self, context):
self.__context = context
def __getitem__(self, name):
func = self.__context.blocks[name][-1]
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
def __repr__(self):
return '<%s %r>' % (
self.__class__.__name__,
self._context.name
)
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
self.index0 = -1
if enforce_length:
len(self)
def cycle(self, *args):
"""A replacement for the old ``{% cycle %}`` tag."""
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)
index = property(lambda x: x.index0 + 1)
revindex = property(lambda x: x.length - x.index0)
revindex0 = property(lambda x: x.length - x.index)
def __len__(self):
return self.length
def __iter__(self):
return self
def next(self):
self.index0 += 1
return self._next(), self
@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
return self._length
def __repr__(self):
return '<%s %r/%r>' % (
self.__class__.__name__,
self.index,
self.length
)
class Macro(object):
"""Wraps a macro."""
def __init__(self, environment, func, name, arguments, defaults,
catch_kwargs, catch_varargs, caller):
self._environment = environment
self._func = func
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
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:
try:
value = kwargs.pop(name)
except KeyError:
try:
value = self.defaults[idx - self.argument_count]
except IndexError:
value = self._environment.undefined(
'parameter %r was not provided' % name)
arguments.append(value)
# it's important that the order of these arguments does not change
# if not also changed in the compiler's `function_scoping` method.
# the order is caller, keyword arguments, positional arguments!
if self.caller:
caller = kwargs.pop('caller', None)
if caller is None:
caller = self._environment.undefined('No caller defined')
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()))
if self.catch_varargs:
arguments.append(args[self.argument_count:])
return self._func(*arguments)
def __repr__(self):
return '<%s %s>' % (
self.__class__.__name__,
self.name is None and 'anonymous' or repr(self.name)
)
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 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):
...
jinja2.exceptions.UndefinedError: 'foo' is undefined
"""
__slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name')
def __init__(self, hint=None, obj=None, name=None):
self._undefined_hint = hint
self._undefined_obj = obj
self._undefined_name = name
__add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
__realdiv__ = __rrealdiv__ = __floordiv__ = __rfloordiv__ = \
__mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
__getattr__ = __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = \
fail_with_undefined_error
def __str__(self):
return self.__unicode__().encode('utf-8')
def __repr__(self):
return 'Undefined'
def __unicode__(self):
return u''
def __len__(self):
return 0
def __iter__(self):
if 0:
yield None
def __nonzero__(self):
return False
class DebugUndefined(Undefined):
"""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):
...
jinja2.exceptions.UndefinedError: 'foo' is undefined
"""
__slots__ = ()
def __unicode__(self):
if self._undefined_hint is None:
if self._undefined_obj is None:
return u'{{ %s }}' % self._undefined_name
return '{{ no such element: %s[%r] }}' % (
self._undefined_obj.__class__.__name__,
self._undefined_name
)
return u'{{ undefined value printed: %s }}' % self._undefined_hint
class StrictUndefined(Undefined):
"""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):
...
jinja2.exceptions.UndefinedError: 'foo' is undefined
>>> not foo
Traceback (most recent call last):
...
jinja2.exceptions.UndefinedError: 'foo' is undefined
>>> foo + 42
Traceback (most recent call last):
...
jinja2.exceptions.UndefinedError: 'foo' is undefined
"""
__slots__ = ()
__iter__ = __unicode__ = __len__ = __nonzero__ = __eq__ = __ne__ = \
fail_with_undefined_error
# remove remaining slots attributes, after the metaclass did the magic they
# are unneeded and irritating as they contain wrong data for the subclasses.
del Undefined.__slots__, DebugUndefined.__slots__, StrictUndefined.__slots__