import re

from mako import compat
from mako import exceptions
from mako import parsetree
from mako import util
from mako.lexer import Lexer
from mako.template import Template
from test import assert_raises_message
from test import eq_
from test import TemplateTest
from test.util import flatten_result

# 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

Node = None
TemplateNode = None
ControlLine = None
Text = None
Code = None
Comment = None
Expression = None
_TagMeta = None
Tag = None
IncludeTag = None
NamespaceTag = None
TextTag = None
DefTag = None
BlockTag = None
CallTag = None
CallNamespaceTag = None
InheritTag = None
PageTag = None

# go through all the elements in parsetree and build out
# mocks of them
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:
            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)),
                ],
            ),
        )
