comparison markup/template.py @ 53:512eb72dbb19 trunk

* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive. * Fix the order of the `py:choose`, `py:when`, and `py:otherwise` directives. * Moved some of the `py:choose` tests to a new `unittest` suite to keep the docstring compact.
author cmlenz
date Tue, 04 Jul 2006 11:57:08 +0000
parents b2383634ec04
children 1f3cd91325d9
comparison
equal deleted inserted replaced
52:1340e3297d19 53:512eb72dbb19
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, directives=None): 179 def __call__(self, stream, ctxt, directives):
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:
185 expr = ' "%s"' % self.expr.source 185 expr = ' "%s"' % self.expr.source
186 return '<%s%s>' % (self.__class__.__name__, expr) 186 return '<%s%s>' % (self.__class__.__name__, expr)
187
188 def _apply_directives(self, stream, ctxt, directives):
189 if directives:
190 stream = directives[0](iter(stream), ctxt, directives[1:])
191 return stream
187 192
188 193
189 class AttrsDirective(Directive): 194 class AttrsDirective(Directive):
190 """Implementation of the `py:attrs` template directive. 195 """Implementation of the `py:attrs` template directive.
191 196
210 <li>Bar</li> 215 <li>Bar</li>
211 </ul> 216 </ul>
212 """ 217 """
213 __slots__ = [] 218 __slots__ = []
214 219
215 def __call__(self, stream, ctxt, directives=None): 220 def __call__(self, stream, ctxt, directives):
216 kind, (tag, attrib), pos = stream.next() 221 def _generate():
217 attrs = self.expr.evaluate(ctxt) 222 kind, (tag, attrib), pos = stream.next()
218 if attrs: 223 attrs = self.expr.evaluate(ctxt)
219 attrib = Attributes(attrib[:]) 224 if attrs:
220 if not isinstance(attrs, list): # assume it's a dict 225 attrib = Attributes(attrib[:])
221 attrs = attrs.items() 226 if not isinstance(attrs, list): # assume it's a dict
222 for name, value in attrs: 227 attrs = attrs.items()
223 if value is None: 228 for name, value in attrs:
224 attrib.remove(name) 229 if value is None:
225 else: 230 attrib.remove(name)
226 attrib.set(name, unicode(value).strip()) 231 else:
227 yield kind, (tag, attrib), pos 232 attrib.set(name, unicode(value).strip())
228 for event in stream: 233 yield kind, (tag, attrib), pos
229 yield event 234 for event in stream:
235 yield event
236 return self._apply_directives(_generate(), ctxt, directives)
230 237
231 238
232 class ContentDirective(Directive): 239 class ContentDirective(Directive):
233 """Implementation of the `py:content` template directive. 240 """Implementation of the `py:content` template directive.
234 241
245 </ul> 252 </ul>
246 """ 253 """
247 __slots__ = [] 254 __slots__ = []
248 255
249 def __call__(self, stream, ctxt, directives): 256 def __call__(self, stream, ctxt, directives):
250 def generate(): 257 def _generate():
251 kind, data, pos = stream.next() 258 kind, data, pos = stream.next()
252 if kind is Stream.START: 259 if kind is Stream.START:
253 yield kind, data, pos # emit start tag 260 yield kind, data, pos # emit start tag
254 yield Template.EXPR, self.expr, pos 261 yield Template.EXPR, self.expr, pos
255 previous = stream.next() 262 previous = stream.next()
256 for event in stream: 263 for event in stream:
257 previous = event 264 previous = event
258 if previous is not None: 265 if previous is not None:
259 yield previous 266 yield previous
260 output = generate() 267 return self._apply_directives(_generate(), ctxt, directives)
261 if directives:
262 output = directives[0](output, ctxt, directives[1:])
263 return output
264 268
265 269
266 class DefDirective(Directive): 270 class DefDirective(Directive):
267 """Implementation of the `py:def` template directive. 271 """Implementation of the `py:def` template directive.
268 272
335 if args: 339 if args:
336 scope[name] = args.pop(0) 340 scope[name] = args.pop(0)
337 else: 341 else:
338 scope[name] = kwargs.pop(name, self.defaults.get(name)) 342 scope[name] = kwargs.pop(name, self.defaults.get(name))
339 ctxt.push(**scope) 343 ctxt.push(**scope)
340 stream = iter(self.stream) 344 stream = self._apply_directives(self.stream, ctxt, self.directives)
341 if self.directives:
342 stream = self.directives[0](stream, ctxt, self.directives[1:])
343 for event in stream: 345 for event in stream:
344 yield event 346 yield event
345 ctxt.pop() 347 ctxt.pop()
346 348
347 349
364 targets, value = value.split(' in ', 1) 366 targets, value = value.split(' in ', 1)
365 self.targets = [str(name.strip()) for name in targets.split(',')] 367 self.targets = [str(name.strip()) for name in targets.split(',')]
366 Directive.__init__(self, value) 368 Directive.__init__(self, value)
367 369
368 def __call__(self, stream, ctxt, directives): 370 def __call__(self, stream, ctxt, directives):
369 iterable = self.expr.evaluate(ctxt) or [] 371 iterable = self.expr.evaluate(ctxt)
370 if iterable is not None: 372 if iterable is not None:
371 stream = list(stream) 373 stream = list(stream)
372 for item in iter(iterable): 374 for item in iter(iterable):
373 if len(self.targets) == 1: 375 if len(self.targets) == 1:
374 item = [item] 376 item = [item]
375 scope = {} 377 scope = {}
376 for idx, name in enumerate(self.targets): 378 for idx, name in enumerate(self.targets):
377 scope[name] = item[idx] 379 scope[name] = item[idx]
378 ctxt.push(**scope) 380 ctxt.push(**scope)
379 output = stream 381 for event in self._apply_directives(stream, ctxt, directives):
380 if directives:
381 output = directives[0](iter(output), ctxt, directives[1:])
382 for event in output:
383 yield event 382 yield event
384 ctxt.pop() 383 ctxt.pop()
385 384
386 def __repr__(self): 385 def __repr__(self):
387 return '<%s "%s in %s">' % (self.__class__.__name__, 386 return '<%s "%s in %s">' % (self.__class__.__name__,
403 """ 402 """
404 __slots__ = [] 403 __slots__ = []
405 404
406 def __call__(self, stream, ctxt, directives): 405 def __call__(self, stream, ctxt, directives):
407 if self.expr.evaluate(ctxt): 406 if self.expr.evaluate(ctxt):
408 if directives: 407 return self._apply_directives(stream, ctxt, directives)
409 stream = directives[0](stream, ctxt, directives[1:])
410 return stream
411 return [] 408 return []
412 409
413 410
414 class MatchDirective(Directive): 411 class MatchDirective(Directive):
415 """Implementation of the `py:match` template directive. 412 """Implementation of the `py:match` template directive.
513 def __call__(self, stream, ctxt, directives=None): 510 def __call__(self, stream, ctxt, directives=None):
514 if self.expr: 511 if self.expr:
515 strip = self.expr.evaluate(ctxt) 512 strip = self.expr.evaluate(ctxt)
516 else: 513 else:
517 strip = True 514 strip = True
515 stream = self._apply_directives(stream, ctxt, directives)
518 if strip: 516 if strip:
519 stream.next() # skip start tag 517 stream.next() # skip start tag
520 previous = stream.next() 518 previous = stream.next()
521 for event in stream: 519 for event in stream:
522 yield previous 520 yield previous
527 525
528 526
529 class ChooseDirective(Directive): 527 class ChooseDirective(Directive):
530 """Implementation of the `py:choose` directive for conditionally selecting 528 """Implementation of the `py:choose` directive for conditionally selecting
531 one of several body elements to display. 529 one of several body elements to display.
532 530
533 If the `py:choose` expression is empty the expressions of nested `py:when` 531 If the `py:choose` expression is empty the expressions of nested `py:when`
534 directives are tested for truth. The first true `py:when` body is output. 532 directives are tested for truth. The first true `py:when` body is output.
535 533 If no `py:when` directive is matched then the fallback directive
534 `py:otherwise` will be used.
535
536 >>> ctxt = Context() 536 >>> ctxt = Context()
537 >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#" 537 >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#"
538 ... py:choose=""> 538 ... py:choose="">
539 ... <span py:when="0 == 1">0</span> 539 ... <span py:when="0 == 1">0</span>
540 ... <span py:when="1 == 1">1</span> 540 ... <span py:when="1 == 1">1</span>
541 ... <span py:otherwise="">2</span>
541 ... </div>''') 542 ... </div>''')
542 >>> print tmpl.generate(ctxt) 543 >>> print tmpl.generate(ctxt)
543 <div> 544 <div>
544 <span>1</span> 545 <span>1</span>
545 </div> 546 </div>
546 547
547 If multiple `py:when` bodies match only the first is output.
548 >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#"
549 ... py:choose="">
550 ... <span py:when="1 == 1">1</span>
551 ... <span py:when="2 == 2">2</span>
552 ... </div>''')
553 >>> print tmpl.generate(ctxt)
554 <div>
555 <span>1</span>
556 </div>
557
558 If the `py:choose` directive contains an expression, the nested `py:when` 548 If the `py:choose` directive contains an expression, the nested `py:when`
559 directives are tested for equality to the `py:choose` expression. 549 directives are tested for equality to the `py:choose` expression:
550
560 >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#" 551 >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#"
561 ... py:choose="2"> 552 ... py:choose="2">
562 ... <span py:when="1">1</span> 553 ... <span py:when="1">1</span>
563 ... <span py:when="2">2</span> 554 ... <span py:when="2">2</span>
564 ... </div>''') 555 ... </div>''')
565 >>> print tmpl.generate(ctxt) 556 >>> print tmpl.generate(ctxt)
566 <div> 557 <div>
567 <span>2</span> 558 <span>2</span>
568 </div> 559 </div>
569 560
570 If no `py:when` directive is matched then the fallback directive
571 `py:otherwise` will be used.
572 >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#"
573 ... py:choose="">
574 ... <span py:when="False">hidden</span>
575 ... <span py:otherwise="">hello</span>
576 ... </div>''')
577 >>> print tmpl.generate(ctxt)
578 <div>
579 <span>hello</span>
580 </div>
581
582 `py:choose` blocks can be nested:
583 >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#"
584 ... py:choose="1">
585 ... <div py:when="1" py:choose="3">
586 ... <span py:when="2">2</span>
587 ... <span py:when="3">3</span>
588 ... </div>
589 ... </div>''')
590 >>> print tmpl.generate(ctxt)
591 <div>
592 <div>
593 <span>3</span>
594 </div>
595 </div>
596
597 Behavior is undefined if a `py:choose` block contains content outside a 561 Behavior is undefined if a `py:choose` block contains content outside a
598 `py:when` or `py:otherwise` block. Behavior is also undefined if a 562 `py:when` or `py:otherwise` block. Behavior is also undefined if a
599 `py:otherwise` occurs before `py:when` blocks. 563 `py:otherwise` occurs before `py:when` blocks.
600 """ 564 """
601 __slots__ = ['matched', 'value'] 565 __slots__ = ['matched', 'value']
602 566
603 def __call__(self, stream, ctxt, directives=None): 567 def __call__(self, stream, ctxt, directives):
604 if self.expr: 568 if self.expr:
605 self.value = self.expr.evaluate(ctxt) 569 self.value = self.expr.evaluate(ctxt)
606 self.matched = False 570 self.matched = False
607 ctxt.push(_choose=self) 571 ctxt.push(_choose=self)
608 for event in stream: 572 for event in self._apply_directives(stream, ctxt, directives):
609 yield event 573 yield event
610 ctxt.pop() 574 ctxt.pop()
611 575
612 576
613 class WhenDirective(Directive): 577 class WhenDirective(Directive):
622 return [] 586 return []
623 value = self.expr.evaluate(ctxt) 587 value = self.expr.evaluate(ctxt)
624 try: 588 try:
625 if value == choose.value: 589 if value == choose.value:
626 choose.matched = True 590 choose.matched = True
627 return stream 591 return self._apply_directives(stream, ctxt, directives)
628 except AttributeError: 592 except AttributeError:
629 if value: 593 if value:
630 choose.matched = True 594 choose.matched = True
631 return stream 595 return self._apply_directives(stream, ctxt, directives)
632 return [] 596 return []
633 597
634 598
635 class OtherwiseDirective(Directive): 599 class OtherwiseDirective(Directive):
636 """Implementation of the `py:otherwise` directive for nesting in a parent 600 """Implementation of the `py:otherwise` directive for nesting in a parent
641 def __call__(self, stream, ctxt, directives=None): 605 def __call__(self, stream, ctxt, directives=None):
642 choose = ctxt['_choose'] 606 choose = ctxt['_choose']
643 if choose.matched: 607 if choose.matched:
644 return [] 608 return []
645 choose.matched = True 609 choose.matched = True
646 return stream 610 return self._apply_directives(stream, ctxt, directives)
647 611
648 612
649 class Template(object): 613 class Template(object):
650 """Can parse a template and transform it into the corresponding output 614 """Can parse a template and transform it into the corresponding output
651 based on context data. 615 based on context data.
657 621
658 directives = [('def', DefDirective), 622 directives = [('def', DefDirective),
659 ('match', MatchDirective), 623 ('match', MatchDirective),
660 ('for', ForDirective), 624 ('for', ForDirective),
661 ('if', IfDirective), 625 ('if', IfDirective),
662 ('choose', ChooseDirective),
663 ('when', WhenDirective), 626 ('when', WhenDirective),
664 ('otherwise', OtherwiseDirective), 627 ('otherwise', OtherwiseDirective),
628 ('choose', ChooseDirective),
665 ('replace', ReplaceDirective), 629 ('replace', ReplaceDirective),
666 ('content', ContentDirective), 630 ('content', ContentDirective),
667 ('attrs', AttrsDirective), 631 ('attrs', AttrsDirective),
668 ('strip', StripDirective)] 632 ('strip', StripDirective)]
669 _dir_by_name = dict(directives) 633 _dir_by_name = dict(directives)
Copyright (C) 2012-2017 Edgewall Software