Mercurial > genshi > genshi-test
comparison 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 |
comparison
equal
deleted
inserted
replaced
500:0742f421caba | 820:1837f39efd6f |
---|---|
1 # -*- coding: utf-8 -*- | 1 # -*- coding: utf-8 -*- |
2 # | 2 # |
3 # Copyright (C) 2006-2007 Edgewall Software | 3 # Copyright (C) 2006-2008 Edgewall Software |
4 # All rights reserved. | 4 # All rights reserved. |
5 # | 5 # |
6 # This software is licensed as described in the file COPYING, which | 6 # This software is licensed as described in the file COPYING, which |
7 # you should have received as part of this distribution. The terms | 7 # you should have received as part of this distribution. The terms |
8 # are also available at http://genshi.edgewall.org/wiki/License. | 8 # are also available at http://genshi.edgewall.org/wiki/License. |
19 class deque(list): | 19 class deque(list): |
20 def appendleft(self, x): self.insert(0, x) | 20 def appendleft(self, x): self.insert(0, x) |
21 def popleft(self): return self.pop(0) | 21 def popleft(self): return self.pop(0) |
22 import os | 22 import os |
23 from StringIO import StringIO | 23 from StringIO import StringIO |
24 import sys | |
24 | 25 |
25 from genshi.core import Attrs, Stream, StreamEventKind, START, TEXT, _ensure | 26 from genshi.core import Attrs, Stream, StreamEventKind, START, TEXT, _ensure |
26 from genshi.input import ParseError | 27 from genshi.input import ParseError |
27 | 28 |
28 __all__ = ['Context', 'Template', 'TemplateError', 'TemplateRuntimeError', | 29 __all__ = ['Context', 'DirectiveFactory', 'Template', 'TemplateError', |
29 'TemplateSyntaxError', 'BadDirectiveError'] | 30 'TemplateRuntimeError', 'TemplateSyntaxError', 'BadDirectiveError'] |
30 __docformat__ = 'restructuredtext en' | 31 __docformat__ = 'restructuredtext en' |
31 | 32 |
32 | 33 |
33 class TemplateError(Exception): | 34 class TemplateError(Exception): |
34 """Base exception class for errors related to template processing.""" | 35 """Base exception class for errors related to template processing.""" |
35 | 36 |
36 def __init__(self, message, filename='<string>', lineno=-1, offset=-1): | 37 def __init__(self, message, filename=None, lineno=-1, offset=-1): |
37 """Create the exception. | 38 """Create the exception. |
38 | 39 |
39 :param message: the error message | 40 :param message: the error message |
40 :param filename: the filename of the template | 41 :param filename: the filename of the template |
41 :param lineno: the number of line in the template at which the error | 42 :param lineno: the number of line in the template at which the error |
42 occurred | 43 occurred |
43 :param offset: the column number at which the error occurred | 44 :param offset: the column number at which the error occurred |
44 """ | 45 """ |
46 if filename is None: | |
47 filename = '<string>' | |
45 self.msg = message #: the error message string | 48 self.msg = message #: the error message string |
46 if filename != '<string>' or lineno >= 0: | 49 if filename != '<string>' or lineno >= 0: |
47 message = '%s (%s, line %d)' % (self.msg, filename, lineno) | 50 message = '%s (%s, line %d)' % (self.msg, filename, lineno) |
48 Exception.__init__(self, message) | 51 Exception.__init__(self, message) |
49 self.filename = filename #: the name of the template file | 52 self.filename = filename #: the name of the template file |
54 class TemplateSyntaxError(TemplateError): | 57 class TemplateSyntaxError(TemplateError): |
55 """Exception raised when an expression in a template causes a Python syntax | 58 """Exception raised when an expression in a template causes a Python syntax |
56 error, or the template is not well-formed. | 59 error, or the template is not well-formed. |
57 """ | 60 """ |
58 | 61 |
59 def __init__(self, message, filename='<string>', lineno=-1, offset=-1): | 62 def __init__(self, message, filename=None, lineno=-1, offset=-1): |
60 """Create the exception | 63 """Create the exception |
61 | 64 |
62 :param message: the error message | 65 :param message: the error message |
63 :param filename: the filename of the template | 66 :param filename: the filename of the template |
64 :param lineno: the number of line in the template at which the error | 67 :param lineno: the number of line in the template at which the error |
76 | 79 |
77 An unknown directive is any attribute using the namespace for directives, | 80 An unknown directive is any attribute using the namespace for directives, |
78 with a local name that doesn't match any registered directive. | 81 with a local name that doesn't match any registered directive. |
79 """ | 82 """ |
80 | 83 |
81 def __init__(self, name, filename='<string>', lineno=-1): | 84 def __init__(self, name, filename=None, lineno=-1): |
82 """Create the exception | 85 """Create the exception |
83 | 86 |
84 :param name: the name of the directive | 87 :param name: the name of the directive |
85 :param filename: the filename of the template | 88 :param filename: the filename of the template |
86 :param lineno: the number of line in the template at which the error | 89 :param lineno: the number of line in the template at which the error |
127 """ | 130 """ |
128 self.frames = deque([data]) | 131 self.frames = deque([data]) |
129 self.pop = self.frames.popleft | 132 self.pop = self.frames.popleft |
130 self.push = self.frames.appendleft | 133 self.push = self.frames.appendleft |
131 self._match_templates = [] | 134 self._match_templates = [] |
135 self._choice_stack = [] | |
132 | 136 |
133 # Helper functions for use in expressions | 137 # Helper functions for use in expressions |
134 def defined(name): | 138 def defined(name): |
135 """Return whether a variable with the specified name exists in the | 139 """Return whether a variable with the specified name exists in the |
136 expression scope.""" | 140 expression scope.""" |
149 """Return whether a variable exists in any of the scopes. | 153 """Return whether a variable exists in any of the scopes. |
150 | 154 |
151 :param key: the name of the variable | 155 :param key: the name of the variable |
152 """ | 156 """ |
153 return self._find(key)[1] is not None | 157 return self._find(key)[1] is not None |
158 has_key = __contains__ | |
154 | 159 |
155 def __delitem__(self, key): | 160 def __delitem__(self, key): |
156 """Remove a variable from all scopes. | 161 """Remove a variable from all scopes. |
157 | 162 |
158 :param key: the name of the variable | 163 :param key: the name of the variable |
232 | 237 |
233 :return: a list of variables | 238 :return: a list of variables |
234 """ | 239 """ |
235 return [(key, self.get(key)) for key in self.keys()] | 240 return [(key, self.get(key)) for key in self.keys()] |
236 | 241 |
242 def update(self, mapping): | |
243 """Update the context from the mapping provided.""" | |
244 self.frames[0].update(mapping) | |
245 | |
237 def push(self, data): | 246 def push(self, data): |
238 """Push a new scope on the stack. | 247 """Push a new scope on the stack. |
239 | 248 |
240 :param data: the data dictionary to push on the context stack. | 249 :param data: the data dictionary to push on the context stack. |
241 """ | 250 """ |
242 | 251 |
243 def pop(self): | 252 def pop(self): |
244 """Pop the top-most scope from the stack.""" | 253 """Pop the top-most scope from the stack.""" |
245 | 254 |
246 | 255 |
247 def _apply_directives(stream, ctxt, directives): | 256 def _apply_directives(stream, directives, ctxt, **vars): |
248 """Apply the given directives to the stream. | 257 """Apply the given directives to the stream. |
249 | 258 |
250 :param stream: the stream the directives should be applied to | 259 :param stream: the stream the directives should be applied to |
260 :param directives: the list of directives to apply | |
251 :param ctxt: the `Context` | 261 :param ctxt: the `Context` |
252 :param directives: the list of directives to apply | 262 :param vars: additional variables that should be available when Python |
263 code is executed | |
253 :return: the stream with the given directives applied | 264 :return: the stream with the given directives applied |
254 """ | 265 """ |
255 if directives: | 266 if directives: |
256 stream = directives[0](iter(stream), ctxt, directives[1:]) | 267 stream = directives[0](iter(stream), directives[1:], ctxt, **vars) |
257 return stream | 268 return stream |
258 | 269 |
259 | 270 def _eval_expr(expr, ctxt, **vars): |
260 class TemplateMeta(type): | 271 """Evaluate the given `Expression` object. |
261 """Meta class for templates.""" | 272 |
273 :param expr: the expression to evaluate | |
274 :param ctxt: the `Context` | |
275 :param vars: additional variables that should be available to the | |
276 expression | |
277 :return: the result of the evaluation | |
278 """ | |
279 if vars: | |
280 ctxt.push(vars) | |
281 retval = expr.evaluate(ctxt) | |
282 if vars: | |
283 ctxt.pop() | |
284 return retval | |
285 | |
286 def _exec_suite(suite, ctxt, **vars): | |
287 """Execute the given `Suite` object. | |
288 | |
289 :param suite: the code suite to execute | |
290 :param ctxt: the `Context` | |
291 :param vars: additional variables that should be available to the | |
292 code | |
293 """ | |
294 if vars: | |
295 ctxt.push(vars) | |
296 ctxt.push({}) | |
297 suite.execute(ctxt) | |
298 if vars: | |
299 top = ctxt.pop() | |
300 ctxt.pop() | |
301 ctxt.frames[0].update(top) | |
302 | |
303 | |
304 class DirectiveFactoryMeta(type): | |
305 """Meta class for directive factories.""" | |
262 | 306 |
263 def __new__(cls, name, bases, d): | 307 def __new__(cls, name, bases, d): |
264 if 'directives' in d: | 308 if 'directives' in d: |
265 d['_dir_by_name'] = dict(d['directives']) | 309 d['_dir_by_name'] = dict(d['directives']) |
266 d['_dir_order'] = [directive[1] for directive in d['directives']] | 310 d['_dir_order'] = [directive[1] for directive in d['directives']] |
267 | 311 |
268 return type.__new__(cls, name, bases, d) | 312 return type.__new__(cls, name, bases, d) |
269 | 313 |
270 | 314 |
271 class Template(object): | 315 class DirectiveFactory(object): |
316 """Base for classes that provide a set of template directives. | |
317 | |
318 :since: version 0.6 | |
319 """ | |
320 __metaclass__ = DirectiveFactoryMeta | |
321 | |
322 directives = [] | |
323 """A list of `(name, cls)` tuples that define the set of directives | |
324 provided by this factory. | |
325 """ | |
326 | |
327 def compare_directives(self): | |
328 """Return a function that takes two directive classes and compares | |
329 them to determine their relative ordering. | |
330 """ | |
331 def _get_index(cls): | |
332 if cls in self._dir_order: | |
333 return self._dir_order.index(cls) | |
334 return 0 | |
335 return lambda a, b: cmp(_get_index(a[0]), _get_index(b[0])) | |
336 | |
337 def get_directive(self, name): | |
338 """Return the directive class for the given name. | |
339 | |
340 :param name: the directive name as used in the template | |
341 :return: the directive class | |
342 :see: `Directive` | |
343 """ | |
344 return self._dir_by_name.get(name) | |
345 | |
346 | |
347 class Template(DirectiveFactory): | |
272 """Abstract template base class. | 348 """Abstract template base class. |
273 | 349 |
274 This class implements most of the template processing model, but does not | 350 This class implements most of the template processing model, but does not |
275 specify the syntax of templates. | 351 specify the syntax of templates. |
276 """ | 352 """ |
277 __metaclass__ = TemplateMeta | 353 |
354 EXEC = StreamEventKind('EXEC') | |
355 """Stream event kind representing a Python code suite to execute.""" | |
278 | 356 |
279 EXPR = StreamEventKind('EXPR') | 357 EXPR = StreamEventKind('EXPR') |
280 """Stream event kind representing a Python expression.""" | 358 """Stream event kind representing a Python expression.""" |
281 | 359 |
282 INCLUDE = StreamEventKind('INCLUDE') | 360 INCLUDE = StreamEventKind('INCLUDE') |
285 SUB = StreamEventKind('SUB') | 363 SUB = StreamEventKind('SUB') |
286 """Stream event kind representing a nested stream to which one or more | 364 """Stream event kind representing a nested stream to which one or more |
287 directives should be applied. | 365 directives should be applied. |
288 """ | 366 """ |
289 | 367 |
290 def __init__(self, source, basedir=None, filename=None, loader=None, | 368 serializer = None |
291 encoding=None, lookup='lenient'): | 369 _number_conv = unicode # function used to convert numbers to event data |
370 | |
371 def __init__(self, source, filepath=None, filename=None, loader=None, | |
372 encoding=None, lookup='strict', allow_exec=True): | |
292 """Initialize a template from either a string, a file-like object, or | 373 """Initialize a template from either a string, a file-like object, or |
293 an already parsed markup stream. | 374 an already parsed markup stream. |
294 | 375 |
295 :param source: a string, file-like object, or markup stream to read the | 376 :param source: a string, file-like object, or markup stream to read the |
296 template from | 377 template from |
297 :param basedir: the base directory containing the template file; when | 378 :param filepath: the absolute path to the template file |
298 loaded from a `TemplateLoader`, this will be the | 379 :param filename: the path to the template file relative to the search |
299 directory on the template search path in which the | 380 path |
300 template was found | |
301 :param filename: the name of the template file, relative to the given | |
302 base directory | |
303 :param loader: the `TemplateLoader` to use for loading included | 381 :param loader: the `TemplateLoader` to use for loading included |
304 templates | 382 templates |
305 :param encoding: the encoding of the `source` | 383 :param encoding: the encoding of the `source` |
306 :param lookup: the variable lookup mechanism; either "lenient" (the | 384 :param lookup: the variable lookup mechanism; either "strict" (the |
307 default), "strict", or a custom lookup class | 385 default), "lenient", or a custom lookup class |
308 """ | 386 :param allow_exec: whether Python code blocks in templates should be |
309 self.basedir = basedir | 387 allowed |
388 | |
389 :note: Changed in 0.5: Added the `allow_exec` argument | |
390 """ | |
391 self.filepath = filepath or filename | |
310 self.filename = filename | 392 self.filename = filename |
311 if basedir and filename: | |
312 self.filepath = os.path.join(basedir, filename) | |
313 else: | |
314 self.filepath = filename | |
315 self.loader = loader | 393 self.loader = loader |
316 self.lookup = lookup | 394 self.lookup = lookup |
395 self.allow_exec = allow_exec | |
396 self._init_filters() | |
397 self._prepared = False | |
317 | 398 |
318 if isinstance(source, basestring): | 399 if isinstance(source, basestring): |
319 source = StringIO(source) | 400 source = StringIO(source) |
320 else: | 401 else: |
321 source = source | 402 source = source |
322 try: | 403 try: |
323 self.stream = list(self._prepare(self._parse(source, encoding))) | 404 self._stream = self._parse(source, encoding) |
324 except ParseError, e: | 405 except ParseError, e: |
325 raise TemplateSyntaxError(e.msg, self.filepath, e.lineno, e.offset) | 406 raise TemplateSyntaxError(e.msg, self.filepath, e.lineno, e.offset) |
326 self.filters = [self._flatten, self._eval] | 407 |
327 if loader: | 408 def __getstate__(self): |
328 self.filters.append(self._include) | 409 state = self.__dict__.copy() |
410 state['filters'] = [] | |
411 return state | |
412 | |
413 def __setstate__(self, state): | |
414 self.__dict__ = state | |
415 self._init_filters() | |
329 | 416 |
330 def __repr__(self): | 417 def __repr__(self): |
331 return '<%s "%s">' % (self.__class__.__name__, self.filename) | 418 return '<%s "%s">' % (self.__class__.__name__, self.filename) |
419 | |
420 def _init_filters(self): | |
421 self.filters = [self._flatten] | |
422 if self.loader: | |
423 self.filters.append(self._include) | |
424 | |
425 def _get_stream(self): | |
426 if not self._prepared: | |
427 self._stream = list(self._prepare(self._stream)) | |
428 self._prepared = True | |
429 return self._stream | |
430 stream = property(_get_stream) | |
332 | 431 |
333 def _parse(self, source, encoding): | 432 def _parse(self, source, encoding): |
334 """Parse the template. | 433 """Parse the template. |
335 | 434 |
336 The parsing stage parses the template and constructs a list of | 435 The parsing stage parses the template and constructs a list of |
347 def _prepare(self, stream): | 446 def _prepare(self, stream): |
348 """Call the `attach` method of every directive found in the template. | 447 """Call the `attach` method of every directive found in the template. |
349 | 448 |
350 :param stream: the event stream of the template | 449 :param stream: the event stream of the template |
351 """ | 450 """ |
451 from genshi.template.loader import TemplateNotFound | |
452 | |
352 for kind, data, pos in stream: | 453 for kind, data, pos in stream: |
353 if kind is SUB: | 454 if kind is SUB: |
354 directives = [] | 455 directives = [] |
355 substream = data[1] | 456 substream = data[1] |
356 for cls, value, namespaces, pos in data[0]: | 457 for cls, value, namespaces, pos in data[0]: |
364 else: | 465 else: |
365 for event in substream: | 466 for event in substream: |
366 yield event | 467 yield event |
367 else: | 468 else: |
368 if kind is INCLUDE: | 469 if kind is INCLUDE: |
369 data = data[0], list(self._prepare(data[1])) | 470 href, cls, fallback = data |
471 if isinstance(href, basestring) and \ | |
472 not getattr(self.loader, 'auto_reload', True): | |
473 # If the path to the included template is static, and | |
474 # auto-reloading is disabled on the template loader, | |
475 # the template is inlined into the stream | |
476 try: | |
477 tmpl = self.loader.load(href, relative_to=pos[0], | |
478 cls=cls or self.__class__) | |
479 for event in tmpl.stream: | |
480 yield event | |
481 except TemplateNotFound: | |
482 if fallback is None: | |
483 raise | |
484 for event in self._prepare(fallback): | |
485 yield event | |
486 continue | |
487 elif fallback: | |
488 # Otherwise the include is performed at run time | |
489 data = href, cls, list(self._prepare(fallback)) | |
490 | |
370 yield kind, data, pos | 491 yield kind, data, pos |
492 | |
493 def compile(self): | |
494 """Compile the template to a Python module, and return the module | |
495 object. | |
496 """ | |
497 from imp import new_module | |
498 from genshi.template.inline import inline | |
499 | |
500 name = (self.filename or '_some_ident').replace('.', '_') | |
501 module = new_module(name) | |
502 source = u'\n'.join(list(inline(self))) | |
503 code = compile(source, self.filepath or '<string>', 'exec') | |
504 exec code in module.__dict__, module.__dict__ | |
505 return module | |
371 | 506 |
372 def generate(self, *args, **kwargs): | 507 def generate(self, *args, **kwargs): |
373 """Apply the template to the given context data. | 508 """Apply the template to the given context data. |
374 | 509 |
375 Any keyword arguments are made available to the template as context | 510 Any keyword arguments are made available to the template as context |
380 This calling style is used for internal processing. | 515 This calling style is used for internal processing. |
381 | 516 |
382 :return: a markup event stream representing the result of applying | 517 :return: a markup event stream representing the result of applying |
383 the template to the context data. | 518 the template to the context data. |
384 """ | 519 """ |
520 vars = {} | |
385 if args: | 521 if args: |
386 assert len(args) == 1 | 522 assert len(args) == 1 |
387 ctxt = args[0] | 523 ctxt = args[0] |
388 if ctxt is None: | 524 if ctxt is None: |
389 ctxt = Context(**kwargs) | 525 ctxt = Context(**kwargs) |
526 else: | |
527 vars = kwargs | |
390 assert isinstance(ctxt, Context) | 528 assert isinstance(ctxt, Context) |
391 else: | 529 else: |
392 ctxt = Context(**kwargs) | 530 ctxt = Context(**kwargs) |
393 | 531 |
394 stream = self.stream | 532 stream = self.stream |
395 for filter_ in self.filters: | 533 for filter_ in self.filters: |
396 stream = filter_(iter(stream), ctxt) | 534 stream = filter_(iter(stream), ctxt, **vars) |
397 return Stream(stream) | 535 return Stream(stream, self.serializer) |
398 | 536 |
399 def _eval(self, stream, ctxt): | 537 def _flatten(self, stream, ctxt, **vars): |
400 """Internal stream filter that evaluates any expressions in `START` and | 538 number_conv = self._number_conv |
401 `TEXT` events. | |
402 """ | |
403 filters = (self._flatten, self._eval) | |
404 | 539 |
405 for kind, data, pos in stream: | 540 for kind, data, pos in stream: |
406 | 541 |
407 if kind is START and data[1]: | 542 if kind is START and data[1]: |
408 # Attributes may still contain expressions in start tags at | 543 # Attributes may still contain expressions in start tags at |
409 # this point, so do some evaluation | 544 # this point, so do some evaluation |
410 tag, attrs = data | 545 tag, attrs = data |
411 new_attrs = [] | 546 new_attrs = [] |
412 for name, substream in attrs: | 547 for name, substream in attrs: |
413 if isinstance(substream, basestring): | 548 if type(substream) is list: |
414 value = substream | |
415 else: | |
416 values = [] | 549 values = [] |
417 for subkind, subdata, subpos in self._eval(substream, | 550 for event in self._flatten(substream, ctxt, **vars): |
418 ctxt): | 551 if event[0] is TEXT: |
419 if subkind is TEXT: | 552 values.append(event[1]) |
420 values.append(subdata) | |
421 value = [x for x in values if x is not None] | 553 value = [x for x in values if x is not None] |
422 if not value: | 554 if not value: |
423 continue | 555 continue |
556 else: | |
557 value = substream | |
424 new_attrs.append((name, u''.join(value))) | 558 new_attrs.append((name, u''.join(value))) |
425 yield kind, (tag, Attrs(new_attrs)), pos | 559 yield kind, (tag, Attrs(new_attrs)), pos |
426 | 560 |
427 elif kind is EXPR: | 561 elif kind is EXPR: |
428 result = data.evaluate(ctxt) | 562 result = _eval_expr(data, ctxt, **vars) |
429 if result is not None: | 563 if result is not None: |
430 # First check for a string, otherwise the iterable test below | 564 # First check for a string, otherwise the iterable test |
431 # succeeds, and the string will be chopped up into individual | 565 # below succeeds, and the string will be chopped up into |
432 # characters | 566 # individual characters |
433 if isinstance(result, basestring): | 567 if isinstance(result, basestring): |
434 yield TEXT, result, pos | 568 yield TEXT, result, pos |
569 elif isinstance(result, (int, float, long)): | |
570 yield TEXT, number_conv(result), pos | |
435 elif hasattr(result, '__iter__'): | 571 elif hasattr(result, '__iter__'): |
436 substream = _ensure(result) | 572 for event in self._flatten(_ensure(result), ctxt, |
437 for filter_ in filters: | 573 **vars): |
438 substream = filter_(substream, ctxt) | |
439 for event in substream: | |
440 yield event | 574 yield event |
441 else: | 575 else: |
442 yield TEXT, unicode(result), pos | 576 yield TEXT, unicode(result), pos |
443 | 577 |
578 elif kind is EXEC: | |
579 _exec_suite(data, ctxt, **vars) | |
580 | |
581 elif kind is SUB: | |
582 # This event is a list of directives and a list of nested | |
583 # events to which those directives should be applied | |
584 substream = _apply_directives(data[1], data[0], ctxt, **vars) | |
585 for event in self._flatten(substream, ctxt, **vars): | |
586 yield event | |
587 | |
444 else: | 588 else: |
445 yield kind, data, pos | 589 yield kind, data, pos |
446 | 590 |
447 def _flatten(self, stream, ctxt): | 591 def _include(self, stream, ctxt, **vars): |
448 """Internal stream filter that expands `SUB` events in the stream.""" | |
449 for event in stream: | |
450 if event[0] is SUB: | |
451 # This event is a list of directives and a list of nested | |
452 # events to which those directives should be applied | |
453 directives, substream = event[1] | |
454 substream = _apply_directives(substream, ctxt, directives) | |
455 for event in self._flatten(substream, ctxt): | |
456 yield event | |
457 else: | |
458 yield event | |
459 | |
460 def _include(self, stream, ctxt): | |
461 """Internal stream filter that performs inclusion of external | 592 """Internal stream filter that performs inclusion of external |
462 template files. | 593 template files. |
463 """ | 594 """ |
464 from genshi.template.loader import TemplateNotFound | 595 from genshi.template.loader import TemplateNotFound |
465 | 596 |
466 for event in stream: | 597 for event in stream: |
467 if event[0] is INCLUDE: | 598 if event[0] is INCLUDE: |
468 href, fallback = event[1] | 599 href, cls, fallback = event[1] |
469 if not isinstance(href, basestring): | 600 if not isinstance(href, basestring): |
470 parts = [] | 601 parts = [] |
471 for subkind, subdata, subpos in self._eval(href, ctxt): | 602 for subkind, subdata, subpos in self._flatten(href, ctxt, |
603 **vars): | |
472 if subkind is TEXT: | 604 if subkind is TEXT: |
473 parts.append(subdata) | 605 parts.append(subdata) |
474 href = u''.join([x for x in parts if x is not None]) | 606 href = u''.join([x for x in parts if x is not None]) |
475 try: | 607 try: |
476 tmpl = self.loader.load(href, relative_to=event[2][0], | 608 tmpl = self.loader.load(href, relative_to=event[2][0], |
477 cls=self.__class__) | 609 cls=cls or self.__class__) |
478 for event in tmpl.generate(ctxt): | 610 for event in tmpl.generate(ctxt, **vars): |
479 yield event | 611 yield event |
480 except TemplateNotFound: | 612 except TemplateNotFound: |
481 if fallback is None: | 613 if fallback is None: |
482 raise | 614 raise |
483 for filter_ in self.filters: | 615 for filter_ in self.filters: |
484 fallback = filter_(iter(fallback), ctxt) | 616 fallback = filter_(iter(fallback), ctxt, **vars) |
485 for event in fallback: | 617 for event in fallback: |
486 yield event | 618 yield event |
487 else: | 619 else: |
488 yield event | 620 yield event |
489 | 621 |
490 | 622 |
623 EXEC = Template.EXEC | |
491 EXPR = Template.EXPR | 624 EXPR = Template.EXPR |
492 INCLUDE = Template.INCLUDE | 625 INCLUDE = Template.INCLUDE |
493 SUB = Template.SUB | 626 SUB = Template.SUB |