Skip to content

Commit

Permalink
Support the direct passing of dictionary literals.
Browse files Browse the repository at this point in the history
Support the direct passing of dictionary literals when calling functions
and fix the errors caused by nested braces.   This revises the fix that was
released in 1.3.4 and then reverted in 1.3.5. Pull request by Hai Zhu and
Jose Galvez.

Fixes: #400
Fixes: #401
Closes: #414
Pull-request: #414
Pull-request-sha: c6aa6be

Change-Id: If51a7d0847552191ca2a919e834ed5040e3a6666
  • Loading branch information
cocolato authored and zzzeek committed Dec 3, 2024
1 parent ee988d2 commit 8606516
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 6 deletions.
8 changes: 8 additions & 0 deletions doc/build/unreleased/400.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.. change::
:tags: bug, lexer
:tickets: 400, 401

Support the direct passing of dictionary literals when calling functions
and fix the errors caused by nested braces. This revises the fix that was
released in 1.3.4 and then reverted in 1.3.5. Pull request by Hai Zhu and
Jose Galvez.
2 changes: 1 addition & 1 deletion mako/parsetree.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ def _parse_attributes(self, expressions, nonexpressions):
for key in self.attributes:
if key in expressions:
expr = []
for x in re.compile(r"(\${.+?})", re.S).split(
for x in re.compile(r"(\${(?:[^$]*?{.+|.+?)})", re.S).split(
self.attributes[key]
):
m = re.compile(r"^\${(.+?)}$", re.S).match(x)
Expand Down
226 changes: 221 additions & 5 deletions test/test_lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -810,20 +810,29 @@ def test_tricky_expression(self):
),
)

def test_dict_expression_issue_400_regression(self):
@pytest.mark.parametrize(
"expr",
[
("${foo}${bar}",),
("file_${foo}_bat_${bar}.py",),
("${foo}_bat_${bar}",),
("${foo}${bar}.py",),
],
)
def test_dict_expression_issue_400_regression(self, expr):
"""test for issue #401.
This was the regression case for #400
"""
template = '<%include file="${foo}${bar}"/>'
template = f'<%include file="{expr}"/>'

nodes = Lexer(template).parse()
self._compare(
nodes,
TemplateNode(
{},
[IncludeTag("include", {"file": "${foo}${bar}"}, (1, 1), [])],
[IncludeTag("include", {"file": f"{expr}"}, (1, 1), [])],
),
)

Expand All @@ -838,7 +847,7 @@ def test_ampersand_issue_412(self):
TemplateNode({}, [Text("\nproperty = <&node>;\n\n", (1, 1))]),
)

def _dont_test_dict_expression_issue_400(self):
def test_dict_expression_issue_400(self):
"""test for issue #400"""
template = """
<%def name="dtest(d)">
Expand Down Expand Up @@ -897,7 +906,7 @@ def _dont_test_dict_expression_issue_400(self):
),
)

def _dont_test_dict_expression_2_issue_400(self):
def test_dict_expression_2_issue_400(self):
"""test for issue #400"""
template = """
<%def name="thing(thing)">
Expand Down Expand Up @@ -1006,6 +1015,213 @@ def _dont_test_dict_expression_2_issue_400(self):
),
)

def test_dict_expression(self):
template = """
<%def name="dtest(d)">
% for k,v in d.items():
${k} = ${v}
% endfor
</%def>
<%self:dtest d="${
{
'id':'4',
'foo':'barr'
}
}" />
"""
nodes = Lexer(template).parse()
self._compare(
nodes,
TemplateNode(
{},
[
Text("\n ", (1, 1)),
DefTag(
"def",
{"name": "dtest(d)"},
(2, 9),
[
Text("\n", (2, 31)),
ControlLine(
"for", "for k,v in d.items():", False, (3, 1)
),
Text(" ", (4, 1)),
Expression("k", [], (4, 13)),
Text(" = ", (4, 17)),
Expression("v", [], (4, 20)),
Text("\n", (4, 24)),
ControlLine("for", "endfor", True, (5, 1)),
Text(" ", (6, 1)),
],
),
Text("\n ", (6, 16)),
CallNamespaceTag(
"self:dtest",
{
"d": "${\n\
{\n\
'id':'4',\n\
'foo':'barr'\n\
}\n\
}"
},
(7, 9),
[],
),
Text("\n ", (12, 30)),
],
),
)

def test_dict_expression_2(self):
template = """
<%def name="thing(thing)">
${type(thing)}
</%def>
<%self:thing thing="foo" />
<%self:thing thing="${5}" />
<%self:thing thing="${[1,2,3]}" />
<%self:thing thing="${{'id':'4'}}" />
<%
foo="this is foo"
g=False
%>
<%def name="bar(x, y)">
${x} ${y}
</%def>
<%self:bar x="${{'id':4}}" y="x${g and '1' or '2'}y"/>
<%def name="dtest(d)">
% for k,v in d.items():
${k} = ${v}
% endfor
% if 'embeded' in d and 'name' in d['embeded']:
${d['embeded']['name']}
% endif
</%def>
<%self:dtest d="${ {
'x-on:click':'foo',
'foo':'bar',
'embeded':{'name':'J Doe'}
} }" />
"""
nodes = Lexer(template).parse()
self._compare(
nodes,
TemplateNode(
{},
[
Text("\n ", (1, 1)),
DefTag(
"def",
{"name": "thing(thing)"},
(2, 9),
[
Text("\n ", (2, 35)),
Expression("type(thing)", [], (3, 13)),
Text("\n ", (3, 27)),
],
),
Text("\n ", (4, 16)),
CallNamespaceTag(
"self:thing", {"thing": "foo"}, (5, 9), []
),
Text("\n ", (5, 36)),
CallNamespaceTag(
"self:thing", {"thing": "${5}"}, (6, 9), []
),
Text("\n ", (6, 37)),
CallNamespaceTag(
"self:thing", {"thing": "${[1,2,3]}"}, (7, 9), []
),
Text("\n ", (7, 43)),
CallNamespaceTag(
"self:thing", {"thing": "${{'id':'4'}}"}, (8, 9), []
),
Text("\n ", (8, 46)),
Code(
'\nfoo="this is foo"\ng=False\n \n',
False,
(9, 9),
),
Text("\n ", (12, 11)),
DefTag(
"def",
{"name": "bar(x, y)"},
(13, 9),
[
Text("\n ", (13, 32)),
Expression("x", [], (14, 13)),
Text(" ", (14, 17)),
Expression("y", [], (14, 18)),
Text("\n ", (14, 22)),
],
),
Text("\n ", (15, 16)),
CallNamespaceTag(
"self:bar",
{"x": "${{'id':4}}", "y": "x${g and '1' or '2'}y"},
(16, 9),
[],
),
Text("\n ", (16, 63)),
DefTag(
"def",
{"name": "dtest(d)"},
(17, 9),
[
Text("\n", (17, 31)),
ControlLine(
"for", "for k,v in d.items():", False, (18, 1)
),
Text(" ", (19, 1)),
Expression("k", [], (19, 9)),
Text(" = ", (19, 13)),
Expression("v", [], (19, 16)),
Text("\n", (19, 20)),
ControlLine("for", "endfor", True, (20, 1)),
ControlLine(
"if",
"if 'embeded' in d and \
'name' in d['embeded']:",
False,
(21, 1),
),
Text(" ", (22, 1)),
Expression("d['embeded']['name']", [], (22, 9)),
Text("\n", (22, 32)),
ControlLine("if", "endif", True, (23, 1)),
Text(" ", (24, 1)),
],
),
Text("\n ", (24, 16)),
CallNamespaceTag(
"self:dtest",
{
"d": "${ {\n\
'x-on:click':'foo',\n\
'foo':'bar',\n\
'embeded':{'name':'J Doe'}\n\
} }"
},
(25, 9),
[],
),
Text("\n ", (29, 16)),
],
),
)

def test_brace_expression(self):
template = '<%include file="${foo}${bar}"/>'
nodes = Lexer(template).parse()
self._compare(
nodes,
TemplateNode(
{},
[IncludeTag("include", {"file": "${foo}${bar}"}, (1, 1), [])],
),
)

def test_tricky_code(self):
template = """<% print('hi %>') %>"""
nodes = Lexer(template).parse()
Expand Down

0 comments on commit 8606516

Please sign in to comment.