Mercurial > genshi > genshi-test
annotate markup/eval.py @ 21:eca77129518a
* Include paths are now interpreted relative to the path of the including template. Closes #3.
* The filename is now included as first item in the `pos` tuple of stream events.
* Simplified the "basic" example so that it actually ''is'' basic.
* Added a more complex example using nested relative includes in [source:/trunk/examples/includes/ examples/includes].
author | cmlenz |
---|---|
date | Tue, 20 Jun 2006 13:05:37 +0000 |
parents | ebe34f07d3eb |
children | b8456279c444 |
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 | |
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 | |
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
|
107 def evaluate(self, data): |
1 | 108 if not self.ast: |
109 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
|
110 return self._visit(self.ast.node, data) |
1 | 111 |
112 def __repr__(self): | |
113 return '<Expression "%s">' % self.source | |
114 | |
115 # AST traversal | |
116 | |
117 def _visit(self, node, data): | |
118 v = self.__visitors.get(node.__class__) | |
119 if not v: | |
120 v = getattr(self, '_visit_%s' % node.__class__.__name__.lower()) | |
121 self.__visitors[node.__class__] = v | |
122 return v(node, data) | |
123 | |
124 def _visit_expression(self, node, data): | |
125 for child in node.getChildNodes(): | |
126 return self._visit(child, data) | |
127 | |
128 # Functions & Accessors | |
129 | |
130 def _visit_callfunc(self, node, data): | |
131 func = self._visit(node.node, data) | |
132 if func is None: | |
133 return None | |
134 args = [self._visit(arg, data) for arg in node.args | |
135 if not isinstance(arg, compiler.ast.Keyword)] | |
136 kwargs = dict([(arg.name, self._visit(arg.expr, data)) for arg | |
137 in node.args if isinstance(arg, compiler.ast.Keyword)]) | |
138 return func(*args, **kwargs) | |
139 | |
140 def _visit_getattr(self, node, data): | |
141 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
|
142 if hasattr(obj, node.attrname): |
1 | 143 return getattr(obj, node.attrname) |
16 | 144 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
|
145 return obj[node.attrname] |
16 | 146 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
|
147 return None |
1 | 148 |
149 def _visit_slice(self, node, data): | |
150 obj = self._visit(node.expr, data) | |
151 lower = node.lower and self._visit(node.lower, data) or None | |
152 upper = node.upper and self._visit(node.upper, data) or None | |
153 return obj[lower:upper] | |
154 | |
155 def _visit_subscript(self, node, data): | |
156 obj = self._visit(node.expr, data) | |
157 subs = map(lambda sub: self._visit(sub, data), node.subs) | |
158 if len(subs) == 1: | |
159 subs = subs[0] | |
160 try: | |
161 return obj[subs] | |
162 except (KeyError, IndexError, TypeError): | |
163 try: | |
164 return getattr(obj, subs) | |
165 except (AttributeError, TypeError): | |
166 return None | |
167 | |
168 # Operators | |
169 | |
170 def _visit_and(self, node, data): | |
7 | 171 return reduce(lambda x, y: x and y, |
172 [self._visit(n, data) for n in node.nodes]) | |
1 | 173 |
174 def _visit_or(self, node, data): | |
7 | 175 return reduce(lambda x, y: x or y, |
176 [self._visit(n, data) for n in node.nodes]) | |
1 | 177 |
178 _OP_MAP = {'==': operator.eq, '!=': operator.ne, | |
179 '<': operator.lt, '<=': operator.le, | |
180 '>': operator.gt, '>=': operator.ge, | |
7 | 181 'in': lambda x, y: operator.contains(y, x), |
182 'not in': lambda x, y: not operator.contains(y, x)} | |
1 | 183 def _visit_compare(self, node, data): |
184 result = self._visit(node.expr, data) | |
185 ops = node.ops[:] | |
186 ops.reverse() | |
187 for op, rval in ops: | |
188 result = self._OP_MAP[op](result, self._visit(rval, data)) | |
189 return result | |
190 | |
191 def _visit_add(self, node, data): | |
192 return self._visit(node.left, data) + self._visit(node.right, data) | |
193 | |
194 def _visit_div(self, node, data): | |
195 return self._visit(node.left, data) / self._visit(node.right, data) | |
196 | |
197 def _visit_floordiv(self, node, data): | |
198 return self._visit(node.left, data) // self._visit(node.right, data) | |
199 | |
200 def _visit_mod(self, node, data): | |
201 return self._visit(node.left, data) % self._visit(node.right, data) | |
202 | |
203 def _visit_mul(self, node, data): | |
204 return self._visit(node.left, data) * self._visit(node.right, data) | |
205 | |
206 def _visit_power(self, node, data): | |
207 return self._visit(node.left, data) ** self._visit(node.right, data) | |
208 | |
209 def _visit_sub(self, node, data): | |
210 return self._visit(node.left, data) - self._visit(node.right, data) | |
211 | |
212 def _visit_not(self, node, data): | |
213 return not self._visit(node.expr, data) | |
214 | |
215 def _visit_unaryadd(self, node, data): | |
216 return +self._visit(node.expr, data) | |
217 | |
218 def _visit_unarysub(self, node, data): | |
219 return -self._visit(node.expr, data) | |
220 | |
221 # Identifiers & Literals | |
222 | |
223 def _visit_name(self, node, data): | |
224 val = data.get(node.name) | |
225 if val is None: | |
226 val = getattr(__builtin__, node.name, None) | |
227 return val | |
228 | |
229 def _visit_const(self, node, data): | |
230 return node.value | |
231 | |
232 def _visit_dict(self, node, data): | |
233 return dict([(self._visit(k, data), self._visit(v, data)) | |
234 for k, v in node.items]) | |
235 | |
236 def _visit_tuple(self, node, data): | |
237 return tuple([self._visit(n, data) for n in node.nodes]) | |
238 | |
239 def _visit_list(self, node, data): | |
240 return [self._visit(n, data) for n in node.nodes] |