1
|
1 # -*- coding: utf-8 -*-
|
|
2 #
|
|
3 # Copyright (C) 2006 Christopher Lenz
|
|
4 # All rights reserved.
|
|
5 #
|
|
6 # This software is licensed as described in the file COPYING, which
|
|
7 # you should have received as part of this distribution. The terms
|
|
8 # are also available at http://trac.edgewall.com/license.html.
|
|
9 #
|
|
10 # This software consists of voluntary contributions made by many
|
|
11 # individuals. For the exact contribution history, see the revision
|
|
12 # history and logs, available at http://projects.edgewall.com/trac/.
|
|
13
|
|
14 import __builtin__
|
|
15 import compiler
|
|
16 import operator
|
|
17
|
|
18 from markup.core import Stream
|
|
19
|
|
20 __all__ = ['Expression']
|
|
21
|
|
22
|
|
23 class Expression(object):
|
|
24 """Evaluates Python expressions used in templates.
|
|
25
|
|
26 >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'})
|
|
27 >>> Expression('test').evaluate(data)
|
|
28 'Foo'
|
|
29 >>> Expression('items[0]').evaluate(data)
|
|
30 1
|
|
31 >>> Expression('items[-1]').evaluate(data)
|
|
32 3
|
|
33 >>> Expression('dict["some"]').evaluate(data)
|
|
34 'thing'
|
|
35
|
|
36 Similar to e.g. Javascript, expressions in templates can use the dot
|
|
37 notation for attribute access to access items in mappings:
|
|
38
|
|
39 >>> Expression('dict.some').evaluate(data)
|
|
40 'thing'
|
|
41
|
|
42 This also works the other way around: item access can be used to access
|
|
43 any object attribute (meaning there's no use for `getattr()` in templates):
|
|
44
|
|
45 >>> class MyClass(object):
|
|
46 ... myattr = 'Bar'
|
|
47 >>> data = dict(mine=MyClass(), key='myattr')
|
|
48 >>> Expression('mine.myattr').evaluate(data)
|
|
49 'Bar'
|
|
50 >>> Expression('mine["myattr"]').evaluate(data)
|
|
51 'Bar'
|
|
52 >>> Expression('mine[key]').evaluate(data)
|
|
53 'Bar'
|
|
54
|
|
55 Most of the standard Python operators are also available to template
|
|
56 expressions. Bitwise operators (including inversion and shifting) are not
|
|
57 supported.
|
|
58
|
|
59 >>> Expression('1 + 1').evaluate(data)
|
|
60 2
|
|
61 >>> Expression('3 - 1').evaluate(data)
|
|
62 2
|
|
63 >>> Expression('1 * 2').evaluate(data)
|
|
64 2
|
|
65 >>> Expression('4 / 2').evaluate(data)
|
|
66 2
|
|
67 >>> Expression('4 // 3').evaluate(data)
|
|
68 1
|
|
69 >>> Expression('4 % 3').evaluate(data)
|
|
70 1
|
|
71 >>> Expression('2 ** 3').evaluate(data)
|
|
72 8
|
|
73 >>> Expression('not True').evaluate(data)
|
|
74 False
|
|
75 >>> Expression('True and False').evaluate(data)
|
|
76 False
|
|
77 >>> Expression('True or False').evaluate(data)
|
|
78 True
|
|
79 >>> Expression('1 == 3').evaluate(data)
|
|
80 False
|
|
81 >>> Expression('1 != 3 == 3').evaluate(data)
|
|
82 True
|
|
83
|
|
84 Built-in functions such as `len()` are also available in template
|
|
85 expressions:
|
|
86
|
|
87 >>> data = dict(items=[1, 2, 3])
|
|
88 >>> Expression('len(items)').evaluate(data)
|
|
89 3
|
|
90 """
|
|
91 __slots__ = ['source', 'ast']
|
|
92 __visitors = {}
|
|
93
|
|
94 def __init__(self, source):
|
|
95 self.source = source
|
|
96 self.ast = None
|
|
97
|
|
98 def evaluate(self, data, default=None):
|
|
99 if not self.ast:
|
|
100 self.ast = compiler.parse(self.source, 'eval')
|
|
101 retval = self._visit(self.ast.node, data)
|
|
102 if retval is not None:
|
|
103 return retval
|
|
104 return default
|
|
105
|
|
106 def __repr__(self):
|
|
107 return '<Expression "%s">' % self.source
|
|
108
|
|
109 # AST traversal
|
|
110
|
|
111 def _visit(self, node, data):
|
|
112 v = self.__visitors.get(node.__class__)
|
|
113 if not v:
|
|
114 v = getattr(self, '_visit_%s' % node.__class__.__name__.lower())
|
|
115 self.__visitors[node.__class__] = v
|
|
116 return v(node, data)
|
|
117
|
|
118 def _visit_expression(self, node, data):
|
|
119 for child in node.getChildNodes():
|
|
120 return self._visit(child, data)
|
|
121
|
|
122 # Functions & Accessors
|
|
123
|
|
124 def _visit_callfunc(self, node, data):
|
|
125 func = self._visit(node.node, data)
|
|
126 if func is None:
|
|
127 return None
|
|
128 args = [self._visit(arg, data) for arg in node.args
|
|
129 if not isinstance(arg, compiler.ast.Keyword)]
|
|
130 kwargs = dict([(arg.name, self._visit(arg.expr, data)) for arg
|
|
131 in node.args if isinstance(arg, compiler.ast.Keyword)])
|
|
132 return func(*args, **kwargs)
|
|
133
|
|
134 def _visit_getattr(self, node, data):
|
|
135 obj = self._visit(node.expr, data)
|
|
136 try:
|
|
137 return getattr(obj, node.attrname)
|
|
138 except AttributeError, e:
|
|
139 try:
|
|
140 return obj[node.attrname]
|
|
141 except (KeyError, TypeError):
|
|
142 return None
|
|
143
|
|
144 def _visit_slice(self, node, data):
|
|
145 obj = self._visit(node.expr, data)
|
|
146 lower = node.lower and self._visit(node.lower, data) or None
|
|
147 upper = node.upper and self._visit(node.upper, data) or None
|
|
148 return obj[lower:upper]
|
|
149
|
|
150 def _visit_subscript(self, node, data):
|
|
151 obj = self._visit(node.expr, data)
|
|
152 subs = map(lambda sub: self._visit(sub, data), node.subs)
|
|
153 if len(subs) == 1:
|
|
154 subs = subs[0]
|
|
155 try:
|
|
156 return obj[subs]
|
|
157 except (KeyError, IndexError, TypeError):
|
|
158 try:
|
|
159 return getattr(obj, subs)
|
|
160 except (AttributeError, TypeError):
|
|
161 return None
|
|
162
|
|
163 # Operators
|
|
164
|
|
165 def _visit_and(self, node, data):
|
|
166 return reduce(operator.and_, [self._visit(n, data) for n in node.nodes])
|
|
167
|
|
168 def _visit_or(self, node, data):
|
|
169 return reduce(operator.or_, [self._visit(n, data) for n in node.nodes])
|
|
170
|
|
171 _OP_MAP = {'==': operator.eq, '!=': operator.ne,
|
|
172 '<': operator.lt, '<=': operator.le,
|
|
173 '>': operator.gt, '>=': operator.ge,
|
|
174 'in': operator.contains}
|
|
175 def _visit_compare(self, node, data):
|
|
176 result = self._visit(node.expr, data)
|
|
177 ops = node.ops[:]
|
|
178 ops.reverse()
|
|
179 for op, rval in ops:
|
|
180 result = self._OP_MAP[op](result, self._visit(rval, data))
|
|
181 return result
|
|
182
|
|
183 def _visit_add(self, node, data):
|
|
184 return self._visit(node.left, data) + self._visit(node.right, data)
|
|
185
|
|
186 def _visit_div(self, node, data):
|
|
187 return self._visit(node.left, data) / self._visit(node.right, data)
|
|
188
|
|
189 def _visit_floordiv(self, node, data):
|
|
190 return self._visit(node.left, data) // self._visit(node.right, data)
|
|
191
|
|
192 def _visit_mod(self, node, data):
|
|
193 return self._visit(node.left, data) % self._visit(node.right, data)
|
|
194
|
|
195 def _visit_mul(self, node, data):
|
|
196 return self._visit(node.left, data) * self._visit(node.right, data)
|
|
197
|
|
198 def _visit_power(self, node, data):
|
|
199 return self._visit(node.left, data) ** self._visit(node.right, data)
|
|
200
|
|
201 def _visit_sub(self, node, data):
|
|
202 return self._visit(node.left, data) - self._visit(node.right, data)
|
|
203
|
|
204 def _visit_not(self, node, data):
|
|
205 return not self._visit(node.expr, data)
|
|
206
|
|
207 def _visit_unaryadd(self, node, data):
|
|
208 return +self._visit(node.expr, data)
|
|
209
|
|
210 def _visit_unarysub(self, node, data):
|
|
211 return -self._visit(node.expr, data)
|
|
212
|
|
213 # Identifiers & Literals
|
|
214
|
|
215 def _visit_name(self, node, data):
|
|
216 val = data.get(node.name)
|
|
217 if val is None:
|
|
218 val = getattr(__builtin__, node.name, None)
|
|
219 return val
|
|
220
|
|
221 def _visit_const(self, node, data):
|
|
222 return node.value
|
|
223
|
|
224 def _visit_dict(self, node, data):
|
|
225 return dict([(self._visit(k, data), self._visit(v, data))
|
|
226 for k, v in node.items])
|
|
227
|
|
228 def _visit_tuple(self, node, data):
|
|
229 return tuple([self._visit(n, data) for n in node.nodes])
|
|
230
|
|
231 def _visit_list(self, node, data):
|
|
232 return [self._visit(n, data) for n in node.nodes]
|