Mercurial > genshi > mirror
diff genshi/template/eval.py @ 418:c478a6fa9e77 trunk
Make expression error handling more strict. Closes #88.
author | cmlenz |
---|---|
date | Fri, 16 Mar 2007 08:58:54 +0000 |
parents | 4675d5cf6c67 |
children | 073640758a42 |
line wrap: on
line diff
--- a/genshi/template/eval.py +++ b/genshi/template/eval.py @@ -24,9 +24,10 @@ import sys from genshi.core import Markup +from genshi.template.base import TemplateRuntimeError from genshi.util import flatten -__all__ = ['Expression', 'Suite', 'Undefined'] +__all__ = ['Expression', 'Suite'] class Code(object): @@ -119,10 +120,13 @@ @param data: a mapping containing the data to evaluate against @return: the result of the evaluation """ + __traceback_hide__ = 'before_and_this' return eval(self.code, {'data': data, '_lookup_name': _lookup_name, '_lookup_attr': _lookup_attr, - '_lookup_item': _lookup_item}, + '_lookup_item': _lookup_item, + 'defined': _defined(data), + 'value_of': _value_of(data)}, {'data': data}) @@ -142,68 +146,30 @@ @param data: a mapping containing the data to execute in """ + __traceback_hide__ = 'before_and_this' exec self.code in {'data': data, '_lookup_name': _lookup_name, '_lookup_attr': _lookup_attr, - '_lookup_item': _lookup_item}, data + '_lookup_item': _lookup_item, + 'defined': _defined(data), + 'value_of': _value_of(data)}, data -class Undefined(object): - """Represents a reference to an undefined variable. - - Unlike the Python runtime, template expressions can refer to an undefined - variable without causing a `NameError` to be raised. The result will be an - instance of the `Undefined´ class, which is treated the same as `False` in - conditions, and acts as an empty collection in iterations: - - >>> foo = Undefined('foo') - >>> bool(foo) - False - >>> list(foo) - [] - >>> print foo - undefined - - However, calling an undefined variable, or trying to access an attribute - of that variable, will raise an exception that includes the name used to - reference that undefined variable. - - >>> foo('bar') - Traceback (most recent call last): - ... - NameError: Variable "foo" is not defined +def _defined(data): + def defined(name): + """Return whether a variable with the specified name exists in the + expression scope. + """ + return name in data + return defined - >>> foo.bar - Traceback (most recent call last): - ... - NameError: Variable "foo" is not defined - """ - __slots__ = ['_name'] - - def __init__(self, name): - self._name = name - - def __call__(self, *args, **kwargs): - __traceback_hide__ = True - self.throw() - - def __getattr__(self, name): - __traceback_hide__ = True - self.throw() - - def __iter__(self): - return iter([]) - - def __nonzero__(self): - return False - - def __repr__(self): - return 'undefined' - - def throw(self): - __traceback_hide__ = True - raise NameError('Variable "%s" is not defined' % self._name) - +def _value_of(data): + def value_of(name, default=None): + """If a variable of the specified name is defined, return its value. + Otherwise, return the provided default value, or `None`. + """ + return data.get(name, default) + return value_of def _parse(source, mode='eval'): if isinstance(source, unicode): @@ -238,42 +204,56 @@ code.co_lnotab, (), ()) BUILTINS = __builtin__.__dict__.copy() -BUILTINS.update({'Markup': Markup, 'Undefined': Undefined}) -_UNDEF = Undefined(None) +BUILTINS.update({'Markup': Markup}) +UNDEFINED = object() + + +class UndefinedError(TemplateRuntimeError): + """Exception thrown when a template expression attempts to access a variable + not defined in the context. + """ + def __init__(self, name, owner=UNDEFINED): + if owner is not UNDEFINED: + orepr = repr(owner) + if len(orepr) > 60: + orepr = orepr[:60] + '...' + message = '%s (%s) has no member named "%s"' % ( + type(owner).__name__, orepr, name + ) + else: + message = '"%s" not defined' % name + TemplateRuntimeError.__init__(self, message) + def _lookup_name(data, name): __traceback_hide__ = True - val = data.get(name, _UNDEF) - if val is _UNDEF: + val = data.get(name, UNDEFINED) + if val is UNDEFINED: val = BUILTINS.get(name, val) - if val is _UNDEF: - return Undefined(name) + if val is UNDEFINED: + raise UndefinedError(name) return val def _lookup_attr(data, obj, key): __traceback_hide__ = True - if type(obj) is Undefined: - obj.throw() if hasattr(obj, key): return getattr(obj, key) try: return obj[key] except (KeyError, TypeError): - return Undefined(key) + raise UndefinedError(key, owner=obj) def _lookup_item(data, obj, key): __traceback_hide__ = True - if type(obj) is Undefined: - obj.throw() if len(key) == 1: key = key[0] try: return obj[key] except (AttributeError, KeyError, IndexError, TypeError), e: if isinstance(key, basestring): - val = getattr(obj, key, _UNDEF) - if val is _UNDEF: - val = Undefined(key) + val = getattr(obj, key, UNDEFINED) + if val is UNDEFINED: + raise UndefinedError(key, owner=obj) return val raise @@ -523,7 +503,7 @@ """ def __init__(self): - self.locals = [] + self.locals = [set(['defined', 'value_of'])] def visitConst(self, node): if isinstance(node.value, str):