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