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
|
7
|
83 >>> Expression('1 > 0').evaluate(data)
|
|
84 True
|
|
85 >>> Expression('True and "Foo"').evaluate(data)
|
|
86 'Foo'
|
|
87 >>> data = dict(items=[1, 2, 3])
|
|
88 >>> Expression('2 in items').evaluate(data)
|
|
89 True
|
|
90 >>> Expression('not 2 in items').evaluate(data)
|
|
91 False
|
1
|
92
|
|
93 Built-in functions such as `len()` are also available in template
|
|
94 expressions:
|
|
95
|
|
96 >>> data = dict(items=[1, 2, 3])
|
|
97 >>> Expression('len(items)').evaluate(data)
|
|
98 3
|
|
99 """
|
|
100 __slots__ = ['source', 'ast']
|
|
101 __visitors = {}
|
|
102
|
|
103 def __init__(self, source):
|
|
104 self.source = source
|
|
105 self.ast = None
|
|
106
|
|
107 def evaluate(self, data, default=None):
|
|
108 if not self.ast:
|
|
109 self.ast = compiler.parse(self.source, 'eval')
|
|
110 retval = self._visit(self.ast.node, data)
|
|
111 if retval is not None:
|
|
112 return retval
|
|
113 return default
|
|
114
|
|
115 def __repr__(self):
|
|
116 return '<Expression "%s">' % self.source
|
|
117
|
|
118 # AST traversal
|
|
119
|
|
120 def _visit(self, node, data):
|
|
121 v = self.__visitors.get(node.__class__)
|
|
122 if not v:
|
|
123 v = getattr(self, '_visit_%s' % node.__class__.__name__.lower())
|
|
124 self.__visitors[node.__class__] = v
|
|
125 return v(node, data)
|
|
126
|
|
127 def _visit_expression(self, node, data):
|
|
128 for child in node.getChildNodes():
|
|
129 return self._visit(child, data)
|
|
130
|
|
131 # Functions & Accessors
|
|
132
|
|
133 def _visit_callfunc(self, node, data):
|
|
134 func = self._visit(node.node, data)
|
|
135 if func is None:
|
|
136 return None
|
|
137 args = [self._visit(arg, data) for arg in node.args
|
|
138 if not isinstance(arg, compiler.ast.Keyword)]
|
|
139 kwargs = dict([(arg.name, self._visit(arg.expr, data)) for arg
|
|
140 in node.args if isinstance(arg, compiler.ast.Keyword)])
|
|
141 return func(*args, **kwargs)
|
|
142
|
|
143 def _visit_getattr(self, node, data):
|
|
144 obj = self._visit(node.expr, data)
|
|
145 try:
|
|
146 return getattr(obj, node.attrname)
|
|
147 except AttributeError, e:
|
|
148 try:
|
|
149 return obj[node.attrname]
|
|
150 except (KeyError, TypeError):
|
|
151 return None
|
|
152
|
|
153 def _visit_slice(self, node, data):
|
|
154 obj = self._visit(node.expr, data)
|
|
155 lower = node.lower and self._visit(node.lower, data) or None
|
|
156 upper = node.upper and self._visit(node.upper, data) or None
|
|
157 return obj[lower:upper]
|
|
158
|
|
159 def _visit_subscript(self, node, data):
|
|
160 obj = self._visit(node.expr, data)
|
|
161 subs = map(lambda sub: self._visit(sub, data), node.subs)
|
|
162 if len(subs) == 1:
|
|
163 subs = subs[0]
|
|
164 try:
|
|
165 return obj[subs]
|
|
166 except (KeyError, IndexError, TypeError):
|
|
167 try:
|
|
168 return getattr(obj, subs)
|
|
169 except (AttributeError, TypeError):
|
|
170 return None
|
|
171
|
|
172 # Operators
|
|
173
|
|
174 def _visit_and(self, node, data):
|
7
|
175 return reduce(lambda x, y: x and y,
|
|
176 [self._visit(n, data) for n in node.nodes])
|
1
|
177
|
|
178 def _visit_or(self, node, data):
|
7
|
179 return reduce(lambda x, y: x or y,
|
|
180 [self._visit(n, data) for n in node.nodes])
|
1
|
181
|
|
182 _OP_MAP = {'==': operator.eq, '!=': operator.ne,
|
|
183 '<': operator.lt, '<=': operator.le,
|
|
184 '>': operator.gt, '>=': operator.ge,
|
7
|
185 'in': lambda x, y: operator.contains(y, x),
|
|
186 'not in': lambda x, y: not operator.contains(y, x)}
|
1
|
187 def _visit_compare(self, node, data):
|
|
188 result = self._visit(node.expr, data)
|
|
189 ops = node.ops[:]
|
|
190 ops.reverse()
|
|
191 for op, rval in ops:
|
|
192 result = self._OP_MAP[op](result, self._visit(rval, data))
|
|
193 return result
|
|
194
|
|
195 def _visit_add(self, node, data):
|
|
196 return self._visit(node.left, data) + self._visit(node.right, data)
|
|
197
|
|
198 def _visit_div(self, node, data):
|
|
199 return self._visit(node.left, data) / self._visit(node.right, data)
|
|
200
|
|
201 def _visit_floordiv(self, node, data):
|
|
202 return self._visit(node.left, data) // self._visit(node.right, data)
|
|
203
|
|
204 def _visit_mod(self, node, data):
|
|
205 return self._visit(node.left, data) % self._visit(node.right, data)
|
|
206
|
|
207 def _visit_mul(self, node, data):
|
|
208 return self._visit(node.left, data) * self._visit(node.right, data)
|
|
209
|
|
210 def _visit_power(self, node, data):
|
|
211 return self._visit(node.left, data) ** self._visit(node.right, data)
|
|
212
|
|
213 def _visit_sub(self, node, data):
|
|
214 return self._visit(node.left, data) - self._visit(node.right, data)
|
|
215
|
|
216 def _visit_not(self, node, data):
|
|
217 return not self._visit(node.expr, data)
|
|
218
|
|
219 def _visit_unaryadd(self, node, data):
|
|
220 return +self._visit(node.expr, data)
|
|
221
|
|
222 def _visit_unarysub(self, node, data):
|
|
223 return -self._visit(node.expr, data)
|
|
224
|
|
225 # Identifiers & Literals
|
|
226
|
|
227 def _visit_name(self, node, data):
|
|
228 val = data.get(node.name)
|
|
229 if val is None:
|
|
230 val = getattr(__builtin__, node.name, None)
|
|
231 return val
|
|
232
|
|
233 def _visit_const(self, node, data):
|
|
234 return node.value
|
|
235
|
|
236 def _visit_dict(self, node, data):
|
|
237 return dict([(self._visit(k, data), self._visit(v, data))
|
|
238 for k, v in node.items])
|
|
239
|
|
240 def _visit_tuple(self, node, data):
|
|
241 return tuple([self._visit(n, data) for n in node.nodes])
|
|
242
|
|
243 def _visit_list(self, node, data):
|
|
244 return [self._visit(n, data) for n in node.nodes]
|