cmlenz@336: # -*- coding: utf-8 -*- cmlenz@336: # cmlenz@902: # Copyright (C) 2006-2009 Edgewall Software cmlenz@336: # All rights reserved. cmlenz@336: # cmlenz@336: # This software is licensed as described in the file COPYING, which cmlenz@336: # you should have received as part of this distribution. The terms cmlenz@336: # are also available at http://genshi.edgewall.org/wiki/License. cmlenz@336: # cmlenz@336: # This software consists of voluntary contributions made by many cmlenz@336: # individuals. For the exact contribution history, see the revision cmlenz@336: # history and logs, available at http://genshi.edgewall.org/log/. cmlenz@336: cmlenz@336: """Implementation of the various template directives.""" cmlenz@336: cmlenz@820: from genshi.core import QName, Stream cmlenz@336: from genshi.path import Path cmlenz@500: from genshi.template.base import TemplateRuntimeError, TemplateSyntaxError, \ cmlenz@820: EXPR, _apply_directives, _eval_expr cmlenz@820: from genshi.template.eval import Expression, ExpressionASTTransformer, \ cmlenz@820: _ast, _parse cmlenz@336: cmlenz@336: __all__ = ['AttrsDirective', 'ChooseDirective', 'ContentDirective', cmlenz@336: 'DefDirective', 'ForDirective', 'IfDirective', 'MatchDirective', cmlenz@336: 'OtherwiseDirective', 'ReplaceDirective', 'StripDirective', cmlenz@336: 'WhenDirective', 'WithDirective'] cmlenz@500: __docformat__ = 'restructuredtext en' cmlenz@336: cmlenz@336: cmlenz@395: class DirectiveMeta(type): cmlenz@395: """Meta class for template directives.""" cmlenz@395: cmlenz@395: def __new__(cls, name, bases, d): cmlenz@395: d['tagname'] = name.lower().replace('directive', '') cmlenz@395: return type.__new__(cls, name, bases, d) cmlenz@395: cmlenz@395: cmlenz@395: class Directive(object): cmlenz@395: """Abstract base class for template directives. cmlenz@395: cmlenz@395: A directive is basically a callable that takes three positional arguments: cmlenz@500: ``ctxt`` is the template data context, ``stream`` is an iterable over the cmlenz@500: events that the directive applies to, and ``directives`` is is a list of cmlenz@395: other directives on the same stream that need to be applied. cmlenz@395: cmlenz@395: Directives can be "anonymous" or "registered". Registered directives can be cmlenz@395: applied by the template author using an XML attribute with the cmlenz@395: corresponding name in the template. Such directives should be subclasses of cmlenz@395: this base class that can be instantiated with the value of the directive cmlenz@395: attribute as parameter. cmlenz@395: cmlenz@395: Anonymous directives are simply functions conforming to the protocol cmlenz@395: described above, and can only be applied programmatically (for example by cmlenz@395: template filters). cmlenz@395: """ cmlenz@395: __metaclass__ = DirectiveMeta cmlenz@395: __slots__ = ['expr'] cmlenz@395: cmlenz@500: def __init__(self, value, template=None, namespaces=None, lineno=-1, cmlenz@395: offset=-1): cmlenz@500: self.expr = self._parse_expr(value, template, lineno, offset) cmlenz@395: cmlenz@830: @classmethod cmlenz@395: def attach(cls, template, stream, value, namespaces, pos): cmlenz@395: """Called after the template stream has been completely parsed. cmlenz@395: cmlenz@500: :param template: the `Template` object cmlenz@500: :param stream: the event stream associated with the directive cmlenz@820: :param value: the argument value for the directive; if the directive was cmlenz@820: specified as an element, this will be an `Attrs` instance cmlenz@820: with all specified attributes, otherwise it will be a cmlenz@820: `unicode` object with just the attribute value cmlenz@500: :param namespaces: a mapping of namespace URIs to prefixes cmlenz@500: :param pos: a ``(filename, lineno, offset)`` tuple describing the cmlenz@500: location where the directive was found in the source cmlenz@395: cmlenz@500: This class method should return a ``(directive, stream)`` tuple. If cmlenz@500: ``directive`` is not ``None``, it should be an instance of the `Directive` cmlenz@395: class, and gets added to the list of directives applied to the substream cmlenz@395: at runtime. `stream` is an event stream that replaces the original cmlenz@395: stream associated with the directive. cmlenz@395: """ cmlenz@500: return cls(value, template, namespaces, *pos[1:]), stream cmlenz@395: cmlenz@820: def __call__(self, stream, directives, ctxt, **vars): cmlenz@395: """Apply the directive to the given stream. cmlenz@395: cmlenz@500: :param stream: the event stream cmlenz@500: :param directives: a list of the remaining directives that should cmlenz@500: process the stream cmlenz@820: :param ctxt: the context data cmlenz@820: :param vars: additional variables that should be made available when cmlenz@820: Python code is executed cmlenz@395: """ cmlenz@395: raise NotImplementedError cmlenz@395: cmlenz@395: def __repr__(self): cmlenz@395: expr = '' cmlenz@395: if getattr(self, 'expr', None) is not None: cmlenz@395: expr = ' "%s"' % self.expr.source cmlenz@902: return '<%s%s>' % (type(self).__name__, expr) cmlenz@395: cmlenz@830: @classmethod cmlenz@500: def _parse_expr(cls, expr, template, lineno=-1, offset=-1): cmlenz@395: """Parses the given expression, raising a useful error message when a cmlenz@395: syntax error is encountered. cmlenz@395: """ cmlenz@395: try: cmlenz@500: return expr and Expression(expr, template.filepath, lineno, cmlenz@500: lookup=template.lookup) or None cmlenz@395: except SyntaxError, err: cmlenz@395: err.msg += ' in expression "%s" of "%s" directive' % (expr, cmlenz@395: cls.tagname) cmlenz@500: raise TemplateSyntaxError(err, template.filepath, lineno, cmlenz@395: offset + (err.offset or 0)) cmlenz@395: cmlenz@395: cmlenz@336: def _assignment(ast): cmlenz@820: """Takes the AST representation of an assignment, and returns a cmlenz@820: function that applies the assignment of a given value to a dictionary. cmlenz@336: """ cmlenz@336: def _names(node): cmlenz@820: if isinstance(node, _ast.Tuple): cmlenz@820: return tuple([_names(child) for child in node.elts]) cmlenz@820: elif isinstance(node, _ast.Name): cmlenz@820: return node.id cmlenz@336: def _assign(data, value, names=_names(ast)): cmlenz@336: if type(names) is tuple: cmlenz@336: for idx in range(len(names)): cmlenz@336: _assign(data, value[idx], names[idx]) cmlenz@336: else: cmlenz@336: data[names] = value cmlenz@336: return _assign cmlenz@336: cmlenz@336: cmlenz@336: class AttrsDirective(Directive): cmlenz@500: """Implementation of the ``py:attrs`` template directive. cmlenz@336: cmlenz@500: The value of the ``py:attrs`` attribute should be a dictionary or a sequence cmlenz@500: of ``(name, value)`` tuples. The items in that dictionary or sequence are cmlenz@336: added as attributes to the element: cmlenz@336: cmlenz@336: >>> from genshi.template import MarkupTemplate cmlenz@336: >>> tmpl = MarkupTemplate('''''') cmlenz@902: >>> print(tmpl.generate(foo={'class': 'collapse'})) cmlenz@336: cmlenz@902: >>> print(tmpl.generate(foo=[('class', 'collapse')])) cmlenz@336: cmlenz@336: cmlenz@500: If the value evaluates to ``None`` (or any other non-truth value), no cmlenz@336: attributes are added: cmlenz@336: cmlenz@902: >>> print(tmpl.generate(foo=None)) cmlenz@336: cmlenz@336: """ cmlenz@336: __slots__ = [] cmlenz@336: cmlenz@820: def __call__(self, stream, directives, ctxt, **vars): cmlenz@336: def _generate(): cmlenz@336: kind, (tag, attrib), pos = stream.next() cmlenz@830: attrs = _eval_expr(self.expr, ctxt, vars) cmlenz@336: if attrs: cmlenz@336: if isinstance(attrs, Stream): cmlenz@336: try: cmlenz@336: attrs = iter(attrs).next() cmlenz@336: except StopIteration: cmlenz@336: attrs = [] cmlenz@336: elif not isinstance(attrs, list): # assume it's a dict cmlenz@336: attrs = attrs.items() cmlenz@347: attrib -= [name for name, val in attrs if val is None] cmlenz@500: attrib |= [(QName(name), unicode(val).strip()) for name, val cmlenz@500: in attrs if val is not None] cmlenz@336: yield kind, (tag, attrib), pos cmlenz@336: for event in stream: cmlenz@336: yield event cmlenz@336: cmlenz@830: return _apply_directives(_generate(), directives, ctxt, vars) cmlenz@336: cmlenz@336: cmlenz@336: class ContentDirective(Directive): cmlenz@500: """Implementation of the ``py:content`` template directive. cmlenz@336: cmlenz@336: This directive replaces the content of the element with the result of cmlenz@500: evaluating the value of the ``py:content`` attribute: cmlenz@336: cmlenz@336: >>> from genshi.template import MarkupTemplate cmlenz@336: >>> tmpl = MarkupTemplate('''''') cmlenz@902: >>> print(tmpl.generate(bar='Bye')) cmlenz@336: cmlenz@336: """ cmlenz@336: __slots__ = [] cmlenz@336: cmlenz@830: @classmethod cmlenz@395: def attach(cls, template, stream, value, namespaces, pos): cmlenz@820: if type(value) is dict: cmlenz@820: raise TemplateSyntaxError('The content directive can not be used ' cmlenz@820: 'as an element', template.filepath, cmlenz@820: *pos[1:]) cmlenz@500: expr = cls._parse_expr(value, template, *pos[1:]) cmlenz@395: return None, [stream[0], (EXPR, expr, pos), stream[-1]] cmlenz@336: cmlenz@336: cmlenz@336: class DefDirective(Directive): cmlenz@500: """Implementation of the ``py:def`` template directive. cmlenz@336: cmlenz@336: This directive can be used to create "Named Template Functions", which cmlenz@336: are template snippets that are not actually output during normal cmlenz@336: processing, but rather can be expanded from expressions in other places cmlenz@336: in the template. cmlenz@336: cmlenz@336: A named template function can be used just like a normal Python function cmlenz@336: from template expressions: cmlenz@336: cmlenz@336: >>> from genshi.template import MarkupTemplate cmlenz@336: >>> tmpl = MarkupTemplate('''
cmlenz@336: ...

cmlenz@336: ... ${greeting}, ${name}! cmlenz@336: ...

cmlenz@336: ... ${echo('Hi', name='you')} cmlenz@336: ...
''') cmlenz@902: >>> print(tmpl.generate(bar='Bye')) cmlenz@336:
cmlenz@336:

cmlenz@336: Hi, you! cmlenz@336:

cmlenz@336:
cmlenz@336: cmlenz@336: If a function does not require parameters, the parenthesis can be omitted cmlenz@344: in the definition: cmlenz@336: cmlenz@336: >>> tmpl = MarkupTemplate('''
cmlenz@336: ...

cmlenz@336: ... Hello, world! cmlenz@336: ...

cmlenz@344: ... ${helloworld()} cmlenz@336: ...
''') cmlenz@902: >>> print(tmpl.generate(bar='Bye')) cmlenz@336:
cmlenz@336:

cmlenz@336: Hello, world! cmlenz@336:

cmlenz@336:
cmlenz@336: """ cmlenz@500: __slots__ = ['name', 'args', 'star_args', 'dstar_args', 'defaults', cmlenz@500: 'signature'] cmlenz@336: cmlenz@500: def __init__(self, args, template, namespaces=None, lineno=-1, offset=-1): cmlenz@500: Directive.__init__(self, None, template, namespaces, lineno, offset) cmlenz@342: self.signature = args.strip() cmlenz@820: ast = _parse(args).body cmlenz@336: self.args = [] cmlenz@500: self.star_args = None cmlenz@500: self.dstar_args = None cmlenz@336: self.defaults = {} cmlenz@820: if isinstance(ast, _ast.Call): cmlenz@820: self.name = ast.func.id cmlenz@336: for arg in ast.args: cmlenz@820: # only names cmlenz@820: self.args.append(arg.id) cmlenz@820: for kwd in ast.keywords: cmlenz@820: self.args.append(kwd.arg) cmlenz@820: exp = Expression(kwd.value, template.filepath, cmlenz@820: lineno, lookup=template.lookup) cmlenz@820: self.defaults[kwd.arg] = exp cmlenz@820: if getattr(ast, 'starargs', None): cmlenz@820: self.star_args = ast.starargs.id cmlenz@820: if getattr(ast, 'kwargs', None): cmlenz@820: self.dstar_args = ast.kwargs.id cmlenz@336: else: cmlenz@820: self.name = ast.id cmlenz@336: cmlenz@830: @classmethod cmlenz@820: def attach(cls, template, stream, value, namespaces, pos): cmlenz@820: if type(value) is dict: cmlenz@820: value = value.get('function') cmlenz@820: return super(DefDirective, cls).attach(template, stream, value, cmlenz@820: namespaces, pos) cmlenz@820: cmlenz@820: def __call__(self, stream, directives, ctxt, **vars): cmlenz@336: stream = list(stream) cmlenz@336: cmlenz@336: def function(*args, **kwargs): cmlenz@336: scope = {} cmlenz@336: args = list(args) # make mutable cmlenz@336: for name in self.args: cmlenz@336: if args: cmlenz@336: scope[name] = args.pop(0) cmlenz@336: else: cmlenz@336: if name in kwargs: cmlenz@336: val = kwargs.pop(name) cmlenz@336: else: cmlenz@830: val = _eval_expr(self.defaults.get(name), ctxt, vars) cmlenz@336: scope[name] = val cmlenz@500: if not self.star_args is None: cmlenz@500: scope[self.star_args] = args cmlenz@500: if not self.dstar_args is None: cmlenz@500: scope[self.dstar_args] = kwargs cmlenz@336: ctxt.push(scope) cmlenz@830: for event in _apply_directives(stream, directives, ctxt, vars): cmlenz@336: yield event cmlenz@336: ctxt.pop() cmlenz@820: function.__name__ = self.name cmlenz@336: cmlenz@336: # Store the function reference in the bottom context frame so that it cmlenz@336: # doesn't get popped off before processing the template has finished cmlenz@336: # FIXME: this makes context data mutable as a side-effect cmlenz@336: ctxt.frames[-1][self.name] = function cmlenz@336: cmlenz@336: return [] cmlenz@336: cmlenz@336: def __repr__(self): cmlenz@902: return '<%s "%s">' % (type(self).__name__, self.name) cmlenz@336: cmlenz@336: cmlenz@336: class ForDirective(Directive): cmlenz@500: """Implementation of the ``py:for`` template directive for repeating an cmlenz@336: element based on an iterable in the context data. cmlenz@336: cmlenz@336: >>> from genshi.template import MarkupTemplate cmlenz@336: >>> tmpl = MarkupTemplate('''''') cmlenz@902: >>> print(tmpl.generate(items=[1, 2, 3])) cmlenz@336: cmlenz@336: """ cmlenz@339: __slots__ = ['assign', 'target', 'filename'] cmlenz@336: cmlenz@500: def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): cmlenz@336: if ' in ' not in value: cmlenz@336: raise TemplateSyntaxError('"in" keyword missing in "for" directive', cmlenz@500: template.filepath, lineno, offset) cmlenz@336: assign, value = value.split(' in ', 1) cmlenz@821: self.target = _parse(assign, 'exec').body[0].value cmlenz@821: self.assign = _assignment(self.target) cmlenz@820: value = 'iter(%s)' % value.strip() cmlenz@500: self.filename = template.filepath cmlenz@820: Directive.__init__(self, value, template, namespaces, lineno, offset) cmlenz@336: cmlenz@830: @classmethod cmlenz@820: def attach(cls, template, stream, value, namespaces, pos): cmlenz@820: if type(value) is dict: cmlenz@820: value = value.get('each') cmlenz@820: return super(ForDirective, cls).attach(template, stream, value, cmlenz@820: namespaces, pos) cmlenz@820: cmlenz@820: def __call__(self, stream, directives, ctxt, **vars): cmlenz@830: iterable = _eval_expr(self.expr, ctxt, vars) cmlenz@336: if iterable is None: cmlenz@336: return cmlenz@336: cmlenz@336: assign = self.assign cmlenz@336: scope = {} cmlenz@336: stream = list(stream) cmlenz@820: for item in iterable: cmlenz@820: assign(scope, item) cmlenz@820: ctxt.push(scope) cmlenz@830: for event in _apply_directives(stream, directives, ctxt, vars): cmlenz@820: yield event cmlenz@820: ctxt.pop() cmlenz@336: cmlenz@336: def __repr__(self): cmlenz@902: return '<%s>' % type(self).__name__ cmlenz@336: cmlenz@336: cmlenz@336: class IfDirective(Directive): cmlenz@500: """Implementation of the ``py:if`` template directive for conditionally cmlenz@336: excluding elements from being output. cmlenz@336: cmlenz@336: >>> from genshi.template import MarkupTemplate cmlenz@336: >>> tmpl = MarkupTemplate('''
cmlenz@336: ... ${bar} cmlenz@336: ...
''') cmlenz@902: >>> print(tmpl.generate(foo=True, bar='Hello')) cmlenz@336:
cmlenz@336: Hello cmlenz@336:
cmlenz@336: """ cmlenz@336: __slots__ = [] cmlenz@336: cmlenz@830: @classmethod cmlenz@820: def attach(cls, template, stream, value, namespaces, pos): cmlenz@820: if type(value) is dict: cmlenz@820: value = value.get('test') cmlenz@820: return super(IfDirective, cls).attach(template, stream, value, cmlenz@820: namespaces, pos) cmlenz@336: cmlenz@820: def __call__(self, stream, directives, ctxt, **vars): cmlenz@830: value = _eval_expr(self.expr, ctxt, vars) cmlenz@820: if value: cmlenz@830: return _apply_directives(stream, directives, ctxt, vars) cmlenz@336: return [] cmlenz@336: cmlenz@336: cmlenz@336: class MatchDirective(Directive): cmlenz@500: """Implementation of the ``py:match`` template directive. cmlenz@336: cmlenz@336: >>> from genshi.template import MarkupTemplate cmlenz@336: >>> tmpl = MarkupTemplate('''
cmlenz@336: ... cmlenz@336: ... Hello ${select('@name')} cmlenz@336: ... cmlenz@336: ... cmlenz@336: ...
''') cmlenz@902: >>> print(tmpl.generate()) cmlenz@336:
cmlenz@336: cmlenz@336: Hello Dude cmlenz@336: cmlenz@336:
cmlenz@336: """ cmlenz@820: __slots__ = ['path', 'namespaces', 'hints'] cmlenz@336: cmlenz@820: def __init__(self, value, template, hints=None, namespaces=None, cmlenz@820: lineno=-1, offset=-1): cmlenz@500: Directive.__init__(self, None, template, namespaces, lineno, offset) cmlenz@500: self.path = Path(value, template.filepath, lineno) cmlenz@395: self.namespaces = namespaces or {} cmlenz@820: self.hints = hints or () cmlenz@336: cmlenz@830: @classmethod cmlenz@820: def attach(cls, template, stream, value, namespaces, pos): cmlenz@820: hints = [] cmlenz@820: if type(value) is dict: cmlenz@820: if value.get('buffer', '').lower() == 'false': cmlenz@820: hints.append('not_buffered') cmlenz@820: if value.get('once', '').lower() == 'true': cmlenz@820: hints.append('match_once') cmlenz@820: if value.get('recursive', '').lower() == 'false': cmlenz@820: hints.append('not_recursive') cmlenz@820: value = value.get('path') cmlenz@820: return cls(value, template, frozenset(hints), namespaces, *pos[1:]), \ cmlenz@820: stream cmlenz@820: cmlenz@820: def __call__(self, stream, directives, ctxt, **vars): cmlenz@336: ctxt._match_templates.append((self.path.test(ignore_context=True), cmlenz@820: self.path, list(stream), self.hints, cmlenz@820: self.namespaces, directives)) cmlenz@336: return [] cmlenz@336: cmlenz@336: def __repr__(self): cmlenz@902: return '<%s "%s">' % (type(self).__name__, self.path.source) cmlenz@336: cmlenz@336: cmlenz@336: class ReplaceDirective(Directive): cmlenz@500: """Implementation of the ``py:replace`` template directive. cmlenz@336: cmlenz@336: This directive replaces the element with the result of evaluating the cmlenz@500: value of the ``py:replace`` attribute: cmlenz@336: cmlenz@336: >>> from genshi.template import MarkupTemplate cmlenz@336: >>> tmpl = MarkupTemplate('''
cmlenz@336: ... Hello cmlenz@336: ...
''') cmlenz@902: >>> print(tmpl.generate(bar='Bye')) cmlenz@336:
cmlenz@336: Bye cmlenz@336:
cmlenz@336: cmlenz@500: This directive is equivalent to ``py:content`` combined with ``py:strip``, cmlenz@336: providing a less verbose way to achieve the same effect: cmlenz@336: cmlenz@336: >>> tmpl = MarkupTemplate('''
cmlenz@336: ... Hello cmlenz@336: ...
''') cmlenz@902: >>> print(tmpl.generate(bar='Bye')) cmlenz@336:
cmlenz@336: Bye cmlenz@336:
cmlenz@336: """ cmlenz@336: __slots__ = [] cmlenz@336: cmlenz@830: @classmethod cmlenz@395: def attach(cls, template, stream, value, namespaces, pos): cmlenz@820: if type(value) is dict: cmlenz@820: value = value.get('value') cmlenz@500: if not value: cmlenz@500: raise TemplateSyntaxError('missing value for "replace" directive', cmlenz@500: template.filepath, *pos[1:]) cmlenz@500: expr = cls._parse_expr(value, template, *pos[1:]) cmlenz@395: return None, [(EXPR, expr, pos)] cmlenz@336: cmlenz@336: cmlenz@336: class StripDirective(Directive): cmlenz@500: """Implementation of the ``py:strip`` template directive. cmlenz@336: cmlenz@500: When the value of the ``py:strip`` attribute evaluates to ``True``, the cmlenz@500: element is stripped from the output cmlenz@336: cmlenz@336: >>> from genshi.template import MarkupTemplate cmlenz@336: >>> tmpl = MarkupTemplate('''
cmlenz@336: ...
foo
cmlenz@336: ...
''') cmlenz@902: >>> print(tmpl.generate()) cmlenz@336:
cmlenz@336: foo cmlenz@336:
cmlenz@336: cmlenz@336: Leaving the attribute value empty is equivalent to a truth value. cmlenz@336: cmlenz@336: This directive is particulary interesting for named template functions or cmlenz@336: match templates that do not generate a top-level element: cmlenz@336: cmlenz@336: >>> tmpl = MarkupTemplate('''
cmlenz@336: ...
cmlenz@336: ... ${what} cmlenz@336: ...
cmlenz@336: ... ${echo('foo')} cmlenz@336: ...
''') cmlenz@902: >>> print(tmpl.generate()) cmlenz@336:
cmlenz@336: foo cmlenz@336:
cmlenz@336: """ cmlenz@336: __slots__ = [] cmlenz@336: cmlenz@820: def __call__(self, stream, directives, ctxt, **vars): cmlenz@336: def _generate(): cmlenz@902: if not self.expr or _eval_expr(self.expr, ctxt, vars): cmlenz@336: stream.next() # skip start tag cmlenz@336: previous = stream.next() cmlenz@336: for event in stream: cmlenz@336: yield previous cmlenz@336: previous = event cmlenz@336: else: cmlenz@336: for event in stream: cmlenz@336: yield event cmlenz@830: return _apply_directives(_generate(), directives, ctxt, vars) cmlenz@336: cmlenz@336: cmlenz@336: class ChooseDirective(Directive): cmlenz@500: """Implementation of the ``py:choose`` directive for conditionally selecting cmlenz@336: one of several body elements to display. cmlenz@336: cmlenz@500: If the ``py:choose`` expression is empty the expressions of nested cmlenz@500: ``py:when`` directives are tested for truth. The first true ``py:when`` cmlenz@500: body is output. If no ``py:when`` directive is matched then the fallback cmlenz@500: directive ``py:otherwise`` will be used. cmlenz@336: cmlenz@336: >>> from genshi.template import MarkupTemplate cmlenz@336: >>> tmpl = MarkupTemplate('''
cmlenz@336: ... 0 cmlenz@336: ... 1 cmlenz@336: ... 2 cmlenz@336: ...
''') cmlenz@902: >>> print(tmpl.generate()) cmlenz@336:
cmlenz@336: 1 cmlenz@336:
cmlenz@336: cmlenz@500: If the ``py:choose`` directive contains an expression, the nested cmlenz@500: ``py:when`` directives are tested for equality to the ``py:choose`` cmlenz@500: expression: cmlenz@336: cmlenz@336: >>> tmpl = MarkupTemplate('''
cmlenz@336: ... 1 cmlenz@336: ... 2 cmlenz@336: ...
''') cmlenz@902: >>> print(tmpl.generate()) cmlenz@336:
cmlenz@336: 2 cmlenz@336:
cmlenz@336: cmlenz@500: Behavior is undefined if a ``py:choose`` block contains content outside a cmlenz@500: ``py:when`` or ``py:otherwise`` block. Behavior is also undefined if a cmlenz@500: ``py:otherwise`` occurs before ``py:when`` blocks. cmlenz@336: """ cmlenz@336: __slots__ = ['matched', 'value'] cmlenz@336: cmlenz@830: @classmethod cmlenz@820: def attach(cls, template, stream, value, namespaces, pos): cmlenz@820: if type(value) is dict: cmlenz@820: value = value.get('test') cmlenz@820: return super(ChooseDirective, cls).attach(template, stream, value, cmlenz@820: namespaces, pos) cmlenz@336: cmlenz@820: def __call__(self, stream, directives, ctxt, **vars): cmlenz@820: info = [False, bool(self.expr), None] cmlenz@336: if self.expr: cmlenz@830: info[2] = _eval_expr(self.expr, ctxt, vars) cmlenz@820: ctxt._choice_stack.append(info) cmlenz@830: for event in _apply_directives(stream, directives, ctxt, vars): cmlenz@336: yield event cmlenz@820: ctxt._choice_stack.pop() cmlenz@336: cmlenz@336: cmlenz@336: class WhenDirective(Directive): cmlenz@500: """Implementation of the ``py:when`` directive for nesting in a parent with cmlenz@500: the ``py:choose`` directive. cmlenz@336: cmlenz@500: See the documentation of the `ChooseDirective` for usage. cmlenz@336: """ cmlenz@336: __slots__ = ['filename'] cmlenz@336: cmlenz@500: def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): cmlenz@500: Directive.__init__(self, value, template, namespaces, lineno, offset) cmlenz@500: self.filename = template.filepath cmlenz@336: cmlenz@830: @classmethod cmlenz@820: def attach(cls, template, stream, value, namespaces, pos): cmlenz@820: if type(value) is dict: cmlenz@820: value = value.get('test') cmlenz@820: return super(WhenDirective, cls).attach(template, stream, value, cmlenz@820: namespaces, pos) cmlenz@820: cmlenz@820: def __call__(self, stream, directives, ctxt, **vars): cmlenz@820: info = ctxt._choice_stack and ctxt._choice_stack[-1] cmlenz@820: if not info: cmlenz@336: raise TemplateRuntimeError('"when" directives can only be used ' cmlenz@336: 'inside a "choose" directive', cmlenz@336: self.filename, *stream.next()[2][1:]) cmlenz@820: if info[0]: cmlenz@336: return [] cmlenz@820: if not self.expr and not info[1]: cmlenz@336: raise TemplateRuntimeError('either "choose" or "when" directive ' cmlenz@336: 'must have a test expression', cmlenz@336: self.filename, *stream.next()[2][1:]) cmlenz@820: if info[1]: cmlenz@820: value = info[2] cmlenz@336: if self.expr: cmlenz@830: matched = value == _eval_expr(self.expr, ctxt, vars) cmlenz@336: else: cmlenz@336: matched = bool(value) cmlenz@336: else: cmlenz@830: matched = bool(_eval_expr(self.expr, ctxt, vars)) cmlenz@820: info[0] = matched cmlenz@336: if not matched: cmlenz@336: return [] cmlenz@336: cmlenz@830: return _apply_directives(stream, directives, ctxt, vars) cmlenz@336: cmlenz@336: cmlenz@336: class OtherwiseDirective(Directive): cmlenz@500: """Implementation of the ``py:otherwise`` directive for nesting in a parent cmlenz@500: with the ``py:choose`` directive. cmlenz@336: cmlenz@500: See the documentation of `ChooseDirective` for usage. cmlenz@336: """ cmlenz@336: __slots__ = ['filename'] cmlenz@336: cmlenz@500: def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): cmlenz@500: Directive.__init__(self, None, template, namespaces, lineno, offset) cmlenz@500: self.filename = template.filepath cmlenz@336: cmlenz@820: def __call__(self, stream, directives, ctxt, **vars): cmlenz@820: info = ctxt._choice_stack and ctxt._choice_stack[-1] cmlenz@820: if not info: cmlenz@336: raise TemplateRuntimeError('an "otherwise" directive can only be ' cmlenz@336: 'used inside a "choose" directive', cmlenz@336: self.filename, *stream.next()[2][1:]) cmlenz@820: if info[0]: cmlenz@336: return [] cmlenz@820: info[0] = True cmlenz@336: cmlenz@830: return _apply_directives(stream, directives, ctxt, vars) cmlenz@336: cmlenz@336: cmlenz@336: class WithDirective(Directive): cmlenz@500: """Implementation of the ``py:with`` template directive, which allows cmlenz@336: shorthand access to variables and expressions. cmlenz@336: cmlenz@336: >>> from genshi.template import MarkupTemplate cmlenz@336: >>> tmpl = MarkupTemplate('''
cmlenz@336: ... $x $y $z cmlenz@336: ...
''') cmlenz@902: >>> print(tmpl.generate(x=42)) cmlenz@336:
cmlenz@336: 42 7 52 cmlenz@336:
cmlenz@336: """ cmlenz@336: __slots__ = ['vars'] cmlenz@336: cmlenz@500: def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): cmlenz@500: Directive.__init__(self, None, template, namespaces, lineno, offset) cmlenz@336: self.vars = [] cmlenz@336: value = value.strip() cmlenz@336: try: cmlenz@820: ast = _parse(value, 'exec') cmlenz@820: for node in ast.body: cmlenz@820: if not isinstance(node, _ast.Assign): cmlenz@336: raise TemplateSyntaxError('only assignment allowed in ' cmlenz@336: 'value of the "with" directive', cmlenz@500: template.filepath, lineno, offset) cmlenz@820: self.vars.append(([(n, _assignment(n)) for n in node.targets], cmlenz@820: Expression(node.value, template.filepath, cmlenz@500: lineno, lookup=template.lookup))) cmlenz@336: except SyntaxError, err: cmlenz@336: err.msg += ' in expression "%s" of "%s" directive' % (value, cmlenz@336: self.tagname) cmlenz@500: raise TemplateSyntaxError(err, template.filepath, lineno, cmlenz@336: offset + (err.offset or 0)) cmlenz@336: cmlenz@830: @classmethod cmlenz@820: def attach(cls, template, stream, value, namespaces, pos): cmlenz@820: if type(value) is dict: cmlenz@820: value = value.get('vars') cmlenz@820: return super(WithDirective, cls).attach(template, stream, value, cmlenz@820: namespaces, pos) cmlenz@820: cmlenz@820: def __call__(self, stream, directives, ctxt, **vars): cmlenz@336: frame = {} cmlenz@336: ctxt.push(frame) cmlenz@336: for targets, expr in self.vars: cmlenz@830: value = _eval_expr(expr, ctxt, vars) cmlenz@339: for _, assign in targets: cmlenz@336: assign(frame, value) cmlenz@830: for event in _apply_directives(stream, directives, ctxt, vars): cmlenz@336: yield event cmlenz@336: ctxt.pop() cmlenz@336: cmlenz@336: def __repr__(self): cmlenz@902: return '<%s>' % (type(self).__name__)