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
16from genshi.compat import add_metaclass, text_type
17from genshi.core import QName, Stream
18from genshi.path import Path
19from genshi.template.base import TemplateRuntimeError, TemplateSyntaxError, \
20 EXPR, _apply_directives, _eval_expr
21from genshi.template.eval import Expression, _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
30class 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@add_metaclass(DirectiveMeta)
39class Directive(object):
40 """Abstract base class for template directives.
41
42 A directive is basically a callable that takes three positional arguments:
43 ``ctxt`` is the template data context, ``stream`` is an iterable over the
44 events that the directive applies to, and ``directives`` is is a list of
45 other directives on the same stream that need to be applied.
46
47 Directives can be "anonymous" or "registered". Registered directives can be
48 applied by the template author using an XML attribute with the
49 corresponding name in the template. Such directives should be subclasses of
50 this base class that can be instantiated with the value of the directive
51 attribute as parameter.
52
53 Anonymous directives are simply functions conforming to the protocol
54 described above, and can only be applied programmatically (for example by
55 template filters).
56 """
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 as 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
118def _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
136class 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 = next(stream)
169 attrs = _eval_expr(self.expr, ctxt, vars)
170 if attrs:
171 if isinstance(attrs, Stream):
172 try:
173 attrs = next(iter(attrs))
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 text_type(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
189class 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
216class 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 if hasattr(_ast, 'Starred') and isinstance(arg, _ast.Starred):
270 # Python 3.5+
271 self.star_args = arg.value.id
272 else:
273 # only names
274 self.args.append(arg.id)
275 for kwd in ast.keywords:
276 if kwd.arg is None:
277 # Python 3.5+
278 self.dstar_args = kwd.value.id
279 else:
280 self.args.append(kwd.arg)
281 exp = Expression(kwd.value, template.filepath,
282 lineno, lookup=template.lookup)
283 self.defaults[kwd.arg] = exp
284 if getattr(ast, 'starargs', None):
285 self.star_args = ast.starargs.id
286 if getattr(ast, 'kwargs', None):
287 self.dstar_args = ast.kwargs.id
288 else:
289 self.name = ast.id
290
291 @classmethod
292 def attach(cls, template, stream, value, namespaces, pos):
293 if type(value) is dict:
294 value = value.get('function')
295 return super(DefDirective, cls).attach(template, stream, value,
296 namespaces, pos)
297
298 def __call__(self, stream, directives, ctxt, **vars):
299 stream = list(stream)
300
301 def function(*args, **kwargs):
302 scope = {}
303 args = list(args) # make mutable
304 for name in self.args:
305 if args:
306 scope[name] = args.pop(0)
307 else:
308 if name in kwargs:
309 val = kwargs.pop(name)
310 else:
311 val = _eval_expr(self.defaults.get(name), ctxt, vars)
312 scope[name] = val
313 if not self.star_args is None:
314 scope[self.star_args] = args
315 if not self.dstar_args is None:
316 scope[self.dstar_args] = kwargs
317 ctxt.push(scope)
318 for event in _apply_directives(stream, directives, ctxt, vars):
319 yield event
320 ctxt.pop()
321 function.__name__ = self.name
322
323 # Store the function reference in the bottom context frame so that it
324 # doesn't get popped off before processing the template has finished
325 # FIXME: this makes context data mutable as a side-effect
326 ctxt.frames[-1][self.name] = function
327
328 return []
329
330 def __repr__(self):
331 return '<%s "%s">' % (type(self).__name__, self.name)
332
333
334class ForDirective(Directive):
335 """Implementation of the ``py:for`` template directive for repeating an
336 element based on an iterable in the context data.
337
338 >>> from genshi.template import MarkupTemplate
339 >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/">
340 ... <li py:for="item in items">${item}</li>
341 ... </ul>''')
342 >>> print(tmpl.generate(items=[1, 2, 3]))
343 <ul>
344 <li>1</li><li>2</li><li>3</li>
345 </ul>
346 """
347 __slots__ = ['assign', 'filename']
348
349 def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
350 if ' in ' not in value:
351 raise TemplateSyntaxError('"in" keyword missing in "for" directive',
352 template.filepath, lineno, offset)
353 assign, value = value.split(' in ', 1)
354 ast = _parse(assign, 'exec')
355 value = 'iter(%s)' % value.strip()
356 self.assign = _assignment(ast.body[0].value)
357 self.filename = template.filepath
358 Directive.__init__(self, value, template, namespaces, lineno, offset)
359
360 @classmethod
361 def attach(cls, template, stream, value, namespaces, pos):
362 if type(value) is dict:
363 value = value.get('each')
364 return super(ForDirective, cls).attach(template, stream, value,
365 namespaces, pos)
366
367 def __call__(self, stream, directives, ctxt, **vars):
368 iterable = _eval_expr(self.expr, ctxt, vars)
369 if iterable is None:
370 return
371
372 assign = self.assign
373 scope = {}
374 stream = list(stream)
375 for item in iterable:
376 assign(scope, item)
377 ctxt.push(scope)
378 for event in _apply_directives(stream, directives, ctxt, vars):
379 yield event
380 ctxt.pop()
381
382 def __repr__(self):
383 return '<%s>' % type(self).__name__
384
385
386class IfDirective(Directive):
387 """Implementation of the ``py:if`` template directive for conditionally
388 excluding elements from being output.
389
390 >>> from genshi.template import MarkupTemplate
391 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
392 ... <b py:if="foo">${bar}</b>
393 ... </div>''')
394 >>> print(tmpl.generate(foo=True, bar='Hello'))
395 <div>
396 <b>Hello</b>
397 </div>
398 """
399 __slots__ = []
400
401 @classmethod
402 def attach(cls, template, stream, value, namespaces, pos):
403 if type(value) is dict:
404 value = value.get('test')
405 return super(IfDirective, cls).attach(template, stream, value,
406 namespaces, pos)
407
408 def __call__(self, stream, directives, ctxt, **vars):
409 value = _eval_expr(self.expr, ctxt, vars)
410 if value:
411 return _apply_directives(stream, directives, ctxt, vars)
412 return []
413
414
415class MatchDirective(Directive):
416 """Implementation of the ``py:match`` template directive.
417
418 >>> from genshi.template import MarkupTemplate
419 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
420 ... <span py:match="greeting">
421 ... Hello ${select('@name')}
422 ... </span>
423 ... <greeting name="Dude" />
424 ... </div>''')
425 >>> print(tmpl.generate())
426 <div>
427 <span>
428 Hello Dude
429 </span>
430 </div>
431 """
432 __slots__ = ['path', 'namespaces', 'hints']
433
434 def __init__(self, value, template, hints=None, namespaces=None,
435 lineno=-1, offset=-1):
436 Directive.__init__(self, None, template, namespaces, lineno, offset)
437 self.path = Path(value, template.filepath, lineno)
438 self.namespaces = namespaces or {}
439 self.hints = hints or ()
440
441 @classmethod
442 def attach(cls, template, stream, value, namespaces, pos):
443 hints = []
444 if type(value) is dict:
445 if value.get('buffer', '').lower() == 'false':
446 hints.append('not_buffered')
447 if value.get('once', '').lower() == 'true':
448 hints.append('match_once')
449 if value.get('recursive', '').lower() == 'false':
450 hints.append('not_recursive')
451 value = value.get('path')
452 return cls(value, template, frozenset(hints), namespaces, *pos[1:]), \
453 stream
454
455 def __call__(self, stream, directives, ctxt, **vars):
456 ctxt._match_templates.append((self.path.test(ignore_context=True),
457 self.path, list(stream), self.hints,
458 self.namespaces, directives))
459 return []
460
461 def __repr__(self):
462 return '<%s "%s">' % (type(self).__name__, self.path.source)
463
464
465class ReplaceDirective(Directive):
466 """Implementation of the ``py:replace`` template directive.
467
468 This directive replaces the element with the result of evaluating the
469 value of the ``py:replace`` attribute:
470
471 >>> from genshi.template import MarkupTemplate
472 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
473 ... <span py:replace="bar">Hello</span>
474 ... </div>''')
475 >>> print(tmpl.generate(bar='Bye'))
476 <div>
477 Bye
478 </div>
479
480 This directive is equivalent to ``py:content`` combined with ``py:strip``,
481 providing a less verbose way to achieve the same effect:
482
483 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
484 ... <span py:content="bar" py:strip="">Hello</span>
485 ... </div>''')
486 >>> print(tmpl.generate(bar='Bye'))
487 <div>
488 Bye
489 </div>
490 """
491 __slots__ = []
492
493 @classmethod
494 def attach(cls, template, stream, value, namespaces, pos):
495 if type(value) is dict:
496 value = value.get('value')
497 if not value:
498 raise TemplateSyntaxError('missing value for "replace" directive',
499 template.filepath, *pos[1:])
500 expr = cls._parse_expr(value, template, *pos[1:])
501 return None, [(EXPR, expr, pos)]
502
503
504class StripDirective(Directive):
505 """Implementation of the ``py:strip`` template directive.
506
507 When the value of the ``py:strip`` attribute evaluates to ``True``, the
508 element is stripped from the output
509
510 >>> from genshi.template import MarkupTemplate
511 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
512 ... <div py:strip="True"><b>foo</b></div>
513 ... </div>''')
514 >>> print(tmpl.generate())
515 <div>
516 <b>foo</b>
517 </div>
518
519 Leaving the attribute value empty is equivalent to a truth value.
520
521 This directive is particulary interesting for named template functions or
522 match templates that do not generate a top-level element:
523
524 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
525 ... <div py:def="echo(what)" py:strip="">
526 ... <b>${what}</b>
527 ... </div>
528 ... ${echo('foo')}
529 ... </div>''')
530 >>> print(tmpl.generate())
531 <div>
532 <b>foo</b>
533 </div>
534 """
535 __slots__ = []
536
537 def __call__(self, stream, directives, ctxt, **vars):
538 def _generate():
539 if not self.expr or _eval_expr(self.expr, ctxt, vars):
540 next(stream) # skip start tag
541 previous = next(stream)
542 for event in stream:
543 yield previous
544 previous = event
545 else:
546 for event in stream:
547 yield event
548 return _apply_directives(_generate(), directives, ctxt, vars)
549
550
551class ChooseDirective(Directive):
552 """Implementation of the ``py:choose`` directive for conditionally selecting
553 one of several body elements to display.
554
555 If the ``py:choose`` expression is empty the expressions of nested
556 ``py:when`` directives are tested for truth. The first true ``py:when``
557 body is output. If no ``py:when`` directive is matched then the fallback
558 directive ``py:otherwise`` will be used.
559
560 >>> from genshi.template import MarkupTemplate
561 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"
562 ... py:choose="">
563 ... <span py:when="0 == 1">0</span>
564 ... <span py:when="1 == 1">1</span>
565 ... <span py:otherwise="">2</span>
566 ... </div>''')
567 >>> print(tmpl.generate())
568 <div>
569 <span>1</span>
570 </div>
571
572 If the ``py:choose`` directive contains an expression, the nested
573 ``py:when`` directives are tested for equality to the ``py:choose``
574 expression:
575
576 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"
577 ... py:choose="2">
578 ... <span py:when="1">1</span>
579 ... <span py:when="2">2</span>
580 ... </div>''')
581 >>> print(tmpl.generate())
582 <div>
583 <span>2</span>
584 </div>
585
586 Behavior is undefined if a ``py:choose`` block contains content outside a
587 ``py:when`` or ``py:otherwise`` block. Behavior is also undefined if a
588 ``py:otherwise`` occurs before ``py:when`` blocks.
589 """
590 __slots__ = ['matched', 'value']
591
592 @classmethod
593 def attach(cls, template, stream, value, namespaces, pos):
594 if type(value) is dict:
595 value = value.get('test')
596 return super(ChooseDirective, cls).attach(template, stream, value,
597 namespaces, pos)
598
599 def __call__(self, stream, directives, ctxt, **vars):
600 info = [False, bool(self.expr), None]
601 if self.expr:
602 info[2] = _eval_expr(self.expr, ctxt, vars)
603 ctxt._choice_stack.append(info)
604 for event in _apply_directives(stream, directives, ctxt, vars):
605 yield event
606 ctxt._choice_stack.pop()
607
608
609class WhenDirective(Directive):
610 """Implementation of the ``py:when`` directive for nesting in a parent with
611 the ``py:choose`` directive.
612
613 See the documentation of the `ChooseDirective` for usage.
614 """
615 __slots__ = ['filename']
616
617 def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
618 Directive.__init__(self, value, template, namespaces, lineno, offset)
619 self.filename = template.filepath
620
621 @classmethod
622 def attach(cls, template, stream, value, namespaces, pos):
623 if type(value) is dict:
624 value = value.get('test')
625 return super(WhenDirective, cls).attach(template, stream, value,
626 namespaces, pos)
627
628 def __call__(self, stream, directives, ctxt, **vars):
629 info = ctxt._choice_stack and ctxt._choice_stack[-1]
630 if not info:
631 raise TemplateRuntimeError('"when" directives can only be used '
632 'inside a "choose" directive',
633 self.filename, *(next(stream))[2][1:])
634 if info[0]:
635 return []
636 if not self.expr and not info[1]:
637 raise TemplateRuntimeError('either "choose" or "when" directive '
638 'must have a test expression',
639 self.filename, *(next(stream))[2][1:])
640 if info[1]:
641 value = info[2]
642 if self.expr:
643 matched = value == _eval_expr(self.expr, ctxt, vars)
644 else:
645 matched = bool(value)
646 else:
647 matched = bool(_eval_expr(self.expr, ctxt, vars))
648 info[0] = matched
649 if not matched:
650 return []
651
652 return _apply_directives(stream, directives, ctxt, vars)
653
654
655class OtherwiseDirective(Directive):
656 """Implementation of the ``py:otherwise`` directive for nesting in a parent
657 with the ``py:choose`` directive.
658
659 See the documentation of `ChooseDirective` for usage.
660 """
661 __slots__ = ['filename']
662
663 def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
664 Directive.__init__(self, None, template, namespaces, lineno, offset)
665 self.filename = template.filepath
666
667 def __call__(self, stream, directives, ctxt, **vars):
668 info = ctxt._choice_stack and ctxt._choice_stack[-1]
669 if not info:
670 raise TemplateRuntimeError('an "otherwise" directive can only be '
671 'used inside a "choose" directive',
672 self.filename, *(next(stream))[2][1:])
673 if info[0]:
674 return []
675 info[0] = True
676
677 return _apply_directives(stream, directives, ctxt, vars)
678
679
680class WithDirective(Directive):
681 """Implementation of the ``py:with`` template directive, which allows
682 shorthand access to variables and expressions.
683
684 >>> from genshi.template import MarkupTemplate
685 >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
686 ... <span py:with="y=7; z=x+10">$x $y $z</span>
687 ... </div>''')
688 >>> print(tmpl.generate(x=42))
689 <div>
690 <span>42 7 52</span>
691 </div>
692 """
693 __slots__ = ['vars']
694
695 def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
696 Directive.__init__(self, None, template, namespaces, lineno, offset)
697 self.vars = []
698 value = value.strip()
699 try:
700 ast = _parse(value, 'exec')
701 for node in ast.body:
702 if not isinstance(node, _ast.Assign):
703 raise TemplateSyntaxError('only assignment allowed in '
704 'value of the "with" directive',
705 template.filepath, lineno, offset)
706 self.vars.append(([_assignment(n) for n in node.targets],
707 Expression(node.value, template.filepath,
708 lineno, lookup=template.lookup)))
709 except SyntaxError as err:
710 err.msg += ' in expression "%s" of "%s" directive' % (value,
711 self.tagname)
712 raise TemplateSyntaxError(err, template.filepath, lineno,
713 offset + (err.offset or 0))
714
715 @classmethod
716 def attach(cls, template, stream, value, namespaces, pos):
717 if type(value) is dict:
718 value = value.get('vars')
719 return super(WithDirective, cls).attach(template, stream, value,
720 namespaces, pos)
721
722 def __call__(self, stream, directives, ctxt, **vars):
723 frame = {}
724 ctxt.push(frame)
725 for targets, expr in self.vars:
726 value = _eval_expr(expr, ctxt, vars)
727 for assign in targets:
728 assign(frame, value)
729 for event in _apply_directives(stream, directives, ctxt, vars):
730 yield event
731 ctxt.pop()
732
733 def __repr__(self):
734 return '<%s>' % (type(self).__name__)