Mercurial > genshi > mirror
comparison markup/template.py @ 50:d3842cd76e92 trunk
Fix the way multiple directives are applied to a single `SUB` in many cases by making the directives themselves responsible for applying any remaining directives.
author | cmlenz |
---|---|
date | Tue, 04 Jul 2006 08:37:25 +0000 |
parents | a5d585dd38c4 |
children | b2383634ec04 |
comparison
equal
deleted
inserted
replaced
49:6d1f79b2f7ef | 50:d3842cd76e92 |
---|---|
174 __slots__ = ['expr'] | 174 __slots__ = ['expr'] |
175 | 175 |
176 def __init__(self, value): | 176 def __init__(self, value): |
177 self.expr = value and Expression(value) or None | 177 self.expr = value and Expression(value) or None |
178 | 178 |
179 def __call__(self, stream, ctxt): | 179 def __call__(self, stream, ctxt, directives=None): |
180 raise NotImplementedError | 180 raise NotImplementedError |
181 | 181 |
182 def __repr__(self): | 182 def __repr__(self): |
183 expr = '' | 183 expr = '' |
184 if self.expr is not None: | 184 if self.expr is not None: |
208 >>> print tmpl.generate(ctxt) | 208 >>> print tmpl.generate(ctxt) |
209 <ul> | 209 <ul> |
210 <li>Bar</li> | 210 <li>Bar</li> |
211 </ul> | 211 </ul> |
212 """ | 212 """ |
213 def __call__(self, stream, ctxt): | 213 __slots__ = [] |
214 | |
215 def __call__(self, stream, ctxt, directives=None): | |
214 kind, (tag, attrib), pos = stream.next() | 216 kind, (tag, attrib), pos = stream.next() |
215 attrs = self.expr.evaluate(ctxt) | 217 attrs = self.expr.evaluate(ctxt) |
216 if attrs: | 218 if attrs: |
217 attrib = Attributes(attrib[:]) | 219 attrib = Attributes(attrib[:]) |
218 if not isinstance(attrs, list): # assume it's a dict | 220 if not isinstance(attrs, list): # assume it's a dict |
240 >>> print tmpl.generate(ctxt) | 242 >>> print tmpl.generate(ctxt) |
241 <ul> | 243 <ul> |
242 <li>Bye</li> | 244 <li>Bye</li> |
243 </ul> | 245 </ul> |
244 """ | 246 """ |
245 def __call__(self, stream, ctxt): | 247 __slots__ = [] |
246 kind, data, pos = stream.next() | 248 |
247 if kind is Stream.START: | 249 def __call__(self, stream, ctxt, directives): |
248 yield kind, data, pos # emit start tag | 250 def generate(): |
249 yield Template.EXPR, self.expr, pos | 251 kind, data, pos = stream.next() |
250 previous = stream.next() | 252 if kind is Stream.START: |
251 for event in stream: | 253 yield kind, data, pos # emit start tag |
252 previous = event | 254 yield Template.EXPR, self.expr, pos |
253 if previous is not None: | 255 previous = stream.next() |
254 yield previous | 256 for event in stream: |
257 previous = event | |
258 if previous is not None: | |
259 yield previous | |
260 output = generate() | |
261 if directives: | |
262 output = directives[0](output, ctxt, directives[1:]) | |
263 return output | |
255 | 264 |
256 | 265 |
257 class DefDirective(Directive): | 266 class DefDirective(Directive): |
258 """Implementation of the `py:def` template directive. | 267 """Implementation of the `py:def` template directive. |
259 | 268 |
291 <p class="message"> | 300 <p class="message"> |
292 hello, world! | 301 hello, world! |
293 </p> | 302 </p> |
294 </div> | 303 </div> |
295 """ | 304 """ |
296 __slots__ = ['name', 'args', 'defaults', 'stream'] | 305 __slots__ = ['name', 'args', 'defaults', 'stream', 'directives'] |
297 | 306 |
298 def __init__(self, args): | 307 def __init__(self, args): |
299 Directive.__init__(self, None) | 308 Directive.__init__(self, None) |
300 ast = compiler.parse(args, 'eval').node | 309 ast = compiler.parse(args, 'eval').node |
301 self.args = [] | 310 self.args = [] |
308 self.defaults[arg.name] = arg.expr.value | 317 self.defaults[arg.name] = arg.expr.value |
309 else: | 318 else: |
310 self.args.append(arg.name) | 319 self.args.append(arg.name) |
311 else: | 320 else: |
312 self.name = ast.name | 321 self.name = ast.name |
313 self.stream = [] | 322 self.stream, self.directives = [], [] |
314 | 323 |
315 def __call__(self, stream, ctxt): | 324 def __call__(self, stream, ctxt, directives): |
316 self.stream = list(stream) | 325 self.stream = list(stream) |
326 self.directives = directives | |
317 ctxt[self.name] = lambda *args, **kwargs: self._exec(ctxt, *args, | 327 ctxt[self.name] = lambda *args, **kwargs: self._exec(ctxt, *args, |
318 **kwargs) | 328 **kwargs) |
319 return [] | 329 return [] |
320 | 330 |
321 def _exec(self, ctxt, *args, **kwargs): | 331 def _exec(self, ctxt, *args, **kwargs): |
325 if args: | 335 if args: |
326 scope[name] = args.pop(0) | 336 scope[name] = args.pop(0) |
327 else: | 337 else: |
328 scope[name] = kwargs.pop(name, self.defaults.get(name)) | 338 scope[name] = kwargs.pop(name, self.defaults.get(name)) |
329 ctxt.push(**scope) | 339 ctxt.push(**scope) |
330 for event in self.stream: | 340 stream = iter(self.stream) |
341 if self.directives: | |
342 stream = self.directives[0](stream, ctxt, self.directives[1:]) | |
343 for event in stream: | |
331 yield event | 344 yield event |
332 ctxt.pop() | 345 ctxt.pop() |
333 | 346 |
334 | 347 |
335 class ForDirective(Directive): | 348 class ForDirective(Directive): |
350 def __init__(self, value): | 363 def __init__(self, value): |
351 targets, value = value.split(' in ', 1) | 364 targets, value = value.split(' in ', 1) |
352 self.targets = [str(name.strip()) for name in targets.split(',')] | 365 self.targets = [str(name.strip()) for name in targets.split(',')] |
353 Directive.__init__(self, value) | 366 Directive.__init__(self, value) |
354 | 367 |
355 def __call__(self, stream, ctxt): | 368 def __call__(self, stream, ctxt, directives): |
356 iterable = self.expr.evaluate(ctxt) or [] | 369 iterable = self.expr.evaluate(ctxt) or [] |
357 if iterable is not None: | 370 if iterable is not None: |
358 stream = list(stream) | 371 stream = list(stream) |
359 for item in iter(iterable): | 372 for item in iter(iterable): |
360 if len(self.targets) == 1: | 373 if len(self.targets) == 1: |
361 item = [item] | 374 item = [item] |
362 scope = {} | 375 scope = {} |
363 for idx, name in enumerate(self.targets): | 376 for idx, name in enumerate(self.targets): |
364 scope[name] = item[idx] | 377 scope[name] = item[idx] |
365 ctxt.push(**scope) | 378 ctxt.push(**scope) |
379 if directives: | |
380 stream = list(directives[0](iter(stream), ctxt, | |
381 directives[1:])) | |
366 for event in stream: | 382 for event in stream: |
367 yield event | 383 yield event |
368 ctxt.pop() | 384 ctxt.pop() |
369 | 385 |
370 def __repr__(self): | 386 def __repr__(self): |
383 >>> print tmpl.generate(ctxt) | 399 >>> print tmpl.generate(ctxt) |
384 <div> | 400 <div> |
385 <b>Hello</b> | 401 <b>Hello</b> |
386 </div> | 402 </div> |
387 """ | 403 """ |
388 def __call__(self, stream, ctxt): | 404 __slots__ = [] |
405 | |
406 def __call__(self, stream, ctxt, directives): | |
389 if self.expr.evaluate(ctxt): | 407 if self.expr.evaluate(ctxt): |
408 if directives: | |
409 stream = directives[0](stream, ctxt, directives[1:]) | |
390 return stream | 410 return stream |
391 return [] | 411 return [] |
392 | 412 |
393 | 413 |
394 class MatchDirective(Directive): | 414 class MatchDirective(Directive): |
412 def __init__(self, value): | 432 def __init__(self, value): |
413 Directive.__init__(self, None) | 433 Directive.__init__(self, None) |
414 self.path = Path(value) | 434 self.path = Path(value) |
415 self.stream = [] | 435 self.stream = [] |
416 | 436 |
417 def __call__(self, stream, ctxt): | 437 def __call__(self, stream, ctxt, directives): |
418 self.stream = list(stream) | 438 self.stream = list(stream) |
419 ctxt._match_templates.append((self.path.test(ignore_context=True), | 439 ctxt._match_templates.append((self.path.test(ignore_context=True), |
420 self.path, self.stream)) | 440 self.path, self.stream, directives)) |
421 return [] | 441 return [] |
422 | 442 |
423 def __repr__(self): | 443 def __repr__(self): |
424 return '<%s "%s">' % (self.__class__.__name__, self.path.source) | 444 return '<%s "%s">' % (self.__class__.__name__, self.path.source) |
425 | 445 |
449 >>> print tmpl.generate(ctxt) | 469 >>> print tmpl.generate(ctxt) |
450 <div> | 470 <div> |
451 Bye | 471 Bye |
452 </div> | 472 </div> |
453 """ | 473 """ |
454 def __call__(self, stream, ctxt): | 474 __slots__ = [] |
475 | |
476 def __call__(self, stream, ctxt, directives=None): | |
455 kind, data, pos = stream.next() | 477 kind, data, pos = stream.next() |
456 yield Template.EXPR, self.expr, pos | 478 yield Template.EXPR, self.expr, pos |
457 | 479 |
458 | 480 |
459 class StripDirective(Directive): | 481 class StripDirective(Directive): |
484 >>> print tmpl.generate() | 506 >>> print tmpl.generate() |
485 <div> | 507 <div> |
486 <b>foo</b> | 508 <b>foo</b> |
487 </div> | 509 </div> |
488 """ | 510 """ |
489 def __call__(self, stream, ctxt): | 511 __slots__ = [] |
512 | |
513 def __call__(self, stream, ctxt, directives=None): | |
490 if self.expr: | 514 if self.expr: |
491 strip = self.expr.evaluate(ctxt) | 515 strip = self.expr.evaluate(ctxt) |
492 else: | 516 else: |
493 strip = True | 517 strip = True |
494 if strip: | 518 if strip: |
572 | 596 |
573 Behavior is undefined if a `py:choose` block contains content outside a | 597 Behavior is undefined if a `py:choose` block contains content outside a |
574 `py:when` or `py:otherwise` block. Behavior is also undefined if a | 598 `py:when` or `py:otherwise` block. Behavior is also undefined if a |
575 `py:otherwise` occurs before `py:when` blocks. | 599 `py:otherwise` occurs before `py:when` blocks. |
576 """ | 600 """ |
577 | 601 __slots__ = ['matched', 'value'] |
578 def __call__(self, stream, ctxt): | 602 |
603 def __call__(self, stream, ctxt, directives=None): | |
579 if self.expr: | 604 if self.expr: |
580 self.value = self.expr.evaluate(ctxt) | 605 self.value = self.expr.evaluate(ctxt) |
581 self.matched = False | 606 self.matched = False |
582 ctxt.push(__choose=self) | 607 ctxt.push(_choose=self) |
583 for event in stream: | 608 for event in stream: |
584 yield event | 609 yield event |
585 ctxt.pop() | 610 ctxt.pop() |
586 | 611 |
587 | 612 |
588 class WhenDirective(Directive): | 613 class WhenDirective(Directive): |
589 """Implementation of the `py:when` directive for nesting in a parent with | 614 """Implementation of the `py:when` directive for nesting in a parent with |
590 the `py:choose` directive. See the documentation of `py:choose` for | 615 the `py:choose` directive. |
591 usage. | 616 |
592 """ | 617 See the documentation of `py:choose` for usage. |
593 def __call__(self, stream, ctxt): | 618 """ |
594 choose = ctxt['__choose'] | 619 def __call__(self, stream, ctxt, directives=None): |
620 choose = ctxt['_choose'] | |
595 if choose.matched: | 621 if choose.matched: |
596 return [] | 622 return [] |
597 value = self.expr.evaluate(ctxt) | 623 value = self.expr.evaluate(ctxt) |
598 try: | 624 try: |
599 if value == choose.value: | 625 if value == choose.value: |
606 return [] | 632 return [] |
607 | 633 |
608 | 634 |
609 class OtherwiseDirective(Directive): | 635 class OtherwiseDirective(Directive): |
610 """Implementation of the `py:otherwise` directive for nesting in a parent | 636 """Implementation of the `py:otherwise` directive for nesting in a parent |
611 with the `py:choose` directive. See the documentation of `py:choose` for | 637 with the `py:choose` directive. |
612 usage. | 638 |
613 """ | 639 See the documentation of `py:choose` for usage. |
614 def __call__(self, stream, ctxt): | 640 """ |
615 choose = ctxt['__choose'] | 641 def __call__(self, stream, ctxt, directives=None): |
642 choose = ctxt['_choose'] | |
616 if choose.matched: | 643 if choose.matched: |
617 return [] | 644 return [] |
618 choose.matched = True | 645 choose.matched = True |
619 return stream | 646 return stream |
620 | 647 |
630 | 657 |
631 directives = [('def', DefDirective), | 658 directives = [('def', DefDirective), |
632 ('match', MatchDirective), | 659 ('match', MatchDirective), |
633 ('for', ForDirective), | 660 ('for', ForDirective), |
634 ('if', IfDirective), | 661 ('if', IfDirective), |
662 ('choose', ChooseDirective), | |
663 ('when', WhenDirective), | |
664 ('otherwise', OtherwiseDirective), | |
635 ('replace', ReplaceDirective), | 665 ('replace', ReplaceDirective), |
636 ('content', ContentDirective), | 666 ('content', ContentDirective), |
637 ('attrs', AttrsDirective), | 667 ('attrs', AttrsDirective), |
638 ('strip', StripDirective), | 668 ('strip', StripDirective)] |
639 ('choose', ChooseDirective), | |
640 ('when', WhenDirective), | |
641 ('otherwise', OtherwiseDirective)] | |
642 _dir_by_name = dict(directives) | 669 _dir_by_name = dict(directives) |
643 _dir_order = [directive[1] for directive in directives] | 670 _dir_order = [directive[1] for directive in directives] |
644 | 671 |
645 def __init__(self, source, basedir=None, filename=None): | 672 def __init__(self, source, basedir=None, filename=None): |
646 """Initialize a template from either a string or a file-like object.""" | 673 """Initialize a template from either a string or a file-like object.""" |
705 directives.append(cls(value)) | 732 directives.append(cls(value)) |
706 else: | 733 else: |
707 value = list(self._interpolate(value, *pos)) | 734 value = list(self._interpolate(value, *pos)) |
708 new_attrib.append((name, value)) | 735 new_attrib.append((name, value)) |
709 if directives: | 736 if directives: |
710 directives.sort(lambda a, b: cmp(self._dir_order.index(b.__class__), | 737 directives.sort(lambda a, b: cmp(self._dir_order.index(a.__class__), |
711 self._dir_order.index(a.__class__))) | 738 self._dir_order.index(b.__class__))) |
712 dirmap[(depth, tag)] = (directives, len(stream)) | 739 dirmap[(depth, tag)] = (directives, len(stream)) |
713 | 740 |
714 stream.append((kind, (tag, Attributes(new_attrib)), pos)) | 741 stream.append((kind, (tag, Attributes(new_attrib)), pos)) |
715 depth += 1 | 742 depth += 1 |
716 | 743 |
840 for kind, data, pos in stream: | 867 for kind, data, pos in stream: |
841 if kind is Template.SUB: | 868 if kind is Template.SUB: |
842 # This event is a list of directives and a list of nested | 869 # This event is a list of directives and a list of nested |
843 # events to which those directives should be applied | 870 # events to which those directives should be applied |
844 directives, substream = data | 871 directives, substream = data |
845 for directive in directives: | 872 substream = directives[0](iter(substream), ctxt, directives[1:]) |
846 substream = directive(iter(substream), ctxt) | |
847 substream = self._match(self._eval(substream, ctxt), ctxt) | 873 substream = self._match(self._eval(substream, ctxt), ctxt) |
848 for event in self._flatten(substream, ctxt): | 874 for event in self._flatten(substream, ctxt): |
849 yield event | 875 yield event |
850 continue | 876 continue |
851 else: | 877 else: |
867 # We might care about namespace events in the future, though | 893 # We might care about namespace events in the future, though |
868 if kind not in (Stream.START, Stream.END): | 894 if kind not in (Stream.START, Stream.END): |
869 yield kind, data, pos | 895 yield kind, data, pos |
870 continue | 896 continue |
871 | 897 |
872 for idx, (test, path, template) in enumerate(match_templates): | 898 for idx, (test, path, template, directives) in \ |
899 enumerate(match_templates): | |
873 result = test(kind, data, pos) | 900 result = test(kind, data, pos) |
874 | 901 |
875 if result: | 902 if result: |
876 # Consume and store all events until an end event | 903 # Consume and store all events until an end event |
877 # corresponding to this start event is encountered | 904 # corresponding to this start event is encountered |
889 test(*event) | 916 test(*event) |
890 | 917 |
891 content = list(self._flatten(content, ctxt)) | 918 content = list(self._flatten(content, ctxt)) |
892 ctxt.push(select=lambda path: Stream(content).select(path)) | 919 ctxt.push(select=lambda path: Stream(content).select(path)) |
893 | 920 |
921 if directives: | |
922 template = directives[0](iter(template), ctxt, | |
923 directives[1:]) | |
894 template = self._match(self._eval(iter(template), ctxt), | 924 template = self._match(self._eval(iter(template), ctxt), |
895 ctxt, match_templates[:idx] + | 925 ctxt, match_templates[:idx] + |
896 match_templates[idx + 1:]) | 926 match_templates[idx + 1:]) |
897 for event in template: | 927 for event in template: |
898 yield event | 928 yield event |