Skip to content

Commit f37777c

Browse files
cocolatozzzeek
authored andcommittedDec 3, 2024
Support passing custom filters with the same name as built-in flags
During the lexical analysis phase, add an additional prefix for undeclared identifiers that have the same name as built-in flags, and determine the final filter to be used during the code generation phase based on the context provided by the user. Pull request by Hai Zhu. Fixes: #140 Closes: #413 Pull-request: #413 Pull-request-sha: 5e64e05 Change-Id: I021043527546e5cb39cacf0471b95f99ce41ba55
1 parent 8606516 commit f37777c

File tree

7 files changed

+124
-17
lines changed

7 files changed

+124
-17
lines changed
 

‎doc/build/unreleased/140.rst

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.. change::
2+
:tags: bug, lexer, codegen
3+
:tickets: 140
4+
5+
During the lexical analysis phase, add an additional prefix for undeclared
6+
identifiers that have the same name as built-in flags, and determine the
7+
final filter to be used during the code generation phase based on the
8+
context provided by the user. Pull request by Hai Zhu.

‎mako/codegen.py

+35-9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from mako import filters
1717
from mako import parsetree
1818
from mako import util
19+
from mako.filters import DEFAULT_ESCAPE_PREFIX
1920
from mako.pygen import PythonPrinter
2021

2122

@@ -26,6 +27,7 @@
2627
# context itself
2728
TOPLEVEL_DECLARED = {"UNDEFINED", "STOP_RENDERING"}
2829
RESERVED_NAMES = {"context", "loop"}.union(TOPLEVEL_DECLARED)
30+
DEFAULT_ESCAPED_N = "%sn" % DEFAULT_ESCAPE_PREFIX
2931

3032

3133
def compile( # noqa
@@ -522,6 +524,7 @@ def write_variable_declares(self, identifiers, toplevel=False, limit=None):
522524
self.printer.writeline("loop = __M_loop = runtime.LoopStack()")
523525

524526
for ident in to_write:
527+
ident = ident.replace(DEFAULT_ESCAPE_PREFIX, "")
525528
if ident in comp_idents:
526529
comp = comp_idents[ident]
527530
if comp.is_block:
@@ -785,25 +788,48 @@ def locate_encode(name):
785788
else:
786789
return filters.DEFAULT_ESCAPES.get(name, name)
787790

788-
if "n" not in args:
791+
filter_args = set()
792+
if DEFAULT_ESCAPED_N not in args:
789793
if is_expression:
790794
if self.compiler.pagetag:
791795
args = self.compiler.pagetag.filter_args.args + args
792-
if self.compiler.default_filters and "n" not in args:
796+
filter_args = set(self.compiler.pagetag.filter_args.args)
797+
if (
798+
self.compiler.default_filters
799+
and DEFAULT_ESCAPED_N not in args
800+
):
793801
args = self.compiler.default_filters + args
794802
for e in args:
795-
# if filter given as a function, get just the identifier portion
796-
if e == "n":
803+
if e == DEFAULT_ESCAPED_N:
797804
continue
805+
806+
if e.startswith(DEFAULT_ESCAPE_PREFIX):
807+
render_e = e.replace(DEFAULT_ESCAPE_PREFIX, "")
808+
is_default_filter = True
809+
else:
810+
render_e = e
811+
is_default_filter = False
812+
813+
# if filter given as a function, get just the identifier portion
798814
m = re.match(r"(.+?)(\(.*\))", e)
799815
if m:
800-
ident, fargs = m.group(1, 2)
801-
f = locate_encode(ident)
802-
e = f + fargs
816+
if not is_default_filter:
817+
ident, fargs = m.group(1, 2)
818+
f = locate_encode(ident)
819+
render_e = f + fargs
820+
target = "%s(%s)" % (render_e, target)
821+
elif is_default_filter and e not in filter_args:
822+
target = "%s(%s) if %s is not UNDEFINED else %s(%s)" % (
823+
render_e,
824+
target,
825+
render_e,
826+
locate_encode(render_e),
827+
target,
828+
)
803829
else:
804-
e = locate_encode(e)
830+
e = locate_encode(render_e)
805831
assert e is not None
806-
target = "%s(%s)" % (e, target)
832+
target = "%s(%s)" % (e, target)
807833
return target
808834

809835
def visitExpression(self, node):

‎mako/filters.py

+3
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,6 @@ def htmlentityreplace_errors(ex):
161161
"str": "str",
162162
"n": "n",
163163
}
164+
165+
166+
DEFAULT_ESCAPE_PREFIX = "__DEFAULT_ESCAPE_"

‎mako/pyparser.py

+19-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
from mako import compat
1919
from mako import exceptions
2020
from mako import util
21+
from mako.filters import DEFAULT_ESCAPE_PREFIX
22+
from mako.filters import DEFAULT_ESCAPES
2123

2224
# words that cannot be assigned to (notably
2325
# smaller than the total keys in __builtins__)
@@ -196,9 +198,24 @@ def visit_Tuple(self, node):
196198
p.declared_identifiers
197199
)
198200
lui = self.listener.undeclared_identifiers
199-
self.listener.undeclared_identifiers = lui.union(
200-
p.undeclared_identifiers
201+
undeclared_identifiers = lui.union(p.undeclared_identifiers)
202+
conflict_identifiers = undeclared_identifiers.intersection(
203+
DEFAULT_ESCAPES
201204
)
205+
if conflict_identifiers:
206+
_map = {
207+
i: DEFAULT_ESCAPE_PREFIX + i for i in conflict_identifiers
208+
}
209+
for i, arg in enumerate(self.listener.args):
210+
if arg in _map:
211+
self.listener.args[i] = _map[arg]
212+
self.listener.undeclared_identifiers = (
213+
undeclared_identifiers.symmetric_difference(
214+
conflict_identifiers
215+
).union(_map.values())
216+
)
217+
else:
218+
self.listener.undeclared_identifiers = undeclared_identifiers
202219

203220

204221
class ParseFunc(_ast_util.NodeVisitor):

‎test/test_ast.py

+25-5
Original file line numberDiff line numberDiff line change
@@ -285,16 +285,36 @@ def test_python_fragment(self):
285285

286286
def test_argument_list(self):
287287
parsed = ast.ArgumentList(
288-
"3, 5, 'hi', x+5, " "context.get('lala')", **exception_kwargs
288+
"3, 5, 'hi', g+5, " "context.get('lala')", **exception_kwargs
289289
)
290-
eq_(parsed.undeclared_identifiers, {"x", "context"})
290+
eq_(parsed.undeclared_identifiers, {"g", "context"})
291291
eq_(
292292
[x for x in parsed.args],
293-
["3", "5", "'hi'", "(x + 5)", "context.get('lala')"],
293+
["3", "5", "'hi'", "(g + 5)", "context.get('lala')"],
294294
)
295295

296-
parsed = ast.ArgumentList("h", **exception_kwargs)
297-
eq_(parsed.args, ["h"])
296+
parsed = ast.ArgumentList("m", **exception_kwargs)
297+
eq_(parsed.args, ["m"])
298+
299+
def test_conflict_argument_list(self):
300+
parsed = ast.ArgumentList(
301+
"x-2, h*2, '(u)', n+5, trim, entity, unicode, decode, str, other",
302+
**exception_kwargs,
303+
)
304+
eq_(
305+
parsed.undeclared_identifiers,
306+
{
307+
"__DEFAULT_ESCAPE_trim",
308+
"__DEFAULT_ESCAPE_h",
309+
"__DEFAULT_ESCAPE_decode",
310+
"__DEFAULT_ESCAPE_unicode",
311+
"__DEFAULT_ESCAPE_x",
312+
"__DEFAULT_ESCAPE_str",
313+
"__DEFAULT_ESCAPE_entity",
314+
"__DEFAULT_ESCAPE_n",
315+
"other",
316+
},
317+
)
298318

299319
def test_function_decl(self):
300320
"""test getting the arguments from a function"""

‎test/test_filters.py

+33
Original file line numberDiff line numberDiff line change
@@ -453,3 +453,36 @@ def test_capture_ccall(self):
453453

454454
# print t.render()
455455
assert flatten_result(t.render()) == "this is foo. body: ccall body"
456+
457+
def test_conflict_filter_ident(self):
458+
class h(object):
459+
foo = str
460+
461+
t = Template(
462+
"""
463+
X:
464+
${"asdf" | h.foo}
465+
"""
466+
)
467+
assert flatten_result(t.render(h=h)) == "X: asdf"
468+
469+
def h(i):
470+
return str(i) + "1"
471+
472+
t = Template(
473+
"""
474+
${123 | h}
475+
"""
476+
)
477+
assert flatten_result(t.render()) == "123"
478+
assert flatten_result(t.render(h=h)) == "1231"
479+
480+
t = Template(
481+
"""
482+
<%def name="foo()" filter="h">
483+
this is foo</%def>
484+
${foo()}
485+
"""
486+
)
487+
assert flatten_result(t.render()) == "this is foo"
488+
assert flatten_result(t.render(h=h)) == "this is foo1"

‎test/test_lexer.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1543,7 +1543,7 @@ def test_integration(self):
15431543
Text(" <tr>\n", (14, 1)),
15441544
ControlLine("for", "for x in j:", False, (15, 1)),
15451545
Text(" <td>Hello ", (16, 1)),
1546-
Expression("x", ["h"], (16, 23)),
1546+
Expression("x", ["__DEFAULT_ESCAPE_h"], (16, 23)),
15471547
Text("</td>\n", (16, 30)),
15481548
ControlLine("for", "endfor", True, (17, 1)),
15491549
Text(" </tr>\n", (18, 1)),

0 commit comments

Comments
 (0)