Mercurial > genshi > mirror
changeset 50:d3842cd76e92 trunk
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 | 6d1f79b2f7ef |
children | b2383634ec04 |
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