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