diff --git a/mako/lexer.py b/mako/lexer.py index 34f17dc..15975b4 100644 --- a/mako/lexer.py +++ b/mako/lexer.py @@ -247,6 +247,8 @@ def parse(self): continue if self.match_python_block(): continue + if self.match_percent(): + continue if self.match_text(): continue @@ -352,14 +354,24 @@ def match_end(self): else: return True + def match_percent(self): + match = self.match(r"(?<=^)(\s*)%%(%*)", re.M) + if match: + self.append_node( + parsetree.Text, match.group(1) + "%" + match.group(2) + ) + return True + else: + return False + def match_text(self): match = self.match( r""" (.*?) # anything, followed by: ( - (?<=\n)(?=[ \t]*(?=%|\#\#)) # an eval or line-based - # comment preceded by a - # consumed newline and whitespace + (?<=\n)(?=[ \t]*(?=%|\#\#)) # an eval or line-based + # comment, preceded by a + # consumed newline and whitespace | (?=\${) # an expression | diff --git a/mako/testing/helpers.py b/mako/testing/helpers.py index 77cca36..5ae9d38 100644 --- a/mako/testing/helpers.py +++ b/mako/testing/helpers.py @@ -19,6 +19,10 @@ def result_lines(result): ] +def result_raw_lines(result): + return [x for x in re.split(r"\r?\n", result) if x.strip() != ""] + + def make_path( filespec: Union[Path, str], make_absolute: bool = True, diff --git a/test/test_lexer.py b/test/test_lexer.py index f4983a3..66c0a1d 100644 --- a/test/test_lexer.py +++ b/test/test_lexer.py @@ -200,9 +200,10 @@ def test_percent_escape(self): TemplateNode( {}, [ - Text("""\n\n""", (1, 1)), - Text("""% some whatever.\n\n""", (3, 2)), - Text(" %% more some whatever\n", (5, 2)), + Text("\n\n%", (1, 1)), + Text(" some whatever.\n\n", (3, 3)), + Text(" %", (5, 1)), + Text(" more some whatever\n", (5, 7)), ControlLine("if", "if foo:", False, (6, 1)), ControlLine("if", "endif", True, (7, 1)), Text(" ", (8, 1)), @@ -210,6 +211,92 @@ def test_percent_escape(self): ), ) + def test_percent_escape2(self): + template = """%% do something +%%% do something +if : + %%%% do something + """ + node = Lexer(template).parse() + self._compare( + node, + TemplateNode( + {}, + [ + Text("%", (1, 1)), + Text(" do something\n", (1, 3)), + Text("%%", (2, 1)), + Text(" do something\nif :\n", (2, 4)), + Text(" %%%", (4, 1)), + Text(" do something\n ", (4, 9)), + ], + ), + ) + + def test_percent_escape_with_control_block(self): + template = """ +% for i in [1, 2, 3]: + %% do something ${i} +% endfor +""" + node = Lexer(template).parse() + self._compare( + node, + TemplateNode( + {}, + [ + Text("\n", (1, 1)), + ControlLine("for", "for i in [1, 2, 3]:", False, (2, 1)), + Text(" %", (3, 1)), + Text(" do something ", (3, 7)), + Expression("i", [], (3, 21)), + Text("\n", (3, 25)), + ControlLine("for", "endfor", True, (4, 1)), + ], + ), + ) + + def test_inline_percent(self): + template = """ +%% foo +bar %% baz +""" + node = Lexer(template).parse() + self._compare( + node, + TemplateNode( + {}, + [Text("\n%", (1, 1)), Text(" foo\nbar %% baz\n", (2, 3))], + ), + ) + + def test_inline_percent_with_control_block(self): + template = """ +% for i in [1, 2, 3]: +%% foo +bar %% baz +% endfor +""" + node = Lexer(template).parse() + self._compare( + node, + TemplateNode( + {}, + [ + Text("\n", (1, 1)), + ControlLine( + "for", "for i in [1, 2, 3]:", False, (2, 1) + ), + Text("%", (3, 1)), + Text(" foo\nbar ", (3, 3)), + Text("%", (3, 3)), + Text("%", (3, 3)), + Text(" baz\n", (4, 7)), + ControlLine("for", "endfor", True, (5, 1)), + ], + ), + ) + def test_old_multiline_comment(self): template = """#*""" node = Lexer(template).parse() diff --git a/test/test_template.py b/test/test_template.py index 62fd21d..e03415e 100644 --- a/test/test_template.py +++ b/test/test_template.py @@ -15,6 +15,7 @@ from mako.testing.fixtures import TemplateTest from mako.testing.helpers import flatten_result from mako.testing.helpers import result_lines +from mako.testing.helpers import result_raw_lines class ctx: @@ -1667,3 +1668,52 @@ class FuturesTest(TemplateTest): def test_future_import(self): t = Template("${ x / y }", future_imports=["division"]) assert result_lines(t.render(x=12, y=5)) == ["2.4"] + + +class EscapeTest(TemplateTest): + def test_percent_escape(self): + t = Template( + """%% do something +%%% do something +if : + %%%% do something +""" + ) + assert result_raw_lines(t.render()) == [ + "% do something", + "%% do something", + "if :", + " %%% do something", + ] + + def test_percent_escape2(self): + t = Template( + """ +% for i in [1, 2, 3]: + %% do something ${i} +% endfor +""" + ) + assert result_raw_lines(t.render()) == [ + " % do something 1", + " % do something 2", + " % do something 3", + ] + + def test_inline_percent(self): + t = Template( + """ +% for i in [1, 2, 3]: +%% foo +bar %% baz +% endfor +""" + ) + assert result_raw_lines(t.render()) == [ + "% foo", + "bar %% baz", + "% foo", + "bar %% baz", + "% foo", + "bar %% baz", + ]