1 | # -*- coding: utf-8 -*- |
---|
2 | # |
---|
3 | # Copyright (C) 2006-2009 Edgewall Software |
---|
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 | """Implementation of the various template directives.""" |
---|
15 | |
---|
16 | from genshi.core import QName, Stream |
---|
17 | from genshi.path import Path |
---|
18 | from genshi.template.base import TemplateRuntimeError, TemplateSyntaxError, \ |
---|
19 | EXPR, _apply_directives, _eval_expr |
---|
20 | from genshi.template.eval import Expression, ExpressionASTTransformer, \ |
---|
21 | _ast, _parse |
---|
22 | |
---|
23 | __all__ = ['AttrsDirective', 'ChooseDirective', 'ContentDirective', |
---|
24 | 'DefDirective', 'ForDirective', 'IfDirective', 'MatchDirective', |
---|
25 | 'OtherwiseDirective', 'ReplaceDirective', 'StripDirective', |
---|
26 | 'WhenDirective', 'WithDirective'] |
---|
27 | __docformat__ = 'restructuredtext en' |
---|
28 | |
---|
29 | |
---|
30 | class DirectiveMeta(type): |
---|
31 | """Meta class for template directives.""" |
---|
32 | |
---|
33 | def __new__(cls, name, bases, d): |
---|
34 | d['tagname'] = name.lower().replace('directive', '') |
---|
35 | return type.__new__(cls, name, bases, d) |
---|
36 | |
---|
37 | |
---|
38 | class Directive(object): |
---|
39 | """Abstract base class for template directives. |
---|
40 | |
---|
41 | A directive is basically a callable that takes three positional arguments: |
---|
42 | ``ctxt`` is the template data context, ``stream`` is an iterable over the |
---|
43 | events that the directive applies to, and ``directives`` is is a list of |
---|
44 | other directives on the same stream that need to be applied. |
---|
45 | |
---|
46 | Directives can be "anonymous" or "registered". Registered directives can be |
---|
47 | applied by the template author using an XML attribute with the |
---|
48 | corresponding name in the template. Such directives should be subclasses of |
---|
49 | this base class that can be instantiated with the value of the directive |
---|
50 | attribute as parameter. |
---|
51 | |
---|
52 | Anonymous directives are simply functions conforming to the protocol |
---|
53 | described above, and can only be applied programmatically (for example by |
---|
54 | template filters). |
---|
55 | """ |
---|
56 | __metaclass__ = DirectiveMeta |
---|
57 | __slots__ = ['expr'] |
---|
58 | |
---|
59 | def __init__(self, value, template=None, namespaces=None, lineno=-1, |
---|
60 | offset=-1): |
---|
61 | self.expr = self._parse_expr(value, template, lineno, offset) |
---|
62 | |
---|
63 | @classmethod |
---|
64 | def attach(cls, template, stream, value, namespaces, pos): |
---|
65 | """Called after the template stream has been completely parsed. |
---|
66 | |
---|
67 | :param template: the `Template` object |
---|
68 | :param stream: the event stream associated with the directive |
---|
69 | :param value: the argument value for the directive; if the directive was |
---|
70 | specified as an element, this will be an `Attrs` instance |
---|
71 | with all specified attributes, otherwise it will be a |
---|
72 | `unicode` object with just the attribute value |
---|
73 | :param namespaces: a mapping of namespace URIs to prefixes |
---|
74 | :param pos: a ``(filename, lineno, offset)`` tuple describing the |
---|
75 | location where the directive was found in the source |
---|
76 | |
---|
77 | This class method should return a ``(directive, stream)`` tuple. If |
---|
78 | ``directive`` is not ``None``, it should be an instance of the `Directive` |
---|
79 | class, and gets added to the list of directives applied to the substream |
---|
80 | at runtime. `stream` is an event stream that replaces the original |
---|
81 | stream associated with the directive. |
---|
82 | """ |
---|
83 | return cls(value, template, namespaces, *pos[1:]), stream |
---|
84 | |
---|
85 | def __call__(self, stream, directives, ctxt, **vars): |
---|
86 | """Apply the directive to the given stream. |
---|
87 | |
---|
88 | :param stream: the event stream |
---|
89 | :param directives: a list of the remaining directives that should |
---|
90 | process the stream |
---|
91 | :param ctxt: the context data |
---|
92 | :param vars: additional variables that should be made available when |
---|
93 | Python code is executed |
---|
94 | """ |
---|
95 | raise NotImplementedError |
---|
96 | |
---|
97 | def __repr__(self): |
---|
98 | expr = '' |
---|
99 | if getattr(self, 'expr', None) is not None: |
---|
100 | expr = ' "%s"' % self.expr.source |
---|
101 | return '<%s%s>' % (type(self).__name__, expr) |
---|
102 | |
---|
103 | @classmethod |
---|
104 | def _parse_expr(cls, expr, template, lineno=-1, offset=-1): |
---|
105 | """Parses the given expression, raising a useful error message when a |
---|
106 | syntax error is encountered. |
---|
107 | """ |
---|
108 | try: |
---|
109 | return expr and Expression(expr, template.filepath, lineno, |
---|
110 | lookup=template.lookup) or None |
---|
111 | except SyntaxError, err: |
---|
112 | err.msg += ' in expression "%s" of "%s" directive' % (expr, |
---|
113 | cls.tagname) |
---|
114 | raise TemplateSyntaxError(err, template.filepath, lineno, |
---|
115 | offset + (err.offset or 0)) |
---|
116 | |
---|
117 | |
---|
118 | def _assignment(ast): |
---|
119 | """Takes the AST representation of an assignment, and returns a |
---|
120 | function that applies the assignment of a given value to a dictionary. |
---|
121 | """ |
---|
122 | def _names(node): |
---|
123 | if isinstance(node, _ast.Tuple): |
---|
124 | return tuple([_names(child) for child in node.elts]) |
---|
125 | elif isinstance(node, _ast.Name): |
---|
126 | return node.id |
---|
127 | def _assign(data, value, names=_names(ast)): |
---|
128 | if type(names) is tuple: |
---|
129 | for idx in range(len(names)): |
---|
130 | _assign(data, value[idx], names[idx]) |
---|
131 | else: |
---|
132 | data[names] = value |
---|
133 | return _assign |
---|
134 | |
---|
135 | |
---|
136 | class AttrsDirective(Directive): |
---|
137 | """Implementation of the ``py:attrs`` template directive. |
---|
138 | |
---|
139 | The value of the ``py:attrs`` attribute should be a dictionary or a sequence |
---|
140 | of ``(name, value)`` tuples. The items in that dictionary or sequence are |
---|
141 | added as attributes to the element: |
---|
142 | |
---|
143 | >>> from genshi.template import MarkupTemplate |
---|
144 | >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/"> |
---|
145 | ... <li py:attrs="foo">Bar</li> |
---|
146 | ... </ul>''') |
---|
147 | >>> print(tmpl.generate(foo={'class': 'collapse'})) |
---|
148 | <ul> |
---|
149 | <li class="collapse">Bar</li> |
---|
150 | </ul> |
---|
151 | >>> print(tmpl.generate(foo=[('class', 'collapse')])) |
---|
152 | <ul> |
---|
153 | <li class="collapse">Bar</li> |
---|
154 | </ul> |
---|
155 | |
---|
156 | If the value evaluates to ``None`` (or any other non-truth value), no |
---|
157 | attributes are added: |
---|
158 | |
---|
159 | >>> print(tmpl.generate(foo=None)) |
---|
160 | <ul> |
---|
161 | <li>Bar</li> |
---|
162 | </ul> |
---|
163 | """ |
---|
164 | __slots__ = [] |
---|
165 | |
---|
166 | def __call__(self, stream, directives, ctxt, **vars): |
---|
167 | def _generate(): |
---|
168 | kind, (tag, attrib), pos = stream.next() |
---|
169 | attrs = _eval_expr(self.expr, ctxt, vars) |
---|
170 | if attrs: |
---|
171 | if isinstance(attrs, Stream): |
---|
172 | try: |
---|
173 | attrs = iter(attrs).next() |
---|
174 | except StopIteration: |
---|
175 | attrs = [] |
---|
176 | elif not isinstance(attrs, list): # assume it's a dict |
---|
177 | attrs = attrs.items() |
---|
178 | attrib |= [ |
---|
179 | (QName(n), v is not None and unicode(v).strip() or None) |
---|
180 | for n, v in attrs |
---|
181 | ] |
---|
182 | yield kind, (tag, attrib), pos |
---|
183 | for event in stream: |
---|
184 | yield event |
---|
185 | |
---|
186 | return _apply_directives(_generate(), directives, ctxt, vars) |
---|
187 | |
---|
188 | |
---|
189 | class ContentDirective(Directive): |
---|
190 | """Implementation of the ``py:content`` template directive. |
---|
191 | |
---|
192 | This directive replaces the content of the element with the result of |
---|
193 | evaluating the value of the ``py:content`` attribute: |
---|
194 | |
---|
195 | >>> from genshi.template import MarkupTemplate |
---|
196 | >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/"> |
---|
197 | ... <li py:content="bar">Hello</li> |
---|
198 | ... </ul>''') |
---|
199 | >>> print(tmpl.generate(bar='Bye')) |
---|
200 | <ul> |
---|
201 | <li>Bye</li> |
---|
202 | </ul> |
---|
203 | """ |
---|
204 | __slots__ = [] |
---|
205 | |
---|
206 | @classmethod |
---|
207 | def attach(cls, template, stream, value, namespaces, pos): |
---|
208 | if type(value) is dict: |
---|
209 | raise TemplateSyntaxError('The content directive can not be used ' |
---|
210 | 'as an element', template.filepath, |
---|
211 | *pos[1:]) |
---|
212 | expr = cls._parse_expr(value, template, *pos[1:]) |
---|
213 | return None, [stream[0], (EXPR, expr, pos), stream[-1]] |
---|
214 | |
---|
215 | |
---|
216 | class DefDirective(Directive): |
---|
217 | """Implementation of the ``py:def`` template directive. |
---|
218 | |
---|
219 | This directive can be used to create "Named Template Functions", which |
---|
220 | are template snippets that are not actually output during normal |
---|
221 | processing, but rather can be expanded from expressions in other places |
---|
222 | in the template. |
---|
223 | |
---|
224 | A named template function can be used just like a normal Python function |
---|
225 | from template expressions: |
---|
226 | |
---|
227 | >>> from genshi.template import MarkupTemplate |
---|
228 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
---|
229 | ... <p py:def="echo(greeting, name='world')" class="message"> |
---|
230 | ... ${greeting}, ${name}! |
---|
231 | ... </p> |
---|
232 | ... ${echo('Hi', name='you')} |
---|
233 | ... </div>''') |
---|
234 | >>> print(tmpl.generate(bar='Bye')) |
---|
235 | <div> |
---|
236 | <p class="message"> |
---|
237 | Hi, you! |
---|
238 | </p> |
---|
239 | </div> |
---|
240 | |
---|
241 | If a function does not require parameters, the parenthesis can be omitted |
---|
242 | in the definition: |
---|
243 | |
---|
244 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
---|
245 | ... <p py:def="helloworld" class="message"> |
---|
246 | ... Hello, world! |
---|
247 | ... </p> |
---|
248 | ... ${helloworld()} |
---|
249 | ... </div>''') |
---|
250 | >>> print(tmpl.generate(bar='Bye')) |
---|
251 | <div> |
---|
252 | <p class="message"> |
---|
253 | Hello, world! |
---|
254 | </p> |
---|
255 | </div> |
---|
256 | """ |
---|
257 | __slots__ = ['name', 'args', 'star_args', 'dstar_args', 'defaults'] |
---|
258 | |
---|
259 | def __init__(self, args, template, namespaces=None, lineno=-1, offset=-1): |
---|
260 | Directive.__init__(self, None, template, namespaces, lineno, offset) |
---|
261 | ast = _parse(args).body |
---|
262 | self.args = [] |
---|
263 | self.star_args = None |
---|
264 | self.dstar_args = None |
---|
265 | self.defaults = {} |
---|
266 | if isinstance(ast, _ast.Call): |
---|
267 | self.name = ast.func.id |
---|
268 | for arg in ast.args: |
---|
269 | # only names |
---|
270 | self.args.append(arg.id) |
---|
271 | for kwd in ast.keywords: |
---|
272 | self.args.append(kwd.arg) |
---|
273 | exp = Expression(kwd.value, template.filepath, |
---|
274 | lineno, lookup=template.lookup) |
---|
275 | self.defaults[kwd.arg] = exp |
---|
276 | if getattr(ast, 'starargs', None): |
---|
277 | self.star_args = ast.starargs.id |
---|
278 | if getattr(ast, 'kwargs', None): |
---|
279 | self.dstar_args = ast.kwargs.id |
---|
280 | else: |
---|
281 | self.name = ast.id |
---|
282 | |
---|
283 | @classmethod |
---|
284 | def attach(cls, template, stream, value, namespaces, pos): |
---|
285 | if type(value) is dict: |
---|
286 | value = value.get('function') |
---|
287 | return super(DefDirective, cls).attach(template, stream, value, |
---|
288 | namespaces, pos) |
---|
289 | |
---|
290 | def __call__(self, stream, directives, ctxt, **vars): |
---|
291 | stream = list(stream) |
---|
292 | |
---|
293 | def function(*args, **kwargs): |
---|
294 | scope = {} |
---|
295 | args = list(args) # make mutable |
---|
296 | for name in self.args: |
---|
297 | if args: |
---|
298 | scope[name] = args.pop(0) |
---|
299 | else: |
---|
300 | if name in kwargs: |
---|
301 | val = kwargs.pop(name) |
---|
302 | else: |
---|
303 | val = _eval_expr(self.defaults.get(name), ctxt, vars) |
---|
304 | scope[name] = val |
---|
305 | if not self.star_args is None: |
---|
306 | scope[self.star_args] = args |
---|
307 | if not self.dstar_args is None: |
---|
308 | scope[self.dstar_args] = kwargs |
---|
309 | ctxt.push(scope) |
---|
310 | for event in _apply_directives(stream, directives, ctxt, vars): |
---|
311 | yield event |
---|
312 | ctxt.pop() |
---|
313 | function.__name__ = self.name |
---|
314 | |
---|
315 | # Store the function reference in the bottom context frame so that it |
---|
316 | # doesn't get popped off before processing the template has finished |
---|
317 | # FIXME: this makes context data mutable as a side-effect |
---|
318 | ctxt.frames[-1][self.name] = function |
---|
319 | |
---|
320 | return [] |
---|
321 | |
---|
322 | def __repr__(self): |
---|
323 | return '<%s "%s">' % (type(self).__name__, self.name) |
---|
324 | |
---|
325 | |
---|
326 | class ForDirective(Directive): |
---|
327 | """Implementation of the ``py:for`` template directive for repeating an |
---|
328 | element based on an iterable in the context data. |
---|
329 | |
---|
330 | >>> from genshi.template import MarkupTemplate |
---|
331 | >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/"> |
---|
332 | ... <li py:for="item in items">${item}</li> |
---|
333 | ... </ul>''') |
---|
334 | >>> print(tmpl.generate(items=[1, 2, 3])) |
---|
335 | <ul> |
---|
336 | <li>1</li><li>2</li><li>3</li> |
---|
337 | </ul> |
---|
338 | """ |
---|
339 | __slots__ = ['assign', 'filename'] |
---|
340 | |
---|
341 | def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): |
---|
342 | if ' in ' not in value: |
---|
343 | raise TemplateSyntaxError('"in" keyword missing in "for" directive', |
---|
344 | template.filepath, lineno, offset) |
---|
345 | assign, value = value.split(' in ', 1) |
---|
346 | ast = _parse(assign, 'exec') |
---|
347 | value = 'iter(%s)' % value.strip() |
---|
348 | self.assign = _assignment(ast.body[0].value) |
---|
349 | self.filename = template.filepath |
---|
350 | Directive.__init__(self, value, template, namespaces, lineno, offset) |
---|
351 | |
---|
352 | @classmethod |
---|
353 | def attach(cls, template, stream, value, namespaces, pos): |
---|
354 | if type(value) is dict: |
---|
355 | value = value.get('each') |
---|
356 | return super(ForDirective, cls).attach(template, stream, value, |
---|
357 | namespaces, pos) |
---|
358 | |
---|
359 | def __call__(self, stream, directives, ctxt, **vars): |
---|
360 | iterable = _eval_expr(self.expr, ctxt, vars) |
---|
361 | if iterable is None: |
---|
362 | return |
---|
363 | |
---|
364 | assign = self.assign |
---|
365 | scope = {} |
---|
366 | stream = list(stream) |
---|
367 | for item in iterable: |
---|
368 | assign(scope, item) |
---|
369 | ctxt.push(scope) |
---|
370 | for event in _apply_directives(stream, directives, ctxt, vars): |
---|
371 | yield event |
---|
372 | ctxt.pop() |
---|
373 | |
---|
374 | def __repr__(self): |
---|
375 | return '<%s>' % type(self).__name__ |
---|
376 | |
---|
377 | |
---|
378 | class IfDirective(Directive): |
---|
379 | """Implementation of the ``py:if`` template directive for conditionally |
---|
380 | excluding elements from being output. |
---|
381 | |
---|
382 | >>> from genshi.template import MarkupTemplate |
---|
383 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
---|
384 | ... <b py:if="foo">${bar}</b> |
---|
385 | ... </div>''') |
---|
386 | >>> print(tmpl.generate(foo=True, bar='Hello')) |
---|
387 | <div> |
---|
388 | <b>Hello</b> |
---|
389 | </div> |
---|
390 | """ |
---|
391 | __slots__ = [] |
---|
392 | |
---|
393 | @classmethod |
---|
394 | def attach(cls, template, stream, value, namespaces, pos): |
---|
395 | if type(value) is dict: |
---|
396 | value = value.get('test') |
---|
397 | return super(IfDirective, cls).attach(template, stream, value, |
---|
398 | namespaces, pos) |
---|
399 | |
---|
400 | def __call__(self, stream, directives, ctxt, **vars): |
---|
401 | value = _eval_expr(self.expr, ctxt, vars) |
---|
402 | if value: |
---|
403 | return _apply_directives(stream, directives, ctxt, vars) |
---|
404 | return [] |
---|
405 | |
---|
406 | |
---|
407 | class MatchDirective(Directive): |
---|
408 | """Implementation of the ``py:match`` template directive. |
---|
409 | |
---|
410 | >>> from genshi.template import MarkupTemplate |
---|
411 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
---|
412 | ... <span py:match="greeting"> |
---|
413 | ... Hello ${select('@name')} |
---|
414 | ... </span> |
---|
415 | ... <greeting name="Dude" /> |
---|
416 | ... </div>''') |
---|
417 | >>> print(tmpl.generate()) |
---|
418 | <div> |
---|
419 | <span> |
---|
420 | Hello Dude |
---|
421 | </span> |
---|
422 | </div> |
---|
423 | """ |
---|
424 | __slots__ = ['path', 'namespaces', 'hints'] |
---|
425 | |
---|
426 | def __init__(self, value, template, hints=None, namespaces=None, |
---|
427 | lineno=-1, offset=-1): |
---|
428 | Directive.__init__(self, None, template, namespaces, lineno, offset) |
---|
429 | self.path = Path(value, template.filepath, lineno) |
---|
430 | self.namespaces = namespaces or {} |
---|
431 | self.hints = hints or () |
---|
432 | |
---|
433 | @classmethod |
---|
434 | def attach(cls, template, stream, value, namespaces, pos): |
---|
435 | hints = [] |
---|
436 | if type(value) is dict: |
---|
437 | if value.get('buffer', '').lower() == 'false': |
---|
438 | hints.append('not_buffered') |
---|
439 | if value.get('once', '').lower() == 'true': |
---|
440 | hints.append('match_once') |
---|
441 | if value.get('recursive', '').lower() == 'false': |
---|
442 | hints.append('not_recursive') |
---|
443 | value = value.get('path') |
---|
444 | return cls(value, template, frozenset(hints), namespaces, *pos[1:]), \ |
---|
445 | stream |
---|
446 | |
---|
447 | def __call__(self, stream, directives, ctxt, **vars): |
---|
448 | ctxt._match_templates.append((self.path.test(ignore_context=True), |
---|
449 | self.path, list(stream), self.hints, |
---|
450 | self.namespaces, directives)) |
---|
451 | return [] |
---|
452 | |
---|
453 | def __repr__(self): |
---|
454 | return '<%s "%s">' % (type(self).__name__, self.path.source) |
---|
455 | |
---|
456 | |
---|
457 | class ReplaceDirective(Directive): |
---|
458 | """Implementation of the ``py:replace`` template directive. |
---|
459 | |
---|
460 | This directive replaces the element with the result of evaluating the |
---|
461 | value of the ``py:replace`` attribute: |
---|
462 | |
---|
463 | >>> from genshi.template import MarkupTemplate |
---|
464 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
---|
465 | ... <span py:replace="bar">Hello</span> |
---|
466 | ... </div>''') |
---|
467 | >>> print(tmpl.generate(bar='Bye')) |
---|
468 | <div> |
---|
469 | Bye |
---|
470 | </div> |
---|
471 | |
---|
472 | This directive is equivalent to ``py:content`` combined with ``py:strip``, |
---|
473 | providing a less verbose way to achieve the same effect: |
---|
474 | |
---|
475 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
---|
476 | ... <span py:content="bar" py:strip="">Hello</span> |
---|
477 | ... </div>''') |
---|
478 | >>> print(tmpl.generate(bar='Bye')) |
---|
479 | <div> |
---|
480 | Bye |
---|
481 | </div> |
---|
482 | """ |
---|
483 | __slots__ = [] |
---|
484 | |
---|
485 | @classmethod |
---|
486 | def attach(cls, template, stream, value, namespaces, pos): |
---|
487 | if type(value) is dict: |
---|
488 | value = value.get('value') |
---|
489 | if not value: |
---|
490 | raise TemplateSyntaxError('missing value for "replace" directive', |
---|
491 | template.filepath, *pos[1:]) |
---|
492 | expr = cls._parse_expr(value, template, *pos[1:]) |
---|
493 | return None, [(EXPR, expr, pos)] |
---|
494 | |
---|
495 | |
---|
496 | class StripDirective(Directive): |
---|
497 | """Implementation of the ``py:strip`` template directive. |
---|
498 | |
---|
499 | When the value of the ``py:strip`` attribute evaluates to ``True``, the |
---|
500 | element is stripped from the output |
---|
501 | |
---|
502 | >>> from genshi.template import MarkupTemplate |
---|
503 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
---|
504 | ... <div py:strip="True"><b>foo</b></div> |
---|
505 | ... </div>''') |
---|
506 | >>> print(tmpl.generate()) |
---|
507 | <div> |
---|
508 | <b>foo</b> |
---|
509 | </div> |
---|
510 | |
---|
511 | Leaving the attribute value empty is equivalent to a truth value. |
---|
512 | |
---|
513 | This directive is particulary interesting for named template functions or |
---|
514 | match templates that do not generate a top-level element: |
---|
515 | |
---|
516 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
---|
517 | ... <div py:def="echo(what)" py:strip=""> |
---|
518 | ... <b>${what}</b> |
---|
519 | ... </div> |
---|
520 | ... ${echo('foo')} |
---|
521 | ... </div>''') |
---|
522 | >>> print(tmpl.generate()) |
---|
523 | <div> |
---|
524 | <b>foo</b> |
---|
525 | </div> |
---|
526 | """ |
---|
527 | __slots__ = [] |
---|
528 | |
---|
529 | def __call__(self, stream, directives, ctxt, **vars): |
---|
530 | def _generate(): |
---|
531 | if not self.expr or _eval_expr(self.expr, ctxt, vars): |
---|
532 | stream.next() # skip start tag |
---|
533 | previous = stream.next() |
---|
534 | for event in stream: |
---|
535 | yield previous |
---|
536 | previous = event |
---|
537 | else: |
---|
538 | for event in stream: |
---|
539 | yield event |
---|
540 | return _apply_directives(_generate(), directives, ctxt, vars) |
---|
541 | |
---|
542 | |
---|
543 | class ChooseDirective(Directive): |
---|
544 | """Implementation of the ``py:choose`` directive for conditionally selecting |
---|
545 | one of several body elements to display. |
---|
546 | |
---|
547 | If the ``py:choose`` expression is empty the expressions of nested |
---|
548 | ``py:when`` directives are tested for truth. The first true ``py:when`` |
---|
549 | body is output. If no ``py:when`` directive is matched then the fallback |
---|
550 | directive ``py:otherwise`` will be used. |
---|
551 | |
---|
552 | >>> from genshi.template import MarkupTemplate |
---|
553 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/" |
---|
554 | ... py:choose=""> |
---|
555 | ... <span py:when="0 == 1">0</span> |
---|
556 | ... <span py:when="1 == 1">1</span> |
---|
557 | ... <span py:otherwise="">2</span> |
---|
558 | ... </div>''') |
---|
559 | >>> print(tmpl.generate()) |
---|
560 | <div> |
---|
561 | <span>1</span> |
---|
562 | </div> |
---|
563 | |
---|
564 | If the ``py:choose`` directive contains an expression, the nested |
---|
565 | ``py:when`` directives are tested for equality to the ``py:choose`` |
---|
566 | expression: |
---|
567 | |
---|
568 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/" |
---|
569 | ... py:choose="2"> |
---|
570 | ... <span py:when="1">1</span> |
---|
571 | ... <span py:when="2">2</span> |
---|
572 | ... </div>''') |
---|
573 | >>> print(tmpl.generate()) |
---|
574 | <div> |
---|
575 | <span>2</span> |
---|
576 | </div> |
---|
577 | |
---|
578 | Behavior is undefined if a ``py:choose`` block contains content outside a |
---|
579 | ``py:when`` or ``py:otherwise`` block. Behavior is also undefined if a |
---|
580 | ``py:otherwise`` occurs before ``py:when`` blocks. |
---|
581 | """ |
---|
582 | __slots__ = ['matched', 'value'] |
---|
583 | |
---|
584 | @classmethod |
---|
585 | def attach(cls, template, stream, value, namespaces, pos): |
---|
586 | if type(value) is dict: |
---|
587 | value = value.get('test') |
---|
588 | return super(ChooseDirective, cls).attach(template, stream, value, |
---|
589 | namespaces, pos) |
---|
590 | |
---|
591 | def __call__(self, stream, directives, ctxt, **vars): |
---|
592 | info = [False, bool(self.expr), None] |
---|
593 | if self.expr: |
---|
594 | info[2] = _eval_expr(self.expr, ctxt, vars) |
---|
595 | ctxt._choice_stack.append(info) |
---|
596 | for event in _apply_directives(stream, directives, ctxt, vars): |
---|
597 | yield event |
---|
598 | ctxt._choice_stack.pop() |
---|
599 | |
---|
600 | |
---|
601 | class WhenDirective(Directive): |
---|
602 | """Implementation of the ``py:when`` directive for nesting in a parent with |
---|
603 | the ``py:choose`` directive. |
---|
604 | |
---|
605 | See the documentation of the `ChooseDirective` for usage. |
---|
606 | """ |
---|
607 | __slots__ = ['filename'] |
---|
608 | |
---|
609 | def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): |
---|
610 | Directive.__init__(self, value, template, namespaces, lineno, offset) |
---|
611 | self.filename = template.filepath |
---|
612 | |
---|
613 | @classmethod |
---|
614 | def attach(cls, template, stream, value, namespaces, pos): |
---|
615 | if type(value) is dict: |
---|
616 | value = value.get('test') |
---|
617 | return super(WhenDirective, cls).attach(template, stream, value, |
---|
618 | namespaces, pos) |
---|
619 | |
---|
620 | def __call__(self, stream, directives, ctxt, **vars): |
---|
621 | info = ctxt._choice_stack and ctxt._choice_stack[-1] |
---|
622 | if not info: |
---|
623 | raise TemplateRuntimeError('"when" directives can only be used ' |
---|
624 | 'inside a "choose" directive', |
---|
625 | self.filename, *(stream.next())[2][1:]) |
---|
626 | if info[0]: |
---|
627 | return [] |
---|
628 | if not self.expr and not info[1]: |
---|
629 | raise TemplateRuntimeError('either "choose" or "when" directive ' |
---|
630 | 'must have a test expression', |
---|
631 | self.filename, *(stream.next())[2][1:]) |
---|
632 | if info[1]: |
---|
633 | value = info[2] |
---|
634 | if self.expr: |
---|
635 | matched = value == _eval_expr(self.expr, ctxt, vars) |
---|
636 | else: |
---|
637 | matched = bool(value) |
---|
638 | else: |
---|
639 | matched = bool(_eval_expr(self.expr, ctxt, vars)) |
---|
640 | info[0] = matched |
---|
641 | if not matched: |
---|
642 | return [] |
---|
643 | |
---|
644 | return _apply_directives(stream, directives, ctxt, vars) |
---|
645 | |
---|
646 | |
---|
647 | class OtherwiseDirective(Directive): |
---|
648 | """Implementation of the ``py:otherwise`` directive for nesting in a parent |
---|
649 | with the ``py:choose`` directive. |
---|
650 | |
---|
651 | See the documentation of `ChooseDirective` for usage. |
---|
652 | """ |
---|
653 | __slots__ = ['filename'] |
---|
654 | |
---|
655 | def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): |
---|
656 | Directive.__init__(self, None, template, namespaces, lineno, offset) |
---|
657 | self.filename = template.filepath |
---|
658 | |
---|
659 | def __call__(self, stream, directives, ctxt, **vars): |
---|
660 | info = ctxt._choice_stack and ctxt._choice_stack[-1] |
---|
661 | if not info: |
---|
662 | raise TemplateRuntimeError('an "otherwise" directive can only be ' |
---|
663 | 'used inside a "choose" directive', |
---|
664 | self.filename, *(stream.next())[2][1:]) |
---|
665 | if info[0]: |
---|
666 | return [] |
---|
667 | info[0] = True |
---|
668 | |
---|
669 | return _apply_directives(stream, directives, ctxt, vars) |
---|
670 | |
---|
671 | |
---|
672 | class WithDirective(Directive): |
---|
673 | """Implementation of the ``py:with`` template directive, which allows |
---|
674 | shorthand access to variables and expressions. |
---|
675 | |
---|
676 | >>> from genshi.template import MarkupTemplate |
---|
677 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
---|
678 | ... <span py:with="y=7; z=x+10">$x $y $z</span> |
---|
679 | ... </div>''') |
---|
680 | >>> print(tmpl.generate(x=42)) |
---|
681 | <div> |
---|
682 | <span>42 7 52</span> |
---|
683 | </div> |
---|
684 | """ |
---|
685 | __slots__ = ['vars'] |
---|
686 | |
---|
687 | def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): |
---|
688 | Directive.__init__(self, None, template, namespaces, lineno, offset) |
---|
689 | self.vars = [] |
---|
690 | value = value.strip() |
---|
691 | try: |
---|
692 | ast = _parse(value, 'exec') |
---|
693 | for node in ast.body: |
---|
694 | if not isinstance(node, _ast.Assign): |
---|
695 | raise TemplateSyntaxError('only assignment allowed in ' |
---|
696 | 'value of the "with" directive', |
---|
697 | template.filepath, lineno, offset) |
---|
698 | self.vars.append(([_assignment(n) for n in node.targets], |
---|
699 | Expression(node.value, template.filepath, |
---|
700 | lineno, lookup=template.lookup))) |
---|
701 | except SyntaxError, err: |
---|
702 | err.msg += ' in expression "%s" of "%s" directive' % (value, |
---|
703 | self.tagname) |
---|
704 | raise TemplateSyntaxError(err, template.filepath, lineno, |
---|
705 | offset + (err.offset or 0)) |
---|
706 | |
---|
707 | @classmethod |
---|
708 | def attach(cls, template, stream, value, namespaces, pos): |
---|
709 | if type(value) is dict: |
---|
710 | value = value.get('vars') |
---|
711 | return super(WithDirective, cls).attach(template, stream, value, |
---|
712 | namespaces, pos) |
---|
713 | |
---|
714 | def __call__(self, stream, directives, ctxt, **vars): |
---|
715 | frame = {} |
---|
716 | ctxt.push(frame) |
---|
717 | for targets, expr in self.vars: |
---|
718 | value = _eval_expr(expr, ctxt, vars) |
---|
719 | for assign in targets: |
---|
720 | assign(frame, value) |
---|
721 | for event in _apply_directives(stream, directives, ctxt, vars): |
---|
722 | yield event |
---|
723 | ctxt.pop() |
---|
724 | |
---|
725 | def __repr__(self): |
---|
726 | return '<%s>' % (type(self).__name__) |
---|