# HG changeset patch # User cmlenz # Date 1178920489 0 # Node ID b373f80f7763c2465dfc510ce592af707aacbdbb # Parent 4ed941aa0cbf34b812ea3bf30183e4dfd8e4b7f4 Added include directive for text templates (#115). Thanks to Alastair for the original patch. diff --git a/ChangeLog b/ChangeLog --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +Version 0.5 +http://svn.edgewall.org/repos/genshi/tags/0.5.0/ +(?, from branches/stable/0.5.x) + + * Added #include directive for text templates (ticket #115). + + Version 0.4.1 http://svn.edgewall.org/repos/genshi/tags/0.4.1/ (?, from branches/stable/0.4.x) diff --git a/genshi/template/base.py b/genshi/template/base.py --- a/genshi/template/base.py +++ b/genshi/template/base.py @@ -279,6 +279,9 @@ EXPR = StreamEventKind('EXPR') """Stream event kind representing a Python expression.""" + INCLUDE = StreamEventKind('INCLUDE') + """Stream event kind representing the inclusion of another template.""" + SUB = StreamEventKind('SUB') """Stream event kind representing a nested stream to which one or more directives should be applied. @@ -320,6 +323,8 @@ except ParseError, e: raise TemplateSyntaxError(e.msg, self.filepath, e.lineno, e.offset) self.filters = [self._flatten, self._eval] + if loader: + self.filters.append(self._include) def __repr__(self): return '<%s "%s">' % (self.__class__.__name__, self.filename) @@ -359,6 +364,8 @@ for event in substream: yield event else: + if kind is INCLUDE: + data = data[0], list(self._prepare(data[1])) yield kind, data, pos def generate(self, *args, **kwargs): @@ -449,6 +456,37 @@ else: yield event + def _include(self, stream, ctxt): + """Internal stream filter that performs inclusion of external + template files. + """ + from genshi.template.loader import TemplateNotFound + + for event in stream: + if event[0] is INCLUDE: + href, fallback = event[1] + if not isinstance(href, basestring): + parts = [] + for subkind, subdata, subpos in self._eval(href, ctxt): + if subkind is TEXT: + parts.append(subdata) + href = u''.join([x for x in parts if x is not None]) + try: + tmpl = self.loader.load(href, relative_to=event[2][0], + cls=self.__class__) + for event in tmpl.generate(ctxt): + yield event + except TemplateNotFound: + if fallback is None: + raise + for filter_ in self.filters: + fallback = filter_(iter(fallback), ctxt) + for event in fallback: + yield event + else: + yield event + EXPR = Template.EXPR +INCLUDE = Template.INCLUDE SUB = Template.SUB diff --git a/genshi/template/markup.py b/genshi/template/markup.py --- a/genshi/template/markup.py +++ b/genshi/template/markup.py @@ -21,10 +21,10 @@ from genshi.core import START, END, START_NS, END_NS, TEXT, PI, COMMENT from genshi.input import XMLParser from genshi.template.base import BadDirectiveError, Template, \ - TemplateSyntaxError, _apply_directives, SUB + TemplateSyntaxError, _apply_directives, \ + INCLUDE, SUB from genshi.template.eval import Suite from genshi.template.interpolation import interpolate -from genshi.template.loader import TemplateNotFound from genshi.template.directives import * if sys.version_info < (2, 4): @@ -50,9 +50,6 @@ EXEC = StreamEventKind('EXEC') """Stream event kind representing a Python code suite to execute.""" - INCLUDE = StreamEventKind('INCLUDE') - """Stream event kind representing the inclusion of another template.""" - DIRECTIVE_NAMESPACE = Namespace('http://genshi.edgewall.org/') XINCLUDE_NAMESPACE = Namespace('http://www.w3.org/2001/XInclude') @@ -73,10 +70,7 @@ encoding=None, lookup='lenient'): Template.__init__(self, source, basedir=basedir, filename=filename, loader=loader, encoding=encoding, lookup=lookup) - self.filters += [self._exec, self._match] - if loader: - self.filters.append(self._include) def _parse(self, source, encoding): streams = [[]] # stacked lists of events of the "compiled" template @@ -223,12 +217,6 @@ assert len(streams) == 1 return streams[0] - def _prepare(self, stream): - for kind, data, pos in Template._prepare(self, stream): - if kind is INCLUDE: - data = data[0], list(self._prepare(data[1])) - yield kind, data, pos - def _exec(self, stream, ctxt): """Internal stream filter that executes code in ```` processing instructions. @@ -239,33 +227,6 @@ else: yield event - def _include(self, stream, ctxt): - """Internal stream filter that performs inclusion of external - template files. - """ - for event in stream: - if event[0] is INCLUDE: - href, fallback = event[1] - if not isinstance(href, basestring): - parts = [] - for subkind, subdata, subpos in self._eval(href, ctxt): - if subkind is TEXT: - parts.append(subdata) - href = u''.join([x for x in parts if x is not None]) - try: - tmpl = self.loader.load(href, relative_to=event[2][0]) - for event in tmpl.generate(ctxt): - yield event - except TemplateNotFound: - if fallback is None: - raise - for filter_ in self.filters: - fallback = filter_(iter(fallback), ctxt) - for event in fallback: - yield event - else: - yield event - def _match(self, stream, ctxt, match_templates=None): """Internal stream filter that applies any defined match templates to the stream. @@ -341,4 +302,3 @@ EXEC = MarkupTemplate.EXEC -INCLUDE = MarkupTemplate.INCLUDE diff --git a/genshi/template/tests/text.py b/genshi/template/tests/text.py --- a/genshi/template/tests/text.py +++ b/genshi/template/tests/text.py @@ -12,14 +12,24 @@ # history and logs, available at http://genshi.edgewall.org/log/. import doctest +import os +import shutil +import tempfile import unittest +from genshi.template.loader import TemplateLoader from genshi.template.text import TextTemplate class TextTemplateTestCase(unittest.TestCase): """Tests for text template processing.""" + def setUp(self): + self.dirname = tempfile.mkdtemp(suffix='markup_test') + + def tearDown(self): + shutil.rmtree(self.dirname) + def test_escaping(self): tmpl = TextTemplate('\\#escaped') self.assertEqual('#escaped', str(tmpl.generate())) @@ -74,7 +84,28 @@ """, tmpl.generate(items=range(3)).render('text')) + def test_include(self): + file1 = open(os.path.join(self.dirname, 'tmpl1.txt'), 'w') + try: + file1.write("Included\n") + finally: + file1.close() + file2 = open(os.path.join(self.dirname, 'tmpl2.txt'), 'w') + try: + file2.write("""----- Included data below this line ----- + #include tmpl1.txt + ----- Included data above this line -----""") + finally: + file2.close() + + loader = TemplateLoader([self.dirname]) + tmpl = loader.load('tmpl2.txt', cls=TextTemplate) + self.assertEqual("""----- Included data below this line ----- +Included + ----- Included data above this line -----""", + tmpl.generate().render()) + def suite(): suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite(TextTemplate.__module__)) diff --git a/genshi/template/text.py b/genshi/template/text.py --- a/genshi/template/text.py +++ b/genshi/template/text.py @@ -15,8 +15,9 @@ import re -from genshi.template.base import BadDirectiveError, Template, SUB +from genshi.template.base import BadDirectiveError, Template, INCLUDE, SUB from genshi.template.directives import * +from genshi.template.directives import Directive, _apply_directives from genshi.template.interpolation import interpolate __all__ = ['TextTemplate'] @@ -95,6 +96,9 @@ substream = stream[start_offset:] stream[start_offset:] = [(SUB, ([directive], substream), (self.filepath, lineno, 0))] + elif command == 'include': + pos = (self.filename, lineno, 0) + stream.append((INCLUDE, (value.strip(), []), pos)) elif command != '#': cls = self._dir_by_name.get(command) if cls is None: