Permalink
Browse files

improved undefined behavior

--HG--
branch : trunk
  • Loading branch information...
1 parent 814f6c2 commit 9a82205d536ca9708768a439373b91651556512f @mitsuhiko mitsuhiko committed Apr 17, 2008
Showing with 126 additions and 76 deletions.
  1. +2 −1 jinja2/__init__.py
  2. +1 −6 jinja2/compiler.py
  3. +1 −1 jinja2/defaults.py
  4. +6 −3 jinja2/environment.py
  5. +12 −9 jinja2/exceptions.py
  6. +3 −6 jinja2/filters.py
  7. +44 −18 jinja2/loaders.py
  8. +1 −1 jinja2/parser.py
  9. +37 −22 jinja2/runtime.py
  10. +19 −9 jinja2/sandbox.py
View
@@ -57,6 +57,7 @@
: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
View
@@ -938,7 +938,7 @@ def visitor(self, node, frame):
def uaop(operator):
def visitor(self, node, frame):
self.write('(' + operator)
- self.visit(node.node)
+ self.visit(node.node, frame)
self.write(')')
return visitor
@@ -977,11 +977,6 @@ def visit_Subscript(self, node, frame):
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(', ')
View
@@ -9,7 +9,7 @@
: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 = {
View
@@ -110,15 +110,14 @@ def subscribe(self, obj, argument):
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.
@@ -198,12 +197,15 @@ def __init__(self, environment, code, globals, uptodate=None):
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)
@@ -240,6 +242,7 @@ def get_corresponding_lineno(self, lineno):
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:
View
@@ -11,23 +11,23 @@
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)
self.name = 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))
@@ -37,14 +37,17 @@ def __init__(self, message, lineno, name):
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)
TemplateSyntaxError.__init__(self, message, lineno, name)
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.
"""
View
@@ -237,8 +237,7 @@ def do_first(environment, seq):
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
@@ -247,8 +246,7 @@ def do_last(environment, seq):
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
@@ -257,8 +255,7 @@ def do_random(environment, seq):
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):
View
@@ -9,12 +9,26 @@
: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
@@ -61,7 +75,7 @@ def load(self, environment, name, globals=None):
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)
@@ -84,27 +98,39 @@ def __init__(self, searchpath, encoding='utf-8', cache_size=50,
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."""
View
@@ -639,7 +639,7 @@ def parse_test(self, node):
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):
View
@@ -13,6 +13,7 @@
except ImportError:
defaultdict = None
from jinja2.utils import Markup
+from jinja2.exceptions import UndefinedError
__all__ = ['LoopContext', 'StaticLoopContext', 'TemplateContext',
@@ -46,8 +47,8 @@ def super(self, block):
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):
@@ -65,10 +66,10 @@ def get_exported(self):
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>' % (
@@ -241,15 +242,13 @@ def __call__(self, *args, **kwargs):
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
@@ -268,19 +267,28 @@ class Undefined(object):
`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__ = \
@@ -310,7 +318,14 @@ class DebugUndefined(Undefined):
"""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):
Oops, something went wrong.

0 comments on commit 9a82205

Please sign in to comment.