Mercurial > genshi > genshi-test
annotate markup/eval.py @ 27:b8456279c444
* Fix the boilerplate in the Python source files.
* Some more docstrings and cosmetic fixes.
author | cmlenz |
---|---|
date | Wed, 28 Jun 2006 09:28:09 +0000 |
parents | ebe34f07d3eb |
children | 2ee9f28e16e5 |
rev | line source |
---|---|
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 | |
27 | 8 # are also available at http://markup.cmlenz.net/wiki/License. |
1 | 9 # |
10 # This software consists of voluntary contributions made by many | |
11 # individuals. For the exact contribution history, see the revision | |
27 | 12 # history and logs, available at http://markup.cmlenz.net/log/. |
13 | |
14 """Support for "safe" evaluation of Python expressions.""" | |
1 | 15 |
16 import __builtin__ | |
17 import compiler | |
18 import operator | |
19 | |
20 from markup.core import Stream | |
21 | |
22 __all__ = ['Expression'] | |
23 | |
24 | |
25 class Expression(object): | |
26 """Evaluates Python expressions used in templates. | |
27 | |
28 >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'}) | |
29 >>> Expression('test').evaluate(data) | |
30 'Foo' | |
31 >>> Expression('items[0]').evaluate(data) | |
32 1 | |
33 >>> Expression('items[-1]').evaluate(data) | |
34 3 | |
35 >>> Expression('dict["some"]').evaluate(data) | |
36 'thing' | |
37 | |
38 Similar to e.g. Javascript, expressions in templates can use the dot | |
39 notation for attribute access to access items in mappings: | |
40 | |
41 >>> Expression('dict.some').evaluate(data) | |
42 'thing' | |
43 | |
44 This also works the other way around: item access can be used to access | |
45 any object attribute (meaning there's no use for `getattr()` in templates): | |
46 | |
47 >>> class MyClass(object): | |
48 ... myattr = 'Bar' | |
49 >>> data = dict(mine=MyClass(), key='myattr') | |
50 >>> Expression('mine.myattr').evaluate(data) | |
51 'Bar' | |
52 >>> Expression('mine["myattr"]').evaluate(data) | |
53 'Bar' | |
54 >>> Expression('mine[key]').evaluate(data) | |
55 'Bar' | |
56 | |
57 Most of the standard Python operators are also available to template | |
58 expressions. Bitwise operators (including inversion and shifting) are not | |
59 supported. | |
60 | |
61 >>> Expression('1 + 1').evaluate(data) | |
62 2 | |
63 >>> Expression('3 - 1').evaluate(data) | |
64 2 | |
65 >>> Expression('1 * 2').evaluate(data) | |
66 2 | |
67 >>> Expression('4 / 2').evaluate(data) | |
68 2 | |
69 >>> Expression('4 // 3').evaluate(data) | |
70 1 | |
71 >>> Expression('4 % 3').evaluate(data) | |
72 1 | |
73 >>> Expression('2 ** 3').evaluate(data) | |
74 8 | |
75 >>> Expression('not True').evaluate(data) | |
76 False | |
77 >>> Expression('True and False').evaluate(data) | |
78 False | |
79 >>> Expression('True or False').evaluate(data) | |
80 True | |
81 >>> Expression('1 == 3').evaluate(data) | |
82 False | |
83 >>> Expression('1 != 3 == 3').evaluate(data) | |
84 True | |
7 | 85 >>> Expression('1 > 0').evaluate(data) |
86 True | |
87 >>> Expression('True and "Foo"').evaluate(data) | |
88 'Foo' | |
89 >>> data = dict(items=[1, 2, 3]) | |
90 >>> Expression('2 in items').evaluate(data) | |
91 True | |
92 >>> Expression('not 2 in items').evaluate(data) | |
93 False | |
1 | 94 |
95 Built-in functions such as `len()` are also available in template | |
96 expressions: | |
97 | |
98 >>> data = dict(items=[1, 2, 3]) | |
99 >>> Expression('len(items)').evaluate(data) | |
100 3 | |
101 """ | |
102 __slots__ = ['source', 'ast'] | |
103 __visitors = {} | |
104 | |
105 def __init__(self, source): | |
27 | 106 """Create the expression. |
107 | |
108 @param source: the expression as string | |
109 """ | |
1 | 110 self.source = source |
111 self.ast = None | |
112 | |
14
76b5d4b189e6
The `<py:match>` directive now protects itself against simple infinite recursion (see MatchDirective), while still allowing recursion in general.
cmlenz
parents:
7
diff
changeset
|
113 def evaluate(self, data): |
27 | 114 """Evaluate the expression against the given data dictionary. |
115 | |
116 @param data: a mapping containing the data to evaluate against | |
117 @return: the result of the evaluation | |
118 """ | |
1 | 119 if not self.ast: |
120 self.ast = compiler.parse(self.source, 'eval') | |
14
76b5d4b189e6
The `<py:match>` directive now protects itself against simple infinite recursion (see MatchDirective), while still allowing recursion in general.
cmlenz
parents:
7
diff
changeset
|
121 return self._visit(self.ast.node, data) |
1 | 122 |
123 def __repr__(self): | |
124 return '<Expression "%s">' % self.source | |
125 | |
126 # AST traversal | |
127 | |
128 def _visit(self, node, data): | |
129 v = self.__visitors.get(node.__class__) | |
130 if not v: | |
131 v = getattr(self, '_visit_%s' % node.__class__.__name__.lower()) | |
132 self.__visitors[node.__class__] = v | |
133 return v(node, data) | |
134 | |
135 def _visit_expression(self, node, data): | |
136 for child in node.getChildNodes(): | |
137 return self._visit(child, data) | |
138 | |
139 # Functions & Accessors | |
140 | |
141 def _visit_callfunc(self, node, data): | |
142 func = self._visit(node.node, data) | |
143 if func is None: | |
144 return None | |
145 args = [self._visit(arg, data) for arg in node.args | |
146 if not isinstance(arg, compiler.ast.Keyword)] | |
147 kwargs = dict([(arg.name, self._visit(arg.expr, data)) for arg | |
148 in node.args if isinstance(arg, compiler.ast.Keyword)]) | |
149 return func(*args, **kwargs) | |
150 | |
151 def _visit_getattr(self, node, data): | |
152 obj = self._visit(node.expr, data) | |
14
76b5d4b189e6
The `<py:match>` directive now protects itself against simple infinite recursion (see MatchDirective), while still allowing recursion in general.
cmlenz
parents:
7
diff
changeset
|
153 if hasattr(obj, node.attrname): |
1 | 154 return getattr(obj, node.attrname) |
16 | 155 try: |
14
76b5d4b189e6
The `<py:match>` directive now protects itself against simple infinite recursion (see MatchDirective), while still allowing recursion in general.
cmlenz
parents:
7
diff
changeset
|
156 return obj[node.attrname] |
16 | 157 except TypeError: |
14
76b5d4b189e6
The `<py:match>` directive now protects itself against simple infinite recursion (see MatchDirective), while still allowing recursion in general.
cmlenz
parents:
7
diff
changeset
|
158 return None |
1 | 159 |
160 def _visit_slice(self, node, data): | |
161 obj = self._visit(node.expr, data) | |
162 lower = node.lower and self._visit(node.lower, data) or None | |
163 upper = node.upper and self._visit(node.upper, data) or None | |
164 return obj[lower:upper] | |
165 | |
166 def _visit_subscript(self, node, data): | |
167 obj = self._visit(node.expr, data) | |
168 subs = map(lambda sub: self._visit(sub, data), node.subs) | |
169 if len(subs) == 1: | |
170 subs = subs[0] | |
171 try: | |
172 return obj[subs] | |
173 except (KeyError, IndexError, TypeError): | |
174 try: | |
175 return getattr(obj, subs) | |
176 except (AttributeError, TypeError): | |
177 return None | |
178 | |
179 # Operators | |
180 | |
181 def _visit_and(self, node, data): | |
7 | 182 return reduce(lambda x, y: x and y, |
183 [self._visit(n, data) for n in node.nodes]) | |
1 | 184 |
185 def _visit_or(self, node, data): | |
7 | 186 return reduce(lambda x, y: x or y, |
187 [self._visit(n, data) for n in node.nodes]) | |
1 | 188 |
189 _OP_MAP = {'==': operator.eq, '!=': operator.ne, | |
190 '<': operator.lt, '<=': operator.le, | |
191 '>': operator.gt, '>=': operator.ge, | |
7 | 192 'in': lambda x, y: operator.contains(y, x), |
193 'not in': lambda x, y: not operator.contains(y, x)} | |
1 | 194 def _visit_compare(self, node, data): |
195 result = self._visit(node.expr, data) | |
196 ops = node.ops[:] | |
197 ops.reverse() | |
198 for op, rval in ops: | |
199 result = self._OP_MAP[op](result, self._visit(rval, data)) | |
200 return result | |
201 | |
202 def _visit_add(self, node, data): | |
203 return self._visit(node.left, data) + self._visit(node.right, data) | |
204 | |
205 def _visit_div(self, node, data): | |
206 return self._visit(node.left, data) / self._visit(node.right, data) | |
207 | |
208 def _visit_floordiv(self, node, data): | |
209 return self._visit(node.left, data) // self._visit(node.right, data) | |
210 | |
211 def _visit_mod(self, node, data): | |
212 return self._visit(node.left, data) % self._visit(node.right, data) | |
213 | |
214 def _visit_mul(self, node, data): | |
215 return self._visit(node.left, data) * self._visit(node.right, data) | |
216 | |
217 def _visit_power(self, node, data): | |
218 return self._visit(node.left, data) ** self._visit(node.right, data) | |
219 | |
220 def _visit_sub(self, node, data): | |
221 return self._visit(node.left, data) - self._visit(node.right, data) | |
222 | |
223 def _visit_not(self, node, data): | |
224 return not self._visit(node.expr, data) | |
225 | |
226 def _visit_unaryadd(self, node, data): | |
227 return +self._visit(node.expr, data) | |
228 | |
229 def _visit_unarysub(self, node, data): | |
230 return -self._visit(node.expr, data) | |
231 | |
232 # Identifiers & Literals | |
233 | |
234 def _visit_name(self, node, data): | |
235 val = data.get(node.name) | |
236 if val is None: | |
237 val = getattr(__builtin__, node.name, None) | |
238 return val | |
239 | |
240 def _visit_const(self, node, data): | |
241 return node.value | |
242 | |
243 def _visit_dict(self, node, data): | |
244 return dict([(self._visit(k, data), self._visit(v, data)) | |
245 for k, v in node.items]) | |
246 | |
247 def _visit_tuple(self, node, data): | |
248 return tuple([self._visit(n, data) for n in node.nodes]) | |
249 | |
250 def _visit_list(self, node, data): | |
251 return [self._visit(n, data) for n in node.nodes] |