# HG changeset patch
# User cmlenz
# Date 1152458606 0
# Node ID b3fdf93057abd41db5b09a9577731b407a108088
# Parent 1db4839e61977594da4f72930155ccb9a17e70ca
Support the use of directives as elements to reduce the need for using `py:strip`.
diff --git a/README.txt b/README.txt
--- a/README.txt
+++ b/README.txt
@@ -8,4 +8,4 @@
For more information visit the Markup web site:
-
+
diff --git a/markup/builder.py b/markup/builder.py
--- 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():
diff --git a/markup/template.py b/markup/template.py
--- 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)]
diff --git a/markup/tests/template.py b/markup/tests/template.py
--- a/markup/tests/template.py
+++ b/markup/tests/template.py
@@ -121,6 +121,21 @@
foo
""", str(tmpl.generate()))
+ def test_as_element(self):
+ """
+ Verify that the directive can also be used as an element.
+ """
+ tmpl = Template("""
+
+ 1
+ 2
+ 3
+
+ """)
+ self.assertEqual("""
+ 1
+ """, str(tmpl.generate()))
+
class DefDirectiveTestCase(unittest.TestCase):
"""Tests for the `py:def` template directive."""
@@ -140,6 +155,20 @@
foo
""", str(tmpl.generate()))
+ def test_as_element(self):
+ """
+ Verify that the directive can also be used as an element.
+ """
+ tmpl = Template("""
+
+ ${what}
+
+ ${echo('foo')}
+ """)
+ self.assertEqual("""
+ foo
+ """, str(tmpl.generate()))
+
class ForDirectiveTestCase(unittest.TestCase):
"""Tests for the `py:for` template directive."""
@@ -162,6 +191,50 @@
5
""", 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("""
+
+ ${item}
+
+ """)
+ self.assertEqual("""
+ 1
+ 2
+ 3
+ 4
+ 5
+ """, 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("""
+ ${bar}
+ """)
+ self.assertEqual("""
+ Hello
+ """, 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("""
+ ${bar}
+ """)
+ self.assertEqual("""
+ Hello
+ """, str(tmpl.generate(Context(foo=True, bar='Hello'))))
+
class MatchDirectiveTestCase(unittest.TestCase):
"""Tests for the `py:match` template directive."""
@@ -198,6 +271,20 @@
""", str(tmpl.generate()))
+ def test_as_element(self):
+ """
+ Verify that the directive can also be used as an element.
+ """
+ tmpl = Template("""
+
+ ${select('*/text()')}
+
+ Hey Joe
+ """)
+ self.assertEqual("""
+ Hey Joe
+ """, str(tmpl.generate()))
+
def test_recursive_match_1(self):
"""
Match directives are applied recursively, meaning that they are also
@@ -397,6 +484,14 @@
""", str(tmpl.generate(Context(myvar='"foo"'))))
+ def test_directive_element(self):
+ tmpl = Template("""
""")
+ self.assertEqual("""
+ bar
+
""", 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