Mercurial > genshi > mirror
changeset 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 | 1340e3297d19 |
children | 1f3cd91325d9 |
files | markup/template.py markup/tests/template.py |
diffstat | 2 files changed, 113 insertions(+), 86 deletions(-) [+] |
line wrap: on
line diff
--- a/markup/template.py +++ b/markup/template.py @@ -176,7 +176,7 @@ def __init__(self, value): self.expr = value and Expression(value) or None - def __call__(self, stream, ctxt, directives=None): + def __call__(self, stream, ctxt, directives): raise NotImplementedError def __repr__(self): @@ -185,6 +185,11 @@ expr = ' "%s"' % self.expr.source return '<%s%s>' % (self.__class__.__name__, expr) + def _apply_directives(self, stream, ctxt, directives): + if directives: + stream = directives[0](iter(stream), ctxt, directives[1:]) + return stream + class AttrsDirective(Directive): """Implementation of the `py:attrs` template directive. @@ -212,21 +217,23 @@ """ __slots__ = [] - def __call__(self, stream, ctxt, directives=None): - kind, (tag, attrib), pos = stream.next() - attrs = self.expr.evaluate(ctxt) - if attrs: - attrib = Attributes(attrib[:]) - if not isinstance(attrs, list): # assume it's a dict - attrs = attrs.items() - for name, value in attrs: - if value is None: - attrib.remove(name) - else: - attrib.set(name, unicode(value).strip()) - yield kind, (tag, attrib), pos - for event in stream: - yield event + def __call__(self, stream, ctxt, directives): + def _generate(): + kind, (tag, attrib), pos = stream.next() + attrs = self.expr.evaluate(ctxt) + if attrs: + attrib = Attributes(attrib[:]) + if not isinstance(attrs, list): # assume it's a dict + attrs = attrs.items() + for name, value in attrs: + if value is None: + attrib.remove(name) + else: + attrib.set(name, unicode(value).strip()) + yield kind, (tag, attrib), pos + for event in stream: + yield event + return self._apply_directives(_generate(), ctxt, directives) class ContentDirective(Directive): @@ -247,7 +254,7 @@ __slots__ = [] def __call__(self, stream, ctxt, directives): - def generate(): + def _generate(): kind, data, pos = stream.next() if kind is Stream.START: yield kind, data, pos # emit start tag @@ -257,10 +264,7 @@ previous = event if previous is not None: yield previous - output = generate() - if directives: - output = directives[0](output, ctxt, directives[1:]) - return output + return self._apply_directives(_generate(), ctxt, directives) class DefDirective(Directive): @@ -337,9 +341,7 @@ else: scope[name] = kwargs.pop(name, self.defaults.get(name)) ctxt.push(**scope) - stream = iter(self.stream) - if self.directives: - stream = self.directives[0](stream, ctxt, self.directives[1:]) + stream = self._apply_directives(self.stream, ctxt, self.directives) for event in stream: yield event ctxt.pop() @@ -366,7 +368,7 @@ Directive.__init__(self, value) def __call__(self, stream, ctxt, directives): - iterable = self.expr.evaluate(ctxt) or [] + iterable = self.expr.evaluate(ctxt) if iterable is not None: stream = list(stream) for item in iter(iterable): @@ -376,10 +378,7 @@ for idx, name in enumerate(self.targets): scope[name] = item[idx] ctxt.push(**scope) - output = stream - if directives: - output = directives[0](iter(output), ctxt, directives[1:]) - for event in output: + for event in self._apply_directives(stream, ctxt, directives): yield event ctxt.pop() @@ -405,9 +404,7 @@ def __call__(self, stream, ctxt, directives): if self.expr.evaluate(ctxt): - if directives: - stream = directives[0](stream, ctxt, directives[1:]) - return stream + return self._apply_directives(stream, ctxt, directives) return [] @@ -515,6 +512,7 @@ strip = self.expr.evaluate(ctxt) else: strip = True + stream = self._apply_directives(stream, ctxt, directives) if strip: stream.next() # skip start tag previous = stream.next() @@ -529,34 +527,27 @@ class ChooseDirective(Directive): """Implementation of the `py:choose` directive for conditionally selecting one of several body elements to display. - + If the `py:choose` expression is empty the expressions of nested `py:when` directives are tested for truth. The first true `py:when` body is output. - + If no `py:when` directive is matched then the fallback directive + `py:otherwise` will be used. + >>> ctxt = Context() >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#" ... py:choose=""> ... <span py:when="0 == 1">0</span> ... <span py:when="1 == 1">1</span> + ... <span py:otherwise="">2</span> ... </div>''') >>> print tmpl.generate(ctxt) <div> <span>1</span> </div> - - If multiple `py:when` bodies match only the first is output. - >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#" - ... py:choose=""> - ... <span py:when="1 == 1">1</span> - ... <span py:when="2 == 2">2</span> - ... </div>''') - >>> print tmpl.generate(ctxt) - <div> - <span>1</span> - </div> - + If the `py:choose` directive contains an expression, the nested `py:when` - directives are tested for equality to the `py:choose` expression. + directives are tested for equality to the `py:choose` expression: + >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#" ... py:choose="2"> ... <span py:when="1">1</span> @@ -566,46 +557,19 @@ <div> <span>2</span> </div> - - If no `py:when` directive is matched then the fallback directive - `py:otherwise` will be used. - >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#" - ... py:choose=""> - ... <span py:when="False">hidden</span> - ... <span py:otherwise="">hello</span> - ... </div>''') - >>> print tmpl.generate(ctxt) - <div> - <span>hello</span> - </div> - - `py:choose` blocks can be nested: - >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#" - ... py:choose="1"> - ... <div py:when="1" py:choose="3"> - ... <span py:when="2">2</span> - ... <span py:when="3">3</span> - ... </div> - ... </div>''') - >>> print tmpl.generate(ctxt) - <div> - <div> - <span>3</span> - </div> - </div> - + Behavior is undefined if a `py:choose` block contains content outside a `py:when` or `py:otherwise` block. Behavior is also undefined if a `py:otherwise` occurs before `py:when` blocks. """ __slots__ = ['matched', 'value'] - def __call__(self, stream, ctxt, directives=None): + def __call__(self, stream, ctxt, directives): if self.expr: self.value = self.expr.evaluate(ctxt) self.matched = False ctxt.push(_choose=self) - for event in stream: + for event in self._apply_directives(stream, ctxt, directives): yield event ctxt.pop() @@ -624,11 +588,11 @@ try: if value == choose.value: choose.matched = True - return stream + return self._apply_directives(stream, ctxt, directives) except AttributeError: if value: choose.matched = True - return stream + return self._apply_directives(stream, ctxt, directives) return [] @@ -643,7 +607,7 @@ if choose.matched: return [] choose.matched = True - return stream + return self._apply_directives(stream, ctxt, directives) class Template(object): @@ -659,9 +623,9 @@ ('match', MatchDirective), ('for', ForDirective), ('if', IfDirective), - ('choose', ChooseDirective), ('when', WhenDirective), ('otherwise', OtherwiseDirective), + ('choose', ChooseDirective), ('replace', ReplaceDirective), ('content', ContentDirective), ('attrs', AttrsDirective),
--- a/markup/tests/template.py +++ b/markup/tests/template.py @@ -36,13 +36,75 @@ </doc>""", str(tmpl.generate(Context(items=items)))) +class ChooseDirectiveTestCase(unittest.TestCase): + """Tests for the `py:choose` template directive and the complementary + directives `py:when` and `py:otherwise`.""" + + def test_multiple_true_whens(self): + """ + Verify that, if multiple `py:when` bodies match, only the first is + output. + """ + tmpl = Template("""<div xmlns:py="http://purl.org/kid/ns#" py:choose=""> + <span py:when="1 == 1">1</span> + <span py:when="2 == 2">2</span> + <span py:when="3 == 3">3</span> + </div>""") + self.assertEqual("""<div> + <span>1</span> + </div>""", str(tmpl.generate())) + + def test_otherwise(self): + tmpl = Template("""<div xmlns:py="http://purl.org/kid/ns#" py:choose=""> + <span py:when="False">hidden</span> + <span py:otherwise="">hello</span> + </div>""") + self.assertEqual("""<div> + <span>hello</span> + </div>""", str(tmpl.generate())) + + def test_nesting(self): + """ + Verify that `py:choose` blocks can be nested: + """ + tmpl = Template("""<doc xmlns:py="http://purl.org/kid/ns#"> + <div py:choose="1"> + <div py:when="1" py:choose="3"> + <span py:when="2">2</span> + <span py:when="3">3</span> + </div> + </div> + </doc>""") + self.assertEqual("""<doc> + <div> + <div> + <span>3</span> + </div> + </div> + </doc>""", str(tmpl.generate())) + + def test_when_with_strip(self): + """ + Verify that a when directive with a strip directive actually strips of + the outer element. + """ + tmpl = Template("""<doc xmlns:py="http://purl.org/kid/ns#"> + <div py:choose="" py:strip=""> + <span py:otherwise="">foo</span> + </div> + </doc>""") + self.assertEqual("""<doc> + <span>foo</span> + </doc>""", str(tmpl.generate())) + + class DefDirectiveTestCase(unittest.TestCase): """Tests for the `py:def` template directive.""" def test_function_with_strip(self): """ - Verify that the a named template function with a strip directive - actually strips of the outer element. + Verify that a named template function with a strip directive actually + strips of the outer element. """ tmpl = Template("""<doc xmlns:py="http://purl.org/kid/ns#"> <div py:def="echo(what)" py:strip=""> @@ -56,12 +118,12 @@ class ForDirectiveTestCase(unittest.TestCase): - """Tests for the `py:def` template directive.""" + """Tests for the `py:for` template directive.""" def test_loop_with_strip(self): """ - Verify that the a named template function with a strip directive - actually strips of the outer element. + Verify that the combining the `py:for` directive with `py:strip` works + correctly. """ tmpl = Template("""<doc xmlns:py="http://purl.org/kid/ns#"> <div py:for="item in items" py:strip=""> @@ -283,6 +345,7 @@ suite.addTest(doctest.DocTestSuite(Template.__module__)) suite.addTest(unittest.makeSuite(TemplateTestCase, 'test')) suite.addTest(unittest.makeSuite(AttrsDirectiveTestCase, 'test')) + suite.addTest(unittest.makeSuite(ChooseDirectiveTestCase, 'test')) suite.addTest(unittest.makeSuite(DefDirectiveTestCase, 'test')) suite.addTest(unittest.makeSuite(ForDirectiveTestCase, 'test')) suite.addTest(unittest.makeSuite(MatchDirectiveTestCase, 'test'))