1import re 2 3import pytest 4 5from mako import compat 6from mako import exceptions 7from mako import parsetree 8from mako import util 9from mako.lexer import Lexer 10from mako.template import Template 11from mako.testing.assertions import assert_raises 12from mako.testing.assertions import assert_raises_message 13from mako.testing.assertions import eq_ 14from mako.testing.fixtures import TemplateTest 15from mako.testing.helpers import flatten_result 16 17# create fake parsetree classes which are constructed 18# exactly as the repr() of a real parsetree object. 19# this allows us to use a Python construct as the source 20# of a comparable repr(), which is also hit by the 2to3 tool. 21 22 23def repr_arg(x): 24 if isinstance(x, dict): 25 return util.sorted_dict_repr(x) 26 else: 27 return repr(x) 28 29 30def _as_unicode(arg): 31 if isinstance(arg, dict): 32 return {k: _as_unicode(v) for k, v in arg.items()} 33 else: 34 return arg 35 36 37Node = None 38TemplateNode = None 39ControlLine = None 40Text = None 41Code = None 42Comment = None 43Expression = None 44_TagMeta = None 45Tag = None 46IncludeTag = None 47NamespaceTag = None 48TextTag = None 49DefTag = None 50BlockTag = None 51CallTag = None 52CallNamespaceTag = None 53InheritTag = None 54PageTag = None 55 56# go through all the elements in parsetree and build out 57# mocks of them 58for cls in list(parsetree.__dict__.values()): 59 if isinstance(cls, type) and issubclass(cls, parsetree.Node): 60 clsname = cls.__name__ 61 exec( 62 ( 63 """ 64class %s: 65 def __init__(self, *args): 66 self.args = [_as_unicode(arg) for arg in args] 67 def __repr__(self): 68 return "%%s(%%s)" %% ( 69 self.__class__.__name__, 70 ", ".join(repr_arg(x) for x in self.args) 71 ) 72""" 73 % clsname 74 ), 75 locals(), 76 ) 77 78# NOTE: most assertion expressions were generated, then formatted 79# by PyTidy, hence the dense formatting. 80 81 82class LexerTest(TemplateTest): 83 def _compare(self, node, expected): 84 eq_(repr(node), repr(expected)) 85 86 def test_text_and_tag(self): 87 template = """ 88<b>Hello world</b> 89 <%def name="foo()"> 90 this is a def. 91 </%def> 92 93 and some more text. 94""" 95 node = Lexer(template).parse() 96 self._compare( 97 node, 98 TemplateNode( 99 {}, 100 [ 101 Text("""\n<b>Hello world</b>\n """, (1, 1)), 102 DefTag( 103 "def", 104 {"name": "foo()"}, 105 (3, 9), 106 [ 107 Text( 108 "\n this is a def.\n ", 109 (3, 28), 110 ) 111 ], 112 ), 113 Text("""\n\n and some more text.\n""", (5, 16)), 114 ], 115 ), 116 ) 117 118 def test_unclosed_tag(self): 119 template = """ 120 121 <%def name="foo()"> 122 other text 123 """ 124 try: 125 Lexer(template).parse() 126 assert False 127 except exceptions.SyntaxException: 128 eq_( 129 str(compat.exception_as()), 130 "Unclosed tag: <%def> at line: 5 char: 9", 131 ) 132 133 def test_onlyclosed_tag(self): 134 template = """ 135 <%def name="foo()"> 136 foo 137 </%def> 138 139 </%namespace> 140 141 hi. 142 """ 143 assert_raises(exceptions.SyntaxException, Lexer(template).parse) 144 145 def test_noexpr_allowed(self): 146 template = """ 147 <%namespace name="${foo}"/> 148 """ 149 assert_raises(exceptions.CompileException, Lexer(template).parse) 150 151 def test_closing_tag_many_spaces(self): 152 """test #367""" 153 template = '<%def name="foo()"> this is a def. </%' + " " * 10000 154 assert_raises(exceptions.SyntaxException, Lexer(template).parse) 155 156 def test_opening_tag_many_quotes(self): 157 """test #366""" 158 template = "<%0" + '"' * 3000 159 assert_raises(exceptions.SyntaxException, Lexer(template).parse) 160 161 def test_unmatched_tag(self): 162 template = """ 163 <%namespace name="bar"> 164 <%def name="foo()"> 165 foo 166 </%namespace> 167 </%def> 168 169 170 hi. 171""" 172 assert_raises(exceptions.SyntaxException, Lexer(template).parse) 173 174 def test_nonexistent_tag(self): 175 template = """ 176 <%lala x="5"/> 177 """ 178 assert_raises(exceptions.CompileException, Lexer(template).parse) 179 180 def test_wrongcase_tag(self): 181 template = """ 182 <%DEF name="foo()"> 183 </%def> 184 185 """ 186 assert_raises(exceptions.CompileException, Lexer(template).parse) 187 188 def test_percent_escape(self): 189 template = """ 190 191%% some whatever. 192 193 %% more some whatever 194 % if foo: 195 % endif 196 """ 197 node = Lexer(template).parse() 198 self._compare( 199 node, 200 TemplateNode( 201 {}, 202 [ 203 Text("""\n\n""", (1, 1)), 204 Text("""% some whatever.\n\n""", (3, 2)), 205 Text(" %% more some whatever\n", (5, 2)), 206 ControlLine("if", "if foo:", False, (6, 1)), 207 ControlLine("if", "endif", True, (7, 1)), 208 Text(" ", (8, 1)), 209 ], 210 ), 211 ) 212 213 def test_old_multiline_comment(self): 214 template = """#*""" 215 node = Lexer(template).parse() 216 self._compare(node, TemplateNode({}, [Text("""#*""", (1, 1))])) 217 218 def test_text_tag(self): 219 template = """ 220 ## comment 221 % if foo: 222 hi 223 % endif 224 <%text> 225 # more code 226 227 % more code 228 <%illegal compionent>/></> 229 <%def name="laal()">def</%def> 230 231 232 </%text> 233 234 <%def name="foo()">this is foo</%def> 235 236 % if bar: 237 code 238 % endif 239 """ 240 node = Lexer(template).parse() 241 self._compare( 242 node, 243 TemplateNode( 244 {}, 245 [ 246 Text("\n", (1, 1)), 247 Comment("comment", (2, 1)), 248 ControlLine("if", "if foo:", False, (3, 1)), 249 Text(" hi\n", (4, 1)), 250 ControlLine("if", "endif", True, (5, 1)), 251 Text(" ", (6, 1)), 252 TextTag( 253 "text", 254 {}, 255 (6, 9), 256 [ 257 Text( 258 "\n # more code\n\n " 259 " % more code\n " 260 "<%illegal compionent>/></>\n" 261 ' <%def name="laal()">def</%def>' 262 "\n\n\n ", 263 (6, 16), 264 ) 265 ], 266 ), 267 Text("\n\n ", (14, 17)), 268 DefTag( 269 "def", 270 {"name": "foo()"}, 271 (16, 9), 272 [Text("this is foo", (16, 28))], 273 ), 274 Text("\n\n", (16, 46)), 275 ControlLine("if", "if bar:", False, (18, 1)), 276 Text(" code\n", (19, 1)), 277 ControlLine("if", "endif", True, (20, 1)), 278 Text(" ", (21, 1)), 279 ], 280 ), 281 ) 282 283 def test_def_syntax(self): 284 template = """ 285 <%def lala> 286 hi 287 </%def> 288""" 289 assert_raises(exceptions.CompileException, Lexer(template).parse) 290 291 def test_def_syntax_2(self): 292 template = """ 293 <%def name="lala"> 294 hi 295 </%def> 296 """ 297 assert_raises(exceptions.CompileException, Lexer(template).parse) 298 299 def test_whitespace_equals(self): 300 template = """ 301 <%def name = "adef()" > 302 adef 303 </%def> 304 """ 305 node = Lexer(template).parse() 306 self._compare( 307 node, 308 TemplateNode( 309 {}, 310 [ 311 Text("\n ", (1, 1)), 312 DefTag( 313 "def", 314 {"name": "adef()"}, 315 (2, 13), 316 [ 317 Text( 318 """\n adef\n """, 319 (2, 36), 320 ) 321 ], 322 ), 323 Text("\n ", (4, 20)), 324 ], 325 ), 326 ) 327 328 def test_ns_tag_closed(self): 329 template = """ 330 331 <%self:go x="1" y="2" z="${'hi' + ' ' + 'there'}"/> 332 """ 333 nodes = Lexer(template).parse() 334 self._compare( 335 nodes, 336 TemplateNode( 337 {}, 338 [ 339 Text( 340 """ 341 342 """, 343 (1, 1), 344 ), 345 CallNamespaceTag( 346 "self:go", 347 {"x": "1", "y": "2", "z": "${'hi' + ' ' + 'there'}"}, 348 (3, 13), 349 [], 350 ), 351 Text("\n ", (3, 64)), 352 ], 353 ), 354 ) 355 356 def test_ns_tag_empty(self): 357 template = """ 358 <%form:option value=""></%form:option> 359 """ 360 nodes = Lexer(template).parse() 361 self._compare( 362 nodes, 363 TemplateNode( 364 {}, 365 [ 366 Text("\n ", (1, 1)), 367 CallNamespaceTag( 368 "form:option", {"value": ""}, (2, 13), [] 369 ), 370 Text("\n ", (2, 51)), 371 ], 372 ), 373 ) 374 375 def test_ns_tag_open(self): 376 template = """ 377 378 <%self:go x="1" y="${process()}"> 379 this is the body 380 </%self:go> 381 """ 382 nodes = Lexer(template).parse() 383 self._compare( 384 nodes, 385 TemplateNode( 386 {}, 387 [ 388 Text( 389 """ 390 391 """, 392 (1, 1), 393 ), 394 CallNamespaceTag( 395 "self:go", 396 {"x": "1", "y": "${process()}"}, 397 (3, 13), 398 [ 399 Text( 400 """ 401 this is the body 402 """, 403 (3, 46), 404 ) 405 ], 406 ), 407 Text("\n ", (5, 24)), 408 ], 409 ), 410 ) 411 412 def test_expr_in_attribute(self): 413 """test some slightly trickier expressions. 414 415 you can still trip up the expression parsing, though, unless we 416 integrated really deeply somehow with AST.""" 417 418 template = """ 419 <%call expr="foo>bar and 'lala' or 'hoho'"/> 420 <%call expr='foo<bar and hoho>lala and "x" + "y"'/> 421 """ 422 nodes = Lexer(template).parse() 423 self._compare( 424 nodes, 425 TemplateNode( 426 {}, 427 [ 428 Text("\n ", (1, 1)), 429 CallTag( 430 "call", 431 {"expr": "foo>bar and 'lala' or 'hoho'"}, 432 (2, 13), 433 [], 434 ), 435 Text("\n ", (2, 57)), 436 CallTag( 437 "call", 438 {"expr": 'foo<bar and hoho>lala and "x" + "y"'}, 439 (3, 13), 440 [], 441 ), 442 Text("\n ", (3, 64)), 443 ], 444 ), 445 ) 446 447 @pytest.mark.parametrize("comma,numchars", [(",", 48), ("", 47)]) 448 def test_pagetag(self, comma, numchars): 449 # note that the comma here looks like: 450 # <%page cached="True", args="a, b"/> 451 # that's what this test has looked like for decades, however, the 452 # comma there is not actually the right syntax. When issue #366 453 # was fixed, the reg was altered to accommodate for this comma to allow 454 # backwards compat 455 template = f""" 456 <%page cached="True"{comma} args="a, b"/> 457 458 some template 459 """ 460 nodes = Lexer(template).parse() 461 self._compare( 462 nodes, 463 TemplateNode( 464 {}, 465 [ 466 Text("\n ", (1, 1)), 467 PageTag( 468 "page", {"args": "a, b", "cached": "True"}, (2, 13), [] 469 ), 470 Text( 471 """ 472 473 some template 474 """, 475 (2, numchars), 476 ), 477 ], 478 ), 479 ) 480 481 def test_nesting(self): 482 template = """ 483 484 <%namespace name="ns"> 485 <%def name="lala(hi, there)"> 486 <%call expr="something()"/> 487 </%def> 488 </%namespace> 489 490 """ 491 nodes = Lexer(template).parse() 492 self._compare( 493 nodes, 494 TemplateNode( 495 {}, 496 [ 497 Text( 498 """ 499 500 """, 501 (1, 1), 502 ), 503 NamespaceTag( 504 "namespace", 505 {"name": "ns"}, 506 (3, 9), 507 [ 508 Text("\n ", (3, 31)), 509 DefTag( 510 "def", 511 {"name": "lala(hi, there)"}, 512 (4, 13), 513 [ 514 Text("\n ", (4, 42)), 515 CallTag( 516 "call", 517 {"expr": "something()"}, 518 (5, 17), 519 [], 520 ), 521 Text("\n ", (5, 44)), 522 ], 523 ), 524 Text("\n ", (6, 20)), 525 ], 526 ), 527 Text( 528 """ 529 530 """, 531 (7, 22), 532 ), 533 ], 534 ), 535 ) 536 537 def test_code(self): 538 template = """text 539 <% 540 print("hi") 541 for x in range(1,5): 542 print(x) 543 %> 544more text 545 <%! 546 import foo 547 %> 548""" 549 nodes = Lexer(template).parse() 550 self._compare( 551 nodes, 552 TemplateNode( 553 {}, 554 [ 555 Text("text\n ", (1, 1)), 556 Code( 557 '\nprint("hi")\nfor x in range(1,5):\n ' 558 "print(x)\n \n", 559 False, 560 (2, 5), 561 ), 562 Text("\nmore text\n ", (6, 7)), 563 Code("\nimport foo\n \n", True, (8, 5)), 564 Text("\n", (10, 7)), 565 ], 566 ), 567 ) 568 569 def test_code_and_tags(self): 570 template = """ 571<%namespace name="foo"> 572 <%def name="x()"> 573 this is x 574 </%def> 575 <%def name="y()"> 576 this is y 577 </%def> 578</%namespace> 579 580<% 581 result = [] 582 data = get_data() 583 for x in data: 584 result.append(x+7) 585%> 586 587 result: <%call expr="foo.x(result)"/> 588""" 589 nodes = Lexer(template).parse() 590 self._compare( 591 nodes, 592 TemplateNode( 593 {}, 594 [ 595 Text("\n", (1, 1)), 596 NamespaceTag( 597 "namespace", 598 {"name": "foo"}, 599 (2, 1), 600 [ 601 Text("\n ", (2, 24)), 602 DefTag( 603 "def", 604 {"name": "x()"}, 605 (3, 5), 606 [ 607 Text( 608 """\n this is x\n """, 609 (3, 22), 610 ) 611 ], 612 ), 613 Text("\n ", (5, 12)), 614 DefTag( 615 "def", 616 {"name": "y()"}, 617 (6, 5), 618 [ 619 Text( 620 """\n this is y\n """, 621 (6, 22), 622 ) 623 ], 624 ), 625 Text("\n", (8, 12)), 626 ], 627 ), 628 Text("""\n\n""", (9, 14)), 629 Code( 630 """\nresult = []\ndata = get_data()\n""" 631 """for x in data:\n result.append(x+7)\n\n""", 632 False, 633 (11, 1), 634 ), 635 Text("""\n\n result: """, (16, 3)), 636 CallTag("call", {"expr": "foo.x(result)"}, (18, 13), []), 637 Text("\n", (18, 42)), 638 ], 639 ), 640 ) 641 642 def test_expression(self): 643 template = """ 644 this is some ${text} and this is ${textwith | escapes, moreescapes} 645 <%def name="hi()"> 646 give me ${foo()} and ${bar()} 647 </%def> 648 ${hi()} 649""" 650 nodes = Lexer(template).parse() 651 self._compare( 652 nodes, 653 TemplateNode( 654 {}, 655 [ 656 Text("\n this is some ", (1, 1)), 657 Expression("text", [], (2, 22)), 658 Text(" and this is ", (2, 29)), 659 Expression( 660 "textwith ", ["escapes", "moreescapes"], (2, 42) 661 ), 662 Text("\n ", (2, 76)), 663 DefTag( 664 "def", 665 {"name": "hi()"}, 666 (3, 9), 667 [ 668 Text("\n give me ", (3, 27)), 669 Expression("foo()", [], (4, 21)), 670 Text(" and ", (4, 29)), 671 Expression("bar()", [], (4, 34)), 672 Text("\n ", (4, 42)), 673 ], 674 ), 675 Text("\n ", (5, 16)), 676 Expression("hi()", [], (6, 9)), 677 Text("\n", (6, 16)), 678 ], 679 ), 680 ) 681 682 def test_tricky_expression(self): 683 template = """ 684 685 ${x and "|" or "hi"} 686 """ 687 nodes = Lexer(template).parse() 688 self._compare( 689 nodes, 690 TemplateNode( 691 {}, 692 [ 693 Text("\n\n ", (1, 1)), 694 Expression('x and "|" or "hi"', [], (3, 13)), 695 Text("\n ", (3, 33)), 696 ], 697 ), 698 ) 699 700 template = r""" 701 702 ${hello + '''heres '{|}' text | | }''' | escape1} 703 ${'Tricky string: ' + '\\\"\\\'|\\'} 704 """ 705 nodes = Lexer(template).parse() 706 self._compare( 707 nodes, 708 TemplateNode( 709 {}, 710 [ 711 Text("\n\n ", (1, 1)), 712 Expression( 713 "hello + '''heres '{|}' text | | }''' ", 714 ["escape1"], 715 (3, 13), 716 ), 717 Text("\n ", (3, 62)), 718 Expression( 719 r"""'Tricky string: ' + '\\\"\\\'|\\'""", [], (4, 13) 720 ), 721 Text("\n ", (4, 49)), 722 ], 723 ), 724 ) 725 726 def test_tricky_code(self): 727 template = """<% print('hi %>') %>""" 728 nodes = Lexer(template).parse() 729 self._compare( 730 nodes, TemplateNode({}, [Code("print('hi %>') \n", False, (1, 1))]) 731 ) 732 733 def test_tricky_code_2(self): 734 template = """<% 735 # someone's comment 736%> 737 """ 738 nodes = Lexer(template).parse() 739 self._compare( 740 nodes, 741 TemplateNode( 742 {}, 743 [ 744 Code( 745 """ 746 # someone's comment 747 748""", 749 False, 750 (1, 1), 751 ), 752 Text("\n ", (3, 3)), 753 ], 754 ), 755 ) 756 757 def test_tricky_code_3(self): 758 template = """<% 759 print('hi') 760 # this is a comment 761 # another comment 762 x = 7 # someone's '''comment 763 print(''' 764 there 765 ''') 766 # someone else's comment 767%> '''and now some text '''""" 768 nodes = Lexer(template).parse() 769 self._compare( 770 nodes, 771 TemplateNode( 772 {}, 773 [ 774 Code( 775 """ 776print('hi') 777# this is a comment 778# another comment 779x = 7 # someone's '''comment 780print(''' 781 there 782 ''') 783# someone else's comment 784 785""", 786 False, 787 (1, 1), 788 ), 789 Text(" '''and now some text '''", (10, 3)), 790 ], 791 ), 792 ) 793 794 def test_tricky_code_4(self): 795 template = """<% foo = "\\"\\\\" %>""" 796 nodes = Lexer(template).parse() 797 self._compare( 798 nodes, 799 TemplateNode({}, [Code("""foo = "\\"\\\\" \n""", False, (1, 1))]), 800 ) 801 802 def test_tricky_code_5(self): 803 template = """before ${ {'key': 'value'} } after""" 804 nodes = Lexer(template).parse() 805 self._compare( 806 nodes, 807 TemplateNode( 808 {}, 809 [ 810 Text("before ", (1, 1)), 811 Expression(" {'key': 'value'} ", [], (1, 8)), 812 Text(" after", (1, 29)), 813 ], 814 ), 815 ) 816 817 def test_tricky_code_6(self): 818 template = """before ${ (0x5302 | 0x0400) } after""" 819 nodes = Lexer(template).parse() 820 self._compare( 821 nodes, 822 TemplateNode( 823 {}, 824 [ 825 Text("before ", (1, 1)), 826 Expression(" (0x5302 | 0x0400) ", [], (1, 8)), 827 Text(" after", (1, 30)), 828 ], 829 ), 830 ) 831 832 def test_control_lines(self): 833 template = """ 834text text la la 835% if foo(): 836 mroe text la la blah blah 837% endif 838 839 and osme more stuff 840 % for l in range(1,5): 841 tex tesl asdl l is ${l} kfmas d 842 % endfor 843 tetx text 844 845""" 846 nodes = Lexer(template).parse() 847 self._compare( 848 nodes, 849 TemplateNode( 850 {}, 851 [ 852 Text("""\ntext text la la\n""", (1, 1)), 853 ControlLine("if", "if foo():", False, (3, 1)), 854 Text(" mroe text la la blah blah\n", (4, 1)), 855 ControlLine("if", "endif", True, (5, 1)), 856 Text("""\n and osme more stuff\n""", (6, 1)), 857 ControlLine("for", "for l in range(1,5):", False, (8, 1)), 858 Text(" tex tesl asdl l is ", (9, 1)), 859 Expression("l", [], (9, 24)), 860 Text(" kfmas d\n", (9, 28)), 861 ControlLine("for", "endfor", True, (10, 1)), 862 Text(""" tetx text\n\n""", (11, 1)), 863 ], 864 ), 865 ) 866 867 def test_control_lines_2(self): 868 template = """% for file in requestattr['toc'].filenames: 869 x 870% endfor 871""" 872 nodes = Lexer(template).parse() 873 self._compare( 874 nodes, 875 TemplateNode( 876 {}, 877 [ 878 ControlLine( 879 "for", 880 "for file in requestattr['toc'].filenames:", 881 False, 882 (1, 1), 883 ), 884 Text(" x\n", (2, 1)), 885 ControlLine("for", "endfor", True, (3, 1)), 886 ], 887 ), 888 ) 889 890 def test_long_control_lines(self): 891 template = """ 892 % for file in \\ 893 requestattr['toc'].filenames: 894 x 895 % endfor 896 """ 897 nodes = Lexer(template).parse() 898 self._compare( 899 nodes, 900 TemplateNode( 901 {}, 902 [ 903 Text("\n", (1, 1)), 904 ControlLine( 905 "for", 906 "for file in \\\n " 907 "requestattr['toc'].filenames:", 908 False, 909 (2, 1), 910 ), 911 Text(" x\n", (4, 1)), 912 ControlLine("for", "endfor", True, (5, 1)), 913 Text(" ", (6, 1)), 914 ], 915 ), 916 ) 917 918 def test_unmatched_control(self): 919 template = """ 920 921 % if foo: 922 % for x in range(1,5): 923 % endif 924""" 925 assert_raises_message( 926 exceptions.SyntaxException, 927 "Keyword 'endif' doesn't match keyword 'for' at line: 5 char: 1", 928 Lexer(template).parse, 929 ) 930 931 def test_unmatched_control_2(self): 932 template = """ 933 934 % if foo: 935 % for x in range(1,5): 936 % endfor 937""" 938 939 assert_raises_message( 940 exceptions.SyntaxException, 941 "Unterminated control keyword: 'if' at line: 3 char: 1", 942 Lexer(template).parse, 943 ) 944 945 def test_unmatched_control_3(self): 946 template = """ 947 948 % if foo: 949 % for x in range(1,5): 950 % endlala 951 % endif 952""" 953 assert_raises_message( 954 exceptions.SyntaxException, 955 "Keyword 'endlala' doesn't match keyword 'for' at line: 5 char: 1", 956 Lexer(template).parse, 957 ) 958 959 def test_ternary_control(self): 960 template = """ 961 % if x: 962 hi 963 % elif y+7==10: 964 there 965 % elif lala: 966 lala 967 % else: 968 hi 969 % endif 970""" 971 nodes = Lexer(template).parse() 972 self._compare( 973 nodes, 974 TemplateNode( 975 {}, 976 [ 977 Text("\n", (1, 1)), 978 ControlLine("if", "if x:", False, (2, 1)), 979 Text(" hi\n", (3, 1)), 980 ControlLine("elif", "elif y+7==10:", False, (4, 1)), 981 Text(" there\n", (5, 1)), 982 ControlLine("elif", "elif lala:", False, (6, 1)), 983 Text(" lala\n", (7, 1)), 984 ControlLine("else", "else:", False, (8, 1)), 985 Text(" hi\n", (9, 1)), 986 ControlLine("if", "endif", True, (10, 1)), 987 ], 988 ), 989 ) 990 991 def test_integration(self): 992 template = """<%namespace name="foo" file="somefile.html"/> 993 ## inherit from foobar.html 994<%inherit file="foobar.html"/> 995 996<%def name="header()"> 997 <div>header</div> 998</%def> 999<%def name="footer()"> 1000 <div> footer</div> 1001</%def> 1002 1003<table> 1004 % for j in data(): 1005 <tr> 1006 % for x in j: 1007 <td>Hello ${x| h}</td> 1008 % endfor 1009 </tr> 1010 % endfor 1011</table> 1012""" 1013 nodes = Lexer(template).parse() 1014 self._compare( 1015 nodes, 1016 TemplateNode( 1017 {}, 1018 [ 1019 NamespaceTag( 1020 "namespace", 1021 {"file": "somefile.html", "name": "foo"}, 1022 (1, 1), 1023 [], 1024 ), 1025 Text("\n", (1, 46)), 1026 Comment("inherit from foobar.html", (2, 1)), 1027 InheritTag("inherit", {"file": "foobar.html"}, (3, 1), []), 1028 Text("""\n\n""", (3, 31)), 1029 DefTag( 1030 "def", 1031 {"name": "header()"}, 1032 (5, 1), 1033 [Text("""\n <div>header</div>\n""", (5, 23))], 1034 ), 1035 Text("\n", (7, 8)), 1036 DefTag( 1037 "def", 1038 {"name": "footer()"}, 1039 (8, 1), 1040 [Text("""\n <div> footer</div>\n""", (8, 23))], 1041 ), 1042 Text("""\n\n<table>\n""", (10, 8)), 1043 ControlLine("for", "for j in data():", False, (13, 1)), 1044 Text(" <tr>\n", (14, 1)), 1045 ControlLine("for", "for x in j:", False, (15, 1)), 1046 Text(" <td>Hello ", (16, 1)), 1047 Expression("x", ["h"], (16, 23)), 1048 Text("</td>\n", (16, 30)), 1049 ControlLine("for", "endfor", True, (17, 1)), 1050 Text(" </tr>\n", (18, 1)), 1051 ControlLine("for", "endfor", True, (19, 1)), 1052 Text("</table>\n", (20, 1)), 1053 ], 1054 ), 1055 ) 1056 1057 def test_comment_after_statement(self): 1058 template = """ 1059 % if x: #comment 1060 hi 1061 % else: #next 1062 hi 1063 % endif #end 1064""" 1065 nodes = Lexer(template).parse() 1066 self._compare( 1067 nodes, 1068 TemplateNode( 1069 {}, 1070 [ 1071 Text("\n", (1, 1)), 1072 ControlLine("if", "if x: #comment", False, (2, 1)), 1073 Text(" hi\n", (3, 1)), 1074 ControlLine("else", "else: #next", False, (4, 1)), 1075 Text(" hi\n", (5, 1)), 1076 ControlLine("if", "endif #end", True, (6, 1)), 1077 ], 1078 ), 1079 ) 1080 1081 def test_crlf(self): 1082 template = util.read_file(self._file_path("crlf.html")) 1083 nodes = Lexer(template).parse() 1084 self._compare( 1085 nodes, 1086 TemplateNode( 1087 {}, 1088 [ 1089 Text("<html>\r\n\r\n", (1, 1)), 1090 PageTag( 1091 "page", 1092 {"args": "a=['foo',\n 'bar']"}, 1093 (3, 1), 1094 [], 1095 ), 1096 Text("\r\n\r\nlike the name says.\r\n\r\n", (4, 26)), 1097 ControlLine("for", "for x in [1,2,3]:", False, (8, 1)), 1098 Text(" ", (9, 1)), 1099 Expression("x", [], (9, 9)), 1100 ControlLine("for", "endfor", True, (10, 1)), 1101 Text("\r\n", (11, 1)), 1102 Expression( 1103 "trumpeter == 'Miles' and " 1104 "trumpeter or \\\n 'Dizzy'", 1105 [], 1106 (12, 1), 1107 ), 1108 Text("\r\n\r\n", (13, 15)), 1109 DefTag( 1110 "def", 1111 {"name": "hi()"}, 1112 (15, 1), 1113 [Text("\r\n hi!\r\n", (15, 19))], 1114 ), 1115 Text("\r\n\r\n</html>\r\n", (17, 8)), 1116 ], 1117 ), 1118 ) 1119 assert ( 1120 flatten_result(Template(template).render()) 1121 == """<html> like the name says. 1 2 3 Dizzy </html>""" 1122 ) 1123 1124 def test_comments(self): 1125 template = """ 1126<style> 1127 #someselector 1128 # other non comment stuff 1129</style> 1130## a comment 1131 1132# also not a comment 1133 1134 ## this is a comment 1135 1136this is ## not a comment 1137 1138<%doc> multiline 1139comment 1140</%doc> 1141 1142hi 1143""" 1144 nodes = Lexer(template).parse() 1145 self._compare( 1146 nodes, 1147 TemplateNode( 1148 {}, 1149 [ 1150 Text( 1151 """\n<style>\n #someselector\n # """ 1152 """other non comment stuff\n</style>\n""", 1153 (1, 1), 1154 ), 1155 Comment("a comment", (6, 1)), 1156 Text("""\n# also not a comment\n\n""", (7, 1)), 1157 Comment("this is a comment", (10, 1)), 1158 Text("""\nthis is ## not a comment\n\n""", (11, 1)), 1159 Comment(""" multiline\ncomment\n""", (14, 1)), 1160 Text( 1161 """ 1162 1163hi 1164""", 1165 (16, 8), 1166 ), 1167 ], 1168 ), 1169 ) 1170 1171 def test_docs(self): 1172 template = """ 1173 <%doc> 1174 this is a comment 1175 </%doc> 1176 <%def name="foo()"> 1177 <%doc> 1178 this is the foo func 1179 </%doc> 1180 </%def> 1181 """ 1182 nodes = Lexer(template).parse() 1183 self._compare( 1184 nodes, 1185 TemplateNode( 1186 {}, 1187 [ 1188 Text("\n ", (1, 1)), 1189 Comment( 1190 """\n this is a comment\n """, (2, 9) 1191 ), 1192 Text("\n ", (4, 16)), 1193 DefTag( 1194 "def", 1195 {"name": "foo()"}, 1196 (5, 9), 1197 [ 1198 Text("\n ", (5, 28)), 1199 Comment( 1200 """\n this is the foo func\n""" 1201 """ """, 1202 (6, 13), 1203 ), 1204 Text("\n ", (8, 20)), 1205 ], 1206 ), 1207 Text("\n ", (9, 16)), 1208 ], 1209 ), 1210 ) 1211 1212 def test_preprocess(self): 1213 def preproc(text): 1214 return re.sub(r"(?<=\n)\s*#[^#]", "##", text) 1215 1216 template = """ 1217 hi 1218 # old style comment 1219# another comment 1220""" 1221 nodes = Lexer(template, preprocessor=preproc).parse() 1222 self._compare( 1223 nodes, 1224 TemplateNode( 1225 {}, 1226 [ 1227 Text("""\n hi\n""", (1, 1)), 1228 Comment("old style comment", (3, 1)), 1229 Comment("another comment", (4, 1)), 1230 ], 1231 ), 1232 ) 1233