1645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez# -*- coding: utf-8 -*-
2645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez"""
3645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    jinja2.loaders
4645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    ~~~~~~~~~~~~~~
5645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
6645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    Jinja loader classes.
7645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
8645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    :copyright: (c) 2010 by the Jinja Team.
9645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    :license: BSD, see LICENSE for more details.
10645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez"""
11645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezimport os
12645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezimport sys
13645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezimport weakref
14645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezfrom types import ModuleType
15645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezfrom os import path
16645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezfrom hashlib import sha1
17645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezfrom jinja2.exceptions import TemplateNotFound
18645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezfrom jinja2.utils import open_if_exists, internalcode
19645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezfrom jinja2._compat import string_types, iteritems
20645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
21645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
22645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezdef split_template_path(template):
23645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """Split a path into segments and perform a sanity check.  If it detects
24645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    '..' in the path it will raise a `TemplateNotFound` error.
25645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """
26645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    pieces = []
27645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    for piece in template.split('/'):
28645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        if path.sep in piece \
29645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez           or (path.altsep and path.altsep in piece) or \
30645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez           piece == path.pardir:
31645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            raise TemplateNotFound(template)
32645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        elif piece and piece != '.':
33645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            pieces.append(piece)
34645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    return pieces
35645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
36645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
37645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezclass BaseLoader(object):
38645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """Baseclass for all loaders.  Subclass this and override `get_source` to
39645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    implement a custom loading mechanism.  The environment provides a
40645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    `get_template` method that calls the loader's `load` method to get the
41645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    :class:`Template` object.
42645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
43645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    A very basic example for a loader that looks up templates on the file
44645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    system could look like this::
45645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
46645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        from jinja2 import BaseLoader, TemplateNotFound
47645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        from os.path import join, exists, getmtime
48645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
49645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        class MyLoader(BaseLoader):
50645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
51645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            def __init__(self, path):
52645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                self.path = path
53645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
54645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            def get_source(self, environment, template):
55645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                path = join(self.path, template)
56645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                if not exists(path):
57645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                    raise TemplateNotFound(template)
58645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                mtime = getmtime(path)
59645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                with file(path) as f:
60645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                    source = f.read().decode('utf-8')
61645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                return source, path, lambda: mtime == getmtime(path)
62645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """
63645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
64645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    #: if set to `False` it indicates that the loader cannot provide access
65645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    #: to the source of templates.
66645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    #:
67645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    #: .. versionadded:: 2.4
68645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    has_source_access = True
69645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
70645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def get_source(self, environment, template):
71645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        """Get the template source, filename and reload helper for a template.
72645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        It's passed the environment and template name and has to return a
73645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        tuple in the form ``(source, filename, uptodate)`` or raise a
74645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        `TemplateNotFound` error if it can't locate the template.
75645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
76645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        The source part of the returned tuple must be the source of the
77645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        template as unicode string or a ASCII bytestring.  The filename should
78645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        be the name of the file on the filesystem if it was loaded from there,
79645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        otherwise `None`.  The filename is used by python for the tracebacks
80645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        if no loader extension is used.
81645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
82645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        The last item in the tuple is the `uptodate` function.  If auto
83645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        reloading is enabled it's always called to check if the template
84645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        changed.  No arguments are passed so the function must store the
85645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        old state somewhere (for example in a closure).  If it returns `False`
86645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        the template will be reloaded.
87645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        """
88645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        if not self.has_source_access:
89645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            raise RuntimeError('%s cannot provide access to the source' %
90645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                               self.__class__.__name__)
91645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        raise TemplateNotFound(template)
92645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
93645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def list_templates(self):
94645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        """Iterates over all templates.  If the loader does not support that
95645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        it should raise a :exc:`TypeError` which is the default behavior.
96645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        """
97645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        raise TypeError('this loader cannot iterate over all templates')
98645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
99645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    @internalcode
100645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def load(self, environment, name, globals=None):
101645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        """Loads a template.  This method looks up the template in the cache
102645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        or loads one by calling :meth:`get_source`.  Subclasses should not
103645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        override this method as loaders working on collections of other
104645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
105645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        will not call this method but `get_source` directly.
106645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        """
107645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        code = None
108645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        if globals is None:
109645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            globals = {}
110645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
111645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        # first we try to get the source for this template together
112645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        # with the filename and the uptodate function.
113645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        source, filename, uptodate = self.get_source(environment, name)
114645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
115645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        # try to load the code from the bytecode cache if there is a
116645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        # bytecode cache configured.
117645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        bcc = environment.bytecode_cache
118645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        if bcc is not None:
119645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            bucket = bcc.get_bucket(environment, name, filename, source)
120645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            code = bucket.code
121645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
122645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        # if we don't have code so far (not cached, no longer up to
123645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        # date) etc. we compile the template
124645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        if code is None:
125645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            code = environment.compile(source, name, filename)
126645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
127645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        # if the bytecode cache is available and the bucket doesn't
128645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        # have a code so far, we give the bucket the new code and put
129645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        # it back to the bytecode cache.
130645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        if bcc is not None and bucket.code is None:
131645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            bucket.code = code
132645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            bcc.set_bucket(bucket)
133645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
134645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        return environment.template_class.from_code(environment, code,
135645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                                                    globals, uptodate)
136645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
137645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
138645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezclass FileSystemLoader(BaseLoader):
139645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """Loads templates from the file system.  This loader can find templates
140645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    in folders on the file system and is the preferred way to load them.
141645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
142645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    The loader takes the path to the templates as string, or if multiple
143645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    locations are wanted a list of them which is then looked up in the
144645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    given order:
145645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
146645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    >>> loader = FileSystemLoader('/path/to/templates')
147645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
148645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
149645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    Per default the template encoding is ``'utf-8'`` which can be changed
150645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    by setting the `encoding` parameter to something else.
151645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """
152645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
153645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def __init__(self, searchpath, encoding='utf-8'):
154645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        if isinstance(searchpath, string_types):
155645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            searchpath = [searchpath]
156645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        self.searchpath = list(searchpath)
157645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        self.encoding = encoding
158645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
159645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def get_source(self, environment, template):
160645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        pieces = split_template_path(template)
161645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        for searchpath in self.searchpath:
162645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            filename = path.join(searchpath, *pieces)
163645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            f = open_if_exists(filename)
164645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            if f is None:
165645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                continue
166645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            try:
167645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                contents = f.read().decode(self.encoding)
168645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            finally:
169645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                f.close()
170645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
171645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            mtime = path.getmtime(filename)
172645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            def uptodate():
173645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                try:
174645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                    return path.getmtime(filename) == mtime
175645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                except OSError:
176645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                    return False
177645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            return contents, filename, uptodate
178645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        raise TemplateNotFound(template)
179645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
180645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def list_templates(self):
181645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        found = set()
182645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        for searchpath in self.searchpath:
183645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            for dirpath, dirnames, filenames in os.walk(searchpath):
184645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                for filename in filenames:
185645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                    template = os.path.join(dirpath, filename) \
186645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                        [len(searchpath):].strip(os.path.sep) \
187645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                                          .replace(os.path.sep, '/')
188645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                    if template[:2] == './':
189645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                        template = template[2:]
190645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                    if template not in found:
191645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                        found.add(template)
192645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        return sorted(found)
193645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
194645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
195645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezclass PackageLoader(BaseLoader):
196645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """Load templates from python eggs or packages.  It is constructed with
197645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    the name of the python package and the path to the templates in that
198645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    package::
199645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
200645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        loader = PackageLoader('mypackage', 'views')
201645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
202645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    If the package path is not given, ``'templates'`` is assumed.
203645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
204645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    Per default the template encoding is ``'utf-8'`` which can be changed
205645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    by setting the `encoding` parameter to something else.  Due to the nature
206645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    of eggs it's only possible to reload templates if the package was loaded
207645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    from the file system and not a zip file.
208645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """
209645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
210645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def __init__(self, package_name, package_path='templates',
211645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                 encoding='utf-8'):
212645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        from pkg_resources import DefaultProvider, ResourceManager, \
213645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                                  get_provider
214645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        provider = get_provider(package_name)
215645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        self.encoding = encoding
216645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        self.manager = ResourceManager()
217645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        self.filesystem_bound = isinstance(provider, DefaultProvider)
218645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        self.provider = provider
219645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        self.package_path = package_path
220645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
221645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def get_source(self, environment, template):
222645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        pieces = split_template_path(template)
223645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        p = '/'.join((self.package_path,) + tuple(pieces))
224645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        if not self.provider.has_resource(p):
225645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            raise TemplateNotFound(template)
226645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
227645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        filename = uptodate = None
228645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        if self.filesystem_bound:
229645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            filename = self.provider.get_resource_filename(self.manager, p)
230645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            mtime = path.getmtime(filename)
231645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            def uptodate():
232645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                try:
233645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                    return path.getmtime(filename) == mtime
234645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                except OSError:
235645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                    return False
236645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
237645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        source = self.provider.get_resource_string(self.manager, p)
238645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        return source.decode(self.encoding), filename, uptodate
239645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
240645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def list_templates(self):
241645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        path = self.package_path
242645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        if path[:2] == './':
243645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            path = path[2:]
244645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        elif path == '.':
245645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            path = ''
246645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        offset = len(path)
247645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        results = []
248645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        def _walk(path):
249645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            for filename in self.provider.resource_listdir(path):
250645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                fullname = path + '/' + filename
251645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                if self.provider.resource_isdir(fullname):
252645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                    _walk(fullname)
253645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                else:
254645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                    results.append(fullname[offset:].lstrip('/'))
255645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        _walk(path)
256645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        results.sort()
257645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        return results
258645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
259645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
260645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezclass DictLoader(BaseLoader):
261645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """Loads a template from a python dict.  It's passed a dict of unicode
262645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    strings bound to template names.  This loader is useful for unittesting:
263645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
264645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    >>> loader = DictLoader({'index.html': 'source here'})
265645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
266645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    Because auto reloading is rarely useful this is disabled per default.
267645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """
268645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
269645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def __init__(self, mapping):
270645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        self.mapping = mapping
271645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
272645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def get_source(self, environment, template):
273645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        if template in self.mapping:
274645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            source = self.mapping[template]
275645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            return source, None, lambda: source == self.mapping.get(template)
276645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        raise TemplateNotFound(template)
277645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
278645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def list_templates(self):
279645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        return sorted(self.mapping)
280645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
281645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
282645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezclass FunctionLoader(BaseLoader):
283645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """A loader that is passed a function which does the loading.  The
284645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    function becomes the name of the template passed and has to return either
285645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    an unicode string with the template source, a tuple in the form ``(source,
286645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    filename, uptodatefunc)`` or `None` if the template does not exist.
287645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
288645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    >>> def load_template(name):
289645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    ...     if name == 'index.html':
290645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    ...         return '...'
291645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    ...
292645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    >>> loader = FunctionLoader(load_template)
293645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
294645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    The `uptodatefunc` is a function that is called if autoreload is enabled
295645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    and has to return `True` if the template is still up to date.  For more
296645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    details have a look at :meth:`BaseLoader.get_source` which has the same
297645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    return value.
298645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """
299645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
300645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def __init__(self, load_func):
301645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        self.load_func = load_func
302645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
303645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def get_source(self, environment, template):
304645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        rv = self.load_func(template)
305645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        if rv is None:
306645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            raise TemplateNotFound(template)
307645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        elif isinstance(rv, string_types):
308645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            return rv, None, None
309645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        return rv
310645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
311645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
312645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezclass PrefixLoader(BaseLoader):
313645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """A loader that is passed a dict of loaders where each loader is bound
314645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    to a prefix.  The prefix is delimited from the template by a slash per
315645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    default, which can be changed by setting the `delimiter` argument to
316645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    something else::
317645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
318645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        loader = PrefixLoader({
319645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            'app1':     PackageLoader('mypackage.app1'),
320645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            'app2':     PackageLoader('mypackage.app2')
321645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        })
322645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
323645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    By loading ``'app1/index.html'`` the file from the app1 package is loaded,
324645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    by loading ``'app2/index.html'`` the file from the second.
325645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """
326645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
327645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def __init__(self, mapping, delimiter='/'):
328645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        self.mapping = mapping
329645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        self.delimiter = delimiter
330645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
331645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def get_loader(self, template):
332645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        try:
333645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            prefix, name = template.split(self.delimiter, 1)
334645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            loader = self.mapping[prefix]
335645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        except (ValueError, KeyError):
336645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            raise TemplateNotFound(template)
337645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        return loader, name
338645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
339645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def get_source(self, environment, template):
340645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        loader, name = self.get_loader(template)
341645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        try:
342645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            return loader.get_source(environment, name)
343645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        except TemplateNotFound:
344645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            # re-raise the exception with the correct fileame here.
345645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            # (the one that includes the prefix)
346645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            raise TemplateNotFound(template)
347645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
348645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    @internalcode
349645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def load(self, environment, name, globals=None):
350645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        loader, local_name = self.get_loader(name)
351645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        try:
352645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            return loader.load(environment, local_name)
353645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        except TemplateNotFound:
354645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            # re-raise the exception with the correct fileame here.
355645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            # (the one that includes the prefix)
356645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            raise TemplateNotFound(name)
357645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
358645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def list_templates(self):
359645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        result = []
360645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        for prefix, loader in iteritems(self.mapping):
361645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            for template in loader.list_templates():
362645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                result.append(prefix + self.delimiter + template)
363645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        return result
364645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
365645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
366645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezclass ChoiceLoader(BaseLoader):
367645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """This loader works like the `PrefixLoader` just that no prefix is
368645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    specified.  If a template could not be found by one loader the next one
369645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    is tried.
370645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
371645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    >>> loader = ChoiceLoader([
372645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    ...     FileSystemLoader('/path/to/user/templates'),
373645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    ...     FileSystemLoader('/path/to/system/templates')
374645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    ... ])
375645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
376645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    This is useful if you want to allow users to override builtin templates
377645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    from a different location.
378645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """
379645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
380645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def __init__(self, loaders):
381645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        self.loaders = loaders
382645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
383645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def get_source(self, environment, template):
384645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        for loader in self.loaders:
385645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            try:
386645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                return loader.get_source(environment, template)
387645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            except TemplateNotFound:
388645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                pass
389645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        raise TemplateNotFound(template)
390645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
391645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    @internalcode
392645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def load(self, environment, name, globals=None):
393645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        for loader in self.loaders:
394645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            try:
395645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                return loader.load(environment, name, globals)
396645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            except TemplateNotFound:
397645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                pass
398645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        raise TemplateNotFound(name)
399645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
400645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def list_templates(self):
401645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        found = set()
402645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        for loader in self.loaders:
403645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            found.update(loader.list_templates())
404645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        return sorted(found)
405645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
406645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
407645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezclass _TemplateModule(ModuleType):
408645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """Like a normal module but with support for weak references"""
409645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
410645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
411645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavezclass ModuleLoader(BaseLoader):
412645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """This loader loads templates from precompiled templates.
413645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
414645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    Example usage:
415645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
416645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    >>> loader = ChoiceLoader([
417645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    ...     ModuleLoader('/path/to/compiled/templates'),
418645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    ...     FileSystemLoader('/path/to/templates')
419645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    ... ])
420645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
421645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    Templates can be precompiled with :meth:`Environment.compile_templates`.
422645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    """
423645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
424645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    has_source_access = False
425645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
426645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def __init__(self, path):
427645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        package_name = '_jinja2_module_templates_%x' % id(self)
428645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
429645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        # create a fake module that looks for the templates in the
430645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        # path given.
431645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        mod = _TemplateModule(package_name)
432645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        if isinstance(path, string_types):
433645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            path = [path]
434645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        else:
435645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            path = list(path)
436645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        mod.__path__ = path
437645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
438645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        sys.modules[package_name] = weakref.proxy(mod,
439645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            lambda x: sys.modules.pop(package_name, None))
440645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
441645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        # the only strong reference, the sys.modules entry is weak
442645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        # so that the garbage collector can remove it once the
443645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        # loader that created it goes out of business.
444645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        self.module = mod
445645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        self.package_name = package_name
446645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
447645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    @staticmethod
448645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def get_template_key(name):
449645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest()
450645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
451645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    @staticmethod
452645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def get_module_filename(name):
453645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        return ModuleLoader.get_template_key(name) + '.py'
454645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
455645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    @internalcode
456645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez    def load(self, environment, name, globals=None):
457645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        key = self.get_template_key(name)
458645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        module = '%s.%s' % (self.package_name, key)
459645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        mod = getattr(self.module, module, None)
460645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        if mod is None:
461645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            try:
462645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                mod = __import__(module, None, None, ['root'])
463645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            except ImportError:
464645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez                raise TemplateNotFound(name)
465645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
466645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            # remove the entry from sys.modules, we only want the attribute
467645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            # on the module object we have stored on the loader.
468645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            sys.modules.pop(module, None)
469645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez
470645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez        return environment.template_class.from_module_dict(
471645501c2ab19a559ce82a1d5a29ced159a4c30fbLuis Hector Chavez            environment, mod.__dict__, globals)
472