blob: 06ebb05ee417beeee93a52d55bb1ae48f07ea224 [file] [log] [blame]
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))]))