changeset 65:b3fdf93057ab trunk

Support the use of directives as elements to reduce the need for using `py:strip`.
author cmlenz
date Sun, 09 Jul 2006 15:23:26 +0000
parents 1db4839e6197
children 59eb24184e9c
files README.txt markup/builder.py markup/template.py markup/tests/template.py
diffstat 4 files changed, 149 insertions(+), 25 deletions(-) [+]
line wrap: on
line diff
--- a/README.txt
+++ b/README.txt
@@ -8,4 +8,4 @@
 
 For more information visit the Markup web site:
 
-  <http://markup.cmlenz.net/>
+  <http://markup.edgewall.org/>
--- a/markup/builder.py
+++ b/markup/builder.py
@@ -25,6 +25,26 @@
     def __init__(self):
         self.children = []
 
+    def __add__(self, other):
+        return Fragment()(self, other)
+
+    def __call__(self, *args):
+        for arg in args:
+            self.append(arg)
+        return self
+
+    def __iter__(self):
+        return iter(self.generate())
+
+    def __repr__(self):
+        return '<%s>' % self.__class__.__name__
+
+    def __str__(self):
+        return str(self.generate())
+
+    def __unicode__(self):
+        return unicode(self.generate())
+
     def append(self, node):
         """Append an element or string as child node."""
         if isinstance(node, (Element, basestring, int, float, long)):
@@ -42,14 +62,6 @@
                 for child in node:
                     self.append(children)
 
-    def __add__(self, other):
-        return Fragment()(self, other)
-
-    def __call__(self, *args):
-        for arg in args:
-            self.append(arg)
-        return self
-
     def generate(self):
         """Return a markup event stream for the fragment."""
         def _generate():
@@ -61,15 +73,6 @@
                     yield Stream.TEXT, unicode(child), (-1, -1)
         return Stream(_generate())
 
-    def __iter__(self):
-        return iter(self.generate())
-
-    def __str__(self):
-        return str(self.generate())
-
-    def __unicode__(self):
-        return unicode(self.generate())
-
 
 class Element(Fragment):
     """Simple XML output generator based on the builder pattern.
@@ -166,6 +169,9 @@
             self.attrib.set(attr, value)
         return Fragment.__call__(self, *args)
 
+    def __repr__(self):
+        return '<%s "%s">' % (self.__class__.__name__, self.tag)
+
     def generate(self):
         """Return a markup event stream for the fragment."""
         def _generate():
--- a/markup/template.py
+++ b/markup/template.py
@@ -31,8 +31,6 @@
 
 Todo items:
  * Improved error reporting
- * Support for using directives as elements and not just as attributes, reducing
-   the need for wrapper elements with py:strip=""
  * Support for list comprehensions and generator expressions in expressions
 
 Random thoughts:
@@ -309,6 +307,8 @@
     """
     __slots__ = ['name', 'args', 'defaults', 'stream', 'directives']
 
+    ATTRIBUTE = 'function'
+
     def __init__(self, args):
         Directive.__init__(self, None)
         ast = compiler.parse(args, 'eval').node
@@ -363,6 +363,8 @@
     """
     __slots__ = ['targets']
 
+    ATTRIBUTE = 'each'
+
     def __init__(self, value):
         targets, value = value.split(' in ', 1)
         self.targets = [str(name.strip()) for name in targets.split(',')]
@@ -403,6 +405,8 @@
     """
     __slots__ = []
 
+    ATTRIBUTE = 'test'
+
     def __call__(self, stream, ctxt, directives):
         if self.expr.evaluate(ctxt):
             return self._apply_directives(stream, ctxt, directives)
@@ -427,6 +431,8 @@
     """
     __slots__ = ['path', 'stream']
 
+    ATTRIBUTE = 'path'
+
     def __init__(self, value):
         Directive.__init__(self, None)
         self.path = Path(value)
@@ -565,6 +571,8 @@
     """
     __slots__ = ['matched', 'value']
 
+    ATTRIBUTE = 'test'
+
     def __call__(self, stream, ctxt, directives):
         if self.expr:
             self.value = self.expr.evaluate(ctxt)
@@ -581,6 +589,9 @@
     
     See the documentation of `py:choose` for usage.
     """
+
+    ATTRIBUTE = 'test'
+
     def __call__(self, stream, ctxt, directives):
         choose = ctxt['_choose']
         if choose.matched:
@@ -687,21 +698,30 @@
                 # Record any directive attributes in start tags
                 tag, attrib = data
                 directives = []
+                strip = False
+
+                if tag in self.NAMESPACE:
+                    cls = self._dir_by_name.get(tag.localname)
+                    if cls is None:
+                        raise BadDirectiveError(tag, pos[0], pos[1])
+                    directives.append(cls(attrib.get(getattr(cls, 'ATTRIBUTE', None), '')))
+                    strip = True
+
                 new_attrib = []
                 for name, value in attrib:
                     if name in self.NAMESPACE:
                         cls = self._dir_by_name.get(name.localname)
                         if cls is None:
-                            raise BadDirectiveError(name, self.filename, pos[1])
-                        else:
-                            directives.append(cls(value))
+                            raise BadDirectiveError(name, pos[0], pos[1])
+                        directives.append(cls(value))
                     else:
                         value = list(self._interpolate(value, *pos))
                         new_attrib.append((name, value))
+
                 if directives:
                     directives.sort(lambda a, b: cmp(self._dir_order.index(a.__class__),
                                                      self._dir_order.index(b.__class__)))
-                    dirmap[(depth, tag)] = (directives, len(stream))
+                    dirmap[(depth, tag)] = (directives, len(stream), strip)
 
                 stream.append((kind, (tag, Attributes(new_attrib)), pos))
                 depth += 1
@@ -713,8 +733,10 @@
                 # If there have have directive attributes with the corresponding
                 # start tag, move the events inbetween into a "subprogram"
                 if (depth, data) in dirmap:
-                    directives, start_offset = dirmap.pop((depth, data))
+                    directives, start_offset, strip = dirmap.pop((depth, data))
                     substream = stream[start_offset:]
+                    if strip:
+                        substream = substream[1:-1]
                     stream[start_offset:] = [(Template.SUB,
                                               (directives, substream), pos)]
 
--- a/markup/tests/template.py
+++ b/markup/tests/template.py
@@ -121,6 +121,21 @@
             <span>foo</span>
         </doc>""", str(tmpl.generate()))
 
+    def test_as_element(self):
+        """
+        Verify that the directive can also be used as an element.
+        """
+        tmpl = Template("""<doc xmlns:py="http://markup.edgewall.org/">
+          <py:choose>
+            <py:when test="1 == 1">1</py:when>
+            <py:when test="2 == 2">2</py:when>
+            <py:when test="3 == 3">3</py:when>
+          </py:choose>
+        </doc>""")
+        self.assertEqual("""<doc>
+            1
+        </doc>""", str(tmpl.generate()))
+
 
 class DefDirectiveTestCase(unittest.TestCase):
     """Tests for the `py:def` template directive."""
@@ -140,6 +155,20 @@
             <b>foo</b>
         </doc>""", str(tmpl.generate()))
 
+    def test_as_element(self):
+        """
+        Verify that the directive can also be used as an element.
+        """
+        tmpl = Template("""<doc xmlns:py="http://markup.edgewall.org/">
+          <py:def function="echo(what)">
+            <b>${what}</b>
+          </py:def>
+          ${echo('foo')}
+        </doc>""")
+        self.assertEqual("""<doc>
+            <b>foo</b>
+        </doc>""", str(tmpl.generate()))
+
 
 class ForDirectiveTestCase(unittest.TestCase):
     """Tests for the `py:for` template directive."""
@@ -162,6 +191,50 @@
             <b>5</b>
         </doc>""", str(tmpl.generate(Context(items=range(1, 6)))))
 
+    def test_as_element(self):
+        """
+        Verify that the directive can also be used as an element.
+        """
+        tmpl = Template("""<doc xmlns:py="http://markup.edgewall.org/">
+          <py:for each="item in items">
+            <b>${item}</b>
+          </py:for>
+        </doc>""")
+        self.assertEqual("""<doc>
+            <b>1</b>
+            <b>2</b>
+            <b>3</b>
+            <b>4</b>
+            <b>5</b>
+        </doc>""", str(tmpl.generate(Context(items=range(1, 6)))))
+
+
+class IfDirectiveTestCase(unittest.TestCase):
+    """Tests for the `py:if` template directive."""
+
+    def test_loop_with_strip(self):
+        """
+        Verify that the combining the `py:if` directive with `py:strip` works
+        correctly.
+        """
+        tmpl = Template("""<doc xmlns:py="http://markup.edgewall.org/">
+          <b py:if="foo" py:strip="">${bar}</b>
+        </doc>""")
+        self.assertEqual("""<doc>
+          Hello
+        </doc>""", str(tmpl.generate(Context(foo=True, bar='Hello'))))
+
+    def test_as_element(self):
+        """
+        Verify that the directive can also be used as an element.
+        """
+        tmpl = Template("""<doc xmlns:py="http://markup.edgewall.org/">
+          <py:if test="foo">${bar}</py:if>
+        </doc>""")
+        self.assertEqual("""<doc>
+          Hello
+        </doc>""", str(tmpl.generate(Context(foo=True, bar='Hello'))))
+
 
 class MatchDirectiveTestCase(unittest.TestCase):
     """Tests for the `py:match` template directive."""
@@ -198,6 +271,20 @@
           </elem>
         </doc>""", str(tmpl.generate()))
 
+    def test_as_element(self):
+        """
+        Verify that the directive can also be used as an element.
+        """
+        tmpl = Template("""<doc xmlns:py="http://markup.edgewall.org/">
+          <py:match path="elem">
+            <div class="elem">${select('*/text()')}</div>
+          </py:match>
+          <elem>Hey Joe</elem>
+        </doc>""")
+        self.assertEqual("""<doc>
+            <div class="elem">Hey Joe</div>
+        </doc>""", str(tmpl.generate()))
+
     def test_recursive_match_1(self):
         """
         Match directives are applied recursively, meaning that they are also
@@ -397,6 +484,14 @@
           <elem class="&#34;foo&#34;"/>
         </div>""", str(tmpl.generate(Context(myvar='"foo"'))))
 
+    def test_directive_element(self):
+        tmpl = Template("""<div xmlns:py="http://markup.edgewall.org/">
+          <py:if test="myvar">bar</py:if>
+        </div>""")
+        self.assertEqual("""<div>
+          bar
+        </div>""", str(tmpl.generate(Context(myvar='"foo"'))))
+
 
 def suite():
     suite = unittest.TestSuite()
@@ -406,6 +501,7 @@
     suite.addTest(unittest.makeSuite(ChooseDirectiveTestCase, 'test'))
     suite.addTest(unittest.makeSuite(DefDirectiveTestCase, 'test'))
     suite.addTest(unittest.makeSuite(ForDirectiveTestCase, 'test'))
+    suite.addTest(unittest.makeSuite(IfDirectiveTestCase, 'test'))
     suite.addTest(unittest.makeSuite(MatchDirectiveTestCase, 'test'))
     suite.addTest(unittest.makeSuite(StripDirectiveTestCase, 'test'))
     return suite
Copyright (C) 2012-2017 Edgewall Software