Mercurial > genshi > mirror
annotate markup/template.py @ 145:47bbd9d2a5af trunk
* Fix error in expression evaluation when the expression evaluates to an iterable that does not produce event tuples.
* The first location step in path expressions longer assumes the `descendant::` axis, but rather the `child::` axis.
* Import cleanups.
author | cmlenz |
---|---|
date | Tue, 15 Aug 2006 09:52:47 +0000 |
parents | c1f4390d50f8 |
children | 537f819c547b |
rev | line source |
---|---|
1 | 1 # -*- coding: utf-8 -*- |
2 # | |
66
59eb24184e9c
Switch copyright to Edgewall and URLs to markup.edgewall.org.
cmlenz
parents:
65
diff
changeset
|
3 # Copyright (C) 2006 Edgewall Software |
1 | 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 | |
66
59eb24184e9c
Switch copyright to Edgewall and URLs to markup.edgewall.org.
cmlenz
parents:
65
diff
changeset
|
8 # are also available at http://markup.edgewall.org/wiki/License. |
1 | 9 # |
10 # This software consists of voluntary contributions made by many | |
11 # individuals. For the exact contribution history, see the revision | |
66
59eb24184e9c
Switch copyright to Edgewall and URLs to markup.edgewall.org.
cmlenz
parents:
65
diff
changeset
|
12 # history and logs, available at http://markup.edgewall.org/log/. |
1 | 13 |
82 | 14 """Implementation of the template engine.""" |
1 | 15 |
70
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
16 try: |
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
17 from collections import deque |
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
18 except ImportError: |
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
19 class deque(list): |
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
20 def appendleft(self, x): self.insert(0, x) |
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
21 def popleft(self): return self.pop(0) |
1 | 22 import compiler |
23 import os | |
21
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
24 import posixpath |
1 | 25 import re |
26 from StringIO import StringIO | |
27 | |
145
47bbd9d2a5af
* Fix error in expression evaluation when the expression evaluates to an iterable that does not produce event tuples.
cmlenz
parents:
140
diff
changeset
|
28 from markup.core import Attributes, Namespace, Stream, StreamEventKind, _ensure |
47bbd9d2a5af
* Fix error in expression evaluation when the expression evaluates to an iterable that does not produce event tuples.
cmlenz
parents:
140
diff
changeset
|
29 from markup.core import START, END, START_NS, END_NS, TEXT, COMMENT |
1 | 30 from markup.eval import Expression |
69 | 31 from markup.input import XMLParser |
14
c7d33e0c9839
The `<py:match>` directive now protects itself against simple infinite recursion (see MatchDirective), while still allowing recursion in general.
cmlenz
parents:
13
diff
changeset
|
32 from markup.path import Path |
1 | 33 |
34 __all__ = ['Context', 'BadDirectiveError', 'TemplateError', | |
35 'TemplateSyntaxError', 'TemplateNotFound', 'Template', | |
36 'TemplateLoader'] | |
37 | |
38 | |
39 class TemplateError(Exception): | |
40 """Base exception class for errors related to template processing.""" | |
41 | |
42 | |
43 class TemplateSyntaxError(TemplateError): | |
44 """Exception raised when an expression in a template causes a Python syntax | |
45 error.""" | |
46 | |
47 def __init__(self, message, filename='<string>', lineno=-1, offset=-1): | |
48 if isinstance(message, SyntaxError) and message.lineno is not None: | |
49 message = str(message).replace(' (line %d)' % message.lineno, '') | |
80 | 50 message = '%s (%s, line %d)' % (message, filename, lineno) |
1 | 51 TemplateError.__init__(self, message) |
52 self.filename = filename | |
53 self.lineno = lineno | |
54 self.offset = offset | |
55 | |
56 | |
57 class BadDirectiveError(TemplateSyntaxError): | |
58 """Exception raised when an unknown directive is encountered when parsing | |
59 a template. | |
60 | |
61 An unknown directive is any attribute using the namespace for directives, | |
62 with a local name that doesn't match any registered directive. | |
63 """ | |
64 | |
65 def __init__(self, name, filename='<string>', lineno=-1): | |
80 | 66 msg = 'bad directive "%s" (%s, line %d)' % (name.localname, filename, |
67 lineno) | |
68 TemplateSyntaxError.__init__(self, msg, filename, lineno) | |
1 | 69 |
70 | |
71 class TemplateNotFound(TemplateError): | |
72 """Exception raised when a specific template file could not be found.""" | |
73 | |
74 def __init__(self, name, search_path): | |
75 TemplateError.__init__(self, 'Template "%s" not found' % name) | |
76 self.search_path = search_path | |
77 | |
78 | |
79 class Context(object): | |
95
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
80 """Container for template input data. |
1 | 81 |
95
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
82 A context provides a stack of scopes (represented by dictionaries). |
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
83 |
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
84 Template directives such as loops can push a new scope on the stack with |
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
85 data that should only be available inside the loop. When the loop |
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
86 terminates, that scope can get popped off the stack again. |
1 | 87 |
88 >>> ctxt = Context(one='foo', other=1) | |
89 >>> ctxt.get('one') | |
90 'foo' | |
91 >>> ctxt.get('other') | |
92 1 | |
95
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
93 >>> ctxt.push(dict(one='frost')) |
1 | 94 >>> ctxt.get('one') |
95 'frost' | |
96 >>> ctxt.get('other') | |
97 1 | |
98 >>> ctxt.pop() | |
95
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
99 {'one': 'frost'} |
1 | 100 >>> ctxt.get('one') |
101 'foo' | |
102 """ | |
103 | |
104 def __init__(self, **data): | |
70
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
105 self.frames = deque([data]) |
95
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
106 self.pop = self.frames.popleft |
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
107 self.push = self.frames.appendleft |
1 | 108 |
109 def __repr__(self): | |
70
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
110 return repr(self.frames) |
1 | 111 |
112 def __setitem__(self, key, value): | |
95
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
113 """Set a variable in the current scope.""" |
70
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
114 self.frames[0][key] = value |
1 | 115 |
116 def get(self, key): | |
95
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
117 """Get a variable's value, starting at the current scope and going |
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
118 upward. |
29
ab8703fa68b8
* Minor simplification of template directives: they no longer get passed the template instance and the position, as no directive was actually using
cmlenz
parents:
27
diff
changeset
|
119 """ |
70
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
120 for frame in self.frames: |
1 | 121 if key in frame: |
122 return frame[key] | |
70
dd73921530e8
Use `collections.deque` for the template context stack on Python 2.4, which improves performance if there are many context frame pop/push operations.
cmlenz
parents:
69
diff
changeset
|
123 __getitem__ = get |
1 | 124 |
95
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
125 def push(self, data): |
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
126 """Push a new scope on the stack.""" |
1 | 127 |
128 def pop(self): | |
95
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
129 """Pop the top-most scope from the stack.""" |
1 | 130 |
131 | |
132 class Directive(object): | |
133 """Abstract base class for template directives. | |
134 | |
54 | 135 A directive is basically a callable that takes three positional arguments: |
136 `ctxt` is the template data context, `stream` is an iterable over the | |
137 events that the directive applies to, and `directives` is is a list of | |
138 other directives on the same stream that need to be applied. | |
1 | 139 |
140 Directives can be "anonymous" or "registered". Registered directives can be | |
141 applied by the template author using an XML attribute with the | |
142 corresponding name in the template. Such directives should be subclasses of | |
31 | 143 this base class that can be instantiated with the value of the directive |
144 attribute as parameter. | |
1 | 145 |
146 Anonymous directives are simply functions conforming to the protocol | |
147 described above, and can only be applied programmatically (for example by | |
148 template filters). | |
149 """ | |
150 __slots__ = ['expr'] | |
151 | |
81
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
152 def __init__(self, value, filename=None, lineno=-1, offset=-1): |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
153 try: |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
154 self.expr = value and Expression(value, filename, lineno) or None |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
155 except SyntaxError, err: |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
156 raise TemplateSyntaxError(err, filename, lineno, |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
157 offset + (err.offset or 0)) |
1 | 158 |
53
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
159 def __call__(self, stream, ctxt, directives): |
1 | 160 raise NotImplementedError |
161 | |
162 def __repr__(self): | |
163 expr = '' | |
164 if self.expr is not None: | |
165 expr = ' "%s"' % self.expr.source | |
166 return '<%s%s>' % (self.__class__.__name__, expr) | |
167 | |
78
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
168 |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
169 def _apply_directives(stream, ctxt, directives): |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
170 if directives: |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
171 stream = directives[0](iter(stream), ctxt, directives[1:]) |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
172 return stream |
53
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
173 |
1 | 174 |
175 class AttrsDirective(Directive): | |
176 """Implementation of the `py:attrs` template directive. | |
177 | |
178 The value of the `py:attrs` attribute should be a dictionary. The keys and | |
179 values of that dictionary will be added as attributes to the element: | |
180 | |
181 >>> ctxt = Context(foo={'class': 'collapse'}) | |
61 | 182 >>> tmpl = Template('''<ul xmlns:py="http://markup.edgewall.org/"> |
1 | 183 ... <li py:attrs="foo">Bar</li> |
184 ... </ul>''') | |
185 >>> print tmpl.generate(ctxt) | |
186 <ul> | |
187 <li class="collapse">Bar</li> | |
188 </ul> | |
189 | |
190 If the value evaluates to `None` (or any other non-truth value), no | |
191 attributes are added: | |
192 | |
193 >>> ctxt = Context(foo=None) | |
194 >>> print tmpl.generate(ctxt) | |
195 <ul> | |
196 <li>Bar</li> | |
197 </ul> | |
198 """ | |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
199 __slots__ = [] |
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
200 |
53
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
201 def __call__(self, stream, ctxt, directives): |
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
202 def _generate(): |
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
203 kind, (tag, attrib), pos = stream.next() |
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
204 attrs = self.expr.evaluate(ctxt) |
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
205 if attrs: |
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
206 attrib = Attributes(attrib[:]) |
77
f5ec6d4a61e4
* Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents:
75
diff
changeset
|
207 if isinstance(attrs, Stream): |
f5ec6d4a61e4
* Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents:
75
diff
changeset
|
208 try: |
f5ec6d4a61e4
* Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents:
75
diff
changeset
|
209 attrs = iter(attrs).next() |
f5ec6d4a61e4
* Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents:
75
diff
changeset
|
210 except StopIteration: |
f5ec6d4a61e4
* Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents:
75
diff
changeset
|
211 attrs = [] |
f5ec6d4a61e4
* Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents:
75
diff
changeset
|
212 elif not isinstance(attrs, list): # assume it's a dict |
53
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
213 attrs = attrs.items() |
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
214 for name, value in attrs: |
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
215 if value is None: |
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
216 attrib.remove(name) |
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
217 else: |
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
218 attrib.set(name, unicode(value).strip()) |
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
219 yield kind, (tag, attrib), pos |
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
220 for event in stream: |
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
221 yield event |
77
f5ec6d4a61e4
* Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents:
75
diff
changeset
|
222 |
78
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
223 return _apply_directives(_generate(), ctxt, directives) |
1 | 224 |
225 | |
226 class ContentDirective(Directive): | |
227 """Implementation of the `py:content` template directive. | |
228 | |
229 This directive replaces the content of the element with the result of | |
230 evaluating the value of the `py:content` attribute: | |
231 | |
232 >>> ctxt = Context(bar='Bye') | |
61 | 233 >>> tmpl = Template('''<ul xmlns:py="http://markup.edgewall.org/"> |
1 | 234 ... <li py:content="bar">Hello</li> |
235 ... </ul>''') | |
236 >>> print tmpl.generate(ctxt) | |
237 <ul> | |
238 <li>Bye</li> | |
239 </ul> | |
240 """ | |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
241 __slots__ = [] |
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
242 |
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
243 def __call__(self, stream, ctxt, directives): |
53
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
244 def _generate(): |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
245 kind, data, pos = stream.next() |
101 | 246 if kind is START: |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
247 yield kind, data, pos # emit start tag |
69 | 248 yield EXPR, self.expr, pos |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
249 previous = stream.next() |
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
250 for event in stream: |
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
251 previous = event |
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
252 if previous is not None: |
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
253 yield previous |
87
1b874f032bde
Fix some problems in expression evaluation by transforming the AST and compiling that to bytecode, instead of generating bytecode directly. Invalidates #13.
cmlenz
parents:
82
diff
changeset
|
254 |
78
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
255 return _apply_directives(_generate(), ctxt, directives) |
1 | 256 |
257 | |
258 class DefDirective(Directive): | |
259 """Implementation of the `py:def` template directive. | |
260 | |
261 This directive can be used to create "Named Template Functions", which | |
262 are template snippets that are not actually output during normal | |
263 processing, but rather can be expanded from expressions in other places | |
264 in the template. | |
265 | |
266 A named template function can be used just like a normal Python function | |
267 from template expressions: | |
268 | |
269 >>> ctxt = Context(bar='Bye') | |
61 | 270 >>> tmpl = Template('''<div xmlns:py="http://markup.edgewall.org/"> |
1 | 271 ... <p py:def="echo(greeting, name='world')" class="message"> |
272 ... ${greeting}, ${name}! | |
273 ... </p> | |
90
c835e81c50af
When an expression evaluates to a callable, it is called implicitly.
cmlenz
parents:
89
diff
changeset
|
274 ... ${echo('Hi', name='you')} |
1 | 275 ... </div>''') |
276 >>> print tmpl.generate(ctxt) | |
277 <div> | |
278 <p class="message"> | |
90
c835e81c50af
When an expression evaluates to a callable, it is called implicitly.
cmlenz
parents:
89
diff
changeset
|
279 Hi, you! |
1 | 280 </p> |
281 </div> | |
282 | |
90
c835e81c50af
When an expression evaluates to a callable, it is called implicitly.
cmlenz
parents:
89
diff
changeset
|
283 If a function does not require parameters, the parenthesis can be omitted |
c835e81c50af
When an expression evaluates to a callable, it is called implicitly.
cmlenz
parents:
89
diff
changeset
|
284 both when defining and when calling it: |
c835e81c50af
When an expression evaluates to a callable, it is called implicitly.
cmlenz
parents:
89
diff
changeset
|
285 |
1 | 286 >>> ctxt = Context(bar='Bye') |
61 | 287 >>> tmpl = Template('''<div xmlns:py="http://markup.edgewall.org/"> |
90
c835e81c50af
When an expression evaluates to a callable, it is called implicitly.
cmlenz
parents:
89
diff
changeset
|
288 ... <p py:def="helloworld" class="message"> |
c835e81c50af
When an expression evaluates to a callable, it is called implicitly.
cmlenz
parents:
89
diff
changeset
|
289 ... Hello, world! |
1 | 290 ... </p> |
90
c835e81c50af
When an expression evaluates to a callable, it is called implicitly.
cmlenz
parents:
89
diff
changeset
|
291 ... ${helloworld} |
1 | 292 ... </div>''') |
293 >>> print tmpl.generate(ctxt) | |
294 <div> | |
295 <p class="message"> | |
90
c835e81c50af
When an expression evaluates to a callable, it is called implicitly.
cmlenz
parents:
89
diff
changeset
|
296 Hello, world! |
1 | 297 </p> |
298 </div> | |
299 """ | |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
300 __slots__ = ['name', 'args', 'defaults', 'stream', 'directives'] |
1 | 301 |
65
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
302 ATTRIBUTE = 'function' |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
303 |
81
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
304 def __init__(self, args, filename=None, lineno=-1, offset=-1): |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
305 Directive.__init__(self, None, filename, lineno, offset) |
1 | 306 ast = compiler.parse(args, 'eval').node |
307 self.args = [] | |
308 self.defaults = {} | |
309 if isinstance(ast, compiler.ast.CallFunc): | |
310 self.name = ast.node.name | |
311 for arg in ast.args: | |
312 if isinstance(arg, compiler.ast.Keyword): | |
313 self.args.append(arg.name) | |
314 self.defaults[arg.name] = arg.expr.value | |
315 else: | |
316 self.args.append(arg.name) | |
317 else: | |
318 self.name = ast.name | |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
319 self.stream, self.directives = [], [] |
1 | 320 |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
321 def __call__(self, stream, ctxt, directives): |
1 | 322 self.stream = list(stream) |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
323 self.directives = directives |
1 | 324 ctxt[self.name] = lambda *args, **kwargs: self._exec(ctxt, *args, |
325 **kwargs) | |
326 return [] | |
327 | |
328 def _exec(self, ctxt, *args, **kwargs): | |
329 scope = {} | |
330 args = list(args) # make mutable | |
331 for name in self.args: | |
332 if args: | |
333 scope[name] = args.pop(0) | |
334 else: | |
335 scope[name] = kwargs.pop(name, self.defaults.get(name)) | |
95
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
336 ctxt.push(scope) |
78
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
337 for event in _apply_directives(self.stream, ctxt, self.directives): |
1 | 338 yield event |
339 ctxt.pop() | |
340 | |
341 | |
342 class ForDirective(Directive): | |
31 | 343 """Implementation of the `py:for` template directive for repeating an |
344 element based on an iterable in the context data. | |
1 | 345 |
346 >>> ctxt = Context(items=[1, 2, 3]) | |
61 | 347 >>> tmpl = Template('''<ul xmlns:py="http://markup.edgewall.org/"> |
1 | 348 ... <li py:for="item in items">${item}</li> |
349 ... </ul>''') | |
350 >>> print tmpl.generate(ctxt) | |
351 <ul> | |
352 <li>1</li><li>2</li><li>3</li> | |
353 </ul> | |
354 """ | |
355 __slots__ = ['targets'] | |
356 | |
65
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
357 ATTRIBUTE = 'each' |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
358 |
81
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
359 def __init__(self, value, filename=None, lineno=-1, offset=-1): |
29
ab8703fa68b8
* Minor simplification of template directives: they no longer get passed the template instance and the position, as no directive was actually using
cmlenz
parents:
27
diff
changeset
|
360 targets, value = value.split(' in ', 1) |
1 | 361 self.targets = [str(name.strip()) for name in targets.split(',')] |
140
c1f4390d50f8
Fix bug in HTML serializer, plus some other minor tweaks.
cmlenz
parents:
139
diff
changeset
|
362 Directive.__init__(self, value.strip(), filename, lineno, offset) |
1 | 363 |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
364 def __call__(self, stream, ctxt, directives): |
53
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
365 iterable = self.expr.evaluate(ctxt) |
101 | 366 if iterable is None: |
367 return | |
368 | |
369 scope = {} | |
370 stream = list(stream) | |
371 targets = self.targets | |
140
c1f4390d50f8
Fix bug in HTML serializer, plus some other minor tweaks.
cmlenz
parents:
139
diff
changeset
|
372 single = len(targets) == 1 |
101 | 373 for item in iter(iterable): |
140
c1f4390d50f8
Fix bug in HTML serializer, plus some other minor tweaks.
cmlenz
parents:
139
diff
changeset
|
374 if single: |
101 | 375 scope[targets[0]] = item |
376 else: | |
377 for idx, name in enumerate(targets): | |
378 scope[name] = item[idx] | |
379 ctxt.push(scope) | |
380 for event in _apply_directives(stream, ctxt, directives): | |
381 yield event | |
382 ctxt.pop() | |
1 | 383 |
384 def __repr__(self): | |
385 return '<%s "%s in %s">' % (self.__class__.__name__, | |
386 ', '.join(self.targets), self.expr.source) | |
387 | |
388 | |
389 class IfDirective(Directive): | |
31 | 390 """Implementation of the `py:if` template directive for conditionally |
391 excluding elements from being output. | |
1 | 392 |
393 >>> ctxt = Context(foo=True, bar='Hello') | |
61 | 394 >>> tmpl = Template('''<div xmlns:py="http://markup.edgewall.org/"> |
1 | 395 ... <b py:if="foo">${bar}</b> |
396 ... </div>''') | |
397 >>> print tmpl.generate(ctxt) | |
398 <div> | |
399 <b>Hello</b> | |
400 </div> | |
401 """ | |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
402 __slots__ = [] |
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
403 |
65
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
404 ATTRIBUTE = 'test' |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
405 |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
406 def __call__(self, stream, ctxt, directives): |
1 | 407 if self.expr.evaluate(ctxt): |
78
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
408 return _apply_directives(stream, ctxt, directives) |
1 | 409 return [] |
410 | |
411 | |
412 class MatchDirective(Directive): | |
413 """Implementation of the `py:match` template directive. | |
14
c7d33e0c9839
The `<py:match>` directive now protects itself against simple infinite recursion (see MatchDirective), while still allowing recursion in general.
cmlenz
parents:
13
diff
changeset
|
414 |
61 | 415 >>> tmpl = Template('''<div xmlns:py="http://markup.edgewall.org/"> |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
416 ... <span py:match="greeting"> |
1 | 417 ... Hello ${select('@name')} |
418 ... </span> | |
419 ... <greeting name="Dude" /> | |
420 ... </div>''') | |
14
c7d33e0c9839
The `<py:match>` directive now protects itself against simple infinite recursion (see MatchDirective), while still allowing recursion in general.
cmlenz
parents:
13
diff
changeset
|
421 >>> print tmpl.generate() |
1 | 422 <div> |
423 <span> | |
424 Hello Dude | |
425 </span> | |
426 </div> | |
427 """ | |
428 __slots__ = ['path', 'stream'] | |
429 | |
65
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
430 ATTRIBUTE = 'path' |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
431 |
81
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
432 def __init__(self, value, filename=None, lineno=-1, offset=-1): |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
433 Directive.__init__(self, None, filename, lineno, offset) |
139
8332287b5508
Implement position reporting for XPath syntax errors. Closes #20.
cmlenz
parents:
134
diff
changeset
|
434 self.path = Path(value, filename, lineno) |
1 | 435 self.stream = [] |
436 | |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
437 def __call__(self, stream, ctxt, directives): |
1 | 438 self.stream = list(stream) |
38
ee669cb9cccc
Fix for #2 (incorrect context node in path expressions). Still some paths that produce incorrect results, but the common case seems to work now.
cmlenz
parents:
37
diff
changeset
|
439 ctxt._match_templates.append((self.path.test(ignore_context=True), |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
440 self.path, self.stream, directives)) |
1 | 441 return [] |
442 | |
443 def __repr__(self): | |
14
c7d33e0c9839
The `<py:match>` directive now protects itself against simple infinite recursion (see MatchDirective), while still allowing recursion in general.
cmlenz
parents:
13
diff
changeset
|
444 return '<%s "%s">' % (self.__class__.__name__, self.path.source) |
1 | 445 |
446 | |
447 class ReplaceDirective(Directive): | |
448 """Implementation of the `py:replace` template directive. | |
449 | |
31 | 450 This directive replaces the element with the result of evaluating the |
451 value of the `py:replace` attribute: | |
452 | |
1 | 453 >>> ctxt = Context(bar='Bye') |
61 | 454 >>> tmpl = Template('''<div xmlns:py="http://markup.edgewall.org/"> |
1 | 455 ... <span py:replace="bar">Hello</span> |
456 ... </div>''') | |
457 >>> print tmpl.generate(ctxt) | |
458 <div> | |
459 Bye | |
460 </div> | |
461 | |
462 This directive is equivalent to `py:content` combined with `py:strip`, | |
463 providing a less verbose way to achieve the same effect: | |
464 | |
465 >>> ctxt = Context(bar='Bye') | |
61 | 466 >>> tmpl = Template('''<div xmlns:py="http://markup.edgewall.org/"> |
1 | 467 ... <span py:content="bar" py:strip="">Hello</span> |
468 ... </div>''') | |
469 >>> print tmpl.generate(ctxt) | |
470 <div> | |
471 Bye | |
472 </div> | |
473 """ | |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
474 __slots__ = [] |
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
475 |
54 | 476 def __call__(self, stream, ctxt, directives): |
1 | 477 kind, data, pos = stream.next() |
69 | 478 yield EXPR, self.expr, pos |
1 | 479 |
480 | |
481 class StripDirective(Directive): | |
482 """Implementation of the `py:strip` template directive. | |
483 | |
484 When the value of the `py:strip` attribute evaluates to `True`, the element | |
485 is stripped from the output | |
486 | |
61 | 487 >>> tmpl = Template('''<div xmlns:py="http://markup.edgewall.org/"> |
1 | 488 ... <div py:strip="True"><b>foo</b></div> |
489 ... </div>''') | |
14
c7d33e0c9839
The `<py:match>` directive now protects itself against simple infinite recursion (see MatchDirective), while still allowing recursion in general.
cmlenz
parents:
13
diff
changeset
|
490 >>> print tmpl.generate() |
1 | 491 <div> |
492 <b>foo</b> | |
493 </div> | |
494 | |
37
37557b8fb925
Moved some of the tests for the strip directive to a new unittest test case to not clutter up the documentation.
cmlenz
parents:
36
diff
changeset
|
495 Leaving the attribute value empty is equivalent to a truth value. |
1 | 496 |
497 This directive is particulary interesting for named template functions or | |
498 match templates that do not generate a top-level element: | |
499 | |
61 | 500 >>> tmpl = Template('''<div xmlns:py="http://markup.edgewall.org/"> |
1 | 501 ... <div py:def="echo(what)" py:strip=""> |
502 ... <b>${what}</b> | |
503 ... </div> | |
504 ... ${echo('foo')} | |
505 ... </div>''') | |
14
c7d33e0c9839
The `<py:match>` directive now protects itself against simple infinite recursion (see MatchDirective), while still allowing recursion in general.
cmlenz
parents:
13
diff
changeset
|
506 >>> print tmpl.generate() |
1 | 507 <div> |
508 <b>foo</b> | |
509 </div> | |
510 """ | |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
511 __slots__ = [] |
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
512 |
54 | 513 def __call__(self, stream, ctxt, directives): |
78
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
514 def _generate(): |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
515 if self.expr: |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
516 strip = self.expr.evaluate(ctxt) |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
517 else: |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
518 strip = True |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
519 if strip: |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
520 stream.next() # skip start tag |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
521 previous = stream.next() |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
522 for event in stream: |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
523 yield previous |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
524 previous = event |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
525 else: |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
526 for event in stream: |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
527 yield event |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
528 |
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
529 return _apply_directives(_generate(), ctxt, directives) |
1 | 530 |
531 | |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
532 class ChooseDirective(Directive): |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
533 """Implementation of the `py:choose` directive for conditionally selecting |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
534 one of several body elements to display. |
53
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
535 |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
536 If the `py:choose` expression is empty the expressions of nested `py:when` |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
537 directives are tested for truth. The first true `py:when` body is output. |
53
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
538 If no `py:when` directive is matched then the fallback directive |
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
539 `py:otherwise` will be used. |
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
540 |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
541 >>> ctxt = Context() |
61 | 542 >>> tmpl = Template('''<div xmlns:py="http://markup.edgewall.org/" |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
543 ... py:choose=""> |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
544 ... <span py:when="0 == 1">0</span> |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
545 ... <span py:when="1 == 1">1</span> |
53
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
546 ... <span py:otherwise="">2</span> |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
547 ... </div>''') |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
548 >>> print tmpl.generate(ctxt) |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
549 <div> |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
550 <span>1</span> |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
551 </div> |
53
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
552 |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
553 If the `py:choose` directive contains an expression, the nested `py:when` |
53
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
554 directives are tested for equality to the `py:choose` expression: |
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
555 |
61 | 556 >>> tmpl = Template('''<div xmlns:py="http://markup.edgewall.org/" |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
557 ... py:choose="2"> |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
558 ... <span py:when="1">1</span> |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
559 ... <span py:when="2">2</span> |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
560 ... </div>''') |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
561 >>> print tmpl.generate(ctxt) |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
562 <div> |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
563 <span>2</span> |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
564 </div> |
53
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
565 |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
566 Behavior is undefined if a `py:choose` block contains content outside a |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
567 `py:when` or `py:otherwise` block. Behavior is also undefined if a |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
568 `py:otherwise` occurs before `py:when` blocks. |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
569 """ |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
570 __slots__ = ['matched', 'value'] |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
571 |
65
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
572 ATTRIBUTE = 'test' |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
573 |
53
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
574 def __call__(self, stream, ctxt, directives): |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
575 if self.expr: |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
576 self.value = self.expr.evaluate(ctxt) |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
577 self.matched = False |
95
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
578 ctxt.push(dict(_choose=self)) |
78
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
579 for event in _apply_directives(stream, ctxt, directives): |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
580 yield event |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
581 ctxt.pop() |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
582 |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
583 |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
584 class WhenDirective(Directive): |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
585 """Implementation of the `py:when` directive for nesting in a parent with |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
586 the `py:choose` directive. |
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
587 |
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
588 See the documentation of `py:choose` for usage. |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
589 """ |
65
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
590 |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
591 ATTRIBUTE = 'test' |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
592 |
54 | 593 def __call__(self, stream, ctxt, directives): |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
594 choose = ctxt['_choose'] |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
595 if choose.matched: |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
596 return [] |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
597 value = self.expr.evaluate(ctxt) |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
598 try: |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
599 if value == choose.value: |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
600 choose.matched = True |
78
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
601 return _apply_directives(stream, ctxt, directives) |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
602 except AttributeError: |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
603 if value: |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
604 choose.matched = True |
78
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
605 return _apply_directives(stream, ctxt, directives) |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
606 return [] |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
607 |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
608 |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
609 class OtherwiseDirective(Directive): |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
610 """Implementation of the `py:otherwise` directive for nesting in a parent |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
611 with the `py:choose` directive. |
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
612 |
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
613 See the documentation of `py:choose` for usage. |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
614 """ |
54 | 615 def __call__(self, stream, ctxt, directives): |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
616 choose = ctxt['_choose'] |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
617 if choose.matched: |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
618 return [] |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
619 choose.matched = True |
78
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
620 return _apply_directives(stream, ctxt, directives) |
44
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
621 |
436e30c8420b
implement `py:choose/when/otherwise` directives for conditionally selecting one of several blocks
mgood
parents:
38
diff
changeset
|
622 |
104
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
623 class WithDirective(Directive): |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
624 """Implementation of the `py:with` template directive, which allows |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
625 shorthand access to variables and expressions. |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
626 |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
627 >>> tmpl = Template('''<div xmlns:py="http://markup.edgewall.org/"> |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
628 ... <span py:with="y=7; z=x+10">$x $y $z</span> |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
629 ... </div>''') |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
630 >>> print tmpl.generate(Context(x=42)) |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
631 <div> |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
632 <span>42 7 52</span> |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
633 </div> |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
634 """ |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
635 __slots__ = ['vars'] |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
636 |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
637 ATTRIBUTE = 'vars' |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
638 |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
639 def __init__(self, value, filename=None, lineno=-1, offset=-1): |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
640 Directive.__init__(self, None, filename, lineno, offset) |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
641 self.vars = [] |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
642 try: |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
643 for stmt in value.split(';'): |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
644 name, value = stmt.split('=', 1) |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
645 self.vars.append((name.strip(), |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
646 Expression(value.strip(), filename, lineno))) |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
647 except SyntaxError, err: |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
648 raise TemplateSyntaxError(err, filename, lineno, |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
649 offset + (err.offset or 0)) |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
650 |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
651 def __call__(self, stream, ctxt, directives): |
120 | 652 ctxt.push(dict([(name, expr.evaluate(ctxt, nocall=True)) |
104
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
653 for name, expr in self.vars])) |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
654 for event in _apply_directives(stream, ctxt, directives): |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
655 yield event |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
656 ctxt.pop() |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
657 |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
658 def __repr__(self): |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
659 return '<%s "%s">' % (self.__class__.__name__, |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
660 '; '.join(['%s = %s' % (name, expr.source) |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
661 for name, expr in self.vars])) |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
662 |
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
663 |
1 | 664 class Template(object): |
665 """Can parse a template and transform it into the corresponding output | |
666 based on context data. | |
667 """ | |
61 | 668 NAMESPACE = Namespace('http://markup.edgewall.org/') |
1 | 669 |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
670 EXPR = StreamEventKind('EXPR') # an expression |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
671 SUB = StreamEventKind('SUB') # a "subprogram" |
10
f77f7a91aa46
Moved the template-specific stream event kinds into the template module.
cmlenz
parents:
6
diff
changeset
|
672 |
1 | 673 directives = [('def', DefDirective), |
674 ('match', MatchDirective), | |
120 | 675 ('when', WhenDirective), |
676 ('otherwise', OtherwiseDirective), | |
1 | 677 ('for', ForDirective), |
678 ('if', IfDirective), | |
53
512eb72dbb19
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
cmlenz
parents:
51
diff
changeset
|
679 ('choose', ChooseDirective), |
104
f12e7987d7f4
Added `py:with` directive based on Jonas' patch in #17.
cmlenz
parents:
101
diff
changeset
|
680 ('with', WithDirective), |
1 | 681 ('replace', ReplaceDirective), |
682 ('content', ContentDirective), | |
683 ('attrs', AttrsDirective), | |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
684 ('strip', StripDirective)] |
1 | 685 _dir_by_name = dict(directives) |
686 _dir_order = [directive[1] for directive in directives] | |
687 | |
21
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
688 def __init__(self, source, basedir=None, filename=None): |
1 | 689 """Initialize a template from either a string or a file-like object.""" |
690 if isinstance(source, basestring): | |
691 self.source = StringIO(source) | |
692 else: | |
693 self.source = source | |
21
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
694 self.basedir = basedir |
1 | 695 self.filename = filename or '<string>' |
21
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
696 if basedir and filename: |
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
697 self.filepath = os.path.join(basedir, filename) |
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
698 else: |
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
699 self.filepath = '<string>' |
1 | 700 |
23
d88358f719fa
Separate match and eval filters from the include and user-supplied filters.
cmlenz
parents:
22
diff
changeset
|
701 self.filters = [] |
1 | 702 self.parse() |
703 | |
704 def __repr__(self): | |
21
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
705 return '<%s "%s">' % (self.__class__.__name__, self.filename) |
1 | 706 |
707 def parse(self): | |
708 """Parse the template. | |
709 | |
710 The parsing stage parses the XML template and constructs a list of | |
711 directives that will be executed in the render stage. The input is | |
712 split up into literal output (markup that does not depend on the | |
713 context data) and actual directives (commands or variable | |
714 substitution). | |
715 """ | |
716 stream = [] # list of events of the "compiled" template | |
717 dirmap = {} # temporary mapping of directives to elements | |
718 ns_prefix = {} | |
719 depth = 0 | |
720 | |
21
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
721 for kind, data, pos in XMLParser(self.source, filename=self.filename): |
1 | 722 |
69 | 723 if kind is START_NS: |
1 | 724 # Strip out the namespace declaration for template directives |
725 prefix, uri = data | |
726 if uri == self.NAMESPACE: | |
727 ns_prefix[prefix] = uri | |
728 else: | |
729 stream.append((kind, data, pos)) | |
730 | |
69 | 731 elif kind is END_NS: |
1 | 732 if data in ns_prefix: |
733 del ns_prefix[data] | |
734 else: | |
735 stream.append((kind, data, pos)) | |
736 | |
69 | 737 elif kind is START: |
1 | 738 # Record any directive attributes in start tags |
739 tag, attrib = data | |
740 directives = [] | |
65
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
741 strip = False |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
742 |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
743 if tag in self.NAMESPACE: |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
744 cls = self._dir_by_name.get(tag.localname) |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
745 if cls is None: |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
746 raise BadDirectiveError(tag, pos[0], pos[1]) |
66
59eb24184e9c
Switch copyright to Edgewall and URLs to markup.edgewall.org.
cmlenz
parents:
65
diff
changeset
|
747 value = attrib.get(getattr(cls, 'ATTRIBUTE', None), '') |
81
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
748 directives.append(cls(value, *pos)) |
65
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
749 strip = True |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
750 |
1 | 751 new_attrib = [] |
752 for name, value in attrib: | |
18
5420cfe42d36
Actually make use of the `markup.core.Namespace` class, and add a couple of doctests.
cmlenz
parents:
17
diff
changeset
|
753 if name in self.NAMESPACE: |
1 | 754 cls = self._dir_by_name.get(name.localname) |
755 if cls is None: | |
65
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
756 raise BadDirectiveError(name, pos[0], pos[1]) |
81
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
757 directives.append(cls(value, *pos)) |
1 | 758 else: |
75
3722696d0343
Empty attributes in templates were being stripped out. Thanks to Jonas for the patch.
cmlenz
parents:
74
diff
changeset
|
759 if value: |
3722696d0343
Empty attributes in templates were being stripped out. Thanks to Jonas for the patch.
cmlenz
parents:
74
diff
changeset
|
760 value = list(self._interpolate(value, *pos)) |
81
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
761 if len(value) == 1 and value[0][0] is TEXT: |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
762 value = value[0][1] |
75
3722696d0343
Empty attributes in templates were being stripped out. Thanks to Jonas for the patch.
cmlenz
parents:
74
diff
changeset
|
763 else: |
3722696d0343
Empty attributes in templates were being stripped out. Thanks to Jonas for the patch.
cmlenz
parents:
74
diff
changeset
|
764 value = [(TEXT, u'', pos)] |
1 | 765 new_attrib.append((name, value)) |
65
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
766 |
1 | 767 if directives: |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
768 directives.sort(lambda a, b: cmp(self._dir_order.index(a.__class__), |
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
769 self._dir_order.index(b.__class__))) |
65
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
770 dirmap[(depth, tag)] = (directives, len(stream), strip) |
1 | 771 |
772 stream.append((kind, (tag, Attributes(new_attrib)), pos)) | |
773 depth += 1 | |
774 | |
69 | 775 elif kind is END: |
1 | 776 depth -= 1 |
777 stream.append((kind, data, pos)) | |
778 | |
779 # If there have have directive attributes with the corresponding | |
780 # start tag, move the events inbetween into a "subprogram" | |
781 if (depth, data) in dirmap: | |
65
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
782 directives, start_offset, strip = dirmap.pop((depth, data)) |
1 | 783 substream = stream[start_offset:] |
65
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
784 if strip: |
b3fdf93057ab
Support the use of directives as elements to reduce the need for using `py:strip`.
cmlenz
parents:
61
diff
changeset
|
785 substream = substream[1:-1] |
69 | 786 stream[start_offset:] = [(SUB, (directives, substream), |
787 pos)] | |
1 | 788 |
69 | 789 elif kind is TEXT: |
1 | 790 for kind, data, pos in self._interpolate(data, *pos): |
791 stream.append((kind, data, pos)) | |
792 | |
89
80386d62814f
Support comments in templates that are not included in the output, in the same way Kid does: if the comment text starts with a `!` character, it is stripped from the output.
cmlenz
parents:
87
diff
changeset
|
793 elif kind is COMMENT: |
80386d62814f
Support comments in templates that are not included in the output, in the same way Kid does: if the comment text starts with a `!` character, it is stripped from the output.
cmlenz
parents:
87
diff
changeset
|
794 if not data.lstrip().startswith('!'): |
80386d62814f
Support comments in templates that are not included in the output, in the same way Kid does: if the comment text starts with a `!` character, it is stripped from the output.
cmlenz
parents:
87
diff
changeset
|
795 stream.append((kind, data, pos)) |
80386d62814f
Support comments in templates that are not included in the output, in the same way Kid does: if the comment text starts with a `!` character, it is stripped from the output.
cmlenz
parents:
87
diff
changeset
|
796 |
1 | 797 else: |
798 stream.append((kind, data, pos)) | |
799 | |
800 self.stream = stream | |
801 | |
802 _FULL_EXPR_RE = re.compile(r'(?<!\$)\$\{(.+?)\}') | |
803 _SHORT_EXPR_RE = re.compile(r'(?<!\$)\$([a-zA-Z][a-zA-Z0-9_\.]*)') | |
804 | |
21
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
805 def _interpolate(cls, text, filename=None, lineno=-1, offset=-1): |
1 | 806 """Parse the given string and extract expressions. |
807 | |
808 This method returns a list containing both literal text and `Expression` | |
809 objects. | |
14
c7d33e0c9839
The `<py:match>` directive now protects itself against simple infinite recursion (see MatchDirective), while still allowing recursion in general.
cmlenz
parents:
13
diff
changeset
|
810 |
1 | 811 @param text: the text to parse |
812 @param lineno: the line number at which the text was found (optional) | |
813 @param offset: the column number at which the text starts in the source | |
814 (optional) | |
815 """ | |
134
d681d2c3cd8d
* Improve the accuracy of line numbers for text nodes, so that reported errors about syntax or evaluation errors in expressions point to the right line (not quite perfect yet, though).
cmlenz
parents:
133
diff
changeset
|
816 def _interpolate(text, patterns, filename=filename, lineno=lineno, |
d681d2c3cd8d
* Improve the accuracy of line numbers for text nodes, so that reported errors about syntax or evaluation errors in expressions point to the right line (not quite perfect yet, though).
cmlenz
parents:
133
diff
changeset
|
817 offset=offset): |
1 | 818 for idx, group in enumerate(patterns.pop(0).split(text)): |
819 if idx % 2: | |
81
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
820 try: |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
821 yield EXPR, Expression(group, filename, lineno), \ |
134
d681d2c3cd8d
* Improve the accuracy of line numbers for text nodes, so that reported errors about syntax or evaluation errors in expressions point to the right line (not quite perfect yet, though).
cmlenz
parents:
133
diff
changeset
|
822 (filename, lineno, offset) |
81
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
823 except SyntaxError, err: |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
824 raise TemplateSyntaxError(err, filename, lineno, |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
825 offset + (err.offset or 0)) |
1 | 826 elif group: |
827 if patterns: | |
74
d54b5fd60b52
Fix expression interpolation where both shorthand notation and full notation are used inside a single text node. Thanks Jonas.
cmlenz
parents:
73
diff
changeset
|
828 for result in _interpolate(group, patterns[:]): |
1 | 829 yield result |
830 else: | |
134
d681d2c3cd8d
* Improve the accuracy of line numbers for text nodes, so that reported errors about syntax or evaluation errors in expressions point to the right line (not quite perfect yet, though).
cmlenz
parents:
133
diff
changeset
|
831 yield TEXT, group.replace('$$', '$'), \ |
d681d2c3cd8d
* Improve the accuracy of line numbers for text nodes, so that reported errors about syntax or evaluation errors in expressions point to the right line (not quite perfect yet, though).
cmlenz
parents:
133
diff
changeset
|
832 (filename, lineno, offset) |
d681d2c3cd8d
* Improve the accuracy of line numbers for text nodes, so that reported errors about syntax or evaluation errors in expressions point to the right line (not quite perfect yet, though).
cmlenz
parents:
133
diff
changeset
|
833 if '\n' in group: |
d681d2c3cd8d
* Improve the accuracy of line numbers for text nodes, so that reported errors about syntax or evaluation errors in expressions point to the right line (not quite perfect yet, though).
cmlenz
parents:
133
diff
changeset
|
834 lines = group.splitlines() |
d681d2c3cd8d
* Improve the accuracy of line numbers for text nodes, so that reported errors about syntax or evaluation errors in expressions point to the right line (not quite perfect yet, though).
cmlenz
parents:
133
diff
changeset
|
835 lineno += len(lines) - 1 |
d681d2c3cd8d
* Improve the accuracy of line numbers for text nodes, so that reported errors about syntax or evaluation errors in expressions point to the right line (not quite perfect yet, though).
cmlenz
parents:
133
diff
changeset
|
836 offset += len(lines[-1]) |
d681d2c3cd8d
* Improve the accuracy of line numbers for text nodes, so that reported errors about syntax or evaluation errors in expressions point to the right line (not quite perfect yet, though).
cmlenz
parents:
133
diff
changeset
|
837 else: |
d681d2c3cd8d
* Improve the accuracy of line numbers for text nodes, so that reported errors about syntax or evaluation errors in expressions point to the right line (not quite perfect yet, though).
cmlenz
parents:
133
diff
changeset
|
838 offset += len(group) |
74
d54b5fd60b52
Fix expression interpolation where both shorthand notation and full notation are used inside a single text node. Thanks Jonas.
cmlenz
parents:
73
diff
changeset
|
839 return _interpolate(text, [cls._FULL_EXPR_RE, cls._SHORT_EXPR_RE]) |
1 | 840 _interpolate = classmethod(_interpolate) |
841 | |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
842 def generate(self, ctxt=None): |
29
ab8703fa68b8
* Minor simplification of template directives: they no longer get passed the template instance and the position, as no directive was actually using
cmlenz
parents:
27
diff
changeset
|
843 """Apply the template to the given context data. |
ab8703fa68b8
* Minor simplification of template directives: they no longer get passed the template instance and the position, as no directive was actually using
cmlenz
parents:
27
diff
changeset
|
844 |
ab8703fa68b8
* Minor simplification of template directives: they no longer get passed the template instance and the position, as no directive was actually using
cmlenz
parents:
27
diff
changeset
|
845 @param ctxt: a `Context` instance containing the data for the template |
ab8703fa68b8
* Minor simplification of template directives: they no longer get passed the template instance and the position, as no directive was actually using
cmlenz
parents:
27
diff
changeset
|
846 @return: a markup event stream representing the result of applying |
ab8703fa68b8
* Minor simplification of template directives: they no longer get passed the template instance and the position, as no directive was actually using
cmlenz
parents:
27
diff
changeset
|
847 the template to the context data. |
ab8703fa68b8
* Minor simplification of template directives: they no longer get passed the template instance and the position, as no directive was actually using
cmlenz
parents:
27
diff
changeset
|
848 """ |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
849 if ctxt is None: |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
850 ctxt = Context() |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
851 if not hasattr(ctxt, '_match_templates'): |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
852 ctxt._match_templates = [] |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
853 |
69 | 854 stream = self.stream |
855 for filter_ in [self._eval, self._match, self._flatten] + self.filters: | |
35
35b9e9318fb1
Simplify template processing model by removing dynamically generated `SUB` events.
cmlenz
parents:
31
diff
changeset
|
856 stream = filter_(iter(stream), ctxt) |
35b9e9318fb1
Simplify template processing model by removing dynamically generated `SUB` events.
cmlenz
parents:
31
diff
changeset
|
857 return Stream(stream) |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
858 |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
859 def _eval(self, stream, ctxt=None): |
29
ab8703fa68b8
* Minor simplification of template directives: they no longer get passed the template instance and the position, as no directive was actually using
cmlenz
parents:
27
diff
changeset
|
860 """Internal stream filter that evaluates any expressions in `START` and |
ab8703fa68b8
* Minor simplification of template directives: they no longer get passed the template instance and the position, as no directive was actually using
cmlenz
parents:
27
diff
changeset
|
861 `TEXT` events. |
ab8703fa68b8
* Minor simplification of template directives: they no longer get passed the template instance and the position, as no directive was actually using
cmlenz
parents:
27
diff
changeset
|
862 """ |
111
2368c3becc52
Some fixes and more unit tests for the XPath engine.
cmlenz
parents:
104
diff
changeset
|
863 filters = (self._eval, self._match) |
81
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
864 |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
865 for kind, data, pos in stream: |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
866 |
81
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
867 if kind is START and data[1]: |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
868 # Attributes may still contain expressions in start tags at |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
869 # this point, so do some evaluation |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
870 tag, attrib = data |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
871 new_attrib = [] |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
872 for name, substream in attrib: |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
873 if isinstance(substream, basestring): |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
874 value = substream |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
875 else: |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
876 values = [] |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
877 for subkind, subdata, subpos in substream: |
69 | 878 if subkind is EXPR: |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
879 values.append(subdata.evaluate(ctxt)) |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
880 else: |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
881 values.append(subdata) |
48
a5d585dd38c4
convert the result of expressions in attributes to strings so that values like ints are output correctly
mgood
parents:
44
diff
changeset
|
882 value = [unicode(x) for x in values if x is not None] |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
883 if not value: |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
884 continue |
23
d88358f719fa
Separate match and eval filters from the include and user-supplied filters.
cmlenz
parents:
22
diff
changeset
|
885 new_attrib.append((name, u''.join(value))) |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
886 yield kind, (tag, Attributes(new_attrib)), pos |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
887 |
69 | 888 elif kind is EXPR: |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
889 result = data.evaluate(ctxt) |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
890 if result is None: |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
891 continue |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
892 |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
893 # First check for a string, otherwise the iterable test below |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
894 # succeeds, and the string will be chopped up into individual |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
895 # characters |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
896 if isinstance(result, basestring): |
69 | 897 yield TEXT, result, pos |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
898 else: |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
899 # Test if the expression evaluated to an iterable, in which |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
900 # case we yield the individual items |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
901 try: |
111
2368c3becc52
Some fixes and more unit tests for the XPath engine.
cmlenz
parents:
104
diff
changeset
|
902 substream = _ensure(result) |
81
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
903 for filter_ in filters: |
77
f5ec6d4a61e4
* Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents:
75
diff
changeset
|
904 substream = filter_(substream, ctxt) |
f5ec6d4a61e4
* Simplify implementation of the individual XPath tests (use closures instead of callable classes)
cmlenz
parents:
75
diff
changeset
|
905 for event in substream: |
35
35b9e9318fb1
Simplify template processing model by removing dynamically generated `SUB` events.
cmlenz
parents:
31
diff
changeset
|
906 yield event |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
907 except TypeError: |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
908 # Neither a string nor an iterable, so just pass it |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
909 # through |
69 | 910 yield TEXT, unicode(result), pos |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
911 |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
912 else: |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
913 yield kind, data, pos |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
914 |
23
d88358f719fa
Separate match and eval filters from the include and user-supplied filters.
cmlenz
parents:
22
diff
changeset
|
915 def _flatten(self, stream, ctxt=None): |
29
ab8703fa68b8
* Minor simplification of template directives: they no longer get passed the template instance and the position, as no directive was actually using
cmlenz
parents:
27
diff
changeset
|
916 """Internal stream filter that expands `SUB` events in the stream.""" |
81
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
917 for kind, data, pos in stream: |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
918 if kind is SUB: |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
919 # This event is a list of directives and a list of nested |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
920 # events to which those directives should be applied |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
921 directives, substream = data |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
922 substream = _apply_directives(substream, ctxt, directives) |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
923 for filter_ in (self._eval, self._match, self._flatten): |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
924 substream = filter_(substream, ctxt) |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
925 for event in substream: |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
926 yield event |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
927 else: |
d60486018004
Template expressions are now compiled to Python bytecode.
cmlenz
parents:
80
diff
changeset
|
928 yield kind, data, pos |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
929 |
36
ed370ebfa794
Fix for #7: match templates no longer process their own output.
cmlenz
parents:
35
diff
changeset
|
930 def _match(self, stream, ctxt=None, match_templates=None): |
29
ab8703fa68b8
* Minor simplification of template directives: they no longer get passed the template instance and the position, as no directive was actually using
cmlenz
parents:
27
diff
changeset
|
931 """Internal stream filter that applies any defined match templates |
ab8703fa68b8
* Minor simplification of template directives: they no longer get passed the template instance and the position, as no directive was actually using
cmlenz
parents:
27
diff
changeset
|
932 to the stream. |
ab8703fa68b8
* Minor simplification of template directives: they no longer get passed the template instance and the position, as no directive was actually using
cmlenz
parents:
27
diff
changeset
|
933 """ |
36
ed370ebfa794
Fix for #7: match templates no longer process their own output.
cmlenz
parents:
35
diff
changeset
|
934 if match_templates is None: |
ed370ebfa794
Fix for #7: match templates no longer process their own output.
cmlenz
parents:
35
diff
changeset
|
935 match_templates = ctxt._match_templates |
ed370ebfa794
Fix for #7: match templates no longer process their own output.
cmlenz
parents:
35
diff
changeset
|
936 |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
937 for kind, data, pos in stream: |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
938 |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
939 # We (currently) only care about start and end events for matching |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
940 # We might care about namespace events in the future, though |
92
01d36818bb3d
More performance improvements... this time for whitespace normalization and template loops.
cmlenz
parents:
90
diff
changeset
|
941 if not match_templates or kind not in (START, END): |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
942 yield kind, data, pos |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
943 continue |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
944 |
50
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
945 for idx, (test, path, template, directives) in \ |
d3842cd76e92
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
cmlenz
parents:
48
diff
changeset
|
946 enumerate(match_templates): |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
947 |
116 | 948 if test(kind, data, pos) is True: |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
949 # Consume and store all events until an end event |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
950 # corresponding to this start event is encountered |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
951 content = [(kind, data, pos)] |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
952 depth = 1 |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
953 while depth > 0: |
73 | 954 kind, data, pos = stream.next() |
955 if kind is START: | |
956 depth += 1 | |
957 elif kind is END: | |
958 depth -= 1 | |
959 content.append((kind, data, pos)) | |
960 test(kind, data, pos) | |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
961 |
23
d88358f719fa
Separate match and eval filters from the include and user-supplied filters.
cmlenz
parents:
22
diff
changeset
|
962 content = list(self._flatten(content, ctxt)) |
95
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
963 select = lambda path: Stream(content).select(path) |
2fe86a99947f
Improve performance of push/pop operations on the context.
cmlenz
parents:
93
diff
changeset
|
964 ctxt.push(dict(select=select)) |
36
ed370ebfa794
Fix for #7: match templates no longer process their own output.
cmlenz
parents:
35
diff
changeset
|
965 |
78
46fed54f23cd
Minor improvements to how directives are applied in template processing.
cmlenz
parents:
77
diff
changeset
|
966 template = _apply_directives(template, ctxt, directives) |
69 | 967 for event in self._match(self._eval(template, ctxt), |
968 ctxt, match_templates[:idx] + | |
969 match_templates[idx + 1:]): | |
35
35b9e9318fb1
Simplify template processing model by removing dynamically generated `SUB` events.
cmlenz
parents:
31
diff
changeset
|
970 yield event |
116 | 971 |
35
35b9e9318fb1
Simplify template processing model by removing dynamically generated `SUB` events.
cmlenz
parents:
31
diff
changeset
|
972 ctxt.pop() |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
973 break |
69 | 974 |
975 else: # no matches | |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
976 yield kind, data, pos |
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
977 |
1 | 978 |
69 | 979 EXPR = Template.EXPR |
980 SUB = Template.SUB | |
981 | |
982 | |
1 | 983 class TemplateLoader(object): |
984 """Responsible for loading templates from files on the specified search | |
985 path. | |
986 | |
987 >>> import tempfile | |
988 >>> fd, path = tempfile.mkstemp(suffix='.html', prefix='template') | |
989 >>> os.write(fd, '<p>$var</p>') | |
990 11 | |
991 >>> os.close(fd) | |
992 | |
993 The template loader accepts a list of directory paths that are then used | |
994 when searching for template files, in the given order: | |
995 | |
996 >>> loader = TemplateLoader([os.path.dirname(path)]) | |
997 | |
998 The `load()` method first checks the template cache whether the requested | |
999 template has already been loaded. If not, it attempts to locate the | |
1000 template file, and returns the corresponding `Template` object: | |
1001 | |
1002 >>> template = loader.load(os.path.basename(path)) | |
1003 >>> isinstance(template, Template) | |
1004 True | |
1005 | |
1006 Template instances are cached: requesting a template with the same name | |
1007 results in the same instance being returned: | |
1008 | |
1009 >>> loader.load(os.path.basename(path)) is template | |
1010 True | |
1011 """ | |
1012 def __init__(self, search_path=None, auto_reload=False): | |
1013 """Create the template laoder. | |
1014 | |
1015 @param search_path: a list of absolute path names that should be | |
1016 searched for template files | |
1017 @param auto_reload: whether to check the last modification time of | |
1018 template files, and reload them if they have changed | |
1019 """ | |
1020 self.search_path = search_path | |
1021 if self.search_path is None: | |
1022 self.search_path = [] | |
1023 self.auto_reload = auto_reload | |
1024 self._cache = {} | |
1025 self._mtime = {} | |
1026 | |
21
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
1027 def load(self, filename, relative_to=None): |
1 | 1028 """Load the template with the given name. |
1029 | |
22
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1030 If the `filename` parameter is relative, this method searches the search |
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1031 path trying to locate a template matching the given name. If the file |
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1032 name is an absolute path, the search path is not bypassed. |
1 | 1033 |
22
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1034 If requested template is not found, a `TemplateNotFound` exception is |
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1035 raised. Otherwise, a `Template` object is returned that represents the |
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1036 parsed template. |
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1037 |
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1038 Template instances are cached to avoid having to parse the same |
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1039 template file more than once. Thus, subsequent calls of this method |
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1040 with the same template file name will return the same `Template` |
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1041 object (unless the `auto_reload` option is enabled and the file was |
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1042 changed since the last parse.) |
1 | 1043 |
21
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
1044 If the `relative_to` parameter is provided, the `filename` is |
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
1045 interpreted as being relative to that path. |
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
1046 |
1 | 1047 @param filename: the relative path of the template file to load |
21
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
1048 @param relative_to: the filename of the template from which the new |
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
1049 template is being loaded, or `None` if the template is being loaded |
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
1050 directly |
1 | 1051 """ |
69 | 1052 from markup.filters import IncludeFilter |
1053 | |
21
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
1054 if relative_to: |
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
1055 filename = posixpath.join(posixpath.dirname(relative_to), filename) |
1 | 1056 filename = os.path.normpath(filename) |
22
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1057 |
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1058 # First check the cache to avoid reparsing the same file |
1 | 1059 try: |
1060 tmpl = self._cache[filename] | |
1061 if not self.auto_reload or \ | |
21
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
1062 os.path.getmtime(tmpl.filepath) == self._mtime[filename]: |
1 | 1063 return tmpl |
1064 except KeyError: | |
1065 pass | |
22
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1066 |
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1067 # Bypass the search path if the filename is absolute |
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1068 search_path = self.search_path |
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1069 if os.path.isabs(filename): |
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1070 search_path = [os.path.dirname(filename)] |
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1071 |
2483fe549959
Fix for the template engine plugin: the search path is now ignored if the requested template path is absolute.
cmlenz
parents:
21
diff
changeset
|
1072 for dirname in search_path: |
1 | 1073 filepath = os.path.join(dirname, filename) |
1074 try: | |
133
79f445396cd7
Minor cleanup and performance improvement for the builder module.
cmlenz
parents:
120
diff
changeset
|
1075 fileobj = open(filepath, 'U') |
1 | 1076 try: |
21
b4d17897d053
* Include paths are now interpreted relative to the path of the including template. Closes #3.
cmlenz
parents:
18
diff
changeset
|
1077 tmpl = Template(fileobj, basedir=dirname, filename=filename) |
17
74cc70129d04
Refactoring to address #6: all match templates are now processed by a single filter, which means that match templates added by included templates are properly applied. A side effect of this refactoring is that `Context` objects may not be reused across multiple template processing runs.
cmlenz
parents:
14
diff
changeset
|
1078 tmpl.filters.append(IncludeFilter(self)) |
1 | 1079 finally: |
1080 fileobj.close() | |
1081 self._cache[filename] = tmpl | |
1082 self._mtime[filename] = os.path.getmtime(filepath) | |
1083 return tmpl | |
1084 except IOError: | |
1085 continue | |
1086 raise TemplateNotFound(filename, self.search_path) |