1import os
2
3from mako import exceptions
4from mako import runtime
5from mako import util
6from mako.ext.preprocessors import convert_comments
7from mako.lookup import TemplateLookup
8from mako.template import ModuleInfo
9from mako.template import ModuleTemplate
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.config import config
15from mako.testing.fixtures import TemplateTest
16from mako.testing.helpers import flatten_result
17from mako.testing.helpers import result_lines
18
19
20class ctx:
21    def __init__(self, a, b):
22        pass
23
24    def __enter__(self):
25        return self
26
27    def __exit__(self, *arg):
28        pass
29
30
31class MiscTest(TemplateTest):
32    def test_crlf_linebreaks(self):
33        crlf = r"""
34<%
35    foo = True
36    bar = True
37%>
38% if foo and \
39     bar:
40     foo and bar
41%endif
42"""
43        crlf = crlf.replace("\n", "\r\n")
44        self._do_test(Template(crlf), "\r\n\r\n     foo and bar\r\n")
45
46
47class EncodingTest(TemplateTest):
48    def test_escapes_html_tags(self):
49        from mako.exceptions import html_error_template
50
51        x = Template(
52            """
53        X:
54        <% raise Exception('<span style="color:red">Foobar</span>') %>
55        """
56        )
57
58        try:
59            x.render()
60        except:
61            # <h3>Exception: <span style="color:red">Foobar</span></h3>
62            markup = html_error_template().render(full=False, css=False)
63            assert (
64                '<span style="color:red">Foobar</span></h3>'.encode("ascii")
65                not in markup
66            )
67            assert (
68                "&lt;span style=&#34;color:red&#34;"
69                "&gt;Foobar&lt;/span&gt;".encode("ascii") in markup
70            )
71
72    def test_unicode(self):
73        self._do_memory_test(
74            (
75                "Alors vous imaginez ma surprise, au lever du jour, quand "
76                "une drôle de petite voix m’a réveillé. Elle disait: "
77                "« S’il vous plaît… dessine-moi un mouton! »"
78            ),
79            (
80                "Alors vous imaginez ma surprise, au lever du jour, quand "
81                "une drôle de petite voix m’a réveillé. Elle disait: "
82                "« S’il vous plaît… dessine-moi un mouton! »"
83            ),
84        )
85
86    def test_encoding_doesnt_conflict(self):
87        self._do_memory_test(
88            (
89                "Alors vous imaginez ma surprise, au lever du jour, quand "
90                "une drôle de petite voix m’a réveillé. Elle disait: "
91                "« S’il vous plaît… dessine-moi un mouton! »"
92            ),
93            (
94                "Alors vous imaginez ma surprise, au lever du jour, quand "
95                "une drôle de petite voix m’a réveillé. Elle disait: "
96                "« S’il vous plaît… dessine-moi un mouton! »"
97            ),
98            output_encoding="utf-8",
99        )
100
101    def test_unicode_arg(self):
102        val = (
103            "Alors vous imaginez ma surprise, au lever du jour, quand "
104            "une drôle de petite voix m’a réveillé. Elle disait: "
105            "« S’il vous plaît… dessine-moi un mouton! »"
106        )
107        self._do_memory_test(
108            "${val}",
109            (
110                "Alors vous imaginez ma surprise, au lever du jour, quand "
111                "une drôle de petite voix m’a réveillé. Elle disait: "
112                "« S’il vous plaît… dessine-moi un mouton! »"
113            ),
114            template_args={"val": val},
115        )
116
117    def test_unicode_file(self):
118        self._do_file_test(
119            "unicode.html",
120            (
121                "Alors vous imaginez ma surprise, au lever du jour, quand "
122                "une drôle de petite voix m’a réveillé. Elle disait: "
123                "« S’il vous plaît… dessine-moi un mouton! »"
124            ),
125        )
126
127    def test_unicode_file_code(self):
128        self._do_file_test(
129            "unicode_code.html",
130            ("""hi, drôle de petite voix m’a réveillé."""),
131            filters=flatten_result,
132        )
133
134    def test_unicode_file_lookup(self):
135        lookup = TemplateLookup(
136            directories=[config.template_base],
137            output_encoding="utf-8",
138            default_filters=["decode.utf8"],
139        )
140        template = lookup.get_template("/chs_unicode_py3k.html")
141        eq_(
142            flatten_result(template.render_unicode(name="毛泽东")),
143            ("毛泽东 是 新中国的主席<br/> Welcome 你 to 北京."),
144        )
145
146    def test_unicode_bom(self):
147        self._do_file_test(
148            "bom.html",
149            (
150                "Alors vous imaginez ma surprise, au lever du jour, quand "
151                "une drôle de petite voix m’a réveillé. Elle disait: "
152                "« S’il vous plaît… dessine-moi un mouton! »"
153            ),
154        )
155
156        self._do_file_test(
157            "bommagic.html",
158            (
159                "Alors vous imaginez ma surprise, au lever du jour, quand "
160                "une drôle de petite voix m’a réveillé. Elle disait: "
161                "« S’il vous plaît… dessine-moi un mouton! »"
162            ),
163        )
164
165        assert_raises(
166            exceptions.CompileException,
167            Template,
168            filename=self._file_path("badbom.html"),
169            module_directory=config.module_base,
170        )
171
172    def test_unicode_memory(self):
173        val = (
174            "Alors vous imaginez ma surprise, au lever du jour, quand "
175            "une drôle de petite voix m’a réveillé. Elle disait: "
176            "« S’il vous plaît… dessine-moi un mouton! »"
177        )
178        self._do_memory_test(
179            ("## -*- coding: utf-8 -*-\n" + val).encode("utf-8"),
180            (
181                "Alors vous imaginez ma surprise, au lever du jour, quand "
182                "une drôle de petite voix m’a réveillé. Elle disait: "
183                "« S’il vous plaît… dessine-moi un mouton! »"
184            ),
185        )
186
187    def test_unicode_text(self):
188        val = (
189            "<%text>Alors vous imaginez ma surprise, au lever du jour, quand "
190            "une drôle de petite voix m’a réveillé. Elle disait: "
191            "« S’il vous plaît… dessine-moi un mouton! »</%text>"
192        )
193        self._do_memory_test(
194            ("## -*- coding: utf-8 -*-\n" + val).encode("utf-8"),
195            (
196                "Alors vous imaginez ma surprise, au lever du jour, quand "
197                "une drôle de petite voix m’a réveillé. Elle disait: "
198                "« S’il vous plaît… dessine-moi un mouton! »"
199            ),
200        )
201
202    def test_unicode_text_ccall(self):
203        val = """
204        <%def name="foo()">
205            ${capture(caller.body)}
206        </%def>
207        <%call expr="foo()">
208        <%text>Alors vous imaginez ma surprise, au lever du jour,
209quand une drôle de petite voix m’a réveillé. Elle disait:
210« S’il vous plaît… dessine-moi un mouton! »</%text>
211        </%call>"""
212        self._do_memory_test(
213            ("## -*- coding: utf-8 -*-\n" + val).encode("utf-8"),
214            (
215                "Alors vous imaginez ma surprise, au lever du jour, quand "
216                "une drôle de petite voix m’a réveillé. Elle disait: "
217                "« S’il vous plaît… dessine-moi un mouton! »"
218            ),
219            filters=flatten_result,
220        )
221
222    def test_unicode_literal_in_expr(self):
223        self._do_memory_test(
224            (
225                "## -*- coding: utf-8 -*-\n"
226                '${"Alors vous imaginez ma surprise, au lever du jour, '
227                "quand une drôle de petite voix m’a réveillé. "
228                "Elle disait: "
229                '« S’il vous plaît… dessine-moi un mouton! »"}\n'
230            ).encode("utf-8"),
231            (
232                "Alors vous imaginez ma surprise, au lever du jour, "
233                "quand une drôle de petite voix m’a réveillé. "
234                "Elle disait: « S’il vous plaît… dessine-moi un mouton! »"
235            ),
236            filters=lambda s: s.strip(),
237        )
238
239    def test_unicode_literal_in_expr_file(self):
240        self._do_file_test(
241            "unicode_expr.html",
242            (
243                "Alors vous imaginez ma surprise, au lever du jour, "
244                "quand une drôle de petite voix m’a réveillé. "
245                "Elle disait: « S’il vous plaît… dessine-moi un mouton! »"
246            ),
247            lambda t: t.strip(),
248        )
249
250    def test_unicode_literal_in_code(self):
251        self._do_memory_test(
252            (
253                """## -*- coding: utf-8 -*-
254            <%
255                context.write("Alors vous imaginez ma surprise, au """
256                """lever du jour, quand une drôle de petite voix m’a """
257                """réveillé. Elle disait: """
258                """« S’il vous plaît… dessine-moi un mouton! »")
259            %>
260            """
261            ).encode("utf-8"),
262            (
263                "Alors vous imaginez ma surprise, au lever du jour, "
264                "quand une drôle de petite voix m’a réveillé. "
265                "Elle disait: « S’il vous plaît… dessine-moi un mouton! »"
266            ),
267            filters=lambda s: s.strip(),
268        )
269
270    def test_unicode_literal_in_controlline(self):
271        self._do_memory_test(
272            (
273                """## -*- coding: utf-8 -*-
274            <%
275                x = "drôle de petite voix m’a réveillé."
276            %>
277            % if x=="drôle de petite voix m’a réveillé.":
278                hi, ${x}
279            % endif
280            """
281            ).encode("utf-8"),
282            ("""hi, drôle de petite voix m’a réveillé."""),
283            filters=lambda s: s.strip(),
284        )
285
286    def test_unicode_literal_in_tag(self):
287        self._do_file_test(
288            "unicode_arguments.html",
289            [
290                ("x is: drôle de petite voix m’a réveillé"),
291                ("x is: drôle de petite voix m’a réveillé"),
292                ("x is: drôle de petite voix m’a réveillé"),
293                ("x is: drôle de petite voix m’a réveillé"),
294            ],
295            filters=result_lines,
296        )
297
298        self._do_memory_test(
299            util.read_file(self._file_path("unicode_arguments.html")),
300            [
301                ("x is: drôle de petite voix m’a réveillé"),
302                ("x is: drôle de petite voix m’a réveillé"),
303                ("x is: drôle de petite voix m’a réveillé"),
304                ("x is: drôle de petite voix m’a réveillé"),
305            ],
306            filters=result_lines,
307        )
308
309    def test_unicode_literal_in_def(self):
310        self._do_memory_test(
311            (
312                """## -*- coding: utf-8 -*-
313            <%def name="bello(foo, bar)">
314            Foo: ${ foo }
315            Bar: ${ bar }
316            </%def>
317            <%call expr="bello(foo='árvíztűrő tükörfúrógép', """
318                """bar='ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP')">
319            </%call>"""
320            ).encode("utf-8"),
321            (
322                """Foo: árvíztűrő tükörfúrógép """
323                """Bar: ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP"""
324            ),
325            filters=flatten_result,
326        )
327
328        self._do_memory_test(
329            (
330                "## -*- coding: utf-8 -*-\n"
331                """<%def name="hello(foo='árvíztűrő tükörfúrógép', """
332                """bar='ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP')">\n"""
333                "Foo: ${ foo }\n"
334                "Bar: ${ bar }\n"
335                "</%def>\n"
336                "${ hello() }"
337            ).encode("utf-8"),
338            (
339                """Foo: árvíztűrő tükörfúrógép Bar: """
340                """ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP"""
341            ),
342            filters=flatten_result,
343        )
344
345    def test_input_encoding(self):
346        """test the 'input_encoding' flag on Template, and that unicode
347        objects arent double-decoded"""
348
349        self._do_memory_test(
350            ("hello ${f('śląsk')}"),
351            ("hello śląsk"),
352            input_encoding="utf-8",
353            template_args={"f": lambda x: x},
354        )
355
356        self._do_memory_test(
357            ("## -*- coding: utf-8 -*-\nhello ${f('śląsk')}"),
358            ("hello śląsk"),
359            template_args={"f": lambda x: x},
360        )
361
362    def test_encoding(self):
363        self._do_memory_test(
364            (
365                "Alors vous imaginez ma surprise, au lever du jour, quand "
366                "une drôle de petite voix m’a réveillé. Elle disait: "
367                "« S’il vous plaît… dessine-moi un mouton! »"
368            ),
369            (
370                "Alors vous imaginez ma surprise, au lever du jour, quand "
371                "une drôle de petite voix m’a réveillé. Elle disait: "
372                "« S’il vous plaît… dessine-moi un mouton! »"
373            ).encode("utf-8"),
374            output_encoding="utf-8",
375            unicode_=False,
376        )
377
378    def test_encoding_errors(self):
379        self._do_memory_test(
380            (
381                """KGB (transliteration of "КГБ") is the Russian-language """
382                """abbreviation for Committee for State Security, """
383                """(Russian: Комит́ет Госуд́арственной Безоп́асности """
384                """(help·info); Komitet Gosudarstvennoy Bezopasnosti)"""
385            ),
386            (
387                """KGB (transliteration of "КГБ") is the Russian-language """
388                """abbreviation for Committee for State Security, """
389                """(Russian: Комит́ет Госуд́арственной Безоп́асности """
390                """(help·info); Komitet Gosudarstvennoy Bezopasnosti)"""
391            ).encode("iso-8859-1", "replace"),
392            output_encoding="iso-8859-1",
393            encoding_errors="replace",
394            unicode_=False,
395        )
396
397    def test_read_unicode(self):
398        lookup = TemplateLookup(
399            directories=[config.template_base],
400            filesystem_checks=True,
401            output_encoding="utf-8",
402        )
403        template = lookup.get_template("/read_unicode_py3k.html")
404        # TODO: I've no idea what encoding this file is, Python 3.1.2
405        # won't read the file even with open(...encoding='utf-8') unless
406        # errors is specified.   or if there's some quirk in 3.1.2
407        # since I'm pretty sure this test worked with py3k when I wrote it.
408        template.render(path=self._file_path("internationalization.html"))
409
410
411class PageArgsTest(TemplateTest):
412    def test_basic(self):
413        template = Template(
414            """
415            <%page args="x, y, z=7"/>
416
417            this is page, ${x}, ${y}, ${z}
418"""
419        )
420
421        assert (
422            flatten_result(template.render(x=5, y=10))
423            == "this is page, 5, 10, 7"
424        )
425        assert (
426            flatten_result(template.render(x=5, y=10, z=32))
427            == "this is page, 5, 10, 32"
428        )
429        assert_raises(TypeError, template.render, y=10)
430
431    def test_inherits(self):
432        lookup = TemplateLookup()
433        lookup.put_string(
434            "base.tmpl",
435            """
436        <%page args="bar" />
437        ${bar}
438        ${pageargs['foo']}
439        ${self.body(**pageargs)}
440        """,
441        )
442        lookup.put_string(
443            "index.tmpl",
444            """
445        <%inherit file="base.tmpl" />
446        <%page args="variable" />
447        ${variable}
448        """,
449        )
450
451        self._do_test(
452            lookup.get_template("index.tmpl"),
453            "bar foo var",
454            filters=flatten_result,
455            template_args={"variable": "var", "bar": "bar", "foo": "foo"},
456        )
457
458    def test_includes(self):
459        lookup = TemplateLookup()
460        lookup.put_string(
461            "incl1.tmpl",
462            """
463        <%page args="bar" />
464        ${bar}
465        ${pageargs['foo']}
466        """,
467        )
468        lookup.put_string(
469            "incl2.tmpl",
470            """
471        ${pageargs}
472        """,
473        )
474        lookup.put_string(
475            "index.tmpl",
476            """
477        <%include file="incl1.tmpl" args="**pageargs"/>
478        <%page args="variable" />
479        ${variable}
480        <%include file="incl2.tmpl" />
481        """,
482        )
483
484        self._do_test(
485            lookup.get_template("index.tmpl"),
486            "bar foo var {}",
487            filters=flatten_result,
488            template_args={"variable": "var", "bar": "bar", "foo": "foo"},
489        )
490
491    def test_context_small(self):
492        ctx = runtime.Context([].append, x=5, y=4)
493        eq_(sorted(ctx.keys()), ["caller", "capture", "x", "y"])
494
495    def test_with_context(self):
496        template = Template(
497            """
498            <%page args="x, y, z=7"/>
499
500            this is page, ${x}, ${y}, ${z}, ${w}
501"""
502        )
503        # print template.code
504        assert (
505            flatten_result(template.render(x=5, y=10, w=17))
506            == "this is page, 5, 10, 7, 17"
507        )
508
509    def test_overrides_builtins(self):
510        template = Template(
511            """
512            <%page args="id"/>
513
514            this is page, id is ${id}
515        """
516        )
517
518        assert (
519            flatten_result(template.render(id="im the id"))
520            == "this is page, id is im the id"
521        )
522
523    def test_canuse_builtin_names(self):
524        template = Template(
525            """
526            exception: ${Exception}
527            id: ${id}
528        """
529        )
530        assert (
531            flatten_result(
532                template.render(id="some id", Exception="some exception")
533            )
534            == "exception: some exception id: some id"
535        )
536
537    def test_builtin_names_dont_clobber_defaults_in_includes(self):
538        lookup = TemplateLookup()
539        lookup.put_string(
540            "test.mako",
541            """
542        <%include file="test1.mako"/>
543
544        """,
545        )
546
547        lookup.put_string(
548            "test1.mako",
549            """
550        <%page args="id='foo'"/>
551
552        ${id}
553        """,
554        )
555
556        for template in ("test.mako", "test1.mako"):
557            assert (
558                flatten_result(lookup.get_template(template).render()) == "foo"
559            )
560            assert (
561                flatten_result(lookup.get_template(template).render(id=5))
562                == "5"
563            )
564            assert (
565                flatten_result(lookup.get_template(template).render(id=id))
566                == "<built-in function id>"
567            )
568
569    def test_dict_locals(self):
570        template = Template(
571            """
572            <%
573                dict = "this is dict"
574                locals = "this is locals"
575            %>
576            dict: ${dict}
577            locals: ${locals}
578        """
579        )
580        assert (
581            flatten_result(template.render())
582            == "dict: this is dict locals: this is locals"
583        )
584
585
586class IncludeTest(TemplateTest):
587    def test_basic(self):
588        lookup = TemplateLookup()
589        lookup.put_string(
590            "a",
591            """
592            this is a
593            <%include file="b" args="a=3,b=4,c=5"/>
594        """,
595        )
596        lookup.put_string(
597            "b",
598            """
599            <%page args="a,b,c"/>
600            this is b.  ${a}, ${b}, ${c}
601        """,
602        )
603        assert (
604            flatten_result(lookup.get_template("a").render())
605            == "this is a this is b. 3, 4, 5"
606        )
607
608    def test_localargs(self):
609        lookup = TemplateLookup()
610        lookup.put_string(
611            "a",
612            """
613            this is a
614            <%include file="b" args="a=a,b=b,c=5"/>
615        """,
616        )
617        lookup.put_string(
618            "b",
619            """
620            <%page args="a,b,c"/>
621            this is b.  ${a}, ${b}, ${c}
622        """,
623        )
624        assert (
625            flatten_result(lookup.get_template("a").render(a=7, b=8))
626            == "this is a this is b. 7, 8, 5"
627        )
628
629    def test_viakwargs(self):
630        lookup = TemplateLookup()
631        lookup.put_string(
632            "a",
633            """
634            this is a
635            <%include file="b" args="c=5, **context.kwargs"/>
636        """,
637        )
638        lookup.put_string(
639            "b",
640            """
641            <%page args="a,b,c"/>
642            this is b.  ${a}, ${b}, ${c}
643        """,
644        )
645        # print lookup.get_template("a").code
646        assert (
647            flatten_result(lookup.get_template("a").render(a=7, b=8))
648            == "this is a this is b. 7, 8, 5"
649        )
650
651    def test_include_withargs(self):
652        lookup = TemplateLookup()
653        lookup.put_string(
654            "a",
655            """
656            this is a
657            <%include file="${i}" args="c=5, **context.kwargs"/>
658        """,
659        )
660        lookup.put_string(
661            "b",
662            """
663            <%page args="a,b,c"/>
664            this is b.  ${a}, ${b}, ${c}
665        """,
666        )
667        assert (
668            flatten_result(lookup.get_template("a").render(a=7, b=8, i="b"))
669            == "this is a this is b. 7, 8, 5"
670        )
671
672    def test_within_ccall(self):
673        lookup = TemplateLookup()
674        lookup.put_string("a", """this is a""")
675        lookup.put_string(
676            "b",
677            """
678        <%def name="bar()">
679            bar: ${caller.body()}
680            <%include file="a"/>
681        </%def>
682        """,
683        )
684        lookup.put_string(
685            "c",
686            """
687        <%namespace name="b" file="b"/>
688        <%b:bar>
689            calling bar
690        </%b:bar>
691        """,
692        )
693        assert (
694            flatten_result(lookup.get_template("c").render())
695            == "bar: calling bar this is a"
696        )
697
698    def test_include_error_handler(self):
699        def handle(context, error):
700            context.write("include error")
701            return True
702
703        lookup = TemplateLookup(include_error_handler=handle)
704        lookup.put_string(
705            "a",
706            """
707            this is a.
708            <%include file="b"/>
709        """,
710        )
711        lookup.put_string(
712            "b",
713            """
714            this is b ${1/0} end.
715        """,
716        )
717        assert (
718            flatten_result(lookup.get_template("a").render())
719            == "this is a. this is b include error"
720        )
721
722
723class UndefinedVarsTest(TemplateTest):
724    def test_undefined(self):
725        t = Template(
726            """
727            % if x is UNDEFINED:
728                undefined
729            % else:
730                x: ${x}
731            % endif
732        """
733        )
734
735        assert result_lines(t.render(x=12)) == ["x: 12"]
736        assert result_lines(t.render(y=12)) == ["undefined"]
737
738    def test_strict(self):
739        t = Template(
740            """
741            % if x is UNDEFINED:
742                undefined
743            % else:
744                x: ${x}
745            % endif
746        """,
747            strict_undefined=True,
748        )
749
750        assert result_lines(t.render(x=12)) == ["x: 12"]
751
752        assert_raises(NameError, t.render, y=12)
753
754        l = TemplateLookup(strict_undefined=True)
755        l.put_string("a", "some template")
756        l.put_string(
757            "b",
758            """
759            <%namespace name='a' file='a' import='*'/>
760            % if x is UNDEFINED:
761                undefined
762            % else:
763                x: ${x}
764            % endif
765        """,
766        )
767
768        assert result_lines(t.render(x=12)) == ["x: 12"]
769
770        assert_raises(NameError, t.render, y=12)
771
772    def test_expression_declared(self):
773        t = Template(
774            """
775            ${",".join([t for t in ("a", "b", "c")])}
776        """,
777            strict_undefined=True,
778        )
779
780        eq_(result_lines(t.render()), ["a,b,c"])
781
782        t = Template(
783            """
784            <%self:foo value="${[(val, n) for val, n in [(1, 2)]]}"/>
785
786            <%def name="foo(value)">
787                ${value}
788            </%def>
789
790        """,
791            strict_undefined=True,
792        )
793
794        eq_(result_lines(t.render()), ["[(1, 2)]"])
795
796        t = Template(
797            """
798            <%call expr="foo(value=[(val, n) for val, n in [(1, 2)]])" />
799
800            <%def name="foo(value)">
801                ${value}
802            </%def>
803
804        """,
805            strict_undefined=True,
806        )
807
808        eq_(result_lines(t.render()), ["[(1, 2)]"])
809
810        l = TemplateLookup(strict_undefined=True)
811        l.put_string("i", "hi, ${pageargs['y']}")
812        l.put_string(
813            "t",
814            """
815            <%include file="i" args="y=[x for x in range(3)]" />
816        """,
817        )
818        eq_(result_lines(l.get_template("t").render()), ["hi, [0, 1, 2]"])
819
820        l.put_string(
821            "q",
822            """
823            <%namespace name="i" file="${(str([x for x in range(3)][2]) + """
824            """'i')[-1]}" />
825            ${i.body(y='x')}
826        """,
827        )
828        eq_(result_lines(l.get_template("q").render()), ["hi, x"])
829
830        t = Template(
831            """
832            <%
833                y = lambda q: str(q)
834            %>
835            ${y('hi')}
836        """,
837            strict_undefined=True,
838        )
839        eq_(result_lines(t.render()), ["hi"])
840
841    def test_list_comprehensions_plus_undeclared_nonstrict(self):
842        # traditional behavior.  variable inside a list comprehension
843        # is treated as an "undefined", so is pulled from the context.
844        t = Template(
845            """
846            t is: ${t}
847
848            ${",".join([t for t in ("a", "b", "c")])}
849        """
850        )
851
852        eq_(result_lines(t.render(t="T")), ["t is: T", "a,b,c"])
853
854    def test_traditional_assignment_plus_undeclared(self):
855        t = Template(
856            """
857            t is: ${t}
858
859            <%
860                t = 12
861            %>
862        """
863        )
864        assert_raises(UnboundLocalError, t.render, t="T")
865
866    def test_list_comprehensions_plus_undeclared_strict(self):
867        # with strict, a list comprehension now behaves
868        # like the undeclared case above.
869        t = Template(
870            """
871            t is: ${t}
872
873            ${",".join([t for t in ("a", "b", "c")])}
874        """,
875            strict_undefined=True,
876        )
877
878        eq_(result_lines(t.render(t="T")), ["t is: T", "a,b,c"])
879
880
881class StopRenderingTest(TemplateTest):
882    def test_return_in_template(self):
883        t = Template(
884            """
885           Line one
886           <% return STOP_RENDERING %>
887           Line Three
888        """,
889            strict_undefined=True,
890        )
891
892        eq_(result_lines(t.render()), ["Line one"])
893
894
895class ReservedNameTest(TemplateTest):
896    def test_names_on_context(self):
897        for name in ("context", "loop", "UNDEFINED", "STOP_RENDERING"):
898            assert_raises_message(
899                exceptions.NameConflictError,
900                r"Reserved words passed to render\(\): %s" % name,
901                Template("x").render,
902                **{name: "foo"},
903            )
904
905    def test_names_in_template(self):
906        for name in ("context", "loop", "UNDEFINED", "STOP_RENDERING"):
907            assert_raises_message(
908                exceptions.NameConflictError,
909                r"Reserved words declared in template: %s" % name,
910                Template,
911                "<%% %s = 5 %%>" % name,
912            )
913
914    def test_exclude_loop_context(self):
915        self._do_memory_test(
916            "loop is ${loop}",
917            "loop is 5",
918            template_args=dict(loop=5),
919            enable_loop=False,
920        )
921
922    def test_exclude_loop_template(self):
923        self._do_memory_test(
924            "<% loop = 12 %>loop is ${loop}", "loop is 12", enable_loop=False
925        )
926
927
928class ControlTest(TemplateTest):
929    def test_control(self):
930        t = Template(
931            """
932    ## this is a template.
933    % for x in y:
934    %   if 'test' in x:
935        yes x has test
936    %   else:
937        no x does not have test
938    %endif
939    %endfor
940"""
941        )
942        assert result_lines(
943            t.render(
944                y=[
945                    {"test": "one"},
946                    {"foo": "bar"},
947                    {"foo": "bar", "test": "two"},
948                ]
949            )
950        ) == ["yes x has test", "no x does not have test", "yes x has test"]
951
952    def test_blank_control_1(self):
953        self._do_memory_test(
954            """
955            % if True:
956            % endif
957            """,
958            "",
959            filters=lambda s: s.strip(),
960        )
961
962    def test_blank_control_2(self):
963        self._do_memory_test(
964            """
965            % if True:
966            % elif True:
967            % endif
968            """,
969            "",
970            filters=lambda s: s.strip(),
971        )
972
973    def test_blank_control_3(self):
974        self._do_memory_test(
975            """
976            % if True:
977            % else:
978            % endif
979            """,
980            "",
981            filters=lambda s: s.strip(),
982        )
983
984    def test_blank_control_4(self):
985        self._do_memory_test(
986            """
987            % if True:
988            % elif True:
989            % else:
990            % endif
991            """,
992            "",
993            filters=lambda s: s.strip(),
994        )
995
996    def test_blank_control_5(self):
997        self._do_memory_test(
998            """
999            % for x in range(10):
1000            % endfor
1001            """,
1002            "",
1003            filters=lambda s: s.strip(),
1004        )
1005
1006    def test_blank_control_6(self):
1007        self._do_memory_test(
1008            """
1009            % while False:
1010            % endwhile
1011            """,
1012            "",
1013            filters=lambda s: s.strip(),
1014        )
1015
1016    def test_blank_control_7(self):
1017        self._do_memory_test(
1018            """
1019            % try:
1020            % except:
1021            % endtry
1022            """,
1023            "",
1024            filters=lambda s: s.strip(),
1025        )
1026
1027    def test_blank_control_8(self):
1028        self._do_memory_test(
1029            """
1030            % with ctx('x', 'w') as fp:
1031            % endwith
1032            """,
1033            "",
1034            filters=lambda s: s.strip(),
1035            template_args={"ctx": ctx},
1036        )
1037
1038    def test_commented_blank_control_1(self):
1039        self._do_memory_test(
1040            """
1041            % if True:
1042            ## comment
1043            % endif
1044            """,
1045            "",
1046            filters=lambda s: s.strip(),
1047        )
1048
1049    def test_commented_blank_control_2(self):
1050        self._do_memory_test(
1051            """
1052            % if True:
1053            ## comment
1054            % elif True:
1055            ## comment
1056            % endif
1057            """,
1058            "",
1059            filters=lambda s: s.strip(),
1060        )
1061
1062    def test_commented_blank_control_3(self):
1063        self._do_memory_test(
1064            """
1065            % if True:
1066            ## comment
1067            % else:
1068            ## comment
1069            % endif
1070            """,
1071            "",
1072            filters=lambda s: s.strip(),
1073        )
1074
1075    def test_commented_blank_control_4(self):
1076        self._do_memory_test(
1077            """
1078            % if True:
1079            ## comment
1080            % elif True:
1081            ## comment
1082            % else:
1083            ## comment
1084            % endif
1085            """,
1086            "",
1087            filters=lambda s: s.strip(),
1088        )
1089
1090    def test_commented_blank_control_5(self):
1091        self._do_memory_test(
1092            """
1093            % for x in range(10):
1094            ## comment
1095            % endfor
1096            """,
1097            "",
1098            filters=lambda s: s.strip(),
1099        )
1100
1101    def test_commented_blank_control_6(self):
1102        self._do_memory_test(
1103            """
1104            % while False:
1105            ## comment
1106            % endwhile
1107            """,
1108            "",
1109            filters=lambda s: s.strip(),
1110        )
1111
1112    def test_commented_blank_control_7(self):
1113        self._do_memory_test(
1114            """
1115            % try:
1116            ## comment
1117            % except:
1118            ## comment
1119            % endtry
1120            """,
1121            "",
1122            filters=lambda s: s.strip(),
1123        )
1124
1125    def test_commented_blank_control_8(self):
1126        self._do_memory_test(
1127            """
1128            % with ctx('x', 'w') as fp:
1129            ## comment
1130            % endwith
1131            """,
1132            "",
1133            filters=lambda s: s.strip(),
1134            template_args={"ctx": ctx},
1135        )
1136
1137    def test_multiline_control(self):
1138        t = Template(
1139            """
1140    % for x in \\
1141        [y for y in [1,2,3]]:
1142        ${x}
1143    % endfor
1144"""
1145        )
1146        # print t.code
1147        assert flatten_result(t.render()) == "1 2 3"
1148
1149
1150class GlobalsTest(TemplateTest):
1151    def test_globals(self):
1152        self._do_memory_test(
1153            """
1154                <%!
1155                    y = "hi"
1156                %>
1157            y is ${y}
1158            """,
1159            "y is hi",
1160            filters=lambda t: t.strip(),
1161        )
1162
1163
1164class RichTracebackTest(TemplateTest):
1165    def _do_test_traceback(self, utf8, memory, syntax):
1166        if memory:
1167            if syntax:
1168                source = (
1169                    '## coding: utf-8\n<% print "m’a réveillé. '
1170                    "Elle disait: « S’il vous plaît… dessine-moi "
1171                    "un mouton! » %>"
1172                )
1173            else:
1174                source = (
1175                    '## coding: utf-8\n<% print u"m’a réveillé. '
1176                    "Elle disait: « S’il vous plaît… dessine-moi un "
1177                    'mouton! »" + str(5/0) %>'
1178                )
1179            if utf8:
1180                source = source.encode("utf-8")
1181            else:
1182                source = source
1183            templateargs = {"text": source}
1184        else:
1185            if syntax:
1186                filename = "unicode_syntax_error.html"
1187            else:
1188                filename = "unicode_runtime_error.html"
1189            source = util.read_file(self._file_path(filename), "rb")
1190            if not utf8:
1191                source = source.decode("utf-8")
1192            templateargs = {"filename": self._file_path(filename)}
1193        try:
1194            template = Template(**templateargs)
1195            if not syntax:
1196                template.render_unicode()
1197            assert False
1198        except Exception:
1199            tback = exceptions.RichTraceback()
1200            if utf8:
1201                assert tback.source == source.decode("utf-8")
1202            else:
1203                assert tback.source == source
1204
1205
1206for utf8 in (True, False):
1207    for memory in (True, False):
1208        for syntax in (True, False):
1209
1210            def _do_test(self):
1211                self._do_test_traceback(utf8, memory, syntax)
1212
1213            name = "test_%s_%s_%s" % (
1214                utf8 and "utf8" or "unicode",
1215                memory and "memory" or "file",
1216                syntax and "syntax" or "runtime",
1217            )
1218            _do_test.__name__ = name
1219            setattr(RichTracebackTest, name, _do_test)
1220            del _do_test
1221
1222
1223class ModuleDirTest(TemplateTest):
1224    def teardown_method(self):
1225        import shutil
1226
1227        shutil.rmtree(config.module_base, True)
1228
1229    def test_basic(self):
1230        t = self._file_template("modtest.html")
1231        t2 = self._file_template("subdir/modtest.html")
1232
1233        eq_(
1234            t.module.__file__,
1235            os.path.join(config.module_base, "modtest.html.py"),
1236        )
1237        eq_(
1238            t2.module.__file__,
1239            os.path.join(config.module_base, "subdir", "modtest.html.py"),
1240        )
1241
1242    def test_callable(self):
1243        def get_modname(filename, uri):
1244            return os.path.join(
1245                config.module_base,
1246                os.path.dirname(uri)[1:],
1247                "foo",
1248                os.path.basename(filename) + ".py",
1249            )
1250
1251        lookup = TemplateLookup(
1252            config.template_base, modulename_callable=get_modname
1253        )
1254        t = lookup.get_template("/modtest.html")
1255        t2 = lookup.get_template("/subdir/modtest.html")
1256        eq_(
1257            t.module.__file__,
1258            os.path.join(config.module_base, "foo", "modtest.html.py"),
1259        )
1260        eq_(
1261            t2.module.__file__,
1262            os.path.join(
1263                config.module_base, "subdir", "foo", "modtest.html.py"
1264            ),
1265        )
1266
1267    def test_custom_writer(self):
1268        canary = []
1269
1270        def write_module(source, outputpath):
1271            f = open(outputpath, "wb")
1272            canary.append(outputpath)
1273            f.write(source)
1274            f.close()
1275
1276        lookup = TemplateLookup(
1277            config.template_base,
1278            module_writer=write_module,
1279            module_directory=config.module_base,
1280        )
1281        lookup.get_template("/modtest.html")
1282        lookup.get_template("/subdir/modtest.html")
1283        eq_(
1284            canary,
1285            [
1286                os.path.join(config.module_base, "modtest.html.py"),
1287                os.path.join(config.module_base, "subdir", "modtest.html.py"),
1288            ],
1289        )
1290
1291
1292class FilenameToURITest(TemplateTest):
1293    def test_windows_paths(self):
1294        """test that windows filenames are handled appropriately by
1295        Template."""
1296
1297        current_path = os.path
1298        import ntpath
1299
1300        os.path = ntpath
1301        try:
1302
1303            class NoCompileTemplate(Template):
1304                def _compile_from_file(self, path, filename):
1305                    self.path = path
1306                    return Template("foo bar").module
1307
1308            t1 = NoCompileTemplate(
1309                filename="c:\\foo\\template.html",
1310                module_directory="c:\\modules\\",
1311            )
1312
1313            eq_(t1.uri, "/foo/template.html")
1314            eq_(t1.path, "c:\\modules\\foo\\template.html.py")
1315
1316            t1 = NoCompileTemplate(
1317                filename="c:\\path\\to\\templates\\template.html",
1318                uri="/bar/template.html",
1319                module_directory="c:\\modules\\",
1320            )
1321
1322            eq_(t1.uri, "/bar/template.html")
1323            eq_(t1.path, "c:\\modules\\bar\\template.html.py")
1324
1325        finally:
1326            os.path = current_path
1327
1328    def test_posix_paths(self):
1329        """test that posixs filenames are handled appropriately by Template."""
1330
1331        current_path = os.path
1332        import posixpath
1333
1334        os.path = posixpath
1335        try:
1336
1337            class NoCompileTemplate(Template):
1338                def _compile_from_file(self, path, filename):
1339                    self.path = path
1340                    return Template("foo bar").module
1341
1342            t1 = NoCompileTemplate(
1343                filename="/var/www/htdocs/includes/template.html",
1344                module_directory="/var/lib/modules",
1345            )
1346
1347            eq_(t1.uri, "/var/www/htdocs/includes/template.html")
1348            eq_(
1349                t1.path,
1350                "/var/lib/modules/var/www/htdocs/includes/template.html.py",
1351            )
1352
1353            t1 = NoCompileTemplate(
1354                filename="/var/www/htdocs/includes/template.html",
1355                uri="/bar/template.html",
1356                module_directory="/var/lib/modules",
1357            )
1358
1359            eq_(t1.uri, "/bar/template.html")
1360            eq_(t1.path, "/var/lib/modules/bar/template.html.py")
1361
1362        finally:
1363            os.path = current_path
1364
1365    def test_dont_accept_relative_outside_of_root(self):
1366        assert_raises_message(
1367            exceptions.TemplateLookupException,
1368            'Template uri "../../foo.html" is invalid - it '
1369            "cannot be relative outside of the root path",
1370            Template,
1371            "test",
1372            uri="../../foo.html",
1373        )
1374
1375        assert_raises_message(
1376            exceptions.TemplateLookupException,
1377            'Template uri "/../../foo.html" is invalid - it '
1378            "cannot be relative outside of the root path",
1379            Template,
1380            "test",
1381            uri="/../../foo.html",
1382        )
1383
1384        # normalizes in the root is OK
1385        t = Template("test", uri="foo/bar/../../foo.html")
1386        eq_(t.uri, "foo/bar/../../foo.html")
1387
1388
1389class ModuleTemplateTest(TemplateTest):
1390    def test_module_roundtrip(self):
1391        lookup = TemplateLookup()
1392
1393        template = Template(
1394            """
1395        <%inherit file="base.html"/>
1396
1397        % for x in range(5):
1398            ${x}
1399        % endfor
1400""",
1401            lookup=lookup,
1402        )
1403
1404        base = Template(
1405            """
1406        This is base.
1407        ${self.body()}
1408""",
1409            lookup=lookup,
1410        )
1411
1412        lookup.put_template("base.html", base)
1413        lookup.put_template("template.html", template)
1414
1415        assert result_lines(template.render()) == [
1416            "This is base.",
1417            "0",
1418            "1",
1419            "2",
1420            "3",
1421            "4",
1422        ]
1423
1424        lookup = TemplateLookup()
1425        template = ModuleTemplate(template.module, lookup=lookup)
1426        base = ModuleTemplate(base.module, lookup=lookup)
1427
1428        lookup.put_template("base.html", base)
1429        lookup.put_template("template.html", template)
1430
1431        assert result_lines(template.render()) == [
1432            "This is base.",
1433            "0",
1434            "1",
1435            "2",
1436            "3",
1437            "4",
1438        ]
1439
1440
1441class TestTemplateAPI:
1442    def test_metadata(self):
1443        t = Template(
1444            """
1445Text
1446Text
1447% if bar:
1448    ${expression}
1449% endif
1450
1451<%include file='bar'/>
1452
1453""",
1454            uri="/some/template",
1455        )
1456        eq_(
1457            ModuleInfo.get_module_source_metadata(t.code, full_line_map=True),
1458            {
1459                "full_line_map": [
1460                    1,
1461                    1,
1462                    1,
1463                    1,
1464                    1,
1465                    1,
1466                    1,
1467                    1,
1468                    1,
1469                    1,
1470                    1,
1471                    1,
1472                    1,
1473                    1,
1474                    0,
1475                    0,
1476                    0,
1477                    0,
1478                    0,
1479                    0,
1480                    0,
1481                    1,
1482                    4,
1483                    5,
1484                    5,
1485                    5,
1486                    7,
1487                    8,
1488                    8,
1489                    8,
1490                    8,
1491                    8,
1492                    8,
1493                    8,
1494                ],
1495                "source_encoding": "utf-8",
1496                "filename": None,
1497                "line_map": {
1498                    35: 29,
1499                    15: 0,
1500                    22: 1,
1501                    23: 4,
1502                    24: 5,
1503                    25: 5,
1504                    26: 5,
1505                    27: 7,
1506                    28: 8,
1507                    29: 8,
1508                },
1509                "uri": "/some/template",
1510            },
1511        )
1512
1513    def test_metadata_two(self):
1514        t = Template(
1515            """
1516Text
1517Text
1518% if bar:
1519    ${expression}
1520% endif
1521
1522    <%block name="foo">
1523        hi block
1524    </%block>
1525
1526
1527""",
1528            uri="/some/template",
1529        )
1530        eq_(
1531            ModuleInfo.get_module_source_metadata(t.code, full_line_map=True),
1532            {
1533                "full_line_map": [
1534                    1,
1535                    1,
1536                    1,
1537                    1,
1538                    1,
1539                    1,
1540                    1,
1541                    1,
1542                    1,
1543                    1,
1544                    1,
1545                    1,
1546                    1,
1547                    1,
1548                    0,
1549                    0,
1550                    0,
1551                    0,
1552                    0,
1553                    0,
1554                    0,
1555                    0,
1556                    0,
1557                    1,
1558                    4,
1559                    5,
1560                    5,
1561                    5,
1562                    7,
1563                    7,
1564                    7,
1565                    7,
1566                    7,
1567                    10,
1568                    10,
1569                    10,
1570                    10,
1571                    10,
1572                    10,
1573                    8,
1574                    8,
1575                    8,
1576                    8,
1577                    8,
1578                    8,
1579                    8,
1580                    8,
1581                    8,
1582                    8,
1583                    8,
1584                    8,
1585                ],
1586                "source_encoding": "utf-8",
1587                "filename": None,
1588                "line_map": {
1589                    34: 10,
1590                    40: 8,
1591                    46: 8,
1592                    15: 0,
1593                    52: 46,
1594                    24: 1,
1595                    25: 4,
1596                    26: 5,
1597                    27: 5,
1598                    28: 5,
1599                    29: 7,
1600                },
1601                "uri": "/some/template",
1602            },
1603        )
1604
1605
1606class PreprocessTest(TemplateTest):
1607    def test_old_comments(self):
1608        t = Template(
1609            """
1610        im a template
1611# old style comment
1612    # more old style comment
1613
1614    ## new style comment
1615    - # not a comment
1616    - ## not a comment
1617""",
1618            preprocessor=convert_comments,
1619        )
1620
1621        assert (
1622            flatten_result(t.render())
1623            == "im a template - # not a comment - ## not a comment"
1624        )
1625
1626
1627class LexerTest(TemplateTest):
1628    def _fixture(self):
1629        from mako.parsetree import TemplateNode, Text
1630
1631        class MyLexer:
1632            encoding = "ascii"
1633
1634            def __init__(self, *arg, **kw):
1635                pass
1636
1637            def parse(self):
1638                t = TemplateNode("foo")
1639                t.nodes.append(
1640                    Text(
1641                        "hello world",
1642                        source="foo",
1643                        lineno=0,
1644                        pos=0,
1645                        filename=None,
1646                    )
1647                )
1648                return t
1649
1650        return MyLexer
1651
1652    def _test_custom_lexer(self, template):
1653        eq_(result_lines(template.render()), ["hello world"])
1654
1655    def test_via_template(self):
1656        t = Template("foo", lexer_cls=self._fixture())
1657        self._test_custom_lexer(t)
1658
1659    def test_via_lookup(self):
1660        tl = TemplateLookup(lexer_cls=self._fixture())
1661        tl.put_string("foo", "foo")
1662        t = tl.get_template("foo")
1663        self._test_custom_lexer(t)
1664
1665
1666class FuturesTest(TemplateTest):
1667    def test_future_import(self):
1668        t = Template("${ x / y }", future_imports=["division"])
1669        assert result_lines(t.render(x=12, y=5)) == ["2.4"]
1670