changeset 50:a053ffb834cb

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 9b5255d72e33
children a572b1018b66
files markup/template.py markup/tests/template.py
diffstat 2 files changed, 123 insertions(+), 41 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):
+    def __call__(self, stream, ctxt, directives=None):
         raise NotImplementedError
 
     def __repr__(self):
@@ -210,7 +210,9 @@
       <li>Bar</li>
     </ul>
     """
-    def __call__(self, stream, ctxt):
+    __slots__ = []
+
+    def __call__(self, stream, ctxt, directives=None):
         kind, (tag, attrib), pos  = stream.next()
         attrs = self.expr.evaluate(ctxt)
         if attrs:
@@ -242,16 +244,23 @@
       <li>Bye</li>
     </ul>
     """
-    def __call__(self, stream, ctxt):
-        kind, data, pos = stream.next()
-        if kind is Stream.START:
-            yield kind, data, pos # emit start tag
-        yield Template.EXPR, self.expr, pos
-        previous = stream.next()
-        for event in stream:
-            previous = event
-        if previous is not None:
-            yield previous
+    __slots__ = []
+
+    def __call__(self, stream, ctxt, directives):
+        def generate():
+            kind, data, pos = stream.next()
+            if kind is Stream.START:
+                yield kind, data, pos # emit start tag
+            yield Template.EXPR, self.expr, pos
+            previous = stream.next()
+            for event in stream:
+                previous = event
+            if previous is not None:
+                yield previous
+        output = generate()
+        if directives:
+            output = directives[0](output, ctxt, directives[1:])
+        return output
 
 
 class DefDirective(Directive):
@@ -293,7 +302,7 @@
       </p>
     </div>
     """
-    __slots__ = ['name', 'args', 'defaults', 'stream']
+    __slots__ = ['name', 'args', 'defaults', 'stream', 'directives']
 
     def __init__(self, args):
         Directive.__init__(self, None)
@@ -310,10 +319,11 @@
                     self.args.append(arg.name)
         else:
             self.name = ast.name
-        self.stream = []
+        self.stream, self.directives = [], []
 
-    def __call__(self, stream, ctxt):
+    def __call__(self, stream, ctxt, directives):
         self.stream = list(stream)
+        self.directives = directives
         ctxt[self.name] = lambda *args, **kwargs: self._exec(ctxt, *args,
                                                              **kwargs)
         return []
@@ -327,7 +337,10 @@
             else:
                 scope[name] = kwargs.pop(name, self.defaults.get(name))
         ctxt.push(**scope)
-        for event in self.stream:
+        stream = iter(self.stream)
+        if self.directives:
+            stream = self.directives[0](stream, ctxt, self.directives[1:])
+        for event in stream:
             yield event
         ctxt.pop()
 
@@ -352,7 +365,7 @@
         self.targets = [str(name.strip()) for name in targets.split(',')]
         Directive.__init__(self, value)
 
-    def __call__(self, stream, ctxt):
+    def __call__(self, stream, ctxt, directives):
         iterable = self.expr.evaluate(ctxt) or []
         if iterable is not None:
             stream = list(stream)
@@ -363,6 +376,9 @@
                 for idx, name in enumerate(self.targets):
                     scope[name] = item[idx]
                 ctxt.push(**scope)
+                if directives:
+                    stream = list(directives[0](iter(stream), ctxt,
+                                  directives[1:]))
                 for event in stream:
                     yield event
                 ctxt.pop()
@@ -385,8 +401,12 @@
       <b>Hello</b>
     </div>
     """
-    def __call__(self, stream, ctxt):
+    __slots__ = []
+
+    def __call__(self, stream, ctxt, directives):
         if self.expr.evaluate(ctxt):
+            if directives:
+                stream = directives[0](stream, ctxt, directives[1:])
             return stream
         return []
 
@@ -414,10 +434,10 @@
         self.path = Path(value)
         self.stream = []
 
-    def __call__(self, stream, ctxt):
+    def __call__(self, stream, ctxt, directives):
         self.stream = list(stream)
         ctxt._match_templates.append((self.path.test(ignore_context=True),
-                                      self.path, self.stream))
+                                      self.path, self.stream, directives))
         return []
 
     def __repr__(self):
@@ -451,7 +471,9 @@
       Bye
     </div>
     """
-    def __call__(self, stream, ctxt):
+    __slots__ = []
+
+    def __call__(self, stream, ctxt, directives=None):
         kind, data, pos = stream.next()
         yield Template.EXPR, self.expr, pos
 
@@ -486,7 +508,9 @@
         <b>foo</b>
     </div>
     """
-    def __call__(self, stream, ctxt):
+    __slots__ = []
+
+    def __call__(self, stream, ctxt, directives=None):
         if self.expr:
             strip = self.expr.evaluate(ctxt)
         else:
@@ -574,12 +598,13 @@
     `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):
+    def __call__(self, stream, ctxt, directives=None):
         if self.expr:
             self.value = self.expr.evaluate(ctxt)
         self.matched = False
-        ctxt.push(__choose=self)
+        ctxt.push(_choose=self)
         for event in stream:
             yield event
         ctxt.pop()
@@ -587,11 +612,12 @@
 
 class WhenDirective(Directive):
     """Implementation of the `py:when` directive for nesting in a parent with
-    the `py:choose` directive.  See the documentation of `py:choose` for
-    usage.
+    the `py:choose` directive.
+    
+    See the documentation of `py:choose` for usage.
     """
-    def __call__(self, stream, ctxt):
-        choose = ctxt['__choose']
+    def __call__(self, stream, ctxt, directives=None):
+        choose = ctxt['_choose']
         if choose.matched:
             return []
         value = self.expr.evaluate(ctxt)
@@ -608,11 +634,12 @@
 
 class OtherwiseDirective(Directive):
     """Implementation of the `py:otherwise` directive for nesting in a parent
-    with the `py:choose` directive.  See the documentation of `py:choose` for
-    usage.
+    with the `py:choose` directive.
+    
+    See the documentation of `py:choose` for usage.
     """
-    def __call__(self, stream, ctxt):
-        choose = ctxt['__choose']
+    def __call__(self, stream, ctxt, directives=None):
+        choose = ctxt['_choose']
         if choose.matched:
             return []
         choose.matched = True
@@ -632,13 +659,13 @@
                   ('match', MatchDirective),
                   ('for', ForDirective),
                   ('if', IfDirective),
+                  ('choose', ChooseDirective),
+                  ('when', WhenDirective),
+                  ('otherwise', OtherwiseDirective),
                   ('replace', ReplaceDirective),
                   ('content', ContentDirective),
                   ('attrs', AttrsDirective),
-                  ('strip', StripDirective),
-                  ('choose', ChooseDirective),
-                  ('when', WhenDirective),
-                  ('otherwise', OtherwiseDirective)]
+                  ('strip', StripDirective)]
     _dir_by_name = dict(directives)
     _dir_order = [directive[1] for directive in directives]
 
@@ -707,8 +734,8 @@
                         value = list(self._interpolate(value, *pos))
                         new_attrib.append((name, value))
                 if directives:
-                    directives.sort(lambda a, b: cmp(self._dir_order.index(b.__class__),
-                                                     self._dir_order.index(a.__class__)))
+                    directives.sort(lambda a, b: cmp(self._dir_order.index(a.__class__),
+                                                     self._dir_order.index(b.__class__)))
                     dirmap[(depth, tag)] = (directives, len(stream))
 
                 stream.append((kind, (tag, Attributes(new_attrib)), pos))
@@ -842,8 +869,7 @@
                     # This event is a list of directives and a list of nested
                     # events to which those directives should be applied
                     directives, substream = data
-                    for directive in directives:
-                        substream = directive(iter(substream), ctxt)
+                    substream = directives[0](iter(substream), ctxt, directives[1:])
                     substream = self._match(self._eval(substream, ctxt), ctxt)
                     for event in self._flatten(substream, ctxt):
                         yield event
@@ -869,7 +895,8 @@
                 yield kind, data, pos
                 continue
 
-            for idx, (test, path, template) in enumerate(match_templates):
+            for idx, (test, path, template, directives) in \
+                    enumerate(match_templates):
                 result = test(kind, data, pos)
 
                 if result:
@@ -891,6 +918,9 @@
                     content = list(self._flatten(content, ctxt))
                     ctxt.push(select=lambda path: Stream(content).select(path))
 
+                    if directives:
+                        template = directives[0](iter(template), ctxt,
+                                                 directives[1:])
                     template = self._match(self._eval(iter(template), ctxt),
                                            ctxt, match_templates[:idx] +
                                            match_templates[idx + 1:])
--- a/markup/tests/template.py
+++ b/markup/tests/template.py
@@ -20,9 +20,59 @@
                             TemplateSyntaxError
 
 
+class AttrsDirectiveTestCase(unittest.TestCase):
+    """Tests for the `py:attrs` template directive."""
+
+    def test_combined_with_loop(self):
+        """
+        Verify that the directive has access to the loop variables.
+        """
+        tmpl = Template("""<doc xmlns:py="http://purl.org/kid/ns#">
+          <elem py:for="item in items" py:attrs="item"/>
+        </doc>""")
+        items = [{'id': 1, 'class': 'foo'}, {'id': 2, 'class': 'bar'}]
+        self.assertEqual("""<doc>
+          <elem id="1" class="foo"/><elem id="2" class="bar"/>
+        </doc>""", str(tmpl.generate(Context(items=items))))
+
+
+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.
+        """
+        tmpl = Template("""<doc xmlns:py="http://purl.org/kid/ns#">
+          <div py:def="echo(what)" py:strip="">
+            <b>${what}</b>
+          </div>
+          ${echo('foo')}
+        </doc>""")
+        self.assertEqual("""<doc>
+            <b>foo</b>
+        </doc>""", str(tmpl.generate()))
+
+
 class MatchDirectiveTestCase(unittest.TestCase):
     """Tests for the `py:match` template directive."""
 
+    def test_with_strip(self):
+        """
+        Verify that a match template can produce the same kind of element that
+        it matched without entering an infinite recursion.
+        """
+        tmpl = Template("""<doc xmlns:py="http://purl.org/kid/ns#">
+          <elem py:match="elem" py:strip="">
+            <div class="elem">${select('*/text()')}</div>
+          </elem>
+          <elem>Hey Joe</elem>
+        </doc>""")
+        self.assertEqual("""<doc>
+            <div class="elem">Hey Joe</div>
+        </doc>""", str(tmpl.generate()))
+
     def test_without_strip(self):
         """
         Verify that a match template can produce the same kind of element that
@@ -210,6 +260,8 @@
     suite = unittest.TestSuite()
     suite.addTest(doctest.DocTestSuite(Template.__module__))
     suite.addTest(unittest.makeSuite(TemplateTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(AttrsDirectiveTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(DefDirectiveTestCase, 'test'))
     suite.addTest(unittest.makeSuite(MatchDirectiveTestCase, 'test'))
     suite.addTest(unittest.makeSuite(StripDirectiveTestCase, 'test'))
     return suite
Copyright (C) 2012-2017 Edgewall Software