cmlenz@1: # -*- coding: utf-8 -*- cmlenz@1: # cmlenz@1: # Copyright (C) 2006 Christopher Lenz cmlenz@1: # All rights reserved. cmlenz@1: # cmlenz@1: # This software is licensed as described in the file COPYING, which cmlenz@1: # you should have received as part of this distribution. The terms cmlenz@27: # are also available at http://markup.cmlenz.net/wiki/License. cmlenz@1: # cmlenz@1: # This software consists of voluntary contributions made by many cmlenz@1: # individuals. For the exact contribution history, see the revision cmlenz@27: # history and logs, available at http://markup.cmlenz.net/log/. cmlenz@27: cmlenz@27: """Support for "safe" evaluation of Python expressions.""" cmlenz@1: cmlenz@1: import __builtin__ cmlenz@30: try: cmlenz@30: import _ast # Python 2.5 cmlenz@30: except ImportError: cmlenz@30: _ast = None cmlenz@30: import compiler cmlenz@1: import operator cmlenz@1: cmlenz@1: from markup.core import Stream cmlenz@1: cmlenz@1: __all__ = ['Expression'] cmlenz@1: cmlenz@1: cmlenz@1: class Expression(object): cmlenz@1: """Evaluates Python expressions used in templates. cmlenz@1: cmlenz@1: >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'}) cmlenz@1: >>> Expression('test').evaluate(data) cmlenz@1: 'Foo' cmlenz@1: >>> Expression('items[0]').evaluate(data) cmlenz@1: 1 cmlenz@1: >>> Expression('items[-1]').evaluate(data) cmlenz@1: 3 cmlenz@1: >>> Expression('dict["some"]').evaluate(data) cmlenz@1: 'thing' cmlenz@1: cmlenz@1: Similar to e.g. Javascript, expressions in templates can use the dot cmlenz@1: notation for attribute access to access items in mappings: cmlenz@1: cmlenz@1: >>> Expression('dict.some').evaluate(data) cmlenz@1: 'thing' cmlenz@1: cmlenz@1: This also works the other way around: item access can be used to access cmlenz@1: any object attribute (meaning there's no use for `getattr()` in templates): cmlenz@1: cmlenz@1: >>> class MyClass(object): cmlenz@1: ... myattr = 'Bar' cmlenz@1: >>> data = dict(mine=MyClass(), key='myattr') cmlenz@1: >>> Expression('mine.myattr').evaluate(data) cmlenz@1: 'Bar' cmlenz@1: >>> Expression('mine["myattr"]').evaluate(data) cmlenz@1: 'Bar' cmlenz@1: >>> Expression('mine[key]').evaluate(data) cmlenz@1: 'Bar' cmlenz@1: cmlenz@1: Most of the standard Python operators are also available to template cmlenz@1: expressions. Bitwise operators (including inversion and shifting) are not cmlenz@1: supported. cmlenz@1: cmlenz@1: >>> Expression('1 + 1').evaluate(data) cmlenz@1: 2 cmlenz@1: >>> Expression('3 - 1').evaluate(data) cmlenz@1: 2 cmlenz@1: >>> Expression('1 * 2').evaluate(data) cmlenz@1: 2 cmlenz@1: >>> Expression('4 / 2').evaluate(data) cmlenz@1: 2 cmlenz@1: >>> Expression('4 // 3').evaluate(data) cmlenz@1: 1 cmlenz@1: >>> Expression('4 % 3').evaluate(data) cmlenz@1: 1 cmlenz@1: >>> Expression('2 ** 3').evaluate(data) cmlenz@1: 8 cmlenz@1: >>> Expression('not True').evaluate(data) cmlenz@1: False cmlenz@1: >>> Expression('True and False').evaluate(data) cmlenz@1: False cmlenz@1: >>> Expression('True or False').evaluate(data) cmlenz@1: True cmlenz@1: >>> Expression('1 == 3').evaluate(data) cmlenz@1: False cmlenz@1: >>> Expression('1 != 3 == 3').evaluate(data) cmlenz@1: True cmlenz@7: >>> Expression('1 > 0').evaluate(data) cmlenz@7: True cmlenz@7: >>> Expression('True and "Foo"').evaluate(data) cmlenz@7: 'Foo' cmlenz@7: >>> data = dict(items=[1, 2, 3]) cmlenz@7: >>> Expression('2 in items').evaluate(data) cmlenz@7: True cmlenz@7: >>> Expression('not 2 in items').evaluate(data) cmlenz@7: False cmlenz@1: cmlenz@1: Built-in functions such as `len()` are also available in template cmlenz@1: expressions: cmlenz@1: cmlenz@1: >>> data = dict(items=[1, 2, 3]) cmlenz@1: >>> Expression('len(items)').evaluate(data) cmlenz@1: 3 cmlenz@1: """ cmlenz@1: __slots__ = ['source', 'ast'] cmlenz@1: __visitors = {} cmlenz@1: cmlenz@1: def __init__(self, source): cmlenz@27: """Create the expression. cmlenz@27: cmlenz@27: @param source: the expression as string cmlenz@27: """ cmlenz@1: self.source = source cmlenz@1: self.ast = None cmlenz@1: cmlenz@1: def __repr__(self): cmlenz@1: return '' % self.source cmlenz@1: cmlenz@30: if _ast is None: cmlenz@1: cmlenz@30: def evaluate(self, data): cmlenz@30: """Evaluate the expression against the given data dictionary. cmlenz@30: cmlenz@30: @param data: a mapping containing the data to evaluate against cmlenz@30: @return: the result of the evaluation cmlenz@30: """ cmlenz@30: if not self.ast: cmlenz@30: self.ast = compiler.parse(self.source, 'eval') cmlenz@30: return self._visit(self.ast.node, data) cmlenz@1: cmlenz@30: # AST traversal cmlenz@1: cmlenz@30: def _visit(self, node, data): cmlenz@30: v = self.__visitors.get(node.__class__) cmlenz@30: if not v: cmlenz@30: v = getattr(self, '_visit_%s' % node.__class__.__name__.lower()) cmlenz@30: self.__visitors[node.__class__] = v cmlenz@30: return v(node, data) cmlenz@30: cmlenz@30: def _visit_expression(self, node, data): cmlenz@30: for child in node.getChildNodes(): cmlenz@30: return self._visit(child, data) cmlenz@30: cmlenz@30: # Functions & Accessors cmlenz@30: cmlenz@30: def _visit_callfunc(self, node, data): cmlenz@30: func = self._visit(node.node, data) cmlenz@30: if func is None: cmlenz@30: return None cmlenz@30: args = [self._visit(arg, data) for arg in node.args cmlenz@30: if not isinstance(arg, compiler.ast.Keyword)] cmlenz@30: kwargs = dict([(arg.name, self._visit(arg.expr, data)) for arg cmlenz@30: in node.args if isinstance(arg, compiler.ast.Keyword)]) cmlenz@30: return func(*args, **kwargs) cmlenz@30: cmlenz@30: def _visit_getattr(self, node, data): cmlenz@30: obj = self._visit(node.expr, data) cmlenz@30: if hasattr(obj, node.attrname): cmlenz@30: return getattr(obj, node.attrname) cmlenz@1: try: cmlenz@30: return obj[node.attrname] cmlenz@30: except TypeError: cmlenz@1: return None cmlenz@1: cmlenz@30: def _visit_slice(self, node, data): cmlenz@30: obj = self._visit(node.expr, data) cmlenz@30: lower = node.lower and self._visit(node.lower, data) or None cmlenz@30: upper = node.upper and self._visit(node.upper, data) or None cmlenz@30: return obj[lower:upper] cmlenz@1: cmlenz@30: def _visit_subscript(self, node, data): cmlenz@30: obj = self._visit(node.expr, data) cmlenz@30: subs = map(lambda sub: self._visit(sub, data), node.subs) cmlenz@30: if len(subs) == 1: cmlenz@30: subs = subs[0] cmlenz@30: try: cmlenz@30: return obj[subs] cmlenz@30: except (KeyError, IndexError, TypeError): cmlenz@30: try: cmlenz@30: return getattr(obj, subs) cmlenz@30: except (AttributeError, TypeError): cmlenz@30: return None cmlenz@1: cmlenz@30: # Operators cmlenz@1: cmlenz@30: def _visit_and(self, node, data): cmlenz@30: return reduce(lambda x, y: x and y, cmlenz@30: [self._visit(n, data) for n in node.nodes]) cmlenz@1: cmlenz@30: def _visit_or(self, node, data): cmlenz@30: return reduce(lambda x, y: x or y, cmlenz@30: [self._visit(n, data) for n in node.nodes]) cmlenz@1: cmlenz@30: _OP_MAP = {'==': operator.eq, '!=': operator.ne, cmlenz@30: '<': operator.lt, '<=': operator.le, cmlenz@30: '>': operator.gt, '>=': operator.ge, cmlenz@30: 'in': lambda x, y: operator.contains(y, x), cmlenz@30: 'not in': lambda x, y: not operator.contains(y, x)} cmlenz@30: def _visit_compare(self, node, data): cmlenz@30: result = self._visit(node.expr, data) cmlenz@30: ops = node.ops[:] cmlenz@30: ops.reverse() cmlenz@30: for op, rval in ops: cmlenz@30: result = self._OP_MAP[op](result, self._visit(rval, data)) cmlenz@30: return result cmlenz@1: cmlenz@30: def _visit_add(self, node, data): cmlenz@30: return self._visit(node.left, data) + self._visit(node.right, data) cmlenz@30: cmlenz@30: def _visit_div(self, node, data): cmlenz@30: return self._visit(node.left, data) / self._visit(node.right, data) cmlenz@30: cmlenz@30: def _visit_floordiv(self, node, data): cmlenz@30: return self._visit(node.left, data) // self._visit(node.right, data) cmlenz@30: cmlenz@30: def _visit_mod(self, node, data): cmlenz@30: return self._visit(node.left, data) % self._visit(node.right, data) cmlenz@30: cmlenz@30: def _visit_mul(self, node, data): cmlenz@30: return self._visit(node.left, data) * self._visit(node.right, data) cmlenz@30: cmlenz@30: def _visit_power(self, node, data): cmlenz@30: return self._visit(node.left, data) ** self._visit(node.right, data) cmlenz@30: cmlenz@30: def _visit_sub(self, node, data): cmlenz@30: return self._visit(node.left, data) - self._visit(node.right, data) cmlenz@30: cmlenz@30: def _visit_not(self, node, data): cmlenz@30: return not self._visit(node.expr, data) cmlenz@30: cmlenz@30: def _visit_unaryadd(self, node, data): cmlenz@30: return +self._visit(node.expr, data) cmlenz@30: cmlenz@30: def _visit_unarysub(self, node, data): cmlenz@30: return -self._visit(node.expr, data) cmlenz@30: cmlenz@30: # Identifiers & Literals cmlenz@30: cmlenz@30: def _visit_name(self, node, data): cmlenz@30: val = data.get(node.name) cmlenz@30: if val is None: cmlenz@30: val = getattr(__builtin__, node.name, None) cmlenz@30: return val cmlenz@30: cmlenz@30: def _visit_const(self, node, data): cmlenz@30: return node.value cmlenz@30: cmlenz@30: def _visit_dict(self, node, data): cmlenz@30: return dict([(self._visit(k, data), self._visit(v, data)) cmlenz@30: for k, v in node.items]) cmlenz@30: cmlenz@30: def _visit_tuple(self, node, data): cmlenz@30: return tuple([self._visit(n, data) for n in node.nodes]) cmlenz@30: cmlenz@30: def _visit_list(self, node, data): cmlenz@30: return [self._visit(n, data) for n in node.nodes] cmlenz@30: cmlenz@30: else: cmlenz@30: cmlenz@30: def evaluate(self, data): cmlenz@30: """Evaluate the expression against the given data dictionary. cmlenz@30: cmlenz@30: @param data: a mapping containing the data to evaluate against cmlenz@30: @return: the result of the evaluation cmlenz@30: """ cmlenz@30: if not self.ast: cmlenz@30: self.ast = compile(self.source, '?', 'eval', 0x400) cmlenz@30: return self._visit(self.ast, data) cmlenz@30: cmlenz@30: # AST traversal cmlenz@30: cmlenz@30: def _visit(self, node, data): cmlenz@30: v = self.__visitors.get(node.__class__) cmlenz@30: if not v: cmlenz@30: v = getattr(self, '_visit_%s' % node.__class__.__name__.lower()) cmlenz@30: self.__visitors[node.__class__] = v cmlenz@30: return v(node, data) cmlenz@30: cmlenz@30: def _visit_expression(self, node, data): cmlenz@30: return self._visit(node.body, data) cmlenz@30: cmlenz@30: # Functions & Accessors cmlenz@30: cmlenz@30: def _visit_attribute(self, node, data): cmlenz@30: obj = self._visit(node.value, data) cmlenz@30: if hasattr(obj, node.attr): cmlenz@30: return getattr(obj, node.attr) cmlenz@30: try: cmlenz@30: return obj[node.attr] cmlenz@30: except TypeError: cmlenz@30: return None cmlenz@30: cmlenz@30: def _visit_call(self, node, data): cmlenz@30: func = self._visit(node.func, data) cmlenz@30: if func is None: cmlenz@30: return None cmlenz@30: args = [self._visit(arg, data) for arg in node.args] cmlenz@30: kwargs = dict([(kwarg.arg, self._visit(kwarg.value, data)) cmlenz@30: for kwarg in node.keywords]) cmlenz@30: return func(*args, **kwargs) cmlenz@30: cmlenz@30: def _visit_subscript(self, node, data): cmlenz@30: obj = self._visit(node.value, data) cmlenz@30: if isinstance(node.slice, _ast.Slice): cmlenz@30: try: cmlenz@30: return obj[self._visit(lower, data): cmlenz@30: self._visit(upper, data): cmlenz@30: self._visit(step, data)] cmlenz@30: except (KeyError, IndexError, TypeError): cmlenz@30: pass cmlenz@30: else: cmlenz@30: index = self._visit(node.slice.value, data) cmlenz@30: try: cmlenz@30: return obj[index] cmlenz@30: except (KeyError, IndexError, TypeError): cmlenz@30: try: cmlenz@30: return getattr(obj, index) cmlenz@30: except (AttributeError, TypeError): cmlenz@30: pass cmlenz@30: return None cmlenz@30: cmlenz@30: # Operators cmlenz@30: cmlenz@30: _OP_MAP = {_ast.Add: operator.add, _ast.And: lambda l, r: l and r, cmlenz@30: _ast.Div: operator.div, _ast.Eq: operator.eq, cmlenz@30: _ast.FloorDiv: operator.floordiv, _ast.Gt: operator.gt, cmlenz@30: _ast.In: lambda l, r: operator.contains(r, l), cmlenz@30: _ast.Mod: operator.mod, _ast.Mult: operator.mul, cmlenz@30: _ast.Not: operator.not_, _ast.NotEq: operator.ne, cmlenz@30: _ast.Or: lambda l, r: l or r, _ast.Pow: operator.pow, cmlenz@30: _ast.Sub: operator.sub, _ast.UAdd: operator.pos, cmlenz@30: _ast.USub: operator.neg} cmlenz@30: cmlenz@30: def _visit_unaryop(self, node, data): cmlenz@30: return self._OP_MAP[node.op.__class__](self._visit(node.operand, data)) cmlenz@30: cmlenz@30: def _visit_binop(self, node, data): cmlenz@30: return self._OP_MAP[node.op.__class__](self._visit(node.left, data), cmlenz@30: self._visit(node.right, data)) cmlenz@30: cmlenz@30: def _visit_boolop(self, node, data): cmlenz@30: return reduce(self._OP_MAP[node.op.__class__], cmlenz@30: [self._visit(n, data) for n in node.values]) cmlenz@30: cmlenz@30: def _visit_compare(self, node, data): cmlenz@30: result = self._visit(node.left, data) cmlenz@30: ops = node.ops[:] cmlenz@30: ops.reverse() cmlenz@30: for op, rval in zip(ops, node.comparators): cmlenz@30: result = self._OP_MAP[op.__class__](result, cmlenz@30: self._visit(rval, data)) cmlenz@30: return result cmlenz@30: cmlenz@30: # Identifiers & Literals cmlenz@30: cmlenz@30: def _visit_dict(self, node, data): cmlenz@30: return dict([(self._visit(k, data), self._visit(v, data)) cmlenz@30: for k, v in zip(node.keys, node.values)]) cmlenz@30: cmlenz@30: def _visit_list(self, node, data): cmlenz@30: return [self._visit(n, data) for n in node.elts] cmlenz@30: cmlenz@30: def _visit_name(self, node, data): cmlenz@30: val = data.get(node.id) cmlenz@30: if val is None: cmlenz@30: val = getattr(__builtin__, node.id, None) cmlenz@30: return val cmlenz@30: cmlenz@30: def _visit_num(self, node, data): cmlenz@30: return node.n cmlenz@30: cmlenz@30: def _visit_str(self, node, data): cmlenz@30: return node.s cmlenz@30: cmlenz@30: def _visit_tuple(self, node, data): cmlenz@30: return tuple([self._visit(n, data) for n in node.elts])