Mercurial > genshi > genshi-test
annotate genshi/template/base.py @ 820:1837f39efd6f experimental-inline
Sync (old) experimental inline branch with trunk@1027.
author | cmlenz |
---|---|
date | Wed, 11 Mar 2009 17:51:06 +0000 |
parents | 0742f421caba |
children | eb8aa8690480 |
rev | line source |
---|---|
500 | 1 # -*- coding: utf-8 -*- |
2 # | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
3 # Copyright (C) 2006-2008 Edgewall Software |
500 | 4 # All rights reserved. |
5 # | |
6 # This software is licensed as described in the file COPYING, which | |
7 # you should have received as part of this distribution. The terms | |
8 # are also available at http://genshi.edgewall.org/wiki/License. | |
9 # | |
10 # This software consists of voluntary contributions made by many | |
11 # individuals. For the exact contribution history, see the revision | |
12 # history and logs, available at http://genshi.edgewall.org/log/. | |
13 | |
14 """Basic templating functionality.""" | |
15 | |
16 try: | |
17 from collections import deque | |
18 except ImportError: | |
19 class deque(list): | |
20 def appendleft(self, x): self.insert(0, x) | |
21 def popleft(self): return self.pop(0) | |
22 import os | |
23 from StringIO import StringIO | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
24 import sys |
500 | 25 |
26 from genshi.core import Attrs, Stream, StreamEventKind, START, TEXT, _ensure | |
27 from genshi.input import ParseError | |
28 | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
29 __all__ = ['Context', 'DirectiveFactory', 'Template', 'TemplateError', |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
30 'TemplateRuntimeError', 'TemplateSyntaxError', 'BadDirectiveError'] |
500 | 31 __docformat__ = 'restructuredtext en' |
32 | |
33 | |
34 class TemplateError(Exception): | |
35 """Base exception class for errors related to template processing.""" | |
36 | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
37 def __init__(self, message, filename=None, lineno=-1, offset=-1): |
500 | 38 """Create the exception. |
39 | |
40 :param message: the error message | |
41 :param filename: the filename of the template | |
42 :param lineno: the number of line in the template at which the error | |
43 occurred | |
44 :param offset: the column number at which the error occurred | |
45 """ | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
46 if filename is None: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
47 filename = '<string>' |
500 | 48 self.msg = message #: the error message string |
49 if filename != '<string>' or lineno >= 0: | |
50 message = '%s (%s, line %d)' % (self.msg, filename, lineno) | |
51 Exception.__init__(self, message) | |
52 self.filename = filename #: the name of the template file | |
53 self.lineno = lineno #: the number of the line containing the error | |
54 self.offset = offset #: the offset on the line | |
55 | |
56 | |
57 class TemplateSyntaxError(TemplateError): | |
58 """Exception raised when an expression in a template causes a Python syntax | |
59 error, or the template is not well-formed. | |
60 """ | |
61 | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
62 def __init__(self, message, filename=None, lineno=-1, offset=-1): |
500 | 63 """Create the exception |
64 | |
65 :param message: the error message | |
66 :param filename: the filename of the template | |
67 :param lineno: the number of line in the template at which the error | |
68 occurred | |
69 :param offset: the column number at which the error occurred | |
70 """ | |
71 if isinstance(message, SyntaxError) and message.lineno is not None: | |
72 message = str(message).replace(' (line %d)' % message.lineno, '') | |
73 TemplateError.__init__(self, message, filename, lineno) | |
74 | |
75 | |
76 class BadDirectiveError(TemplateSyntaxError): | |
77 """Exception raised when an unknown directive is encountered when parsing | |
78 a template. | |
79 | |
80 An unknown directive is any attribute using the namespace for directives, | |
81 with a local name that doesn't match any registered directive. | |
82 """ | |
83 | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
84 def __init__(self, name, filename=None, lineno=-1): |
500 | 85 """Create the exception |
86 | |
87 :param name: the name of the directive | |
88 :param filename: the filename of the template | |
89 :param lineno: the number of line in the template at which the error | |
90 occurred | |
91 """ | |
92 TemplateSyntaxError.__init__(self, 'bad directive "%s"' % name, | |
93 filename, lineno) | |
94 | |
95 | |
96 class TemplateRuntimeError(TemplateError): | |
97 """Exception raised when an the evaluation of a Python expression in a | |
98 template causes an error. | |
99 """ | |
100 | |
101 | |
102 class Context(object): | |
103 """Container for template input data. | |
104 | |
105 A context provides a stack of scopes (represented by dictionaries). | |
106 | |
107 Template directives such as loops can push a new scope on the stack with | |
108 data that should only be available inside the loop. When the loop | |
109 terminates, that scope can get popped off the stack again. | |
110 | |
111 >>> ctxt = Context(one='foo', other=1) | |
112 >>> ctxt.get('one') | |
113 'foo' | |
114 >>> ctxt.get('other') | |
115 1 | |
116 >>> ctxt.push(dict(one='frost')) | |
117 >>> ctxt.get('one') | |
118 'frost' | |
119 >>> ctxt.get('other') | |
120 1 | |
121 >>> ctxt.pop() | |
122 {'one': 'frost'} | |
123 >>> ctxt.get('one') | |
124 'foo' | |
125 """ | |
126 | |
127 def __init__(self, **data): | |
128 """Initialize the template context with the given keyword arguments as | |
129 data. | |
130 """ | |
131 self.frames = deque([data]) | |
132 self.pop = self.frames.popleft | |
133 self.push = self.frames.appendleft | |
134 self._match_templates = [] | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
135 self._choice_stack = [] |
500 | 136 |
137 # Helper functions for use in expressions | |
138 def defined(name): | |
139 """Return whether a variable with the specified name exists in the | |
140 expression scope.""" | |
141 return name in self | |
142 def value_of(name, default=None): | |
143 """If a variable of the specified name is defined, return its value. | |
144 Otherwise, return the provided default value, or ``None``.""" | |
145 return self.get(name, default) | |
146 data.setdefault('defined', defined) | |
147 data.setdefault('value_of', value_of) | |
148 | |
149 def __repr__(self): | |
150 return repr(list(self.frames)) | |
151 | |
152 def __contains__(self, key): | |
153 """Return whether a variable exists in any of the scopes. | |
154 | |
155 :param key: the name of the variable | |
156 """ | |
157 return self._find(key)[1] is not None | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
158 has_key = __contains__ |
500 | 159 |
160 def __delitem__(self, key): | |
161 """Remove a variable from all scopes. | |
162 | |
163 :param key: the name of the variable | |
164 """ | |
165 for frame in self.frames: | |
166 if key in frame: | |
167 del frame[key] | |
168 | |
169 def __getitem__(self, key): | |
170 """Get a variables's value, starting at the current scope and going | |
171 upward. | |
172 | |
173 :param key: the name of the variable | |
174 :return: the variable value | |
175 :raises KeyError: if the requested variable wasn't found in any scope | |
176 """ | |
177 value, frame = self._find(key) | |
178 if frame is None: | |
179 raise KeyError(key) | |
180 return value | |
181 | |
182 def __len__(self): | |
183 """Return the number of distinctly named variables in the context. | |
184 | |
185 :return: the number of variables in the context | |
186 """ | |
187 return len(self.items()) | |
188 | |
189 def __setitem__(self, key, value): | |
190 """Set a variable in the current scope. | |
191 | |
192 :param key: the name of the variable | |
193 :param value: the variable value | |
194 """ | |
195 self.frames[0][key] = value | |
196 | |
197 def _find(self, key, default=None): | |
198 """Retrieve a given variable's value and the frame it was found in. | |
199 | |
200 Intended primarily for internal use by directives. | |
201 | |
202 :param key: the name of the variable | |
203 :param default: the default value to return when the variable is not | |
204 found | |
205 """ | |
206 for frame in self.frames: | |
207 if key in frame: | |
208 return frame[key], frame | |
209 return default, None | |
210 | |
211 def get(self, key, default=None): | |
212 """Get a variable's value, starting at the current scope and going | |
213 upward. | |
214 | |
215 :param key: the name of the variable | |
216 :param default: the default value to return when the variable is not | |
217 found | |
218 """ | |
219 for frame in self.frames: | |
220 if key in frame: | |
221 return frame[key] | |
222 return default | |
223 | |
224 def keys(self): | |
225 """Return the name of all variables in the context. | |
226 | |
227 :return: a list of variable names | |
228 """ | |
229 keys = [] | |
230 for frame in self.frames: | |
231 keys += [key for key in frame if key not in keys] | |
232 return keys | |
233 | |
234 def items(self): | |
235 """Return a list of ``(name, value)`` tuples for all variables in the | |
236 context. | |
237 | |
238 :return: a list of variables | |
239 """ | |
240 return [(key, self.get(key)) for key in self.keys()] | |
241 | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
242 def update(self, mapping): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
243 """Update the context from the mapping provided.""" |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
244 self.frames[0].update(mapping) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
245 |
500 | 246 def push(self, data): |
247 """Push a new scope on the stack. | |
248 | |
249 :param data: the data dictionary to push on the context stack. | |
250 """ | |
251 | |
252 def pop(self): | |
253 """Pop the top-most scope from the stack.""" | |
254 | |
255 | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
256 def _apply_directives(stream, directives, ctxt, **vars): |
500 | 257 """Apply the given directives to the stream. |
258 | |
259 :param stream: the stream the directives should be applied to | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
260 :param directives: the list of directives to apply |
500 | 261 :param ctxt: the `Context` |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
262 :param vars: additional variables that should be available when Python |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
263 code is executed |
500 | 264 :return: the stream with the given directives applied |
265 """ | |
266 if directives: | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
267 stream = directives[0](iter(stream), directives[1:], ctxt, **vars) |
500 | 268 return stream |
269 | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
270 def _eval_expr(expr, ctxt, **vars): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
271 """Evaluate the given `Expression` object. |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
272 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
273 :param expr: the expression to evaluate |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
274 :param ctxt: the `Context` |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
275 :param vars: additional variables that should be available to the |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
276 expression |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
277 :return: the result of the evaluation |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
278 """ |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
279 if vars: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
280 ctxt.push(vars) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
281 retval = expr.evaluate(ctxt) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
282 if vars: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
283 ctxt.pop() |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
284 return retval |
500 | 285 |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
286 def _exec_suite(suite, ctxt, **vars): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
287 """Execute the given `Suite` object. |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
288 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
289 :param suite: the code suite to execute |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
290 :param ctxt: the `Context` |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
291 :param vars: additional variables that should be available to the |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
292 code |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
293 """ |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
294 if vars: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
295 ctxt.push(vars) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
296 ctxt.push({}) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
297 suite.execute(ctxt) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
298 if vars: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
299 top = ctxt.pop() |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
300 ctxt.pop() |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
301 ctxt.frames[0].update(top) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
302 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
303 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
304 class DirectiveFactoryMeta(type): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
305 """Meta class for directive factories.""" |
500 | 306 |
307 def __new__(cls, name, bases, d): | |
308 if 'directives' in d: | |
309 d['_dir_by_name'] = dict(d['directives']) | |
310 d['_dir_order'] = [directive[1] for directive in d['directives']] | |
311 | |
312 return type.__new__(cls, name, bases, d) | |
313 | |
314 | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
315 class DirectiveFactory(object): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
316 """Base for classes that provide a set of template directives. |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
317 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
318 :since: version 0.6 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
319 """ |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
320 __metaclass__ = DirectiveFactoryMeta |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
321 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
322 directives = [] |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
323 """A list of `(name, cls)` tuples that define the set of directives |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
324 provided by this factory. |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
325 """ |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
326 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
327 def compare_directives(self): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
328 """Return a function that takes two directive classes and compares |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
329 them to determine their relative ordering. |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
330 """ |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
331 def _get_index(cls): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
332 if cls in self._dir_order: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
333 return self._dir_order.index(cls) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
334 return 0 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
335 return lambda a, b: cmp(_get_index(a[0]), _get_index(b[0])) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
336 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
337 def get_directive(self, name): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
338 """Return the directive class for the given name. |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
339 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
340 :param name: the directive name as used in the template |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
341 :return: the directive class |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
342 :see: `Directive` |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
343 """ |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
344 return self._dir_by_name.get(name) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
345 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
346 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
347 class Template(DirectiveFactory): |
500 | 348 """Abstract template base class. |
349 | |
350 This class implements most of the template processing model, but does not | |
351 specify the syntax of templates. | |
352 """ | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
353 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
354 EXEC = StreamEventKind('EXEC') |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
355 """Stream event kind representing a Python code suite to execute.""" |
500 | 356 |
357 EXPR = StreamEventKind('EXPR') | |
358 """Stream event kind representing a Python expression.""" | |
359 | |
360 INCLUDE = StreamEventKind('INCLUDE') | |
361 """Stream event kind representing the inclusion of another template.""" | |
362 | |
363 SUB = StreamEventKind('SUB') | |
364 """Stream event kind representing a nested stream to which one or more | |
365 directives should be applied. | |
366 """ | |
367 | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
368 serializer = None |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
369 _number_conv = unicode # function used to convert numbers to event data |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
370 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
371 def __init__(self, source, filepath=None, filename=None, loader=None, |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
372 encoding=None, lookup='strict', allow_exec=True): |
500 | 373 """Initialize a template from either a string, a file-like object, or |
374 an already parsed markup stream. | |
375 | |
376 :param source: a string, file-like object, or markup stream to read the | |
377 template from | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
378 :param filepath: the absolute path to the template file |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
379 :param filename: the path to the template file relative to the search |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
380 path |
500 | 381 :param loader: the `TemplateLoader` to use for loading included |
382 templates | |
383 :param encoding: the encoding of the `source` | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
384 :param lookup: the variable lookup mechanism; either "strict" (the |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
385 default), "lenient", or a custom lookup class |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
386 :param allow_exec: whether Python code blocks in templates should be |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
387 allowed |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
388 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
389 :note: Changed in 0.5: Added the `allow_exec` argument |
500 | 390 """ |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
391 self.filepath = filepath or filename |
500 | 392 self.filename = filename |
393 self.loader = loader | |
394 self.lookup = lookup | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
395 self.allow_exec = allow_exec |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
396 self._init_filters() |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
397 self._prepared = False |
500 | 398 |
399 if isinstance(source, basestring): | |
400 source = StringIO(source) | |
401 else: | |
402 source = source | |
403 try: | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
404 self._stream = self._parse(source, encoding) |
500 | 405 except ParseError, e: |
406 raise TemplateSyntaxError(e.msg, self.filepath, e.lineno, e.offset) | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
407 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
408 def __getstate__(self): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
409 state = self.__dict__.copy() |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
410 state['filters'] = [] |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
411 return state |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
412 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
413 def __setstate__(self, state): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
414 self.__dict__ = state |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
415 self._init_filters() |
500 | 416 |
417 def __repr__(self): | |
418 return '<%s "%s">' % (self.__class__.__name__, self.filename) | |
419 | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
420 def _init_filters(self): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
421 self.filters = [self._flatten] |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
422 if self.loader: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
423 self.filters.append(self._include) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
424 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
425 def _get_stream(self): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
426 if not self._prepared: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
427 self._stream = list(self._prepare(self._stream)) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
428 self._prepared = True |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
429 return self._stream |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
430 stream = property(_get_stream) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
431 |
500 | 432 def _parse(self, source, encoding): |
433 """Parse the template. | |
434 | |
435 The parsing stage parses the template and constructs a list of | |
436 directives that will be executed in the render stage. The input is | |
437 split up into literal output (text that does not depend on the context | |
438 data) and directives or expressions. | |
439 | |
440 :param source: a file-like object containing the XML source of the | |
441 template, or an XML event stream | |
442 :param encoding: the encoding of the `source` | |
443 """ | |
444 raise NotImplementedError | |
445 | |
446 def _prepare(self, stream): | |
447 """Call the `attach` method of every directive found in the template. | |
448 | |
449 :param stream: the event stream of the template | |
450 """ | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
451 from genshi.template.loader import TemplateNotFound |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
452 |
500 | 453 for kind, data, pos in stream: |
454 if kind is SUB: | |
455 directives = [] | |
456 substream = data[1] | |
457 for cls, value, namespaces, pos in data[0]: | |
458 directive, substream = cls.attach(self, substream, value, | |
459 namespaces, pos) | |
460 if directive: | |
461 directives.append(directive) | |
462 substream = self._prepare(substream) | |
463 if directives: | |
464 yield kind, (directives, list(substream)), pos | |
465 else: | |
466 for event in substream: | |
467 yield event | |
468 else: | |
469 if kind is INCLUDE: | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
470 href, cls, fallback = data |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
471 if isinstance(href, basestring) and \ |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
472 not getattr(self.loader, 'auto_reload', True): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
473 # If the path to the included template is static, and |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
474 # auto-reloading is disabled on the template loader, |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
475 # the template is inlined into the stream |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
476 try: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
477 tmpl = self.loader.load(href, relative_to=pos[0], |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
478 cls=cls or self.__class__) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
479 for event in tmpl.stream: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
480 yield event |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
481 except TemplateNotFound: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
482 if fallback is None: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
483 raise |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
484 for event in self._prepare(fallback): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
485 yield event |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
486 continue |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
487 elif fallback: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
488 # Otherwise the include is performed at run time |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
489 data = href, cls, list(self._prepare(fallback)) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
490 |
500 | 491 yield kind, data, pos |
492 | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
493 def compile(self): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
494 """Compile the template to a Python module, and return the module |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
495 object. |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
496 """ |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
497 from imp import new_module |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
498 from genshi.template.inline import inline |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
499 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
500 name = (self.filename or '_some_ident').replace('.', '_') |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
501 module = new_module(name) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
502 source = u'\n'.join(list(inline(self))) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
503 code = compile(source, self.filepath or '<string>', 'exec') |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
504 exec code in module.__dict__, module.__dict__ |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
505 return module |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
506 |
500 | 507 def generate(self, *args, **kwargs): |
508 """Apply the template to the given context data. | |
509 | |
510 Any keyword arguments are made available to the template as context | |
511 data. | |
512 | |
513 Only one positional argument is accepted: if it is provided, it must be | |
514 an instance of the `Context` class, and keyword arguments are ignored. | |
515 This calling style is used for internal processing. | |
516 | |
517 :return: a markup event stream representing the result of applying | |
518 the template to the context data. | |
519 """ | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
520 vars = {} |
500 | 521 if args: |
522 assert len(args) == 1 | |
523 ctxt = args[0] | |
524 if ctxt is None: | |
525 ctxt = Context(**kwargs) | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
526 else: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
527 vars = kwargs |
500 | 528 assert isinstance(ctxt, Context) |
529 else: | |
530 ctxt = Context(**kwargs) | |
531 | |
532 stream = self.stream | |
533 for filter_ in self.filters: | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
534 stream = filter_(iter(stream), ctxt, **vars) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
535 return Stream(stream, self.serializer) |
500 | 536 |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
537 def _flatten(self, stream, ctxt, **vars): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
538 number_conv = self._number_conv |
500 | 539 |
540 for kind, data, pos in stream: | |
541 | |
542 if kind is START and data[1]: | |
543 # Attributes may still contain expressions in start tags at | |
544 # this point, so do some evaluation | |
545 tag, attrs = data | |
546 new_attrs = [] | |
547 for name, substream in attrs: | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
548 if type(substream) is list: |
500 | 549 values = [] |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
550 for event in self._flatten(substream, ctxt, **vars): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
551 if event[0] is TEXT: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
552 values.append(event[1]) |
500 | 553 value = [x for x in values if x is not None] |
554 if not value: | |
555 continue | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
556 else: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
557 value = substream |
500 | 558 new_attrs.append((name, u''.join(value))) |
559 yield kind, (tag, Attrs(new_attrs)), pos | |
560 | |
561 elif kind is EXPR: | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
562 result = _eval_expr(data, ctxt, **vars) |
500 | 563 if result is not None: |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
564 # First check for a string, otherwise the iterable test |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
565 # below succeeds, and the string will be chopped up into |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
566 # individual characters |
500 | 567 if isinstance(result, basestring): |
568 yield TEXT, result, pos | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
569 elif isinstance(result, (int, float, long)): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
570 yield TEXT, number_conv(result), pos |
500 | 571 elif hasattr(result, '__iter__'): |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
572 for event in self._flatten(_ensure(result), ctxt, |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
573 **vars): |
500 | 574 yield event |
575 else: | |
576 yield TEXT, unicode(result), pos | |
577 | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
578 elif kind is EXEC: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
579 _exec_suite(data, ctxt, **vars) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
580 |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
581 elif kind is SUB: |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
582 # This event is a list of directives and a list of nested |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
583 # events to which those directives should be applied |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
584 substream = _apply_directives(data[1], data[0], ctxt, **vars) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
585 for event in self._flatten(substream, ctxt, **vars): |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
586 yield event |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
587 |
500 | 588 else: |
589 yield kind, data, pos | |
590 | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
591 def _include(self, stream, ctxt, **vars): |
500 | 592 """Internal stream filter that performs inclusion of external |
593 template files. | |
594 """ | |
595 from genshi.template.loader import TemplateNotFound | |
596 | |
597 for event in stream: | |
598 if event[0] is INCLUDE: | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
599 href, cls, fallback = event[1] |
500 | 600 if not isinstance(href, basestring): |
601 parts = [] | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
602 for subkind, subdata, subpos in self._flatten(href, ctxt, |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
603 **vars): |
500 | 604 if subkind is TEXT: |
605 parts.append(subdata) | |
606 href = u''.join([x for x in parts if x is not None]) | |
607 try: | |
608 tmpl = self.loader.load(href, relative_to=event[2][0], | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
609 cls=cls or self.__class__) |
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
610 for event in tmpl.generate(ctxt, **vars): |
500 | 611 yield event |
612 except TemplateNotFound: | |
613 if fallback is None: | |
614 raise | |
615 for filter_ in self.filters: | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
616 fallback = filter_(iter(fallback), ctxt, **vars) |
500 | 617 for event in fallback: |
618 yield event | |
619 else: | |
620 yield event | |
621 | |
622 | |
820
1837f39efd6f
Sync (old) experimental inline branch with trunk@1027.
cmlenz
parents:
500
diff
changeset
|
623 EXEC = Template.EXEC |
500 | 624 EXPR = Template.EXPR |
625 INCLUDE = Template.INCLUDE | |
626 SUB = Template.SUB |