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'))
Copyright (C) 2012-2017 Edgewall Software