# HG changeset patch # User cmlenz # Date 1172067442 0 # Node ID f37d8e6acdf2bd7957139c1783021014b496f75e # Parent 01be13831f5e571935f7348c51abf1fa52fb5ef0 Move string interpolation code into separate module (`genshi.template.interpolation`). diff --git a/genshi/template/base.py b/genshi/template/base.py --- a/genshi/template/base.py +++ b/genshi/template/base.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2006 Edgewall Software +# Copyright (C) 2006-2007 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which @@ -17,14 +17,10 @@ class deque(list): def appendleft(self, x): self.insert(0, x) def popleft(self): return self.pop(0) -from itertools import chain import os -import re from StringIO import StringIO -from tokenize import tokenprog from genshi.core import Attrs, Stream, StreamEventKind, START, TEXT, _ensure -from genshi.template.eval import Expression __all__ = ['Context', 'Template', 'TemplateError', 'TemplateRuntimeError', 'TemplateSyntaxError', 'BadDirectiveError'] @@ -40,7 +36,8 @@ def __init__(self, message, filename='', lineno=-1, offset=-1): self.msg = message - message = '%s (%s, line %d)' % (self.msg, filename, lineno) + if filename != '' or lineno >= 0: + message = '%s (%s, line %d)' % (self.msg, filename, lineno) TemplateError.__init__(self, message) self.filename = filename self.lineno = lineno @@ -229,111 +226,6 @@ """ raise NotImplementedError - def _interpolate(cls, text, basedir=None, filename=None, lineno=-1, - offset=0): - """Parse the given string and extract expressions. - - This method returns a list containing both literal text and `Expression` - objects. - - @param text: the text to parse - @param lineno: the line number at which the text was found (optional) - @param offset: the column number at which the text starts in the source - (optional) - """ - filepath = filename - if filepath and basedir: - filepath = os.path.join(basedir, filepath) - - namestart = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_' - namechars = namestart + '.0123456789' - - def _split(): - pos = 0 - end = len(text) - escaped = False - - while 1: - if escaped: - offset = text.find('$', offset + 2) - escaped = False - else: - offset = text.find('$', pos) - if offset < 0 or offset == end - 1: - break - next = text[offset + 1] - - if next == '{': - if offset > pos: - yield False, text[pos:offset] - pos = offset + 2 - level = 1 - while level: - match = tokenprog.match(text, pos) - if match is None: - raise TemplateSyntaxError('invalid syntax', - filename, lineno, offset) - pos = match.end() - tstart, tend = match.regs[3] - token = text[tstart:tend] - if token == '{': - level += 1 - elif token == '}': - level -= 1 - yield True, text[offset + 2:pos - 1] - - elif next in namestart: - if offset > pos: - yield False, text[pos:offset] - pos = offset - pos += 1 - while pos < end: - char = text[pos] - if char not in namechars: - break - pos += 1 - yield True, text[offset + 1:pos].strip() - - elif not escaped and next == '$': - escaped = True - pos = offset + 1 - - else: - yield False, text[pos:offset + 1] - pos = offset + 1 - - if pos < end: - yield False, text[pos:] - - textbuf = [] - textpos = None - for is_expr, chunk in chain(_split(), [(True, '')]): - if is_expr: - if textbuf: - yield TEXT, u''.join(textbuf), textpos - del textbuf[:] - textpos = None - if chunk: - try: - expr = Expression(chunk.strip(), filename, lineno) - yield EXPR, expr, (filename, lineno, offset) - except SyntaxError, err: - raise TemplateSyntaxError(err, filename, lineno, - offset + (err.offset or 0)) - else: - textbuf.append(chunk) - if textpos is None: - textpos = (filename, lineno, offset) - - if '\n' in chunk: - lines = chunk.splitlines() - lineno += len(lines) - 1 - offset += len(lines[-1]) - else: - offset += len(chunk) - - _interpolate = classmethod(_interpolate) - def _prepare(self, stream): """Call the `attach` method of every directive found in the template.""" for kind, data, pos in stream: diff --git a/genshi/template/interpolation.py b/genshi/template/interpolation.py new file mode 100644 --- /dev/null +++ b/genshi/template/interpolation.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://genshi.edgewall.org/wiki/License. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://genshi.edgewall.org/log/. + +"""String interpolation routines, i.e. the splitting up a given text into some +parts that are literal strings, and others that are Python expressions. +""" + +from itertools import chain +import os +from tokenize import tokenprog + +from genshi.core import TEXT +from genshi.template.base import TemplateSyntaxError, EXPR +from genshi.template.eval import Expression + +__all__ = ['interpolate'] + +NAMESTART = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_' +NAMECHARS = NAMESTART + '.0123456789' +PREFIX = '$' + +def interpolate(text, basedir=None, filename=None, lineno=-1, offset=0): + """Parse the given string and extract expressions. + + This method returns a list containing both literal text and `Expression` + objects. + + >>> for kind, data, pos in interpolate("$foo bar"): + ... print kind, `data` + EXPR Expression('foo') + TEXT u' bar' + + @param text: the text to parse + @param basedir: base directory of the file in which the text was found + (optional) + @param filename: basename of the file in which the text was found (optional) + @param lineno: the line number at which the text was found (optional) + @param offset: the column number at which the text starts in the source + (optional) + """ + filepath = filename + if filepath and basedir: + filepath = os.path.join(basedir, filepath) + pos = [filepath, lineno, offset] + + textbuf = [] + textpos = None + for is_expr, chunk in chain(lex(text, pos), [(True, '')]): + if is_expr: + if textbuf: + yield TEXT, u''.join(textbuf), textpos + del textbuf[:] + textpos = None + if chunk: + try: + expr = Expression(chunk.strip(), pos[0], pos[1]) + yield EXPR, expr, tuple(pos) + except SyntaxError, err: + raise TemplateSyntaxError(err, pos[0], pos[1], + pos[2] + (err.offset or 0)) + else: + textbuf.append(chunk) + if textpos is None: + textpos = tuple(pos) + + if '\n' in chunk: + lines = chunk.splitlines() + pos[1] += len(lines) - 1 + pos[2] += len(lines[-1]) + else: + pos[2] += len(chunk) + +def lex(text, textpos): + offset = pos = 0 + end = len(text) + escaped = False + + while 1: + if escaped: + offset = text.find(PREFIX, offset + 2) + escaped = False + else: + offset = text.find(PREFIX, pos) + if offset < 0 or offset == end - 1: + break + next = text[offset + 1] + + if next == '{': + if offset > pos: + yield False, text[pos:offset] + pos = offset + 2 + level = 1 + while level: + match = tokenprog.match(text, pos) + if match is None: + raise TemplateSyntaxError('invalid syntax', *textpos) + pos = match.end() + tstart, tend = match.regs[3] + token = text[tstart:tend] + if token == '{': + level += 1 + elif token == '}': + level -= 1 + yield True, text[offset + 2:pos - 1] + + elif next in NAMESTART: + if offset > pos: + yield False, text[pos:offset] + pos = offset + pos += 1 + while pos < end: + char = text[pos] + if char not in NAMECHARS: + break + pos += 1 + yield True, text[offset + 1:pos].strip() + + elif not escaped and next == PREFIX: + escaped = True + pos = offset + 1 + + else: + yield False, text[pos:offset + 1] + pos = offset + 1 + + if pos < end: + yield False, text[pos:] diff --git a/genshi/template/markup.py b/genshi/template/markup.py --- a/genshi/template/markup.py +++ b/genshi/template/markup.py @@ -23,6 +23,7 @@ from genshi.template.base import BadDirectiveError, Template, \ TemplateSyntaxError, _apply_directives, SUB from genshi.template.eval import Suite +from genshi.template.interpolation import interpolate from genshi.template.loader import TemplateNotFound from genshi.template.directives import * @@ -127,8 +128,7 @@ directives.append((cls, value, ns_prefix.copy(), pos)) else: if value: - value = list(self._interpolate(value, self.basedir, - *pos)) + value = list(interpolate(value, self.basedir, *pos)) if len(value) == 1 and value[0][0] is TEXT: value = value[0][1] else: @@ -200,8 +200,7 @@ stream.append((EXEC, suite, pos)) elif kind is TEXT: - for kind, data, pos in self._interpolate(data, self.basedir, - *pos): + for kind, data, pos in interpolate(data, self.basedir, *pos): stream.append((kind, data, pos)) elif kind is COMMENT: diff --git a/genshi/template/tests/__init__.py b/genshi/template/tests/__init__.py --- a/genshi/template/tests/__init__.py +++ b/genshi/template/tests/__init__.py @@ -16,12 +16,13 @@ def suite(): - from genshi.template.tests import base, directives, eval, loader, markup, \ - plugin, text + from genshi.template.tests import base, directives, eval, interpolation, \ + loader, markup, plugin, text suite = unittest.TestSuite() suite.addTest(base.suite()) suite.addTest(directives.suite()) suite.addTest(eval.suite()) + suite.addTest(interpolation.suite()) suite.addTest(loader.suite()) suite.addTest(markup.suite()) suite.addTest(plugin.suite()) diff --git a/genshi/template/tests/base.py b/genshi/template/tests/base.py --- a/genshi/template/tests/base.py +++ b/genshi/template/tests/base.py @@ -14,178 +14,11 @@ import doctest import unittest -from genshi.core import Stream -from genshi.template.base import Template, TemplateSyntaxError - - -class TemplateTestCase(unittest.TestCase): - """Tests for basic template processing, expression evaluation and error - reporting. - """ - - def test_interpolate_string(self): - parts = list(Template._interpolate('bla')) - self.assertEqual(1, len(parts)) - self.assertEqual(Stream.TEXT, parts[0][0]) - self.assertEqual('bla', parts[0][1]) - - def test_interpolate_simple(self): - parts = list(Template._interpolate('${bla}')) - self.assertEqual(1, len(parts)) - self.assertEqual(Template.EXPR, parts[0][0]) - self.assertEqual('bla', parts[0][1].source) - - def test_interpolate_escaped(self): - parts = list(Template._interpolate('$${bla}')) - self.assertEqual(1, len(parts)) - self.assertEqual(Stream.TEXT, parts[0][0]) - self.assertEqual('${bla}', parts[0][1]) - - def test_interpolate_dobuleescaped(self): - parts = list(Template._interpolate('$$${bla}')) - self.assertEqual(2, len(parts)) - self.assertEqual(Stream.TEXT, parts[0][0]) - self.assertEqual('$', parts[0][1]) - self.assertEqual(Template.EXPR, parts[1][0]) - self.assertEqual('bla', parts[1][1].source) - - def test_interpolate_short(self): - parts = list(Template._interpolate('$bla')) - self.assertEqual(1, len(parts)) - self.assertEqual(Template.EXPR, parts[0][0]) - self.assertEqual('bla', parts[0][1].source) - - def test_interpolate_short_escaped(self): - parts = list(Template._interpolate('$$bla')) - self.assertEqual(1, len(parts)) - self.assertEqual(Stream.TEXT, parts[0][0]) - self.assertEqual('$bla', parts[0][1]) - - def test_interpolate_short_doubleescaped(self): - parts = list(Template._interpolate('$$$bla')) - self.assertEqual(2, len(parts)) - self.assertEqual(Stream.TEXT, parts[0][0]) - self.assertEqual('$', parts[0][1]) - self.assertEqual(Template.EXPR, parts[1][0]) - self.assertEqual('bla', parts[1][1].source) - - def test_interpolate_short_starting_with_underscore(self): - parts = list(Template._interpolate('$_bla')) - self.assertEqual(1, len(parts)) - self.assertEqual(Template.EXPR, parts[0][0]) - self.assertEqual('_bla', parts[0][1].source) - - def test_interpolate_short_containing_underscore(self): - parts = list(Template._interpolate('$foo_bar')) - self.assertEqual(1, len(parts)) - self.assertEqual(Template.EXPR, parts[0][0]) - self.assertEqual('foo_bar', parts[0][1].source) - - def test_interpolate_short_starting_with_dot(self): - parts = list(Template._interpolate('$.bla')) - self.assertEqual(1, len(parts)) - self.assertEqual(Stream.TEXT, parts[0][0]) - self.assertEqual('$.bla', parts[0][1]) - - def test_interpolate_short_containing_dot(self): - parts = list(Template._interpolate('$foo.bar')) - self.assertEqual(1, len(parts)) - self.assertEqual(Template.EXPR, parts[0][0]) - self.assertEqual('foo.bar', parts[0][1].source) - - def test_interpolate_short_starting_with_digit(self): - parts = list(Template._interpolate('$0bla')) - self.assertEqual(1, len(parts)) - self.assertEqual(Stream.TEXT, parts[0][0]) - self.assertEqual('$0bla', parts[0][1]) - - def test_interpolate_short_containing_digit(self): - parts = list(Template._interpolate('$foo0')) - self.assertEqual(1, len(parts)) - self.assertEqual(Template.EXPR, parts[0][0]) - self.assertEqual('foo0', parts[0][1].source) - - def test_interpolate_short_starting_with_digit(self): - parts = list(Template._interpolate('$0bla')) - self.assertEqual(1, len(parts)) - self.assertEqual(Stream.TEXT, parts[0][0]) - self.assertEqual('$0bla', parts[0][1]) - - def test_interpolate_short_containing_digit(self): - parts = list(Template._interpolate('$foo0')) - self.assertEqual(1, len(parts)) - self.assertEqual(Template.EXPR, parts[0][0]) - self.assertEqual('foo0', parts[0][1].source) - - def test_interpolate_full_nested_brackets(self): - parts = list(Template._interpolate('${{1:2}}')) - self.assertEqual(1, len(parts)) - self.assertEqual(Template.EXPR, parts[0][0]) - self.assertEqual('{1:2}', parts[0][1].source) - - def test_interpolate_full_mismatched_brackets(self): - try: - list(Template._interpolate('${{1:2}')) - except TemplateSyntaxError, e: - pass - else: - self.fail('Expected TemplateSyntaxError') - - def test_interpolate_quoted_brackets_1(self): - parts = list(Template._interpolate('${"}"}')) - self.assertEqual(1, len(parts)) - self.assertEqual(Template.EXPR, parts[0][0]) - self.assertEqual('"}"', parts[0][1].source) - - def test_interpolate_quoted_brackets_2(self): - parts = list(Template._interpolate("${'}'}")) - self.assertEqual(1, len(parts)) - self.assertEqual(Template.EXPR, parts[0][0]) - self.assertEqual("'}'", parts[0][1].source) - - def test_interpolate_quoted_brackets_3(self): - parts = list(Template._interpolate("${'''}'''}")) - self.assertEqual(1, len(parts)) - self.assertEqual(Template.EXPR, parts[0][0]) - self.assertEqual("'''}'''", parts[0][1].source) - - def test_interpolate_quoted_brackets_4(self): - parts = list(Template._interpolate("${'''}\"\"\"'''}")) - self.assertEqual(1, len(parts)) - self.assertEqual(Template.EXPR, parts[0][0]) - self.assertEqual("'''}\"\"\"'''", parts[0][1].source) - - def test_interpolate_quoted_brackets_5(self): - parts = list(Template._interpolate(r"${'\'}'}")) - self.assertEqual(1, len(parts)) - self.assertEqual(Template.EXPR, parts[0][0]) - self.assertEqual(r"'\'}'", parts[0][1].source) - - def test_interpolate_mixed1(self): - parts = list(Template._interpolate('$foo bar $baz')) - self.assertEqual(3, len(parts)) - self.assertEqual(Template.EXPR, parts[0][0]) - self.assertEqual('foo', parts[0][1].source) - self.assertEqual(Stream.TEXT, parts[1][0]) - self.assertEqual(' bar ', parts[1][1]) - self.assertEqual(Template.EXPR, parts[2][0]) - self.assertEqual('baz', parts[2][1].source) - - def test_interpolate_mixed2(self): - parts = list(Template._interpolate('foo $bar baz')) - self.assertEqual(3, len(parts)) - self.assertEqual(Stream.TEXT, parts[0][0]) - self.assertEqual('foo ', parts[0][1]) - self.assertEqual(Template.EXPR, parts[1][0]) - self.assertEqual('bar', parts[1][1].source) - self.assertEqual(Stream.TEXT, parts[2][0]) - self.assertEqual(' baz', parts[2][1]) - +from genshi.template.base import Template def suite(): suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite(Template.__module__)) - suite.addTest(unittest.makeSuite(TemplateTestCase, 'test')) return suite if __name__ == '__main__': diff --git a/genshi/template/tests/eval.py b/genshi/template/tests/eval.py --- a/genshi/template/tests/eval.py +++ b/genshi/template/tests/eval.py @@ -338,7 +338,7 @@ frame = frame.tb_next frames.append(frame) self.assertEqual('Variable "nothing" is not defined', str(e)) - self.assertEqual('', + self.assertEqual("", frames[-3].tb_frame.f_code.co_name) self.assertEqual('index.html', frames[-3].tb_frame.f_code.co_filename) @@ -357,7 +357,7 @@ frame = frame.tb_next frames.append(frame) self.assertEqual('Variable "nothing" is not defined', str(e)) - self.assertEqual('', + self.assertEqual("", frames[-3].tb_frame.f_code.co_name) self.assertEqual('index.html', frames[-3].tb_frame.f_code.co_filename) @@ -376,7 +376,7 @@ frame = frame.tb_next frames.append(frame) self.assertEqual('Variable "nothing" is not defined', str(e)) - self.assertEqual('', + self.assertEqual("", frames[-3].tb_frame.f_code.co_name) self.assertEqual('index.html', frames[-3].tb_frame.f_code.co_filename) diff --git a/genshi/template/tests/interpolation.py b/genshi/template/tests/interpolation.py new file mode 100644 --- /dev/null +++ b/genshi/template/tests/interpolation.py @@ -0,0 +1,191 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2007 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://genshi.edgewall.org/wiki/License. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://genshi.edgewall.org/log/. + +import doctest +import sys +import unittest + +from genshi.core import TEXT +from genshi.template.base import TemplateSyntaxError, EXPR +from genshi.template.interpolation import interpolate + + +class InterpolateTestCase(unittest.TestCase): + + def test_interpolate_string(self): + parts = list(interpolate('bla')) + self.assertEqual(1, len(parts)) + self.assertEqual(TEXT, parts[0][0]) + self.assertEqual('bla', parts[0][1]) + + def test_interpolate_simple(self): + parts = list(interpolate('${bla}')) + self.assertEqual(1, len(parts)) + self.assertEqual(EXPR, parts[0][0]) + self.assertEqual('bla', parts[0][1].source) + + def test_interpolate_escaped(self): + parts = list(interpolate('$${bla}')) + self.assertEqual(1, len(parts)) + self.assertEqual(TEXT, parts[0][0]) + self.assertEqual('${bla}', parts[0][1]) + + def test_interpolate_dobuleescaped(self): + parts = list(interpolate('$$${bla}')) + self.assertEqual(2, len(parts)) + self.assertEqual(TEXT, parts[0][0]) + self.assertEqual('$', parts[0][1]) + self.assertEqual(EXPR, parts[1][0]) + self.assertEqual('bla', parts[1][1].source) + + def test_interpolate_short(self): + parts = list(interpolate('$bla')) + self.assertEqual(1, len(parts)) + self.assertEqual(EXPR, parts[0][0]) + self.assertEqual('bla', parts[0][1].source) + + def test_interpolate_short_escaped(self): + parts = list(interpolate('$$bla')) + self.assertEqual(1, len(parts)) + self.assertEqual(TEXT, parts[0][0]) + self.assertEqual('$bla', parts[0][1]) + + def test_interpolate_short_doubleescaped(self): + parts = list(interpolate('$$$bla')) + self.assertEqual(2, len(parts)) + self.assertEqual(TEXT, parts[0][0]) + self.assertEqual('$', parts[0][1]) + self.assertEqual(EXPR, parts[1][0]) + self.assertEqual('bla', parts[1][1].source) + + def test_interpolate_short_starting_with_underscore(self): + parts = list(interpolate('$_bla')) + self.assertEqual(1, len(parts)) + self.assertEqual(EXPR, parts[0][0]) + self.assertEqual('_bla', parts[0][1].source) + + def test_interpolate_short_containing_underscore(self): + parts = list(interpolate('$foo_bar')) + self.assertEqual(1, len(parts)) + self.assertEqual(EXPR, parts[0][0]) + self.assertEqual('foo_bar', parts[0][1].source) + + def test_interpolate_short_starting_with_dot(self): + parts = list(interpolate('$.bla')) + self.assertEqual(1, len(parts)) + self.assertEqual(TEXT, parts[0][0]) + self.assertEqual('$.bla', parts[0][1]) + + def test_interpolate_short_containing_dot(self): + parts = list(interpolate('$foo.bar')) + self.assertEqual(1, len(parts)) + self.assertEqual(EXPR, parts[0][0]) + self.assertEqual('foo.bar', parts[0][1].source) + + def test_interpolate_short_starting_with_digit(self): + parts = list(interpolate('$0bla')) + self.assertEqual(1, len(parts)) + self.assertEqual(TEXT, parts[0][0]) + self.assertEqual('$0bla', parts[0][1]) + + def test_interpolate_short_containing_digit(self): + parts = list(interpolate('$foo0')) + self.assertEqual(1, len(parts)) + self.assertEqual(EXPR, parts[0][0]) + self.assertEqual('foo0', parts[0][1].source) + + def test_interpolate_short_starting_with_digit(self): + parts = list(interpolate('$0bla')) + self.assertEqual(1, len(parts)) + self.assertEqual(TEXT, parts[0][0]) + self.assertEqual('$0bla', parts[0][1]) + + def test_interpolate_short_containing_digit(self): + parts = list(interpolate('$foo0')) + self.assertEqual(1, len(parts)) + self.assertEqual(EXPR, parts[0][0]) + self.assertEqual('foo0', parts[0][1].source) + + def test_interpolate_full_nested_brackets(self): + parts = list(interpolate('${{1:2}}')) + self.assertEqual(1, len(parts)) + self.assertEqual(EXPR, parts[0][0]) + self.assertEqual('{1:2}', parts[0][1].source) + + def test_interpolate_full_mismatched_brackets(self): + try: + list(interpolate('${{1:2}')) + except TemplateSyntaxError, e: + pass + else: + self.fail('Expected TemplateSyntaxError') + + def test_interpolate_quoted_brackets_1(self): + parts = list(interpolate('${"}"}')) + self.assertEqual(1, len(parts)) + self.assertEqual(EXPR, parts[0][0]) + self.assertEqual('"}"', parts[0][1].source) + + def test_interpolate_quoted_brackets_2(self): + parts = list(interpolate("${'}'}")) + self.assertEqual(1, len(parts)) + self.assertEqual(EXPR, parts[0][0]) + self.assertEqual("'}'", parts[0][1].source) + + def test_interpolate_quoted_brackets_3(self): + parts = list(interpolate("${'''}'''}")) + self.assertEqual(1, len(parts)) + self.assertEqual(EXPR, parts[0][0]) + self.assertEqual("'''}'''", parts[0][1].source) + + def test_interpolate_quoted_brackets_4(self): + parts = list(interpolate("${'''}\"\"\"'''}")) + self.assertEqual(1, len(parts)) + self.assertEqual(EXPR, parts[0][0]) + self.assertEqual("'''}\"\"\"'''", parts[0][1].source) + + def test_interpolate_quoted_brackets_5(self): + parts = list(interpolate(r"${'\'}'}")) + self.assertEqual(1, len(parts)) + self.assertEqual(EXPR, parts[0][0]) + self.assertEqual(r"'\'}'", parts[0][1].source) + + def test_interpolate_mixed1(self): + parts = list(interpolate('$foo bar $baz')) + self.assertEqual(3, len(parts)) + self.assertEqual(EXPR, parts[0][0]) + self.assertEqual('foo', parts[0][1].source) + self.assertEqual(TEXT, parts[1][0]) + self.assertEqual(' bar ', parts[1][1]) + self.assertEqual(EXPR, parts[2][0]) + self.assertEqual('baz', parts[2][1].source) + + def test_interpolate_mixed2(self): + parts = list(interpolate('foo $bar baz')) + self.assertEqual(3, len(parts)) + self.assertEqual(TEXT, parts[0][0]) + self.assertEqual('foo ', parts[0][1]) + self.assertEqual(EXPR, parts[1][0]) + self.assertEqual('bar', parts[1][1].source) + self.assertEqual(TEXT, parts[2][0]) + self.assertEqual(' baz', parts[2][1]) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(doctest.DocTestSuite(interpolate.__module__)) + suite.addTest(unittest.makeSuite(InterpolateTestCase, 'test')) + return suite + +if __name__ == '__main__': + unittest.main(defaultTest='suite') diff --git a/genshi/template/text.py b/genshi/template/text.py --- a/genshi/template/text.py +++ b/genshi/template/text.py @@ -17,6 +17,7 @@ from genshi.template.base import BadDirectiveError, Template, SUB from genshi.template.directives import * +from genshi.template.interpolation import interpolate class TextTemplate(Template): @@ -70,8 +71,8 @@ start, end = mo.span() if start > offset: text = source[offset:start] - for kind, data, pos in self._interpolate(text, self.basedir, - self.filename, lineno): + for kind, data, pos in interpolate(text, self.basedir, + self.filename, lineno): stream.append((kind, data, pos)) lineno += len(text.splitlines()) @@ -102,8 +103,8 @@ if offset < len(source): text = source[offset:].replace('\\#', '#') - for kind, data, pos in self._interpolate(text, self.basedir, - self.filename, lineno): + for kind, data, pos in interpolate(text, self.basedir, + self.filename, lineno): stream.append((kind, data, pos)) return stream