Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
Mako / test / test_lexer.py
Size: Mime:
from mako.lexer import Lexer
from mako import exceptions, util, compat
from test.util import flatten_result
from mako.template import Template
import re
from test import TemplateTest, eq_, assert_raises_message

# create fake parsetree classes which are constructed
# exactly as the repr() of a real parsetree object.
# this allows us to use a Python construct as the source
# of a comparable repr(), which is also hit by the 2to3 tool.

def repr_arg(x):
    if isinstance(x, dict):
        return util.sorted_dict_repr(x)
    else:
        return repr(x)

def _as_unicode(arg):
    if isinstance(arg, compat.string_types):
        return compat.text_type(arg)
    elif isinstance(arg, dict):
        return dict(
            (_as_unicode(k), _as_unicode(v))
            for k, v in arg.items()
        )
    else:
        return arg
from mako import parsetree
for cls in list(parsetree.__dict__.values()):
    if isinstance(cls, type) and \
        issubclass(cls, parsetree.Node):
        clsname = cls.__name__
        exec(("""
class %s(object):
    def __init__(self, *args):
        self.args = [_as_unicode(arg) for arg in args]
    def __repr__(self):
        return "%%s(%%s)" %% (
            self.__class__.__name__,
            ", ".join(repr_arg(x) for x in self.args)
            )
""" % clsname), locals())

# NOTE: most assertion expressions were generated, then formatted
# by PyTidy, hence the dense formatting.

class LexerTest(TemplateTest):

    def _compare(self, node, expected):
        eq_(repr(node), repr(expected))

    def test_text_and_tag(self):
        template = """
<b>Hello world</b>
        <%def name="foo()">
                this is a def.
        </%def>

        and some more text.
"""
        node = Lexer(template).parse()
        self._compare(node, TemplateNode({},
                      [Text('''\n<b>Hello world</b>\n        ''', (1,
                      1)), DefTag('def', {'name': 'foo()'}, (3, 9),
                      [Text('''\n                this is a def.\n        ''',
                      (3, 28))]),
                      Text('''\n\n        and some more text.\n''',
                      (5, 16))]))

    def test_unclosed_tag(self):
        template = """

            <%def name="foo()">
             other text
        """
        try:
            nodes = Lexer(template).parse()
            assert False
        except exceptions.SyntaxException:
            eq_(
                str(compat.exception_as()),
                "Unclosed tag: <%def> at line: 5 char: 9"
            )

    def test_onlyclosed_tag(self):
        template = \
            """
            <%def name="foo()">
                foo
            </%def>

            </%namespace>

            hi.
        """
        self.assertRaises(exceptions.SyntaxException,
                          Lexer(template).parse)

    def test_noexpr_allowed(self):
        template = \
            """
            <%namespace name="${foo}"/>
        """
        self.assertRaises(exceptions.CompileException,
                          Lexer(template).parse)

    def test_unmatched_tag(self):
        template = \
            """
        <%namespace name="bar">
        <%def name="foo()">
            foo
            </%namespace>
        </%def>


        hi.
"""
        self.assertRaises(exceptions.SyntaxException,
                          Lexer(template).parse)

    def test_nonexistent_tag(self):
        template = """
            <%lala x="5"/>
        """
        self.assertRaises(exceptions.CompileException,
                          Lexer(template).parse)

    def test_wrongcase_tag(self):
        template = \
            """
            <%DEF name="foo()">
            </%def>

        """
        self.assertRaises(exceptions.CompileException,
                          Lexer(template).parse)

    def test_percent_escape(self):
        template = \
            """

%% some whatever.

    %% more some whatever
    % if foo:
    % endif
        """
        node = Lexer(template).parse()
        self._compare(node, TemplateNode({}, [Text('''\n\n''',
                      (1, 1)), Text('''% some whatever.\n\n''', (3, 2)),
                      Text('   %% more some whatever\n', (5, 2)),
                      ControlLine('if', 'if foo:', False, (6, 1)),
                      ControlLine('if', 'endif', True, (7, 1)),
                      Text('        ', (8, 1))]))

    def test_old_multiline_comment(self):
        template = """#*"""
        node = Lexer(template).parse()
        self._compare(node, TemplateNode({}, [Text('''#*''', (1, 1))]))


    def test_text_tag(self):
        template = \
            """
        ## comment
        % if foo:
            hi
        % endif
        <%text>
            # more code

            % more code
            <%illegal compionent>/></>
            <%def name="laal()">def</%def>


        </%text>

        <%def name="foo()">this is foo</%def>

        % if bar:
            code
        % endif
        """
        node = Lexer(template).parse()
        self._compare(node,
            TemplateNode({}, [Text('\n', (1, 1)),
                    Comment('comment', (2, 1)),
                    ControlLine('if', 'if foo:', False, (3, 1)),
                    Text('            hi\n', (4, 1)),
                    ControlLine('if', 'endif', True, (5, 1)),
                    Text('        ', (6, 1)),
                    TextTag('text', {}, (6, 9),
                    [Text('\n            # more code\n\n           '
                        ' % more code\n            <%illegal compionent>/></>\n'
                        '            <%def name="laal()">def</%def>\n\n\n        ',
                        (6, 16))]), Text('\n\n        ', (14, 17)),
                    DefTag('def', {'name': 'foo()'}, (16, 9),
                    [Text('this is foo', (16, 28))]), Text('\n\n', (16, 46)),
                    ControlLine('if', 'if bar:', False, (18, 1)),
                    Text('            code\n', (19, 1)),
                    ControlLine('if', 'endif', True, (20, 1)),
                    Text('        ', (21, 1))])
        )

    def test_def_syntax(self):
        template = \
            """
        <%def lala>
            hi
        </%def>
"""
        self.assertRaises(exceptions.CompileException,
                          Lexer(template).parse)

    def test_def_syntax_2(self):
        template = \
            """
        <%def name="lala">
            hi
        </%def>
    """
        self.assertRaises(exceptions.CompileException,
                          Lexer(template).parse)

    def test_whitespace_equals(self):
        template = \
            """
            <%def name = "adef()" >
              adef
            </%def>
        """
        node = Lexer(template).parse()
        self._compare(node, TemplateNode({}, [Text('\n            ',
                      (1, 1)), DefTag('def', {'name': 'adef()'}, (2,
                      13),
                      [Text('''\n              adef\n            ''',
                      (2, 36))]), Text('\n        ', (4, 20))]))

    def test_ns_tag_closed(self):
        template = \
            """

            <%self:go x="1" y="2" z="${'hi' + ' ' + 'there'}"/>
        """
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({},
                      [Text('''

            ''', (1, 1)),
                      CallNamespaceTag('self:go', {'x': '1', 'y'
                      : '2', 'z': "${'hi' + ' ' + 'there'}"}, (3,
                      13), []), Text('\n        ', (3, 64))]))

    def test_ns_tag_empty(self):
        template = \
            """
            <%form:option value=""></%form:option>
        """
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({}, [Text('\n            ',
                      (1, 1)), CallNamespaceTag('form:option',
                      {'value': ''}, (2, 13), []), Text('\n        '
                      , (2, 51))]))

    def test_ns_tag_open(self):
        template = \
            """

            <%self:go x="1" y="${process()}">
                this is the body
            </%self:go>
        """
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({},
                      [Text('''

            ''', (1, 1)),
                      CallNamespaceTag('self:go', {'x': '1', 'y'
                      : '${process()}'}, (3, 13),
                      [Text('''
                this is the body
            ''',
                      (3, 46))]), Text('\n        ', (5, 24))]))

    def test_expr_in_attribute(self):
        """test some slightly trickier expressions.

        you can still trip up the expression parsing, though, unless we
        integrated really deeply somehow with AST."""

        template = \
            """
            <%call expr="foo>bar and 'lala' or 'hoho'"/>
            <%call expr='foo<bar and hoho>lala and "x" + "y"'/>
        """
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({}, [Text('\n            ',
                      (1, 1)), CallTag('call', {'expr'
                      : "foo>bar and 'lala' or 'hoho'"}, (2, 13), []),
                      Text('\n            ', (2, 57)), CallTag('call'
                      , {'expr': 'foo<bar and hoho>lala and "x" + "y"'
                      }, (3, 13), []), Text('\n        ', (3, 64))]))

    def test_pagetag(self):
        template = \
            """
            <%page cached="True", args="a, b"/>

            some template
        """
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({}, [Text('\n            ',
                      (1, 1)), PageTag('page', {'args': 'a, b',
                      'cached': 'True'}, (2, 13), []),
                      Text('''

            some template
        ''',
                      (2, 48))]))

    def test_nesting(self):
        template = \
            """

        <%namespace name="ns">
            <%def name="lala(hi, there)">
                <%call expr="something()"/>
            </%def>
        </%namespace>

        """
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({},
                      [Text('''

        ''', (1, 1)),
                      NamespaceTag('namespace', {'name': 'ns'}, (3,
                      9), [Text('\n            ', (3, 31)),
                      DefTag('def', {'name': 'lala(hi, there)'}, (4,
                      13), [Text('\n                ', (4, 42)),
                      CallTag('call', {'expr': 'something()'}, (5,
                      17), []), Text('\n            ', (5, 44))]),
                      Text('\n        ', (6, 20))]),
                      Text('''

        ''', (7, 22))]))

    if compat.py3k:
        def test_code(self):
            template = \
"""text
    <%
        print("hi")
        for x in range(1,5):
            print(x)
    %>
more text
    <%!
        import foo
    %>
"""
            nodes = Lexer(template).parse()
            self._compare(nodes,
            TemplateNode({}, [
                Text('text\n    ', (1, 1)),
                Code('\nprint("hi")\nfor x in range(1,5):\n    '
                            'print(x)\n    \n', False, (2, 5)),
                Text('\nmore text\n    ', (6, 7)),
                Code('\nimport foo\n    \n', True, (8, 5)),
                Text('\n', (10, 7))])
            )


    else:

        def test_code(self):
            template = \
"""text
    <%
        print "hi"
        for x in range(1,5):
            print x
    %>
more text
    <%!
        import foo
    %>
"""
            nodes = Lexer(template).parse()
            self._compare(nodes,
            TemplateNode({}, [
                Text('text\n    ', (1, 1)),
                Code('\nprint "hi"\nfor x in range(1,5):\n    '
                            'print x\n    \n', False, (2, 5)),
                Text('\nmore text\n    ', (6, 7)),
                Code('\nimport foo\n    \n', True, (8, 5)),
                Text('\n', (10, 7))])
            )

    def test_code_and_tags(self):
        template = \
            """
<%namespace name="foo">
    <%def name="x()">
        this is x
    </%def>
    <%def name="y()">
        this is y
    </%def>
</%namespace>

<%
    result = []
    data = get_data()
    for x in data:
        result.append(x+7)
%>

    result: <%call expr="foo.x(result)"/>
"""
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({}, [Text('\n', (1, 1)),
                      NamespaceTag('namespace', {'name': 'foo'}, (2,
                      1), [Text('\n    ', (2, 24)), DefTag('def',
                      {'name': 'x()'}, (3, 5),
                      [Text('''\n        this is x\n    ''', (3, 22))]),
                      Text('\n    ', (5, 12)), DefTag('def', {'name'
                      : 'y()'}, (6, 5),
                      [Text('''\n        this is y\n    ''', (6, 22))]),
                      Text('\n', (8, 12))]), Text('''\n\n''', (9, 14)),
                      Code('''\nresult = []\ndata = get_data()\n'''
                      '''for x in data:\n    result.append(x+7)\n\n''',
                      False, (11, 1)), Text('''\n\n    result: ''', (16,
                      3)), CallTag('call', {'expr': 'foo.x(result)'
                      }, (18, 13), []), Text('\n', (18, 42))]))

    def test_expression(self):
        template = \
            """
        this is some ${text} and this is ${textwith | escapes, moreescapes}
        <%def name="hi()">
            give me ${foo()} and ${bar()}
        </%def>
        ${hi()}
"""
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({},
                      [Text('\n        this is some ', (1, 1)),
                      Expression('text', [], (2, 22)),
                      Text(' and this is ', (2, 29)),
                      Expression('textwith ', ['escapes', 'moreescapes'
                      ], (2, 42)), Text('\n        ', (2, 76)),
                      DefTag('def', {'name': 'hi()'}, (3, 9),
                      [Text('\n            give me ', (3, 27)),
                      Expression('foo()', [], (4, 21)), Text(' and ',
                      (4, 29)), Expression('bar()', [], (4, 34)),
                      Text('\n        ', (4, 42))]), Text('\n        '
                      , (5, 16)), Expression('hi()', [], (6, 9)),
                      Text('\n', (6, 16))]))


    def test_tricky_expression(self):
        template = """

            ${x and "|" or "hi"}
        """
        nodes = Lexer(template).parse()
        self._compare(
            nodes,
            TemplateNode({}, [
                Text('\n\n            ', (1, 1)),
                Expression('x and "|" or "hi"', [], (3, 13)),
                Text('\n        ', (3, 33))
            ])
        )

        template = r"""

            ${hello + '''heres '{|}' text | | }''' | escape1}
            ${'Tricky string: ' + '\\\"\\\'|\\'}
        """
        nodes = Lexer(template).parse()
        self._compare(
            nodes,
            TemplateNode({}, [
                Text('\n\n            ', (1, 1)),
                Expression("hello + '''heres '{|}' text | | }''' ",
                                ['escape1'], (3, 13)),
                Text('\n            ', (3, 62)),
                Expression(r"""'Tricky string: ' + '\\\"\\\'|\\'""",
                                [], (4, 13)),
                Text('\n        ', (4, 49))
            ])
        )

    def test_tricky_code(self):
        if compat.py3k:
            template = """<% print('hi %>') %>"""
            nodes = Lexer(template).parse()
            self._compare(nodes, TemplateNode({},
                          [Code("print('hi %>') \n", False, (1, 1))]))
        else:
            template = """<% print 'hi %>' %>"""
            nodes = Lexer(template).parse()
            self._compare(nodes, TemplateNode({},
                          [Code("print 'hi %>' \n", False, (1, 1))]))

    def test_tricky_code_2(self):
        template = \
            """<%
        # someone's comment
%>
        """
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({},
                      [Code("""
        # someone's comment

""",
                      False, (1, 1)), Text('\n        ', (3, 3))]))

    if compat.py3k:
        def test_tricky_code_3(self):
            template = \
                """<%
            print('hi')
            # this is a comment
            # another comment
            x = 7 # someone's '''comment
            print('''
        there
        ''')
            # someone else's comment
%> '''and now some text '''"""
            nodes = Lexer(template).parse()
            self._compare(nodes, TemplateNode({},
                          [Code("""
print('hi')
# this is a comment
# another comment
x = 7 # someone's '''comment
print('''
        there
        ''')
# someone else's comment

""",
                          False, (1, 1)),
                          Text(" '''and now some text '''", (10,
                          3))]))
    else:
        def test_tricky_code_3(self):
            template = \
                """<%
            print 'hi'
            # this is a comment
            # another comment
            x = 7 # someone's '''comment
            print '''
        there
        '''
            # someone else's comment
%> '''and now some text '''"""
            nodes = Lexer(template).parse()
            self._compare(nodes, TemplateNode({},
                      [Code("""\nprint 'hi'\n# this is a comment\n"""
                      """# another comment\nx = 7 """
                      """# someone's '''comment\nprint '''\n        """
                      """there\n        '''\n# someone else's """
                      """comment\n\n""",
                      False, (1, 1)),
                      Text(" '''and now some text '''", (10, 3))]))

    def test_tricky_code_4(self):
        template = \
            """<% foo = "\\"\\\\" %>"""
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({},
                      [Code("""foo = "\\"\\\\" \n""",
                      False, (1, 1))]))

    def test_tricky_code_5(self):
        template = \
            """before ${ {'key': 'value'} } after"""
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({},
                      [Text('before ', (1, 1)),
                      Expression(" {'key': 'value'} ", [], (1, 8)),
                      Text(' after', (1, 29))]))

    def test_tricky_code_6(self):
        template = \
            """before ${ (0x5302 | 0x0400) } after"""
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({},
                      [Text('before ', (1, 1)),
                      Expression(" (0x5302 | 0x0400) ", [], (1, 8)),
                      Text(' after', (1, 30))]))

    def test_control_lines(self):
        template = \
            """
text text la la
% if foo():
 mroe text la la blah blah
% endif

        and osme more stuff
        % for l in range(1,5):
    tex tesl asdl l is ${l} kfmas d
      % endfor
    tetx text

"""
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({},
                      [Text('''\ntext text la la\n''', (1, 1)),
                      ControlLine('if', 'if foo():', False, (3, 1)),
                      Text(' mroe text la la blah blah\n', (4, 1)),
                      ControlLine('if', 'endif', True, (5, 1)),
                      Text('''\n        and osme more stuff\n''', (6,
                      1)), ControlLine('for', 'for l in range(1,5):',
                      False, (8, 1)), Text('    tex tesl asdl l is ',
                      (9, 1)), Expression('l', [], (9, 24)),
                      Text(' kfmas d\n', (9, 28)), ControlLine('for',
                      'endfor', True, (10, 1)),
                      Text('''    tetx text\n\n''', (11, 1))]))

    def test_control_lines_2(self):
        template = \
"""% for file in requestattr['toc'].filenames:
    x
% endfor
"""
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({}, [ControlLine('for',
                      "for file in requestattr['toc'].filenames:",
                      False, (1, 1)), Text('    x\n', (2, 1)),
                      ControlLine('for', 'endfor', True, (3, 1))]))

    def test_long_control_lines(self):
        template = \
        """
    % for file in \\
        requestattr['toc'].filenames:
        x
    % endfor
        """
        nodes = Lexer(template).parse()
        self._compare(
            nodes,
            TemplateNode({}, [
                Text('\n', (1, 1)),
                ControlLine('for', "for file in \\\n        "
                                "requestattr['toc'].filenames:",
                                False, (2, 1)),
                Text('        x\n', (4, 1)),
                ControlLine('for', 'endfor', True, (5, 1)),
                Text('        ', (6, 1))
            ])
        )

    def test_unmatched_control(self):
        template = """

        % if foo:
            % for x in range(1,5):
        % endif
"""
        assert_raises_message(
            exceptions.SyntaxException,
            "Keyword 'endif' doesn't match keyword 'for' at line: 5 char: 1",
            Lexer(template).parse
        )

    def test_unmatched_control_2(self):
        template = """

        % if foo:
            % for x in range(1,5):
            % endfor
"""

        assert_raises_message(
            exceptions.SyntaxException,
            "Unterminated control keyword: 'if' at line: 3 char: 1",
            Lexer(template).parse
        )

    def test_unmatched_control_3(self):
        template = """

        % if foo:
            % for x in range(1,5):
            % endlala
        % endif
"""
        assert_raises_message(
            exceptions.SyntaxException,
            "Keyword 'endlala' doesn't match keyword 'for' at line: 5 char: 1",
            Lexer(template).parse
        )

    def test_ternary_control(self):
        template = \
            """
        % if x:
            hi
        % elif y+7==10:
            there
        % elif lala:
            lala
        % else:
            hi
        % endif
"""
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({}, [Text('\n', (1, 1)),
                      ControlLine('if', 'if x:', False, (2, 1)),
                      Text('            hi\n', (3, 1)),
                      ControlLine('elif', 'elif y+7==10:', False, (4,
                      1)), Text('            there\n', (5, 1)),
                      ControlLine('elif', 'elif lala:', False, (6,
                      1)), Text('            lala\n', (7, 1)),
                      ControlLine('else', 'else:', False, (8, 1)),
                      Text('            hi\n', (9, 1)),
                      ControlLine('if', 'endif', True, (10, 1))]))

    def test_integration(self):
        template = \
            """<%namespace name="foo" file="somefile.html"/>
 ## inherit from foobar.html
<%inherit file="foobar.html"/>

<%def name="header()">
     <div>header</div>
</%def>
<%def name="footer()">
    <div> footer</div>
</%def>

<table>
    % for j in data():
    <tr>
        % for x in j:
            <td>Hello ${x| h}</td>
        % endfor
    </tr>
    % endfor
</table>
"""
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({}, [NamespaceTag('namespace'
                      , {'file': 'somefile.html', 'name': 'foo'},
                      (1, 1), []), Text('\n', (1, 46)),
                      Comment('inherit from foobar.html', (2, 1)),
                      InheritTag('inherit', {'file': 'foobar.html'},
                      (3, 1), []), Text('''\n\n''', (3, 31)),
                      DefTag('def', {'name': 'header()'}, (5, 1),
                      [Text('''\n     <div>header</div>\n''', (5,
                      23))]), Text('\n', (7, 8)), DefTag('def',
                      {'name': 'footer()'}, (8, 1),
                      [Text('''\n    <div> footer</div>\n''', (8,
                      23))]), Text('''\n\n<table>\n''', (10, 8)),
                      ControlLine('for', 'for j in data():', False,
                      (13, 1)), Text('    <tr>\n', (14, 1)),
                      ControlLine('for', 'for x in j:', False, (15,
                      1)), Text('            <td>Hello ', (16, 1)),
                      Expression('x', ['h'], (16, 23)), Text('</td>\n'
                      , (16, 30)), ControlLine('for', 'endfor', True,
                      (17, 1)), Text('    </tr>\n', (18, 1)),
                      ControlLine('for', 'endfor', True, (19, 1)),
                      Text('</table>\n', (20, 1))]))

    def test_comment_after_statement(self):
        template = \
            """
        % if x: #comment
            hi
        % else: #next
            hi
        % endif #end
"""
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({}, [Text('\n', (1, 1)),
                      ControlLine('if', 'if x: #comment', False, (2,
                      1)), Text('            hi\n', (3, 1)),
                      ControlLine('else', 'else: #next', False, (4,
                      1)), Text('            hi\n', (5, 1)),
                      ControlLine('if', 'endif #end', True, (6, 1))]))

    def test_crlf(self):
        template = util.read_file(self._file_path("crlf.html"))
        nodes = Lexer(template).parse()
        self._compare(
            nodes,
            TemplateNode({}, [
                Text('<html>\r\n\r\n', (1, 1)),
                PageTag('page', {
                            'args': "a=['foo',\n                'bar']"
                        }, (3, 1), []),
                Text('\r\n\r\nlike the name says.\r\n\r\n', (4, 26)),
                ControlLine('for', 'for x in [1,2,3]:', False, (8, 1)),
                Text('        ', (9, 1)),
                Expression('x', [], (9, 9)),
                ControlLine('for', 'endfor', True, (10, 1)),
                Text('\r\n', (11, 1)),
                Expression("trumpeter == 'Miles' and "
                                "trumpeter or \\\n      'Dizzy'",
                                [], (12, 1)),
                Text('\r\n\r\n', (13, 15)),
                DefTag('def', {'name': 'hi()'}, (15, 1), [
                    Text('\r\n    hi!\r\n', (15, 19))]),
                    Text('\r\n\r\n</html>\r\n', (17, 8))
                ])
        )
        assert flatten_result(Template(template).render()) \
            == """<html> like the name says. 1 2 3 Dizzy </html>"""

    def test_comments(self):
        template = \
            """
<style>
 #someselector
 # other non comment stuff
</style>
## a comment

# also not a comment

   ## this is a comment

this is ## not a comment

<%doc> multiline
comment
</%doc>

hi
"""
        nodes = Lexer(template).parse()
        self._compare(nodes, TemplateNode({},
                      [Text('''\n<style>\n #someselector\n # '''
                        '''other non comment stuff\n</style>\n''',
                      (1, 1)), Comment('a comment', (6, 1)),
                      Text('''\n# also not a comment\n\n''', (7, 1)),
                      Comment('this is a comment', (10, 1)),
                      Text('''\nthis is ## not a comment\n\n''', (11,
                      1)), Comment(''' multiline\ncomment\n''', (14,
                      1)), Text('''

hi
''', (16, 8))]))

    def test_docs(self):
        template = \
            """
        <%doc>
            this is a comment
        </%doc>
        <%def name="foo()">
            <%doc>
                this is the foo func
            </%doc>
        </%def>
        """
        nodes = Lexer(template).parse()
        self._compare(nodes,
            TemplateNode({}, [Text('\n        ', (1,
              1)),
              Comment('''\n            this is a comment\n        ''',
              (2, 9)), Text('\n        ', (4, 16)),
              DefTag('def', {'name': 'foo()'}, (5, 9),
              [Text('\n            ', (5, 28)),
              Comment('''\n                this is the foo func\n'''
                '''            ''',
              (6, 13)), Text('\n        ', (8, 20))]),
              Text('\n        ', (9, 16))]))

    def test_preprocess(self):

        def preproc(text):
            return re.sub(r'(?<=\n)\s*#[^#]', '##', text)

        template = \
            """
    hi
    # old style comment
# another comment
"""
        nodes = Lexer(template, preprocessor=preproc).parse()
        self._compare(nodes, TemplateNode({}, [Text('''\n    hi\n''',
                      (1, 1)), Comment('old style comment', (3, 1)),
                      Comment('another comment', (4, 1))]))