# 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("""
+ bar +
""") + 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