582 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			582 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: utf-8 -*-
 | |
| """
 | |
|     jinja2.runtime
 | |
|     ~~~~~~~~~~~~~~
 | |
| 
 | |
|     Runtime helpers.
 | |
| 
 | |
|     :copyright: (c) 2010 by the Jinja Team.
 | |
|     :license: BSD.
 | |
| """
 | |
| from itertools import chain
 | |
| from jinja2.nodes import EvalContext, _context_function_types
 | |
| from jinja2.utils import Markup, soft_unicode, escape, missing, concat, \
 | |
|      internalcode, object_type_repr
 | |
| from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
 | |
|      TemplateNotFound
 | |
| from jinja2._compat import next, imap, text_type, iteritems, \
 | |
|      implements_iterator, implements_to_string, string_types, PY2
 | |
| 
 | |
| 
 | |
| # these variables are exported to the template runtime
 | |
| __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.  We can just use the text type here.
 | |
| to_string = text_type
 | |
| 
 | |
| #: the identity function.  Useful for certain things in the environment
 | |
| identity = lambda x: x
 | |
| 
 | |
| _last_iteration = object()
 | |
| 
 | |
| 
 | |
| 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(text_type, 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 iteritems(locals):
 | |
|             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]
 | |
|         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.
 | |
|     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 = {}
 | |
|         self.environment = environment
 | |
|         self.eval_ctx = EvalContext(self.environment, name)
 | |
|         self.exported_vars = set()
 | |
|         self.name = name
 | |
| 
 | |
|         # 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 iteritems(blocks))
 | |
| 
 | |
|     def super(self, name, current):
 | |
|         """Render a parent block."""
 | |
|         try:
 | |
|             blocks = self.blocks[name]
 | |
|             index = blocks.index(current) + 1
 | |
|             blocks[index]
 | |
|         except LookupError:
 | |
|             return self.environment.undefined('there is no parent block '
 | |
|                                               'called %r.' % name,
 | |
|                                               name='super')
 | |
|         return BlockReference(name, self, blocks, index)
 | |
| 
 | |
|     def get(self, key, default=None):
 | |
|         """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 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_all(self):
 | |
|         """Return a copy of the complete context as dict including the
 | |
|         exported variables.
 | |
|         """
 | |
|         return dict(self.parent, **self.vars)
 | |
| 
 | |
|     @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
 | |
| 
 | |
|         # Allow callable classes to take a context
 | |
|         fn = __obj.__call__
 | |
|         for fn_type in ('contextfunction',
 | |
|                         'evalcontextfunction',
 | |
|                         'environmentfunction'):
 | |
|             if hasattr(fn, fn_type):
 | |
|                 __obj = fn
 | |
|                 break
 | |
| 
 | |
|         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
 | |
|         try:
 | |
|             return __obj(*args, **kwargs)
 | |
|         except StopIteration:
 | |
|             return __self.environment.undefined('value was undefined because '
 | |
|                                                 'a callable raised a '
 | |
|                                                 'StopIteration exception')
 | |
| 
 | |
|     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.vars.update(self.vars)
 | |
|         context.eval_ctx = self.eval_ctx
 | |
|         context.blocks.update((k, list(v)) for k, v in iteritems(self.blocks))
 | |
|         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 PY2:
 | |
|         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):
 | |
|         """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>' % (
 | |
|             self.__class__.__name__,
 | |
|             repr(self.get_all()),
 | |
|             self.name
 | |
|         )
 | |
| 
 | |
| 
 | |
| # register the context as mapping if possible
 | |
| try:
 | |
|     from collections import Mapping
 | |
|     Mapping.register(Context)
 | |
| except ImportError:
 | |
|     pass
 | |
| 
 | |
| 
 | |
| class BlockReference(object):
 | |
|     """One block on a template reference."""
 | |
| 
 | |
|     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, recurse=None, depth0=0):
 | |
|         self._iterator = iter(iterable)
 | |
|         self._recurse = recurse
 | |
|         self._after = self._safe_next()
 | |
|         self.index0 = -1
 | |
|         self.depth0 = depth0
 | |
| 
 | |
|         # 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."""
 | |
|         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._after is _last_iteration)
 | |
|     index = property(lambda x: x.index0 + 1)
 | |
|     revindex = property(lambda x: x.length - x.index0)
 | |
|     revindex0 = property(lambda x: x.length - x.index)
 | |
|     depth = property(lambda x: x.depth0 + 1)
 | |
| 
 | |
|     def __len__(self):
 | |
|         return self.length
 | |
| 
 | |
|     def __iter__(self):
 | |
|         return LoopContextIterator(self)
 | |
| 
 | |
|     def _safe_next(self):
 | |
|         try:
 | |
|             return next(self._iterator)
 | |
|         except StopIteration:
 | |
|             return _last_iteration
 | |
| 
 | |
|     @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, self.depth0 + 1)
 | |
| 
 | |
|     # 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:
 | |
|             # 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):
 | |
|         return '<%s %r/%r>' % (
 | |
|             self.__class__.__name__,
 | |
|             self.index,
 | |
|             self.length
 | |
|         )
 | |
| 
 | |
| 
 | |
| @implements_iterator
 | |
| 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
 | |
|         if ctx._after is _last_iteration:
 | |
|             raise StopIteration()
 | |
|         next_elem = ctx._after
 | |
|         ctx._after = ctx._safe_next()
 | |
|         return next_elem, ctx
 | |
| 
 | |
| 
 | |
| class Macro(object):
 | |
|     """Wraps a macro function."""
 | |
| 
 | |
|     def __init__(self, environment, func, name, arguments, defaults,
 | |
|                  catch_kwargs, catch_varargs, caller):
 | |
|         self._environment = environment
 | |
|         self._func = func
 | |
|         self._argument_count = len(arguments)
 | |
|         self.name = name
 | |
|         self.arguments = arguments
 | |
|         self.defaults = defaults
 | |
|         self.catch_kwargs = catch_kwargs
 | |
|         self.catch_varargs = catch_varargs
 | |
|         self.caller = caller
 | |
| 
 | |
|     @internalcode
 | |
|     def __call__(self, *args, **kwargs):
 | |
|         # try to consume the positional arguments
 | |
|         arguments = list(args[:self._argument_count])
 | |
|         off = len(arguments)
 | |
| 
 | |
|         # if the number of arguments consumed is not the number of
 | |
|         # arguments expected we start filling in keyword arguments
 | |
|         # and defaults.
 | |
|         if off != self._argument_count:
 | |
|             for idx, name in enumerate(self.arguments[len(arguments):]):
 | |
|                 try:
 | |
|                     value = kwargs.pop(name)
 | |
|                 except KeyError:
 | |
|                     try:
 | |
|                         value = self.defaults[idx - self._argument_count + off]
 | |
|                     except IndexError:
 | |
|                         value = self._environment.undefined(
 | |
|                             'parameter %r was not provided' % name, name=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',
 | |
|                                                      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, next(iter(kwargs))))
 | |
|         if self.catch_varargs:
 | |
|             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):
 | |
|         return '<%s %s>' % (
 | |
|             self.__class__.__name__,
 | |
|             self.name is None and 'anonymous' or repr(self.name)
 | |
|         )
 | |
| 
 | |
| 
 | |
| @implements_to_string
 | |
| 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):
 | |
|       ...
 | |
|     UndefinedError: 'foo' is undefined
 | |
|     """
 | |
|     __slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name',
 | |
|                  '_undefined_exception')
 | |
| 
 | |
|     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, string_types):
 | |
|                 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)
 | |
| 
 | |
|     @internalcode
 | |
|     def __getattr__(self, name):
 | |
|         if name[:2] == '__':
 | |
|             raise AttributeError(name)
 | |
|         return self._fail_with_undefined_error()
 | |
| 
 | |
|     __add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
 | |
|     __truediv__ = __rtruediv__ = __floordiv__ = __rfloordiv__ = \
 | |
|     __mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
 | |
|     __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = __int__ = \
 | |
|     __float__ = __complex__ = __pow__ = __rpow__ = \
 | |
|         _fail_with_undefined_error
 | |
| 
 | |
|     def __eq__(self, other):
 | |
|         return type(self) is type(other)
 | |
| 
 | |
|     def __ne__(self, other):
 | |
|         return not self.__eq__(other)
 | |
| 
 | |
|     def __hash__(self):
 | |
|         return id(type(self))
 | |
| 
 | |
|     def __str__(self):
 | |
|         return u''
 | |
| 
 | |
|     def __len__(self):
 | |
|         return 0
 | |
| 
 | |
|     def __iter__(self):
 | |
|         if 0:
 | |
|             yield None
 | |
| 
 | |
|     def __nonzero__(self):
 | |
|         return False
 | |
| 
 | |
|     def __repr__(self):
 | |
|         return 'Undefined'
 | |
| 
 | |
| 
 | |
| @implements_to_string
 | |
| 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):
 | |
|       ...
 | |
|     UndefinedError: 'foo' is undefined
 | |
|     """
 | |
|     __slots__ = ()
 | |
| 
 | |
|     def __str__(self):
 | |
|         if self._undefined_hint is None:
 | |
|             if self._undefined_obj is missing:
 | |
|                 return u'{{ %s }}' % self._undefined_name
 | |
|             return '{{ no such element: %s[%r] }}' % (
 | |
|                 object_type_repr(self._undefined_obj),
 | |
|                 self._undefined_name
 | |
|             )
 | |
|         return u'{{ undefined value printed: %s }}' % self._undefined_hint
 | |
| 
 | |
| 
 | |
| @implements_to_string
 | |
| 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):
 | |
|       ...
 | |
|     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__ = __str__ = __len__ = __nonzero__ = __eq__ = \
 | |
|         __ne__ = __bool__ = __hash__ = \
 | |
|         Undefined._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__
 | 
