diff markup/eval.py @ 1:5479aae32f5a trunk

Initial import.
author cmlenz
date Sat, 03 Jun 2006 07:16:01 +0000
parents
children 798ebd2425dc
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/markup/eval.py
@@ -0,0 +1,232 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006 Christopher Lenz
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.com/license.html.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://projects.edgewall.com/trac/.
+
+import __builtin__
+import compiler
+import operator
+
+from markup.core import Stream
+
+__all__ = ['Expression']
+
+
+class Expression(object):
+    """Evaluates Python expressions used in templates.
+
+    >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'})
+    >>> Expression('test').evaluate(data)
+    'Foo'
+    >>> Expression('items[0]').evaluate(data)
+    1
+    >>> Expression('items[-1]').evaluate(data)
+    3
+    >>> Expression('dict["some"]').evaluate(data)
+    'thing'
+    
+    Similar to e.g. Javascript, expressions in templates can use the dot
+    notation for attribute access to access items in mappings:
+    
+    >>> Expression('dict.some').evaluate(data)
+    'thing'
+    
+    This also works the other way around: item access can be used to access
+    any object attribute (meaning there's no use for `getattr()` in templates):
+    
+    >>> class MyClass(object):
+    ...     myattr = 'Bar'
+    >>> data = dict(mine=MyClass(), key='myattr')
+    >>> Expression('mine.myattr').evaluate(data)
+    'Bar'
+    >>> Expression('mine["myattr"]').evaluate(data)
+    'Bar'
+    >>> Expression('mine[key]').evaluate(data)
+    'Bar'
+    
+    Most of the standard Python operators are also available to template
+    expressions. Bitwise operators (including inversion and shifting) are not
+    supported.
+    
+    >>> Expression('1 + 1').evaluate(data)
+    2
+    >>> Expression('3 - 1').evaluate(data)
+    2
+    >>> Expression('1 * 2').evaluate(data)
+    2
+    >>> Expression('4 / 2').evaluate(data)
+    2
+    >>> Expression('4 // 3').evaluate(data)
+    1
+    >>> Expression('4 % 3').evaluate(data)
+    1
+    >>> Expression('2 ** 3').evaluate(data)
+    8
+    >>> Expression('not True').evaluate(data)
+    False
+    >>> Expression('True and False').evaluate(data)
+    False
+    >>> Expression('True or False').evaluate(data)
+    True
+    >>> Expression('1 == 3').evaluate(data)
+    False
+    >>> Expression('1 != 3 == 3').evaluate(data)
+    True
+    
+    Built-in functions such as `len()` are also available in template
+    expressions:
+    
+    >>> data = dict(items=[1, 2, 3])
+    >>> Expression('len(items)').evaluate(data)
+    3
+    """
+    __slots__ = ['source', 'ast']
+    __visitors = {}
+
+    def __init__(self, source):
+        self.source = source
+        self.ast = None
+
+    def evaluate(self, data, default=None):
+        if not self.ast:
+            self.ast = compiler.parse(self.source, 'eval')
+        retval = self._visit(self.ast.node, data)
+        if retval is not None:
+            return retval
+        return default
+
+    def __repr__(self):
+        return '<Expression "%s">' % self.source
+
+    # AST traversal
+
+    def _visit(self, node, data):
+        v = self.__visitors.get(node.__class__)
+        if not v:
+            v = getattr(self, '_visit_%s' % node.__class__.__name__.lower())
+            self.__visitors[node.__class__] = v
+        return v(node, data)
+
+    def _visit_expression(self, node, data):
+        for child in node.getChildNodes():
+            return self._visit(child, data)
+
+    # Functions & Accessors
+
+    def _visit_callfunc(self, node, data):
+        func = self._visit(node.node, data)
+        if func is None:
+            return None
+        args = [self._visit(arg, data) for arg in node.args
+                if not isinstance(arg, compiler.ast.Keyword)]
+        kwargs = dict([(arg.name, self._visit(arg.expr, data)) for arg
+                       in node.args if isinstance(arg, compiler.ast.Keyword)])
+        return func(*args, **kwargs)
+
+    def _visit_getattr(self, node, data):
+        obj = self._visit(node.expr, data)
+        try:
+            return getattr(obj, node.attrname)
+        except AttributeError, e:
+            try:
+                return obj[node.attrname]
+            except (KeyError, TypeError):
+                return None
+
+    def _visit_slice(self, node, data):
+        obj = self._visit(node.expr, data)
+        lower = node.lower and self._visit(node.lower, data) or None
+        upper = node.upper and self._visit(node.upper, data) or None
+        return obj[lower:upper]
+
+    def _visit_subscript(self, node, data):
+        obj = self._visit(node.expr, data)
+        subs = map(lambda sub: self._visit(sub, data), node.subs)
+        if len(subs) == 1:
+            subs = subs[0]
+        try:
+            return obj[subs]
+        except (KeyError, IndexError, TypeError):
+            try:
+                return getattr(obj, subs)
+            except (AttributeError, TypeError):
+                return None
+
+    # Operators
+
+    def _visit_and(self, node, data):
+        return reduce(operator.and_, [self._visit(n, data) for n in node.nodes])
+
+    def _visit_or(self, node, data):
+        return reduce(operator.or_, [self._visit(n, data) for n in node.nodes])
+
+    _OP_MAP = {'==': operator.eq, '!=': operator.ne,
+               '<':  operator.lt, '<=': operator.le,
+               '>':  operator.gt, '>=': operator.ge,
+               'in': operator.contains}
+    def _visit_compare(self, node, data):
+        result = self._visit(node.expr, data)
+        ops = node.ops[:]
+        ops.reverse()
+        for op, rval in ops:
+            result = self._OP_MAP[op](result, self._visit(rval, data))
+        return result
+
+    def _visit_add(self, node, data):
+        return self._visit(node.left, data) + self._visit(node.right, data)
+
+    def _visit_div(self, node, data):
+        return self._visit(node.left, data) / self._visit(node.right, data)
+
+    def _visit_floordiv(self, node, data):
+        return self._visit(node.left, data) // self._visit(node.right, data)
+
+    def _visit_mod(self, node, data):
+        return self._visit(node.left, data) % self._visit(node.right, data)
+
+    def _visit_mul(self, node, data):
+        return self._visit(node.left, data) * self._visit(node.right, data)
+
+    def _visit_power(self, node, data):
+        return self._visit(node.left, data) ** self._visit(node.right, data)
+
+    def _visit_sub(self, node, data):
+        return self._visit(node.left, data) - self._visit(node.right, data)
+
+    def _visit_not(self, node, data):
+        return not self._visit(node.expr, data)
+
+    def _visit_unaryadd(self, node, data):
+        return +self._visit(node.expr, data)
+
+    def _visit_unarysub(self, node, data):
+        return -self._visit(node.expr, data)
+
+    # Identifiers & Literals
+
+    def _visit_name(self, node, data):
+        val = data.get(node.name)
+        if val is None:
+            val = getattr(__builtin__, node.name, None)
+        return val
+
+    def _visit_const(self, node, data):
+        return node.value
+
+    def _visit_dict(self, node, data):
+        return dict([(self._visit(k, data), self._visit(v, data))
+                     for k, v in node.items])
+
+    def _visit_tuple(self, node, data):
+        return tuple([self._visit(n, data) for n in node.nodes])
+
+    def _visit_list(self, node, data):
+        return [self._visit(n, data) for n in node.nodes]
Copyright (C) 2012-2017 Edgewall Software