| 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__) |
|---|