:license: BSD, see LICENSE for more details.
"""
from jinja2.environment import Environment
-from jinja2.loaders import BaseLoader, FileSystemLoader, DictLoader
+from jinja2.loaders import BaseLoader, FileSystemLoader, PackageLoader, \
+ DictLoader
from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined
from jinja2.utils import Markup, escape
def uaop(operator):
def visitor(self, node, frame):
self.write('(' + operator)
- self.visit(node.node)
+ self.visit(node.node, frame)
self.write(')')
return visitor
have_const = True
except nodes.Impossible:
have_const = False
- if have_const:
- if isinstance(const, (int, long, float)):
- self.visit(node.node, frame)
- self.write('[%s]' % const)
- return
self.write('environment.subscribe(')
self.visit(node.node, frame)
self.write(', ')
:license: BSD, see LICENSE for more details.
"""
from jinja2.filters import FILTERS as DEFAULT_FILTERS
-from jinja.tests import TESTS as DEFAULT_TESTS
+from jinja2.tests import TESTS as DEFAULT_TESTS
DEFAULT_NAMESPACE = {
try:
return obj[argument]
except (TypeError, LookupError):
- return self.undefined(obj, argument)
+ return self.undefined(obj=obj, name=argument)
def parse(self, source, name=None):
"""Parse the sourcecode and return the abstract syntax tree. This tree
of nodes is used by the compiler to convert the template into
executable source- or bytecode.
"""
- parser = Parser(self, source, name)
- return parser.parse()
+ return Parser(self, source, name).parse()
def lex(self, source, name=None):
"""Lex the given sourcecode and return a generator that yields tokens.
namespace['__jinja_template__'] = self
def render(self, *args, **kwargs):
+ """Render the template into a string."""
return u''.join(self.generate(*args, **kwargs))
def stream(self, *args, **kwargs):
+ """Return a `TemplateStream` that generates the template."""
return TemplateStream(self.generate(*args, **kwargs))
def generate(self, *args, **kwargs):
+ """Return a generator that generates the template."""
# assemble the context
context = dict(*args, **kwargs)
return template_line
return 1
+ @property
def is_up_to_date(self):
"""Check if the template is still up to date."""
if self._uptodate is None:
class TemplateError(Exception):
- pass
+ """Baseclass for all template errors."""
+
+
+class UndefinedError(TemplateError):
+ """Raised if a template tries to operate on `Undefined`."""
class TemplateNotFound(IOError, LookupError, TemplateError):
- """
- Raised if a template does not exist.
- """
+ """Raised if a template does not exist."""
def __init__(self, name):
IOError.__init__(self, name)
class TemplateSyntaxError(TemplateError):
- """
- Raised to tell the user that there is a problem with the template.
- """
+ """Raised to tell the user that there is a problem with the template."""
def __init__(self, message, lineno, name):
TEmplateError.__init__(self, '%s (line %s)' % (message, lineno))
class TemplateAssertionError(AssertionError, TemplateSyntaxError):
+ """Like a template syntax error, but covers cases where something in the
+ template caused an error at compile time that wasn't necessarily caused
+ by a syntax error.
+ """
def __init__(self, message, lineno, name):
AssertionError.__init__(self, message)
class TemplateRuntimeError(TemplateError):
- """
- Raised by the template engine if a tag encountered an error when
+ """Raised by the template engine if a tag encountered an error when
rendering.
"""
try:
return iter(seq).next()
except StopIteration:
- return environment.undefined('seq|first',
- extra='the sequence was empty')
+ return environment.undefined('No first item, sequence was empty.')
@environmentfilter
try:
return iter(reversed(seq)).next()
except StopIteration:
- return environment.undefined('seq|last',
- extra='the sequence was empty')
+ return environment.undefined('No last item, sequence was empty.')
@environmentfilter
try:
return choice(seq)
except IndexError:
- return environment.undefined('seq|random',
- extra='the sequence was empty')
+ return environment.undefined('No random item, sequence was empty.')
def do_filesizeformat(value):
:license: BSD, see LICENSE for more details.
"""
from os import path
-from time import time
from jinja2.exceptions import TemplateNotFound
from jinja2.environment import Template
from jinja2.utils import LRUCache
+def split_template_path(template):
+ """Split a path into segments and perform a sanity check. If it detects
+ '..' in the path it will raise a `TemplateNotFound` error.
+ """
+ pieces = []
+ for piece in template.split('/'):
+ if path.sep in piece \
+ or (path.altsep and path.altsep in piece) or \
+ piece == path.pardir:
+ raise TemplateNotFound(template)
+ elif piece != '.':
+ pieces.append(piece)
+ return pieces
+
+
class BaseLoader(object):
"""
Baseclass for all loaders. Subclass this and override `get_source` to
if self.cache is not None:
template = self.cache.get(name)
if template is not None and (not self.auto_reload or \
- template.is_up_to_date()):
+ template.is_up_to_date):
return template
source, filename, uptodate = self.get_source(environment, name)
self.encoding = encoding
def get_source(self, environment, template):
- pieces = []
- for piece in template.split('/'):
- if piece == '..':
- raise TemplateNotFound(template)
- elif piece != '.':
- pieces.append(piece)
+ pieces = split_template_path(template)
for searchpath in self.searchpath:
filename = path.join(searchpath, *pieces)
- if path.isfile(filename):
- f = file(filename)
- try:
- contents = f.read().decode(self.encoding)
- finally:
- f.close()
- mtime = path.getmtime(filename)
- def uptodate():
- return path.getmtime(filename) != mtime
- return contents, filename, uptodate
+ if not path.isfile(filename):
+ continue
+ f = file(filename)
+ try:
+ contents = f.read().decode(self.encoding)
+ finally:
+ f.close()
+ old = path.getmtime(filename)
+ return contents, filename, lambda: path.getmtime(filename) != old
raise TemplateNotFound(template)
+class PackageLoader(BaseLoader):
+ """Load templates from python eggs."""
+
+ def __init__(self, package_name, package_path, charset='utf-8',
+ cache_size=50, auto_reload=True):
+ BaseLoader.__init__(self, cache_size, auto_reload)
+ import pkg_resources
+ self._pkg = pkg_resources
+ self.package_name = package_name
+ self.package_path = package_path
+
+ def get_source(self, environment, template):
+ path = '/'.join(split_template_path(template))
+ if not self._pkg.resource_exists(self.package_name, path):
+ raise TemplateNotFound(template)
+ return self._pkg.resource_string(self.package_name, path), None, None
+
+
class DictLoader(BaseLoader):
"""Loads a template from a python dict. Used for unittests mostly."""
node = nodes.Test(node, name, args, kwargs, dyn_args,
dyn_kwargs, lineno=token.lineno)
if negated:
- node = nodes.NotExpression(node, lineno=token.lineno)
+ node = nodes.Not(node, lineno=token.lineno)
return node
def subparse(self, end_tokens=None):
except ImportError:
defaultdict = None
from jinja2.utils import Markup
+from jinja2.exceptions import UndefinedError
__all__ = ['LoopContext', 'StaticLoopContext', 'TemplateContext',
try:
func = self.blocks[block][-2]
except LookupError:
- return self.environment.undefined('super',
- extra='there is probably no parent block with this name')
+ return self.environment.undefined('there is no parent block '
+ 'called %r.' % block)
return SuperBlock(block, self, func)
def __setitem__(self, key, value):
def __getitem__(self, name):
if name in self:
return self[name]
- return self.environment.undefined(name)
+ return self.environment.undefined(name=name)
else:
- def __missing__(self, key):
- return self.environment.undefined(key)
+ def __missing__(self, name):
+ return self.environment.undefined(name=name)
def __repr__(self):
return '<%s %s of %r>' % (
try:
value = self.defaults[idx - arg_count]
except IndexError:
- value = self._environment.undefined(name,
- extra='parameter not provided')
+ value = self._environment.undefined(
+ 'parameter %r was not provided' % name)
arguments['l_' + name] = value
if self.caller:
caller = kwargs.pop('caller', None)
if caller is None:
- caller = self._environment.undefined('caller',
- extra='The macro was called from an expression and not '
- 'a call block.')
+ caller = self._environment.undefined('No caller defined')
arguments['l_caller'] = caller
if self.catch_all:
arguments['l_arguments'] = kwargs
`NameError`. Custom undefined classes must subclass this.
"""
- def __init__(self, name=None, attr=None, extra=None):
- if attr is None:
- self._undefined_hint = '%r is undefined' % name
- self._error_class = NameError
- else:
- self._undefined_hint = '%r has no attribute named %r' \
- % (name, attr)
- self._error_class = AttributeError
- if extra is not None:
- self._undefined_hint += ' (' + extra + ')'
+ def __init__(self, hint=None, obj=None, name=None):
+ self._undefined_hint = hint
+ self._undefined_obj = obj
+ self._undefined_name = name
def _fail_with_error(self, *args, **kwargs):
- raise self._error_class(self._undefined_hint)
+ 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 %s' % (
+ self._undefined_obj.__class__.__name__,
+ self._undefined_name
+ )
+ else:
+ hint = self._undefined_hint
+ raise UndefinedError(hint)
__add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \
__realdiv__ = __rrealdiv__ = __floordiv__ = __rfloordiv__ = \
__mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \
"""An undefined that returns the debug info when printed."""
def __unicode__(self):
- return u'{{ %s }}' % self._undefined_hint
+ 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):
Environment.__init__(self, *args, **kwargs)
self.globals['range'] = safe_range
- def is_safe_attribute(self, obj, attr):
+ def is_safe_attribute(self, obj, attr, value):
"""The sandboxed environment will call this method to check if the
attribute of an object is safe to access. Per default all attributes
starting with an underscore are considered private as well as the
"""
return not getattr(obj, 'unsafe_callable', False)
- def subscribe(self, obj, arg):
+ def subscribe(self, obj, argument):
"""Subscribe an object from sandboxed code."""
+ is_unsafe = False
try:
- return obj[arg]
+ value = getattr(obj, str(argument))
+ except (AttributeError, UnicodeError):
+ pass
+ else:
+ if self.is_safe_attribute(obj, argument, value):
+ return value
+ is_unsafe = True
+ try:
+ return obj[argument]
except (TypeError, LookupError):
- if not self.is_safe_attribute(obj, arg):
- return Undefined(obj, arg, extra='attribute unsafe')
- try:
- return getattr(obj, str(arg))
- except (AttributeError, UnicodeError):
- return Undefined(obj, arg)
+ if is_unsafe:
+ return self.undefined('access to attribute %r of %r object is'
+ ' unsafe.' % (
+ argument,
+ obj.__class__.__name__
+ ))
+ return self.undefined(obj=obj, name=argument)
def call(__self, __obj, *args, **kwargs):
"""Call an object from sandboxed code."""