Edgewall Software

source: branches/stable/0.6.x/genshi/template/directives.py

Last change on this file was 1083, checked in by cmlenz, 14 years ago

A bit of cleanup of the Markup Python implementation.

  • Property svn:eol-style set to native
File size: 26.7 KB
Line 
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.core import QName, Stream
17from genshi.path import Path
18from genshi.template.base import TemplateRuntimeError, TemplateSyntaxError, \
19                                 EXPR, _apply_directives, _eval_expr
20from 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
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
38class 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
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  = 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 -= [name for name, val in attrs if val is None]
179                attrib |= [(QName(name), unicode(val).strip()) for name, val
180                           in attrs if val is not None]
181            yield kind, (tag, attrib), pos
182            for event in stream:
183                yield event
184
185        return _apply_directives(_generate(), directives, ctxt, vars)
186
187
188class ContentDirective(Directive):
189    """Implementation of the ``py:content`` template directive.
190   
191    This directive replaces the content of the element with the result of
192    evaluating the value of the ``py:content`` attribute:
193   
194    >>> from genshi.template import MarkupTemplate
195    >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/">
196    ...   <li py:content="bar">Hello</li>
197    ... </ul>''')
198    >>> print(tmpl.generate(bar='Bye'))
199    <ul>
200      <li>Bye</li>
201    </ul>
202    """
203    __slots__ = []
204
205    @classmethod
206    def attach(cls, template, stream, value, namespaces, pos):
207        if type(value) is dict:
208            raise TemplateSyntaxError('The content directive can not be used '
209                                      'as an element', template.filepath,
210                                      *pos[1:])
211        expr = cls._parse_expr(value, template, *pos[1:])
212        return None, [stream[0], (EXPR, expr, pos),  stream[-1]]
213
214
215class DefDirective(Directive):
216    """Implementation of the ``py:def`` template directive.
217   
218    This directive can be used to create "Named Template Functions", which
219    are template snippets that are not actually output during normal
220    processing, but rather can be expanded from expressions in other places
221    in the template.
222   
223    A named template function can be used just like a normal Python function
224    from template expressions:
225   
226    >>> from genshi.template import MarkupTemplate
227    >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
228    ...   <p py:def="echo(greeting, name='world')" class="message">
229    ...     ${greeting}, ${name}!
230    ...   </p>
231    ...   ${echo('Hi', name='you')}
232    ... </div>''')
233    >>> print(tmpl.generate(bar='Bye'))
234    <div>
235      <p class="message">
236        Hi, you!
237      </p>
238    </div>
239   
240    If a function does not require parameters, the parenthesis can be omitted
241    in the definition:
242   
243    >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
244    ...   <p py:def="helloworld" class="message">
245    ...     Hello, world!
246    ...   </p>
247    ...   ${helloworld()}
248    ... </div>''')
249    >>> print(tmpl.generate(bar='Bye'))
250    <div>
251      <p class="message">
252        Hello, world!
253      </p>
254    </div>
255    """
256    __slots__ = ['name', 'args', 'star_args', 'dstar_args', 'defaults']
257
258    def __init__(self, args, template, namespaces=None, lineno=-1, offset=-1):
259        Directive.__init__(self, None, template, namespaces, lineno, offset)
260        ast = _parse(args).body
261        self.args = []
262        self.star_args = None
263        self.dstar_args = None
264        self.defaults = {}
265        if isinstance(ast, _ast.Call):
266            self.name = ast.func.id
267            for arg in ast.args:
268                # only names
269                self.args.append(arg.id)
270            for kwd in ast.keywords:
271                self.args.append(kwd.arg)
272                exp = Expression(kwd.value, template.filepath,
273                                 lineno, lookup=template.lookup)
274                self.defaults[kwd.arg] = exp
275            if getattr(ast, 'starargs', None):
276                self.star_args = ast.starargs.id
277            if getattr(ast, 'kwargs', None):
278                self.dstar_args = ast.kwargs.id
279        else:
280            self.name = ast.id
281
282    @classmethod
283    def attach(cls, template, stream, value, namespaces, pos):
284        if type(value) is dict:
285            value = value.get('function')
286        return super(DefDirective, cls).attach(template, stream, value,
287                                               namespaces, pos)
288
289    def __call__(self, stream, directives, ctxt, **vars):
290        stream = list(stream)
291
292        def function(*args, **kwargs):
293            scope = {}
294            args = list(args) # make mutable
295            for name in self.args:
296                if args:
297                    scope[name] = args.pop(0)
298                else:
299                    if name in kwargs:
300                        val = kwargs.pop(name)
301                    else:
302                        val = _eval_expr(self.defaults.get(name), ctxt, vars)
303                    scope[name] = val
304            if not self.star_args is None:
305                scope[self.star_args] = args
306            if not self.dstar_args is None:
307                scope[self.dstar_args] = kwargs
308            ctxt.push(scope)
309            for event in _apply_directives(stream, directives, ctxt, vars):
310                yield event
311            ctxt.pop()
312        function.__name__ = self.name
313
314        # Store the function reference in the bottom context frame so that it
315        # doesn't get popped off before processing the template has finished
316        # FIXME: this makes context data mutable as a side-effect
317        ctxt.frames[-1][self.name] = function
318
319        return []
320
321    def __repr__(self):
322        return '<%s "%s">' % (type(self).__name__, self.name)
323
324
325class ForDirective(Directive):
326    """Implementation of the ``py:for`` template directive for repeating an
327    element based on an iterable in the context data.
328   
329    >>> from genshi.template import MarkupTemplate
330    >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/">
331    ...   <li py:for="item in items">${item}</li>
332    ... </ul>''')
333    >>> print(tmpl.generate(items=[1, 2, 3]))
334    <ul>
335      <li>1</li><li>2</li><li>3</li>
336    </ul>
337    """
338    __slots__ = ['assign', 'filename']
339
340    def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
341        if ' in ' not in value:
342            raise TemplateSyntaxError('"in" keyword missing in "for" directive',
343                                      template.filepath, lineno, offset)
344        assign, value = value.split(' in ', 1)
345        ast = _parse(assign, 'exec')
346        value = 'iter(%s)' % value.strip()
347        self.assign = _assignment(ast.body[0].value)
348        self.filename = template.filepath
349        Directive.__init__(self, value, template, namespaces, lineno, offset)
350
351    @classmethod
352    def attach(cls, template, stream, value, namespaces, pos):
353        if type(value) is dict:
354            value = value.get('each')
355        return super(ForDirective, cls).attach(template, stream, value,
356                                               namespaces, pos)
357
358    def __call__(self, stream, directives, ctxt, **vars):
359        iterable = _eval_expr(self.expr, ctxt, vars)
360        if iterable is None:
361            return
362
363        assign = self.assign
364        scope = {}
365        stream = list(stream)
366        for item in iterable:
367            assign(scope, item)
368            ctxt.push(scope)
369            for event in _apply_directives(stream, directives, ctxt, vars):
370                yield event
371            ctxt.pop()
372
373    def __repr__(self):
374        return '<%s>' % type(self).__name__
375
376
377class IfDirective(Directive):
378    """Implementation of the ``py:if`` template directive for conditionally
379    excluding elements from being output.
380   
381    >>> from genshi.template import MarkupTemplate
382    >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
383    ...   <b py:if="foo">${bar}</b>
384    ... </div>''')
385    >>> print(tmpl.generate(foo=True, bar='Hello'))
386    <div>
387      <b>Hello</b>
388    </div>
389    """
390    __slots__ = []
391
392    @classmethod
393    def attach(cls, template, stream, value, namespaces, pos):
394        if type(value) is dict:
395            value = value.get('test')
396        return super(IfDirective, cls).attach(template, stream, value,
397                                              namespaces, pos)
398
399    def __call__(self, stream, directives, ctxt, **vars):
400        value = _eval_expr(self.expr, ctxt, vars)
401        if value:
402            return _apply_directives(stream, directives, ctxt, vars)
403        return []
404
405
406class MatchDirective(Directive):
407    """Implementation of the ``py:match`` template directive.
408
409    >>> from genshi.template import MarkupTemplate
410    >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
411    ...   <span py:match="greeting">
412    ...     Hello ${select('@name')}
413    ...   </span>
414    ...   <greeting name="Dude" />
415    ... </div>''')
416    >>> print(tmpl.generate())
417    <div>
418      <span>
419        Hello Dude
420      </span>
421    </div>
422    """
423    __slots__ = ['path', 'namespaces', 'hints']
424
425    def __init__(self, value, template, hints=None, namespaces=None,
426                 lineno=-1, offset=-1):
427        Directive.__init__(self, None, template, namespaces, lineno, offset)
428        self.path = Path(value, template.filepath, lineno)
429        self.namespaces = namespaces or {}
430        self.hints = hints or ()
431
432    @classmethod
433    def attach(cls, template, stream, value, namespaces, pos):
434        hints = []
435        if type(value) is dict:
436            if value.get('buffer', '').lower() == 'false':
437                hints.append('not_buffered')
438            if value.get('once', '').lower() == 'true':
439                hints.append('match_once')
440            if value.get('recursive', '').lower() == 'false':
441                hints.append('not_recursive')
442            value = value.get('path')
443        return cls(value, template, frozenset(hints), namespaces, *pos[1:]), \
444               stream
445
446    def __call__(self, stream, directives, ctxt, **vars):
447        ctxt._match_templates.append((self.path.test(ignore_context=True),
448                                      self.path, list(stream), self.hints,
449                                      self.namespaces, directives))
450        return []
451
452    def __repr__(self):
453        return '<%s "%s">' % (type(self).__name__, self.path.source)
454
455
456class ReplaceDirective(Directive):
457    """Implementation of the ``py:replace`` template directive.
458   
459    This directive replaces the element with the result of evaluating the
460    value of the ``py:replace`` attribute:
461   
462    >>> from genshi.template import MarkupTemplate
463    >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
464    ...   <span py:replace="bar">Hello</span>
465    ... </div>''')
466    >>> print(tmpl.generate(bar='Bye'))
467    <div>
468      Bye
469    </div>
470   
471    This directive is equivalent to ``py:content`` combined with ``py:strip``,
472    providing a less verbose way to achieve the same effect:
473   
474    >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
475    ...   <span py:content="bar" py:strip="">Hello</span>
476    ... </div>''')
477    >>> print(tmpl.generate(bar='Bye'))
478    <div>
479      Bye
480    </div>
481    """
482    __slots__ = []
483
484    @classmethod
485    def attach(cls, template, stream, value, namespaces, pos):
486        if type(value) is dict:
487            value = value.get('value')
488        if not value:
489            raise TemplateSyntaxError('missing value for "replace" directive',
490                                      template.filepath, *pos[1:])
491        expr = cls._parse_expr(value, template, *pos[1:])
492        return None, [(EXPR, expr, pos)]
493
494
495class StripDirective(Directive):
496    """Implementation of the ``py:strip`` template directive.
497   
498    When the value of the ``py:strip`` attribute evaluates to ``True``, the
499    element is stripped from the output
500   
501    >>> from genshi.template import MarkupTemplate
502    >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
503    ...   <div py:strip="True"><b>foo</b></div>
504    ... </div>''')
505    >>> print(tmpl.generate())
506    <div>
507      <b>foo</b>
508    </div>
509   
510    Leaving the attribute value empty is equivalent to a truth value.
511   
512    This directive is particulary interesting for named template functions or
513    match templates that do not generate a top-level element:
514   
515    >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
516    ...   <div py:def="echo(what)" py:strip="">
517    ...     <b>${what}</b>
518    ...   </div>
519    ...   ${echo('foo')}
520    ... </div>''')
521    >>> print(tmpl.generate())
522    <div>
523        <b>foo</b>
524    </div>
525    """
526    __slots__ = []
527
528    def __call__(self, stream, directives, ctxt, **vars):
529        def _generate():
530            if not self.expr or _eval_expr(self.expr, ctxt, vars):
531                stream.next() # skip start tag
532                previous = stream.next()
533                for event in stream:
534                    yield previous
535                    previous = event
536            else:
537                for event in stream:
538                    yield event
539        return _apply_directives(_generate(), directives, ctxt, vars)
540
541
542class ChooseDirective(Directive):
543    """Implementation of the ``py:choose`` directive for conditionally selecting
544    one of several body elements to display.
545   
546    If the ``py:choose`` expression is empty the expressions of nested
547    ``py:when`` directives are tested for truth.  The first true ``py:when``
548    body is output. If no ``py:when`` directive is matched then the fallback
549    directive ``py:otherwise`` will be used.
550   
551    >>> from genshi.template import MarkupTemplate
552    >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"
553    ...   py:choose="">
554    ...   <span py:when="0 == 1">0</span>
555    ...   <span py:when="1 == 1">1</span>
556    ...   <span py:otherwise="">2</span>
557    ... </div>''')
558    >>> print(tmpl.generate())
559    <div>
560      <span>1</span>
561    </div>
562   
563    If the ``py:choose`` directive contains an expression, the nested
564    ``py:when`` directives are tested for equality to the ``py:choose``
565    expression:
566   
567    >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"
568    ...   py:choose="2">
569    ...   <span py:when="1">1</span>
570    ...   <span py:when="2">2</span>
571    ... </div>''')
572    >>> print(tmpl.generate())
573    <div>
574      <span>2</span>
575    </div>
576   
577    Behavior is undefined if a ``py:choose`` block contains content outside a
578    ``py:when`` or ``py:otherwise`` block.  Behavior is also undefined if a
579    ``py:otherwise`` occurs before ``py:when`` blocks.
580    """
581    __slots__ = ['matched', 'value']
582
583    @classmethod
584    def attach(cls, template, stream, value, namespaces, pos):
585        if type(value) is dict:
586            value = value.get('test')
587        return super(ChooseDirective, cls).attach(template, stream, value,
588                                                  namespaces, pos)
589
590    def __call__(self, stream, directives, ctxt, **vars):
591        info = [False, bool(self.expr), None]
592        if self.expr:
593            info[2] = _eval_expr(self.expr, ctxt, vars)
594        ctxt._choice_stack.append(info)
595        for event in _apply_directives(stream, directives, ctxt, vars):
596            yield event
597        ctxt._choice_stack.pop()
598
599
600class WhenDirective(Directive):
601    """Implementation of the ``py:when`` directive for nesting in a parent with
602    the ``py:choose`` directive.
603   
604    See the documentation of the `ChooseDirective` for usage.
605    """
606    __slots__ = ['filename']
607
608    def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
609        Directive.__init__(self, value, template, namespaces, lineno, offset)
610        self.filename = template.filepath
611
612    @classmethod
613    def attach(cls, template, stream, value, namespaces, pos):
614        if type(value) is dict:
615            value = value.get('test')
616        return super(WhenDirective, cls).attach(template, stream, value,
617                                                namespaces, pos)
618
619    def __call__(self, stream, directives, ctxt, **vars):
620        info = ctxt._choice_stack and ctxt._choice_stack[-1]
621        if not info:
622            raise TemplateRuntimeError('"when" directives can only be used '
623                                       'inside a "choose" directive',
624                                       self.filename, *stream.next()[2][1:])
625        if info[0]:
626            return []
627        if not self.expr and not info[1]:
628            raise TemplateRuntimeError('either "choose" or "when" directive '
629                                       'must have a test expression',
630                                       self.filename, *stream.next()[2][1:])
631        if info[1]:
632            value = info[2]
633            if self.expr:
634                matched = value == _eval_expr(self.expr, ctxt, vars)
635            else:
636                matched = bool(value)
637        else:
638            matched = bool(_eval_expr(self.expr, ctxt, vars))
639        info[0] = matched
640        if not matched:
641            return []
642
643        return _apply_directives(stream, directives, ctxt, vars)
644
645
646class OtherwiseDirective(Directive):
647    """Implementation of the ``py:otherwise`` directive for nesting in a parent
648    with the ``py:choose`` directive.
649   
650    See the documentation of `ChooseDirective` for usage.
651    """
652    __slots__ = ['filename']
653
654    def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
655        Directive.__init__(self, None, template, namespaces, lineno, offset)
656        self.filename = template.filepath
657
658    def __call__(self, stream, directives, ctxt, **vars):
659        info = ctxt._choice_stack and ctxt._choice_stack[-1]
660        if not info:
661            raise TemplateRuntimeError('an "otherwise" directive can only be '
662                                       'used inside a "choose" directive',
663                                       self.filename, *stream.next()[2][1:])
664        if info[0]:
665            return []
666        info[0] = True
667
668        return _apply_directives(stream, directives, ctxt, vars)
669
670
671class WithDirective(Directive):
672    """Implementation of the ``py:with`` template directive, which allows
673    shorthand access to variables and expressions.
674   
675    >>> from genshi.template import MarkupTemplate
676    >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
677    ...   <span py:with="y=7; z=x+10">$x $y $z</span>
678    ... </div>''')
679    >>> print(tmpl.generate(x=42))
680    <div>
681      <span>42 7 52</span>
682    </div>
683    """
684    __slots__ = ['vars']
685
686    def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
687        Directive.__init__(self, None, template, namespaces, lineno, offset)
688        self.vars = []
689        value = value.strip()
690        try:
691            ast = _parse(value, 'exec')
692            for node in ast.body:
693                if not isinstance(node, _ast.Assign):
694                    raise TemplateSyntaxError('only assignment allowed in '
695                                              'value of the "with" directive',
696                                              template.filepath, lineno, offset)
697                self.vars.append(([_assignment(n) for n in node.targets],
698                                  Expression(node.value, template.filepath,
699                                             lineno, lookup=template.lookup)))
700        except SyntaxError, err:
701            err.msg += ' in expression "%s" of "%s" directive' % (value,
702                                                                  self.tagname)
703            raise TemplateSyntaxError(err, template.filepath, lineno,
704                                      offset + (err.offset or 0))
705
706    @classmethod
707    def attach(cls, template, stream, value, namespaces, pos):
708        if type(value) is dict:
709            value = value.get('vars')
710        return super(WithDirective, cls).attach(template, stream, value,
711                                                namespaces, pos)
712
713    def __call__(self, stream, directives, ctxt, **vars):
714        frame = {}
715        ctxt.push(frame)
716        for targets, expr in self.vars:
717            value = _eval_expr(expr, ctxt, vars)
718            for assign in targets:
719                assign(frame, value)
720        for event in _apply_directives(stream, directives, ctxt, vars):
721            yield event
722        ctxt.pop()
723
724    def __repr__(self):
725        return '<%s>' % (type(self).__name__)
Note: See TracBrowser for help on using the repository browser.