diff --git a/doc/build/changelog.rst b/doc/build/changelog.rst index 2d99415..d59bfe8 100644 --- a/doc/build/changelog.rst +++ b/doc/build/changelog.rst @@ -11,7 +11,7 @@ Changelog :include_notes_from: unreleased .. changelog:: - :version: 1.3.7 + :version: 1.3.7 (yanked) :released: Tue Dec 3 2024 .. change:: diff --git a/doc/build/unreleased/415.rst b/doc/build/unreleased/415.rst new file mode 100644 index 0000000..e38c72d --- /dev/null +++ b/doc/build/unreleased/415.rst @@ -0,0 +1,7 @@ +.. change:: + :tags: bug, lexer + :tickets: 415, 140 + + Reverted the fix for :ticket:`140` released in Mako 1.3.7 as it produced + regressions in existing user code. + diff --git a/mako/codegen.py b/mako/codegen.py index c3804e6..b9fea93 100644 --- a/mako/codegen.py +++ b/mako/codegen.py @@ -16,7 +16,6 @@ from mako import filters from mako import parsetree from mako import util -from mako.filters import DEFAULT_ESCAPE_PREFIX from mako.pygen import PythonPrinter @@ -27,7 +26,6 @@ # context itself TOPLEVEL_DECLARED = {"UNDEFINED", "STOP_RENDERING"} RESERVED_NAMES = {"context", "loop"}.union(TOPLEVEL_DECLARED) -DEFAULT_ESCAPED_N = "%sn" % DEFAULT_ESCAPE_PREFIX def compile( # noqa @@ -524,7 +522,6 @@ def write_variable_declares(self, identifiers, toplevel=False, limit=None): self.printer.writeline("loop = __M_loop = runtime.LoopStack()") for ident in to_write: - ident = ident.replace(DEFAULT_ESCAPE_PREFIX, "") if ident in comp_idents: comp = comp_idents[ident] if comp.is_block: @@ -788,48 +785,25 @@ def locate_encode(name): else: return filters.DEFAULT_ESCAPES.get(name, name) - filter_args = set() - if DEFAULT_ESCAPED_N not in args: + if "n" not in args: if is_expression: if self.compiler.pagetag: args = self.compiler.pagetag.filter_args.args + args - filter_args = set(self.compiler.pagetag.filter_args.args) - if ( - self.compiler.default_filters - and DEFAULT_ESCAPED_N not in args - ): + if self.compiler.default_filters and "n" not in args: args = self.compiler.default_filters + args for e in args: - if e == DEFAULT_ESCAPED_N: - continue - - if e.startswith(DEFAULT_ESCAPE_PREFIX): - render_e = e.replace(DEFAULT_ESCAPE_PREFIX, "") - is_default_filter = True - else: - render_e = e - is_default_filter = False - # if filter given as a function, get just the identifier portion + if e == "n": + continue m = re.match(r"(.+?)(\(.*\))", e) if m: - if not is_default_filter: - ident, fargs = m.group(1, 2) - f = locate_encode(ident) - render_e = f + fargs - target = "%s(%s)" % (render_e, target) - elif is_default_filter and e not in filter_args: - target = "%s(%s) if %s is not UNDEFINED else %s(%s)" % ( - render_e, - target, - render_e, - locate_encode(render_e), - target, - ) + ident, fargs = m.group(1, 2) + f = locate_encode(ident) + e = f + fargs else: - e = locate_encode(render_e) + e = locate_encode(e) assert e is not None - target = "%s(%s)" % (e, target) + target = "%s(%s)" % (e, target) return target def visitExpression(self, node): diff --git a/mako/filters.py b/mako/filters.py index 33575e0..2a4b438 100644 --- a/mako/filters.py +++ b/mako/filters.py @@ -161,6 +161,3 @@ def htmlentityreplace_errors(ex): "str": "str", "n": "n", } - - -DEFAULT_ESCAPE_PREFIX = "__DEFAULT_ESCAPE_" diff --git a/mako/pyparser.py b/mako/pyparser.py index f5ed367..714e004 100644 --- a/mako/pyparser.py +++ b/mako/pyparser.py @@ -18,8 +18,6 @@ from mako import compat from mako import exceptions from mako import util -from mako.filters import DEFAULT_ESCAPE_PREFIX -from mako.filters import DEFAULT_ESCAPES # words that cannot be assigned to (notably # smaller than the total keys in __builtins__) @@ -198,24 +196,9 @@ def visit_Tuple(self, node): p.declared_identifiers ) lui = self.listener.undeclared_identifiers - undeclared_identifiers = lui.union(p.undeclared_identifiers) - conflict_identifiers = undeclared_identifiers.intersection( - DEFAULT_ESCAPES + self.listener.undeclared_identifiers = lui.union( + p.undeclared_identifiers ) - if conflict_identifiers: - _map = { - i: DEFAULT_ESCAPE_PREFIX + i for i in conflict_identifiers - } - for i, arg in enumerate(self.listener.args): - if arg in _map: - self.listener.args[i] = _map[arg] - self.listener.undeclared_identifiers = ( - undeclared_identifiers.symmetric_difference( - conflict_identifiers - ).union(_map.values()) - ) - else: - self.listener.undeclared_identifiers = undeclared_identifiers class ParseFunc(_ast_util.NodeVisitor): diff --git a/test/test_ast.py b/test/test_ast.py index 079c411..84e2338 100644 --- a/test/test_ast.py +++ b/test/test_ast.py @@ -285,36 +285,16 @@ def test_python_fragment(self): def test_argument_list(self): parsed = ast.ArgumentList( - "3, 5, 'hi', g+5, " "context.get('lala')", **exception_kwargs + "3, 5, 'hi', x+5, " "context.get('lala')", **exception_kwargs ) - eq_(parsed.undeclared_identifiers, {"g", "context"}) + eq_(parsed.undeclared_identifiers, {"x", "context"}) eq_( [x for x in parsed.args], - ["3", "5", "'hi'", "(g + 5)", "context.get('lala')"], + ["3", "5", "'hi'", "(x + 5)", "context.get('lala')"], ) - parsed = ast.ArgumentList("m", **exception_kwargs) - eq_(parsed.args, ["m"]) - - def test_conflict_argument_list(self): - parsed = ast.ArgumentList( - "x-2, h*2, '(u)', n+5, trim, entity, unicode, decode, str, other", - **exception_kwargs, - ) - eq_( - parsed.undeclared_identifiers, - { - "__DEFAULT_ESCAPE_trim", - "__DEFAULT_ESCAPE_h", - "__DEFAULT_ESCAPE_decode", - "__DEFAULT_ESCAPE_unicode", - "__DEFAULT_ESCAPE_x", - "__DEFAULT_ESCAPE_str", - "__DEFAULT_ESCAPE_entity", - "__DEFAULT_ESCAPE_n", - "other", - }, - ) + parsed = ast.ArgumentList("h", **exception_kwargs) + eq_(parsed.args, ["h"]) def test_function_decl(self): """test getting the arguments from a function""" diff --git a/test/test_filters.py b/test/test_filters.py index 2728361..726f5d7 100644 --- a/test/test_filters.py +++ b/test/test_filters.py @@ -453,36 +453,3 @@ def test_capture_ccall(self): # print t.render() assert flatten_result(t.render()) == "this is foo. body: ccall body" - - def test_conflict_filter_ident(self): - class h(object): - foo = str - - t = Template( - """ -X: - ${"asdf" | h.foo} -""" - ) - assert flatten_result(t.render(h=h)) == "X: asdf" - - def h(i): - return str(i) + "1" - - t = Template( - """ - ${123 | h} -""" - ) - assert flatten_result(t.render()) == "123" - assert flatten_result(t.render(h=h)) == "1231" - - t = Template( - """ - <%def name="foo()" filter="h"> - this is foo - ${foo()} -""" - ) - assert flatten_result(t.render()) == "this is foo" - assert flatten_result(t.render(h=h)) == "this is foo1" diff --git a/test/test_lexer.py b/test/test_lexer.py index 96b1bbe..aca6eab 100644 --- a/test/test_lexer.py +++ b/test/test_lexer.py @@ -1543,7 +1543,7 @@ def test_integration(self): Text(" \n", (14, 1)), ControlLine("for", "for x in j:", False, (15, 1)), Text(" Hello ", (16, 1)), - Expression("x", ["__DEFAULT_ESCAPE_h"], (16, 23)), + Expression("x", ["h"], (16, 23)), Text("\n", (16, 30)), ControlLine("for", "endfor", True, (17, 1)), Text(" \n", (18, 1)), diff --git a/test/test_template.py b/test/test_template.py index 4ab60d8..2b278fa 100644 --- a/test/test_template.py +++ b/test/test_template.py @@ -1,5 +1,7 @@ import os +import pytest + from mako import exceptions from mako import runtime from mako import util @@ -722,6 +724,27 @@ def handle(context, error): class UndefinedVarsTest(TemplateTest): + @pytest.mark.parametrize( + "filters", + [ + ["str", "n"], + ["n"], + ["str", "h"], + ["h"], + [], + ], + ) + def test_140_regression(self, filters): + """test #415, regression on #140""" + + t1 = Template( + "hello world ${x}", + strict_undefined=True, + default_filters=filters, + ) + + t1.render_unicode(x="hi") + def test_undefined(self): t = Template( """