1# mako/template.py
2# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file>
3#
4# This module is part of Mako and is released under
5# the MIT License: http://www.opensource.org/licenses/mit-license.php
6
7"""Provides the Template class, a facade for parsing, generating and executing
8template strings, as well as template runtime operations."""
9
10import json
11import os
12import re
13import shutil
14import stat
15import tempfile
16import types
17import weakref
18
19from mako import cache
20from mako import codegen
21from mako import compat
22from mako import exceptions
23from mako import runtime
24from mako import util
25from mako.lexer import Lexer
26
27
28class Template(object):
29
30    r"""Represents a compiled template.
31
32    :class:`.Template` includes a reference to the original
33    template source (via the :attr:`.source` attribute)
34    as well as the source code of the
35    generated Python module (i.e. the :attr:`.code` attribute),
36    as well as a reference to an actual Python module.
37
38    :class:`.Template` is constructed using either a literal string
39    representing the template text, or a filename representing a filesystem
40    path to a source file.
41
42    :param text: textual template source.  This argument is mutually
43     exclusive versus the ``filename`` parameter.
44
45    :param filename: filename of the source template.  This argument is
46     mutually exclusive versus the ``text`` parameter.
47
48    :param buffer_filters: string list of filters to be applied
49     to the output of ``%def``\ s which are buffered, cached, or otherwise
50     filtered, after all filters
51     defined with the ``%def`` itself have been applied. Allows the
52     creation of default expression filters that let the output
53     of return-valued ``%def``\ s "opt out" of that filtering via
54     passing special attributes or objects.
55
56    :param bytestring_passthrough: When ``True``, and ``output_encoding`` is
57     set to ``None``, and :meth:`.Template.render` is used to render,
58     the `StringIO` or `cStringIO` buffer will be used instead of the
59     default "fast" buffer.   This allows raw bytestrings in the
60     output stream, such as in expressions, to pass straight
61     through to the buffer.  This flag is forced
62     to ``True`` if ``disable_unicode`` is also configured.
63
64     .. versionadded:: 0.4
65        Added to provide the same behavior as that of the previous series.
66
67    :param cache_args: Dictionary of cache configuration arguments that
68     will be passed to the :class:`.CacheImpl`.   See :ref:`caching_toplevel`.
69
70    :param cache_dir:
71
72     .. deprecated:: 0.6
73        Use the ``'dir'`` argument in the ``cache_args`` dictionary.
74        See :ref:`caching_toplevel`.
75
76    :param cache_enabled: Boolean flag which enables caching of this
77     template.  See :ref:`caching_toplevel`.
78
79    :param cache_impl: String name of a :class:`.CacheImpl` caching
80     implementation to use.   Defaults to ``'beaker'``.
81
82    :param cache_type:
83
84     .. deprecated:: 0.6
85        Use the ``'type'`` argument in the ``cache_args`` dictionary.
86        See :ref:`caching_toplevel`.
87
88    :param cache_url:
89
90     .. deprecated:: 0.6
91        Use the ``'url'`` argument in the ``cache_args`` dictionary.
92        See :ref:`caching_toplevel`.
93
94    :param default_filters: List of string filter names that will
95     be applied to all expressions.  See :ref:`filtering_default_filters`.
96
97    :param disable_unicode: Disables all awareness of Python Unicode
98     objects.  See :ref:`unicode_disabled`.
99
100    :param enable_loop: When ``True``, enable the ``loop`` context variable.
101     This can be set to ``False`` to support templates that may
102     be making usage of the name "``loop``".   Individual templates can
103     re-enable the "loop" context by placing the directive
104     ``enable_loop="True"`` inside the ``<%page>`` tag -- see
105     :ref:`migrating_loop`.
106
107    :param encoding_errors: Error parameter passed to ``encode()`` when
108     string encoding is performed. See :ref:`usage_unicode`.
109
110    :param error_handler: Python callable which is called whenever
111     compile or runtime exceptions occur. The callable is passed
112     the current context as well as the exception. If the
113     callable returns ``True``, the exception is considered to
114     be handled, else it is re-raised after the function
115     completes. Is used to provide custom error-rendering
116     functions.
117
118     .. seealso::
119
120        :paramref:`.Template.include_error_handler` - include-specific
121        error handler function
122
123    :param format_exceptions: if ``True``, exceptions which occur during
124     the render phase of this template will be caught and
125     formatted into an HTML error page, which then becomes the
126     rendered result of the :meth:`.render` call. Otherwise,
127     runtime exceptions are propagated outwards.
128
129    :param imports: String list of Python statements, typically individual
130     "import" lines, which will be placed into the module level
131     preamble of all generated Python modules. See the example
132     in :ref:`filtering_default_filters`.
133
134    :param future_imports: String list of names to import from `__future__`.
135     These will be concatenated into a comma-separated string and inserted
136     into the beginning of the template, e.g. ``futures_imports=['FOO',
137     'BAR']`` results in ``from __future__ import FOO, BAR``.  If you're
138     interested in using features like the new division operator, you must
139     use future_imports to convey that to the renderer, as otherwise the
140     import will not appear as the first executed statement in the generated
141     code and will therefore not have the desired effect.
142
143    :param include_error_handler: An error handler that runs when this template
144     is included within another one via the ``<%include>`` tag, and raises an
145     error.  Compare to the :paramref:`.Template.error_handler` option.
146
147     .. versionadded:: 1.0.6
148
149     .. seealso::
150
151        :paramref:`.Template.error_handler` - top-level error handler function
152
153    :param input_encoding: Encoding of the template's source code.  Can
154     be used in lieu of the coding comment. See
155     :ref:`usage_unicode` as well as :ref:`unicode_toplevel` for
156     details on source encoding.
157
158    :param lookup: a :class:`.TemplateLookup` instance that will be used
159     for all file lookups via the ``<%namespace>``,
160     ``<%include>``, and ``<%inherit>`` tags. See
161     :ref:`usage_templatelookup`.
162
163    :param module_directory: Filesystem location where generated
164     Python module files will be placed.
165
166    :param module_filename: Overrides the filename of the generated
167     Python module file. For advanced usage only.
168
169    :param module_writer: A callable which overrides how the Python
170     module is written entirely.  The callable is passed the
171     encoded source content of the module and the destination
172     path to be written to.   The default behavior of module writing
173     uses a tempfile in conjunction with a file move in order
174     to make the operation atomic.   So a user-defined module
175     writing function that mimics the default behavior would be:
176
177     .. sourcecode:: python
178
179         import tempfile
180         import os
181         import shutil
182
183         def module_writer(source, outputpath):
184             (dest, name) = \\
185                 tempfile.mkstemp(
186                     dir=os.path.dirname(outputpath)
187                 )
188
189             os.write(dest, source)
190             os.close(dest)
191             shutil.move(name, outputpath)
192
193         from mako.template import Template
194         mytemplate = Template(
195                         filename="index.html",
196                         module_directory="/path/to/modules",
197                         module_writer=module_writer
198                     )
199
200     The function is provided for unusual configurations where
201     certain platform-specific permissions or other special
202     steps are needed.
203
204    :param output_encoding: The encoding to use when :meth:`.render`
205     is called.
206     See :ref:`usage_unicode` as well as :ref:`unicode_toplevel`.
207
208    :param preprocessor: Python callable which will be passed
209     the full template source before it is parsed. The return
210     result of the callable will be used as the template source
211     code.
212
213    :param lexer_cls: A :class:`.Lexer` class used to parse
214     the template.   The :class:`.Lexer` class is used by
215     default.
216
217     .. versionadded:: 0.7.4
218
219    :param strict_undefined: Replaces the automatic usage of
220     ``UNDEFINED`` for any undeclared variables not located in
221     the :class:`.Context` with an immediate raise of
222     ``NameError``. The advantage is immediate reporting of
223     missing variables which include the name.
224
225     .. versionadded:: 0.3.6
226
227    :param uri: string URI or other identifier for this template.
228     If not provided, the ``uri`` is generated from the filesystem
229     path, or from the in-memory identity of a non-file-based
230     template. The primary usage of the ``uri`` is to provide a key
231     within :class:`.TemplateLookup`, as well as to generate the
232     file path of the generated Python module file, if
233     ``module_directory`` is specified.
234
235    """
236
237    lexer_cls = Lexer
238
239    def __init__(
240        self,
241        text=None,
242        filename=None,
243        uri=None,
244        format_exceptions=False,
245        error_handler=None,
246        lookup=None,
247        output_encoding=None,
248        encoding_errors="strict",
249        module_directory=None,
250        cache_args=None,
251        cache_impl="beaker",
252        cache_enabled=True,
253        cache_type=None,
254        cache_dir=None,
255        cache_url=None,
256        module_filename=None,
257        input_encoding=None,
258        disable_unicode=False,
259        module_writer=None,
260        bytestring_passthrough=False,
261        default_filters=None,
262        buffer_filters=(),
263        strict_undefined=False,
264        imports=None,
265        future_imports=None,
266        enable_loop=True,
267        preprocessor=None,
268        lexer_cls=None,
269        include_error_handler=None,
270    ):
271        if uri:
272            self.module_id = re.sub(r"\W", "_", uri)
273            self.uri = uri
274        elif filename:
275            self.module_id = re.sub(r"\W", "_", filename)
276            drive, path = os.path.splitdrive(filename)
277            path = os.path.normpath(path).replace(os.path.sep, "/")
278            self.uri = path
279        else:
280            self.module_id = "memory:" + hex(id(self))
281            self.uri = self.module_id
282
283        u_norm = self.uri
284        if u_norm.startswith("/"):
285            u_norm = u_norm[1:]
286        u_norm = os.path.normpath(u_norm)
287        if u_norm.startswith(".."):
288            raise exceptions.TemplateLookupException(
289                'Template uri "%s" is invalid - '
290                "it cannot be relative outside "
291                "of the root path." % self.uri
292            )
293
294        self.input_encoding = input_encoding
295        self.output_encoding = output_encoding
296        self.encoding_errors = encoding_errors
297        self.disable_unicode = disable_unicode
298        self.bytestring_passthrough = bytestring_passthrough or disable_unicode
299        self.enable_loop = enable_loop
300        self.strict_undefined = strict_undefined
301        self.module_writer = module_writer
302
303        if compat.py3k and disable_unicode:
304            raise exceptions.UnsupportedError(
305                "Mako for Python 3 does not " "support disabling Unicode"
306            )
307        elif output_encoding and disable_unicode:
308            raise exceptions.UnsupportedError(
309                "output_encoding must be set to "
310                "None when disable_unicode is used."
311            )
312        if default_filters is None:
313            if compat.py3k or self.disable_unicode:
314                self.default_filters = ["str"]
315            else:
316                self.default_filters = ["unicode"]
317        else:
318            self.default_filters = default_filters
319        self.buffer_filters = buffer_filters
320
321        self.imports = imports
322        self.future_imports = future_imports
323        self.preprocessor = preprocessor
324
325        if lexer_cls is not None:
326            self.lexer_cls = lexer_cls
327
328        # if plain text, compile code in memory only
329        if text is not None:
330            (code, module) = _compile_text(self, text, filename)
331            self._code = code
332            self._source = text
333            ModuleInfo(module, None, self, filename, code, text, uri)
334        elif filename is not None:
335            # if template filename and a module directory, load
336            # a filesystem-based module file, generating if needed
337            if module_filename is not None:
338                path = module_filename
339            elif module_directory is not None:
340                path = os.path.abspath(
341                    os.path.join(
342                        os.path.normpath(module_directory), u_norm + ".py"
343                    )
344                )
345            else:
346                path = None
347            module = self._compile_from_file(path, filename)
348        else:
349            raise exceptions.RuntimeException(
350                "Template requires text or filename"
351            )
352
353        self.module = module
354        self.filename = filename
355        self.callable_ = self.module.render_body
356        self.format_exceptions = format_exceptions
357        self.error_handler = error_handler
358        self.include_error_handler = include_error_handler
359        self.lookup = lookup
360
361        self.module_directory = module_directory
362
363        self._setup_cache_args(
364            cache_impl,
365            cache_enabled,
366            cache_args,
367            cache_type,
368            cache_dir,
369            cache_url,
370        )
371
372    @util.memoized_property
373    def reserved_names(self):
374        if self.enable_loop:
375            return codegen.RESERVED_NAMES
376        else:
377            return codegen.RESERVED_NAMES.difference(["loop"])
378
379    def _setup_cache_args(
380        self,
381        cache_impl,
382        cache_enabled,
383        cache_args,
384        cache_type,
385        cache_dir,
386        cache_url,
387    ):
388        self.cache_impl = cache_impl
389        self.cache_enabled = cache_enabled
390        if cache_args:
391            self.cache_args = cache_args
392        else:
393            self.cache_args = {}
394
395        # transfer deprecated cache_* args
396        if cache_type:
397            self.cache_args["type"] = cache_type
398        if cache_dir:
399            self.cache_args["dir"] = cache_dir
400        if cache_url:
401            self.cache_args["url"] = cache_url
402
403    def _compile_from_file(self, path, filename):
404        if path is not None:
405            util.verify_directory(os.path.dirname(path))
406            filemtime = os.stat(filename)[stat.ST_MTIME]
407            if (
408                not os.path.exists(path)
409                or os.stat(path)[stat.ST_MTIME] < filemtime
410            ):
411                data = util.read_file(filename)
412                _compile_module_file(
413                    self, data, filename, path, self.module_writer
414                )
415            module = compat.load_module(self.module_id, path)
416            if module._magic_number != codegen.MAGIC_NUMBER:
417                data = util.read_file(filename)
418                _compile_module_file(
419                    self, data, filename, path, self.module_writer
420                )
421                module = compat.load_module(self.module_id, path)
422            ModuleInfo(module, path, self, filename, None, None, None)
423        else:
424            # template filename and no module directory, compile code
425            # in memory
426            data = util.read_file(filename)
427            code, module = _compile_text(self, data, filename)
428            self._source = None
429            self._code = code
430            ModuleInfo(module, None, self, filename, code, None, None)
431        return module
432
433    @property
434    def source(self):
435        """Return the template source code for this :class:`.Template`."""
436
437        return _get_module_info_from_callable(self.callable_).source
438
439    @property
440    def code(self):
441        """Return the module source code for this :class:`.Template`."""
442
443        return _get_module_info_from_callable(self.callable_).code
444
445    @util.memoized_property
446    def cache(self):
447        return cache.Cache(self)
448
449    @property
450    def cache_dir(self):
451        return self.cache_args["dir"]
452
453    @property
454    def cache_url(self):
455        return self.cache_args["url"]
456
457    @property
458    def cache_type(self):
459        return self.cache_args["type"]
460
461    def render(self, *args, **data):
462        """Render the output of this template as a string.
463
464        If the template specifies an output encoding, the string
465        will be encoded accordingly, else the output is raw (raw
466        output uses `cStringIO` and can't handle multibyte
467        characters). A :class:`.Context` object is created corresponding
468        to the given data. Arguments that are explicitly declared
469        by this template's internal rendering method are also
470        pulled from the given ``*args``, ``**data`` members.
471
472        """
473        return runtime._render(self, self.callable_, args, data)
474
475    def render_unicode(self, *args, **data):
476        """Render the output of this template as a unicode object."""
477
478        return runtime._render(
479            self, self.callable_, args, data, as_unicode=True
480        )
481
482    def render_context(self, context, *args, **kwargs):
483        """Render this :class:`.Template` with the given context.
484
485        The data is written to the context's buffer.
486
487        """
488        if getattr(context, "_with_template", None) is None:
489            context._set_with_template(self)
490        runtime._render_context(self, self.callable_, context, *args, **kwargs)
491
492    def has_def(self, name):
493        return hasattr(self.module, "render_%s" % name)
494
495    def get_def(self, name):
496        """Return a def of this template as a :class:`.DefTemplate`."""
497
498        return DefTemplate(self, getattr(self.module, "render_%s" % name))
499
500    def list_defs(self):
501        """return a list of defs in the template.
502
503        .. versionadded:: 1.0.4
504
505        """
506        return [i[7:] for i in dir(self.module) if i[:7] == "render_"]
507
508    def _get_def_callable(self, name):
509        return getattr(self.module, "render_%s" % name)
510
511    @property
512    def last_modified(self):
513        return self.module._modified_time
514
515
516class ModuleTemplate(Template):
517
518    """A Template which is constructed given an existing Python module.
519
520       e.g.::
521
522            t = Template("this is a template")
523            f = file("mymodule.py", "w")
524            f.write(t.code)
525            f.close()
526
527            import mymodule
528
529            t = ModuleTemplate(mymodule)
530            print(t.render())
531
532    """
533
534    def __init__(
535        self,
536        module,
537        module_filename=None,
538        template=None,
539        template_filename=None,
540        module_source=None,
541        template_source=None,
542        output_encoding=None,
543        encoding_errors="strict",
544        disable_unicode=False,
545        bytestring_passthrough=False,
546        format_exceptions=False,
547        error_handler=None,
548        lookup=None,
549        cache_args=None,
550        cache_impl="beaker",
551        cache_enabled=True,
552        cache_type=None,
553        cache_dir=None,
554        cache_url=None,
555        include_error_handler=None,
556    ):
557        self.module_id = re.sub(r"\W", "_", module._template_uri)
558        self.uri = module._template_uri
559        self.input_encoding = module._source_encoding
560        self.output_encoding = output_encoding
561        self.encoding_errors = encoding_errors
562        self.disable_unicode = disable_unicode
563        self.bytestring_passthrough = bytestring_passthrough or disable_unicode
564        self.enable_loop = module._enable_loop
565
566        if compat.py3k and disable_unicode:
567            raise exceptions.UnsupportedError(
568                "Mako for Python 3 does not " "support disabling Unicode"
569            )
570        elif output_encoding and disable_unicode:
571            raise exceptions.UnsupportedError(
572                "output_encoding must be set to "
573                "None when disable_unicode is used."
574            )
575
576        self.module = module
577        self.filename = template_filename
578        ModuleInfo(
579            module,
580            module_filename,
581            self,
582            template_filename,
583            module_source,
584            template_source,
585            module._template_uri,
586        )
587
588        self.callable_ = self.module.render_body
589        self.format_exceptions = format_exceptions
590        self.error_handler = error_handler
591        self.include_error_handler = include_error_handler
592        self.lookup = lookup
593        self._setup_cache_args(
594            cache_impl,
595            cache_enabled,
596            cache_args,
597            cache_type,
598            cache_dir,
599            cache_url,
600        )
601
602
603class DefTemplate(Template):
604
605    """A :class:`.Template` which represents a callable def in a parent
606    template."""
607
608    def __init__(self, parent, callable_):
609        self.parent = parent
610        self.callable_ = callable_
611        self.output_encoding = parent.output_encoding
612        self.module = parent.module
613        self.encoding_errors = parent.encoding_errors
614        self.format_exceptions = parent.format_exceptions
615        self.error_handler = parent.error_handler
616        self.include_error_handler = parent.include_error_handler
617        self.enable_loop = parent.enable_loop
618        self.lookup = parent.lookup
619        self.bytestring_passthrough = parent.bytestring_passthrough
620
621    def get_def(self, name):
622        return self.parent.get_def(name)
623
624
625class ModuleInfo(object):
626
627    """Stores information about a module currently loaded into
628    memory, provides reverse lookups of template source, module
629    source code based on a module's identifier.
630
631     """
632
633    _modules = weakref.WeakValueDictionary()
634
635    def __init__(
636        self,
637        module,
638        module_filename,
639        template,
640        template_filename,
641        module_source,
642        template_source,
643        template_uri,
644    ):
645        self.module = module
646        self.module_filename = module_filename
647        self.template_filename = template_filename
648        self.module_source = module_source
649        self.template_source = template_source
650        self.template_uri = template_uri
651        self._modules[module.__name__] = template._mmarker = self
652        if module_filename:
653            self._modules[module_filename] = self
654
655    @classmethod
656    def get_module_source_metadata(cls, module_source, full_line_map=False):
657        source_map = re.search(
658            r"__M_BEGIN_METADATA(.+?)__M_END_METADATA", module_source, re.S
659        ).group(1)
660        source_map = json.loads(source_map)
661        source_map["line_map"] = dict(
662            (int(k), int(v)) for k, v in source_map["line_map"].items()
663        )
664        if full_line_map:
665            f_line_map = source_map["full_line_map"] = []
666            line_map = source_map["line_map"]
667
668            curr_templ_line = 1
669            for mod_line in range(1, max(line_map)):
670                if mod_line in line_map:
671                    curr_templ_line = line_map[mod_line]
672                f_line_map.append(curr_templ_line)
673        return source_map
674
675    @property
676    def code(self):
677        if self.module_source is not None:
678            return self.module_source
679        else:
680            return util.read_python_file(self.module_filename)
681
682    @property
683    def source(self):
684        if self.template_source is not None:
685            if self.module._source_encoding and not isinstance(
686                self.template_source, compat.text_type
687            ):
688                return self.template_source.decode(
689                    self.module._source_encoding
690                )
691            else:
692                return self.template_source
693        else:
694            data = util.read_file(self.template_filename)
695            if self.module._source_encoding:
696                return data.decode(self.module._source_encoding)
697            else:
698                return data
699
700
701def _compile(template, text, filename, generate_magic_comment):
702    lexer = template.lexer_cls(
703        text,
704        filename,
705        disable_unicode=template.disable_unicode,
706        input_encoding=template.input_encoding,
707        preprocessor=template.preprocessor,
708    )
709    node = lexer.parse()
710    source = codegen.compile(
711        node,
712        template.uri,
713        filename,
714        default_filters=template.default_filters,
715        buffer_filters=template.buffer_filters,
716        imports=template.imports,
717        future_imports=template.future_imports,
718        source_encoding=lexer.encoding,
719        generate_magic_comment=generate_magic_comment,
720        disable_unicode=template.disable_unicode,
721        strict_undefined=template.strict_undefined,
722        enable_loop=template.enable_loop,
723        reserved_names=template.reserved_names,
724    )
725    return source, lexer
726
727
728def _compile_text(template, text, filename):
729    identifier = template.module_id
730    source, lexer = _compile(
731        template,
732        text,
733        filename,
734        generate_magic_comment=template.disable_unicode,
735    )
736
737    cid = identifier
738    if not compat.py3k and isinstance(cid, compat.text_type):
739        cid = cid.encode()
740    module = types.ModuleType(cid)
741    code = compile(source, cid, "exec")
742
743    # this exec() works for 2.4->3.3.
744    exec(code, module.__dict__, module.__dict__)
745    return (source, module)
746
747
748def _compile_module_file(template, text, filename, outputpath, module_writer):
749    source, lexer = _compile(
750        template, text, filename, generate_magic_comment=True
751    )
752
753    if isinstance(source, compat.text_type):
754        source = source.encode(lexer.encoding or "ascii")
755
756    if module_writer:
757        module_writer(source, outputpath)
758    else:
759        # make tempfiles in the same location as the ultimate
760        # location.   this ensures they're on the same filesystem,
761        # avoiding synchronization issues.
762        (dest, name) = tempfile.mkstemp(dir=os.path.dirname(outputpath))
763
764        os.write(dest, source)
765        os.close(dest)
766        shutil.move(name, outputpath)
767
768
769def _get_module_info_from_callable(callable_):
770    if compat.py3k:
771        return _get_module_info(callable_.__globals__["__name__"])
772    else:
773        return _get_module_info(callable_.func_globals["__name__"])
774
775
776def _get_module_info(filename):
777    return ModuleInfo._modules[filename]
778