xref: /aosp_15_r20/external/libchrome/third_party/jinja2/loaders.py (revision 635a864187cb8b6c713ff48b7e790a6b21769273)
1*635a8641SAndroid Build Coastguard Worker# -*- coding: utf-8 -*-
2*635a8641SAndroid Build Coastguard Worker"""
3*635a8641SAndroid Build Coastguard Worker    jinja2.loaders
4*635a8641SAndroid Build Coastguard Worker    ~~~~~~~~~~~~~~
5*635a8641SAndroid Build Coastguard Worker
6*635a8641SAndroid Build Coastguard Worker    Jinja loader classes.
7*635a8641SAndroid Build Coastguard Worker
8*635a8641SAndroid Build Coastguard Worker    :copyright: (c) 2017 by the Jinja Team.
9*635a8641SAndroid Build Coastguard Worker    :license: BSD, see LICENSE for more details.
10*635a8641SAndroid Build Coastguard Worker"""
11*635a8641SAndroid Build Coastguard Workerimport os
12*635a8641SAndroid Build Coastguard Workerimport sys
13*635a8641SAndroid Build Coastguard Workerimport weakref
14*635a8641SAndroid Build Coastguard Workerfrom types import ModuleType
15*635a8641SAndroid Build Coastguard Workerfrom os import path
16*635a8641SAndroid Build Coastguard Workerfrom hashlib import sha1
17*635a8641SAndroid Build Coastguard Workerfrom jinja2.exceptions import TemplateNotFound
18*635a8641SAndroid Build Coastguard Workerfrom jinja2.utils import open_if_exists, internalcode
19*635a8641SAndroid Build Coastguard Workerfrom jinja2._compat import string_types, iteritems
20*635a8641SAndroid Build Coastguard Worker
21*635a8641SAndroid Build Coastguard Worker
22*635a8641SAndroid Build Coastguard Workerdef split_template_path(template):
23*635a8641SAndroid Build Coastguard Worker    """Split a path into segments and perform a sanity check.  If it detects
24*635a8641SAndroid Build Coastguard Worker    '..' in the path it will raise a `TemplateNotFound` error.
25*635a8641SAndroid Build Coastguard Worker    """
26*635a8641SAndroid Build Coastguard Worker    pieces = []
27*635a8641SAndroid Build Coastguard Worker    for piece in template.split('/'):
28*635a8641SAndroid Build Coastguard Worker        if path.sep in piece \
29*635a8641SAndroid Build Coastguard Worker           or (path.altsep and path.altsep in piece) or \
30*635a8641SAndroid Build Coastguard Worker           piece == path.pardir:
31*635a8641SAndroid Build Coastguard Worker            raise TemplateNotFound(template)
32*635a8641SAndroid Build Coastguard Worker        elif piece and piece != '.':
33*635a8641SAndroid Build Coastguard Worker            pieces.append(piece)
34*635a8641SAndroid Build Coastguard Worker    return pieces
35*635a8641SAndroid Build Coastguard Worker
36*635a8641SAndroid Build Coastguard Worker
37*635a8641SAndroid Build Coastguard Workerclass BaseLoader(object):
38*635a8641SAndroid Build Coastguard Worker    """Baseclass for all loaders.  Subclass this and override `get_source` to
39*635a8641SAndroid Build Coastguard Worker    implement a custom loading mechanism.  The environment provides a
40*635a8641SAndroid Build Coastguard Worker    `get_template` method that calls the loader's `load` method to get the
41*635a8641SAndroid Build Coastguard Worker    :class:`Template` object.
42*635a8641SAndroid Build Coastguard Worker
43*635a8641SAndroid Build Coastguard Worker    A very basic example for a loader that looks up templates on the file
44*635a8641SAndroid Build Coastguard Worker    system could look like this::
45*635a8641SAndroid Build Coastguard Worker
46*635a8641SAndroid Build Coastguard Worker        from jinja2 import BaseLoader, TemplateNotFound
47*635a8641SAndroid Build Coastguard Worker        from os.path import join, exists, getmtime
48*635a8641SAndroid Build Coastguard Worker
49*635a8641SAndroid Build Coastguard Worker        class MyLoader(BaseLoader):
50*635a8641SAndroid Build Coastguard Worker
51*635a8641SAndroid Build Coastguard Worker            def __init__(self, path):
52*635a8641SAndroid Build Coastguard Worker                self.path = path
53*635a8641SAndroid Build Coastguard Worker
54*635a8641SAndroid Build Coastguard Worker            def get_source(self, environment, template):
55*635a8641SAndroid Build Coastguard Worker                path = join(self.path, template)
56*635a8641SAndroid Build Coastguard Worker                if not exists(path):
57*635a8641SAndroid Build Coastguard Worker                    raise TemplateNotFound(template)
58*635a8641SAndroid Build Coastguard Worker                mtime = getmtime(path)
59*635a8641SAndroid Build Coastguard Worker                with file(path) as f:
60*635a8641SAndroid Build Coastguard Worker                    source = f.read().decode('utf-8')
61*635a8641SAndroid Build Coastguard Worker                return source, path, lambda: mtime == getmtime(path)
62*635a8641SAndroid Build Coastguard Worker    """
63*635a8641SAndroid Build Coastguard Worker
64*635a8641SAndroid Build Coastguard Worker    #: if set to `False` it indicates that the loader cannot provide access
65*635a8641SAndroid Build Coastguard Worker    #: to the source of templates.
66*635a8641SAndroid Build Coastguard Worker    #:
67*635a8641SAndroid Build Coastguard Worker    #: .. versionadded:: 2.4
68*635a8641SAndroid Build Coastguard Worker    has_source_access = True
69*635a8641SAndroid Build Coastguard Worker
70*635a8641SAndroid Build Coastguard Worker    def get_source(self, environment, template):
71*635a8641SAndroid Build Coastguard Worker        """Get the template source, filename and reload helper for a template.
72*635a8641SAndroid Build Coastguard Worker        It's passed the environment and template name and has to return a
73*635a8641SAndroid Build Coastguard Worker        tuple in the form ``(source, filename, uptodate)`` or raise a
74*635a8641SAndroid Build Coastguard Worker        `TemplateNotFound` error if it can't locate the template.
75*635a8641SAndroid Build Coastguard Worker
76*635a8641SAndroid Build Coastguard Worker        The source part of the returned tuple must be the source of the
77*635a8641SAndroid Build Coastguard Worker        template as unicode string or a ASCII bytestring.  The filename should
78*635a8641SAndroid Build Coastguard Worker        be the name of the file on the filesystem if it was loaded from there,
79*635a8641SAndroid Build Coastguard Worker        otherwise `None`.  The filename is used by python for the tracebacks
80*635a8641SAndroid Build Coastguard Worker        if no loader extension is used.
81*635a8641SAndroid Build Coastguard Worker
82*635a8641SAndroid Build Coastguard Worker        The last item in the tuple is the `uptodate` function.  If auto
83*635a8641SAndroid Build Coastguard Worker        reloading is enabled it's always called to check if the template
84*635a8641SAndroid Build Coastguard Worker        changed.  No arguments are passed so the function must store the
85*635a8641SAndroid Build Coastguard Worker        old state somewhere (for example in a closure).  If it returns `False`
86*635a8641SAndroid Build Coastguard Worker        the template will be reloaded.
87*635a8641SAndroid Build Coastguard Worker        """
88*635a8641SAndroid Build Coastguard Worker        if not self.has_source_access:
89*635a8641SAndroid Build Coastguard Worker            raise RuntimeError('%s cannot provide access to the source' %
90*635a8641SAndroid Build Coastguard Worker                               self.__class__.__name__)
91*635a8641SAndroid Build Coastguard Worker        raise TemplateNotFound(template)
92*635a8641SAndroid Build Coastguard Worker
93*635a8641SAndroid Build Coastguard Worker    def list_templates(self):
94*635a8641SAndroid Build Coastguard Worker        """Iterates over all templates.  If the loader does not support that
95*635a8641SAndroid Build Coastguard Worker        it should raise a :exc:`TypeError` which is the default behavior.
96*635a8641SAndroid Build Coastguard Worker        """
97*635a8641SAndroid Build Coastguard Worker        raise TypeError('this loader cannot iterate over all templates')
98*635a8641SAndroid Build Coastguard Worker
99*635a8641SAndroid Build Coastguard Worker    @internalcode
100*635a8641SAndroid Build Coastguard Worker    def load(self, environment, name, globals=None):
101*635a8641SAndroid Build Coastguard Worker        """Loads a template.  This method looks up the template in the cache
102*635a8641SAndroid Build Coastguard Worker        or loads one by calling :meth:`get_source`.  Subclasses should not
103*635a8641SAndroid Build Coastguard Worker        override this method as loaders working on collections of other
104*635a8641SAndroid Build Coastguard Worker        loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
105*635a8641SAndroid Build Coastguard Worker        will not call this method but `get_source` directly.
106*635a8641SAndroid Build Coastguard Worker        """
107*635a8641SAndroid Build Coastguard Worker        code = None
108*635a8641SAndroid Build Coastguard Worker        if globals is None:
109*635a8641SAndroid Build Coastguard Worker            globals = {}
110*635a8641SAndroid Build Coastguard Worker
111*635a8641SAndroid Build Coastguard Worker        # first we try to get the source for this template together
112*635a8641SAndroid Build Coastguard Worker        # with the filename and the uptodate function.
113*635a8641SAndroid Build Coastguard Worker        source, filename, uptodate = self.get_source(environment, name)
114*635a8641SAndroid Build Coastguard Worker
115*635a8641SAndroid Build Coastguard Worker        # try to load the code from the bytecode cache if there is a
116*635a8641SAndroid Build Coastguard Worker        # bytecode cache configured.
117*635a8641SAndroid Build Coastguard Worker        bcc = environment.bytecode_cache
118*635a8641SAndroid Build Coastguard Worker        if bcc is not None:
119*635a8641SAndroid Build Coastguard Worker            bucket = bcc.get_bucket(environment, name, filename, source)
120*635a8641SAndroid Build Coastguard Worker            code = bucket.code
121*635a8641SAndroid Build Coastguard Worker
122*635a8641SAndroid Build Coastguard Worker        # if we don't have code so far (not cached, no longer up to
123*635a8641SAndroid Build Coastguard Worker        # date) etc. we compile the template
124*635a8641SAndroid Build Coastguard Worker        if code is None:
125*635a8641SAndroid Build Coastguard Worker            code = environment.compile(source, name, filename)
126*635a8641SAndroid Build Coastguard Worker
127*635a8641SAndroid Build Coastguard Worker        # if the bytecode cache is available and the bucket doesn't
128*635a8641SAndroid Build Coastguard Worker        # have a code so far, we give the bucket the new code and put
129*635a8641SAndroid Build Coastguard Worker        # it back to the bytecode cache.
130*635a8641SAndroid Build Coastguard Worker        if bcc is not None and bucket.code is None:
131*635a8641SAndroid Build Coastguard Worker            bucket.code = code
132*635a8641SAndroid Build Coastguard Worker            bcc.set_bucket(bucket)
133*635a8641SAndroid Build Coastguard Worker
134*635a8641SAndroid Build Coastguard Worker        return environment.template_class.from_code(environment, code,
135*635a8641SAndroid Build Coastguard Worker                                                    globals, uptodate)
136*635a8641SAndroid Build Coastguard Worker
137*635a8641SAndroid Build Coastguard Worker
138*635a8641SAndroid Build Coastguard Workerclass FileSystemLoader(BaseLoader):
139*635a8641SAndroid Build Coastguard Worker    """Loads templates from the file system.  This loader can find templates
140*635a8641SAndroid Build Coastguard Worker    in folders on the file system and is the preferred way to load them.
141*635a8641SAndroid Build Coastguard Worker
142*635a8641SAndroid Build Coastguard Worker    The loader takes the path to the templates as string, or if multiple
143*635a8641SAndroid Build Coastguard Worker    locations are wanted a list of them which is then looked up in the
144*635a8641SAndroid Build Coastguard Worker    given order::
145*635a8641SAndroid Build Coastguard Worker
146*635a8641SAndroid Build Coastguard Worker    >>> loader = FileSystemLoader('/path/to/templates')
147*635a8641SAndroid Build Coastguard Worker    >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
148*635a8641SAndroid Build Coastguard Worker
149*635a8641SAndroid Build Coastguard Worker    Per default the template encoding is ``'utf-8'`` which can be changed
150*635a8641SAndroid Build Coastguard Worker    by setting the `encoding` parameter to something else.
151*635a8641SAndroid Build Coastguard Worker
152*635a8641SAndroid Build Coastguard Worker    To follow symbolic links, set the *followlinks* parameter to ``True``::
153*635a8641SAndroid Build Coastguard Worker
154*635a8641SAndroid Build Coastguard Worker    >>> loader = FileSystemLoader('/path/to/templates', followlinks=True)
155*635a8641SAndroid Build Coastguard Worker
156*635a8641SAndroid Build Coastguard Worker    .. versionchanged:: 2.8+
157*635a8641SAndroid Build Coastguard Worker       The *followlinks* parameter was added.
158*635a8641SAndroid Build Coastguard Worker    """
159*635a8641SAndroid Build Coastguard Worker
160*635a8641SAndroid Build Coastguard Worker    def __init__(self, searchpath, encoding='utf-8', followlinks=False):
161*635a8641SAndroid Build Coastguard Worker        if isinstance(searchpath, string_types):
162*635a8641SAndroid Build Coastguard Worker            searchpath = [searchpath]
163*635a8641SAndroid Build Coastguard Worker        self.searchpath = list(searchpath)
164*635a8641SAndroid Build Coastguard Worker        self.encoding = encoding
165*635a8641SAndroid Build Coastguard Worker        self.followlinks = followlinks
166*635a8641SAndroid Build Coastguard Worker
167*635a8641SAndroid Build Coastguard Worker    def get_source(self, environment, template):
168*635a8641SAndroid Build Coastguard Worker        pieces = split_template_path(template)
169*635a8641SAndroid Build Coastguard Worker        for searchpath in self.searchpath:
170*635a8641SAndroid Build Coastguard Worker            filename = path.join(searchpath, *pieces)
171*635a8641SAndroid Build Coastguard Worker            f = open_if_exists(filename)
172*635a8641SAndroid Build Coastguard Worker            if f is None:
173*635a8641SAndroid Build Coastguard Worker                continue
174*635a8641SAndroid Build Coastguard Worker            try:
175*635a8641SAndroid Build Coastguard Worker                contents = f.read().decode(self.encoding)
176*635a8641SAndroid Build Coastguard Worker            finally:
177*635a8641SAndroid Build Coastguard Worker                f.close()
178*635a8641SAndroid Build Coastguard Worker
179*635a8641SAndroid Build Coastguard Worker            mtime = path.getmtime(filename)
180*635a8641SAndroid Build Coastguard Worker
181*635a8641SAndroid Build Coastguard Worker            def uptodate():
182*635a8641SAndroid Build Coastguard Worker                try:
183*635a8641SAndroid Build Coastguard Worker                    return path.getmtime(filename) == mtime
184*635a8641SAndroid Build Coastguard Worker                except OSError:
185*635a8641SAndroid Build Coastguard Worker                    return False
186*635a8641SAndroid Build Coastguard Worker            return contents, filename, uptodate
187*635a8641SAndroid Build Coastguard Worker        raise TemplateNotFound(template)
188*635a8641SAndroid Build Coastguard Worker
189*635a8641SAndroid Build Coastguard Worker    def list_templates(self):
190*635a8641SAndroid Build Coastguard Worker        found = set()
191*635a8641SAndroid Build Coastguard Worker        for searchpath in self.searchpath:
192*635a8641SAndroid Build Coastguard Worker            walk_dir = os.walk(searchpath, followlinks=self.followlinks)
193*635a8641SAndroid Build Coastguard Worker            for dirpath, dirnames, filenames in walk_dir:
194*635a8641SAndroid Build Coastguard Worker                for filename in filenames:
195*635a8641SAndroid Build Coastguard Worker                    template = os.path.join(dirpath, filename) \
196*635a8641SAndroid Build Coastguard Worker                        [len(searchpath):].strip(os.path.sep) \
197*635a8641SAndroid Build Coastguard Worker                                          .replace(os.path.sep, '/')
198*635a8641SAndroid Build Coastguard Worker                    if template[:2] == './':
199*635a8641SAndroid Build Coastguard Worker                        template = template[2:]
200*635a8641SAndroid Build Coastguard Worker                    if template not in found:
201*635a8641SAndroid Build Coastguard Worker                        found.add(template)
202*635a8641SAndroid Build Coastguard Worker        return sorted(found)
203*635a8641SAndroid Build Coastguard Worker
204*635a8641SAndroid Build Coastguard Worker
205*635a8641SAndroid Build Coastguard Workerclass PackageLoader(BaseLoader):
206*635a8641SAndroid Build Coastguard Worker    """Load templates from python eggs or packages.  It is constructed with
207*635a8641SAndroid Build Coastguard Worker    the name of the python package and the path to the templates in that
208*635a8641SAndroid Build Coastguard Worker    package::
209*635a8641SAndroid Build Coastguard Worker
210*635a8641SAndroid Build Coastguard Worker        loader = PackageLoader('mypackage', 'views')
211*635a8641SAndroid Build Coastguard Worker
212*635a8641SAndroid Build Coastguard Worker    If the package path is not given, ``'templates'`` is assumed.
213*635a8641SAndroid Build Coastguard Worker
214*635a8641SAndroid Build Coastguard Worker    Per default the template encoding is ``'utf-8'`` which can be changed
215*635a8641SAndroid Build Coastguard Worker    by setting the `encoding` parameter to something else.  Due to the nature
216*635a8641SAndroid Build Coastguard Worker    of eggs it's only possible to reload templates if the package was loaded
217*635a8641SAndroid Build Coastguard Worker    from the file system and not a zip file.
218*635a8641SAndroid Build Coastguard Worker    """
219*635a8641SAndroid Build Coastguard Worker
220*635a8641SAndroid Build Coastguard Worker    def __init__(self, package_name, package_path='templates',
221*635a8641SAndroid Build Coastguard Worker                 encoding='utf-8'):
222*635a8641SAndroid Build Coastguard Worker        from pkg_resources import DefaultProvider, ResourceManager, \
223*635a8641SAndroid Build Coastguard Worker                                  get_provider
224*635a8641SAndroid Build Coastguard Worker        provider = get_provider(package_name)
225*635a8641SAndroid Build Coastguard Worker        self.encoding = encoding
226*635a8641SAndroid Build Coastguard Worker        self.manager = ResourceManager()
227*635a8641SAndroid Build Coastguard Worker        self.filesystem_bound = isinstance(provider, DefaultProvider)
228*635a8641SAndroid Build Coastguard Worker        self.provider = provider
229*635a8641SAndroid Build Coastguard Worker        self.package_path = package_path
230*635a8641SAndroid Build Coastguard Worker
231*635a8641SAndroid Build Coastguard Worker    def get_source(self, environment, template):
232*635a8641SAndroid Build Coastguard Worker        pieces = split_template_path(template)
233*635a8641SAndroid Build Coastguard Worker        p = '/'.join((self.package_path,) + tuple(pieces))
234*635a8641SAndroid Build Coastguard Worker        if not self.provider.has_resource(p):
235*635a8641SAndroid Build Coastguard Worker            raise TemplateNotFound(template)
236*635a8641SAndroid Build Coastguard Worker
237*635a8641SAndroid Build Coastguard Worker        filename = uptodate = None
238*635a8641SAndroid Build Coastguard Worker        if self.filesystem_bound:
239*635a8641SAndroid Build Coastguard Worker            filename = self.provider.get_resource_filename(self.manager, p)
240*635a8641SAndroid Build Coastguard Worker            mtime = path.getmtime(filename)
241*635a8641SAndroid Build Coastguard Worker            def uptodate():
242*635a8641SAndroid Build Coastguard Worker                try:
243*635a8641SAndroid Build Coastguard Worker                    return path.getmtime(filename) == mtime
244*635a8641SAndroid Build Coastguard Worker                except OSError:
245*635a8641SAndroid Build Coastguard Worker                    return False
246*635a8641SAndroid Build Coastguard Worker
247*635a8641SAndroid Build Coastguard Worker        source = self.provider.get_resource_string(self.manager, p)
248*635a8641SAndroid Build Coastguard Worker        return source.decode(self.encoding), filename, uptodate
249*635a8641SAndroid Build Coastguard Worker
250*635a8641SAndroid Build Coastguard Worker    def list_templates(self):
251*635a8641SAndroid Build Coastguard Worker        path = self.package_path
252*635a8641SAndroid Build Coastguard Worker        if path[:2] == './':
253*635a8641SAndroid Build Coastguard Worker            path = path[2:]
254*635a8641SAndroid Build Coastguard Worker        elif path == '.':
255*635a8641SAndroid Build Coastguard Worker            path = ''
256*635a8641SAndroid Build Coastguard Worker        offset = len(path)
257*635a8641SAndroid Build Coastguard Worker        results = []
258*635a8641SAndroid Build Coastguard Worker        def _walk(path):
259*635a8641SAndroid Build Coastguard Worker            for filename in self.provider.resource_listdir(path):
260*635a8641SAndroid Build Coastguard Worker                fullname = path + '/' + filename
261*635a8641SAndroid Build Coastguard Worker                if self.provider.resource_isdir(fullname):
262*635a8641SAndroid Build Coastguard Worker                    _walk(fullname)
263*635a8641SAndroid Build Coastguard Worker                else:
264*635a8641SAndroid Build Coastguard Worker                    results.append(fullname[offset:].lstrip('/'))
265*635a8641SAndroid Build Coastguard Worker        _walk(path)
266*635a8641SAndroid Build Coastguard Worker        results.sort()
267*635a8641SAndroid Build Coastguard Worker        return results
268*635a8641SAndroid Build Coastguard Worker
269*635a8641SAndroid Build Coastguard Worker
270*635a8641SAndroid Build Coastguard Workerclass DictLoader(BaseLoader):
271*635a8641SAndroid Build Coastguard Worker    """Loads a template from a python dict.  It's passed a dict of unicode
272*635a8641SAndroid Build Coastguard Worker    strings bound to template names.  This loader is useful for unittesting:
273*635a8641SAndroid Build Coastguard Worker
274*635a8641SAndroid Build Coastguard Worker    >>> loader = DictLoader({'index.html': 'source here'})
275*635a8641SAndroid Build Coastguard Worker
276*635a8641SAndroid Build Coastguard Worker    Because auto reloading is rarely useful this is disabled per default.
277*635a8641SAndroid Build Coastguard Worker    """
278*635a8641SAndroid Build Coastguard Worker
279*635a8641SAndroid Build Coastguard Worker    def __init__(self, mapping):
280*635a8641SAndroid Build Coastguard Worker        self.mapping = mapping
281*635a8641SAndroid Build Coastguard Worker
282*635a8641SAndroid Build Coastguard Worker    def get_source(self, environment, template):
283*635a8641SAndroid Build Coastguard Worker        if template in self.mapping:
284*635a8641SAndroid Build Coastguard Worker            source = self.mapping[template]
285*635a8641SAndroid Build Coastguard Worker            return source, None, lambda: source == self.mapping.get(template)
286*635a8641SAndroid Build Coastguard Worker        raise TemplateNotFound(template)
287*635a8641SAndroid Build Coastguard Worker
288*635a8641SAndroid Build Coastguard Worker    def list_templates(self):
289*635a8641SAndroid Build Coastguard Worker        return sorted(self.mapping)
290*635a8641SAndroid Build Coastguard Worker
291*635a8641SAndroid Build Coastguard Worker
292*635a8641SAndroid Build Coastguard Workerclass FunctionLoader(BaseLoader):
293*635a8641SAndroid Build Coastguard Worker    """A loader that is passed a function which does the loading.  The
294*635a8641SAndroid Build Coastguard Worker    function receives the name of the template and has to return either
295*635a8641SAndroid Build Coastguard Worker    an unicode string with the template source, a tuple in the form ``(source,
296*635a8641SAndroid Build Coastguard Worker    filename, uptodatefunc)`` or `None` if the template does not exist.
297*635a8641SAndroid Build Coastguard Worker
298*635a8641SAndroid Build Coastguard Worker    >>> def load_template(name):
299*635a8641SAndroid Build Coastguard Worker    ...     if name == 'index.html':
300*635a8641SAndroid Build Coastguard Worker    ...         return '...'
301*635a8641SAndroid Build Coastguard Worker    ...
302*635a8641SAndroid Build Coastguard Worker    >>> loader = FunctionLoader(load_template)
303*635a8641SAndroid Build Coastguard Worker
304*635a8641SAndroid Build Coastguard Worker    The `uptodatefunc` is a function that is called if autoreload is enabled
305*635a8641SAndroid Build Coastguard Worker    and has to return `True` if the template is still up to date.  For more
306*635a8641SAndroid Build Coastguard Worker    details have a look at :meth:`BaseLoader.get_source` which has the same
307*635a8641SAndroid Build Coastguard Worker    return value.
308*635a8641SAndroid Build Coastguard Worker    """
309*635a8641SAndroid Build Coastguard Worker
310*635a8641SAndroid Build Coastguard Worker    def __init__(self, load_func):
311*635a8641SAndroid Build Coastguard Worker        self.load_func = load_func
312*635a8641SAndroid Build Coastguard Worker
313*635a8641SAndroid Build Coastguard Worker    def get_source(self, environment, template):
314*635a8641SAndroid Build Coastguard Worker        rv = self.load_func(template)
315*635a8641SAndroid Build Coastguard Worker        if rv is None:
316*635a8641SAndroid Build Coastguard Worker            raise TemplateNotFound(template)
317*635a8641SAndroid Build Coastguard Worker        elif isinstance(rv, string_types):
318*635a8641SAndroid Build Coastguard Worker            return rv, None, None
319*635a8641SAndroid Build Coastguard Worker        return rv
320*635a8641SAndroid Build Coastguard Worker
321*635a8641SAndroid Build Coastguard Worker
322*635a8641SAndroid Build Coastguard Workerclass PrefixLoader(BaseLoader):
323*635a8641SAndroid Build Coastguard Worker    """A loader that is passed a dict of loaders where each loader is bound
324*635a8641SAndroid Build Coastguard Worker    to a prefix.  The prefix is delimited from the template by a slash per
325*635a8641SAndroid Build Coastguard Worker    default, which can be changed by setting the `delimiter` argument to
326*635a8641SAndroid Build Coastguard Worker    something else::
327*635a8641SAndroid Build Coastguard Worker
328*635a8641SAndroid Build Coastguard Worker        loader = PrefixLoader({
329*635a8641SAndroid Build Coastguard Worker            'app1':     PackageLoader('mypackage.app1'),
330*635a8641SAndroid Build Coastguard Worker            'app2':     PackageLoader('mypackage.app2')
331*635a8641SAndroid Build Coastguard Worker        })
332*635a8641SAndroid Build Coastguard Worker
333*635a8641SAndroid Build Coastguard Worker    By loading ``'app1/index.html'`` the file from the app1 package is loaded,
334*635a8641SAndroid Build Coastguard Worker    by loading ``'app2/index.html'`` the file from the second.
335*635a8641SAndroid Build Coastguard Worker    """
336*635a8641SAndroid Build Coastguard Worker
337*635a8641SAndroid Build Coastguard Worker    def __init__(self, mapping, delimiter='/'):
338*635a8641SAndroid Build Coastguard Worker        self.mapping = mapping
339*635a8641SAndroid Build Coastguard Worker        self.delimiter = delimiter
340*635a8641SAndroid Build Coastguard Worker
341*635a8641SAndroid Build Coastguard Worker    def get_loader(self, template):
342*635a8641SAndroid Build Coastguard Worker        try:
343*635a8641SAndroid Build Coastguard Worker            prefix, name = template.split(self.delimiter, 1)
344*635a8641SAndroid Build Coastguard Worker            loader = self.mapping[prefix]
345*635a8641SAndroid Build Coastguard Worker        except (ValueError, KeyError):
346*635a8641SAndroid Build Coastguard Worker            raise TemplateNotFound(template)
347*635a8641SAndroid Build Coastguard Worker        return loader, name
348*635a8641SAndroid Build Coastguard Worker
349*635a8641SAndroid Build Coastguard Worker    def get_source(self, environment, template):
350*635a8641SAndroid Build Coastguard Worker        loader, name = self.get_loader(template)
351*635a8641SAndroid Build Coastguard Worker        try:
352*635a8641SAndroid Build Coastguard Worker            return loader.get_source(environment, name)
353*635a8641SAndroid Build Coastguard Worker        except TemplateNotFound:
354*635a8641SAndroid Build Coastguard Worker            # re-raise the exception with the correct filename here.
355*635a8641SAndroid Build Coastguard Worker            # (the one that includes the prefix)
356*635a8641SAndroid Build Coastguard Worker            raise TemplateNotFound(template)
357*635a8641SAndroid Build Coastguard Worker
358*635a8641SAndroid Build Coastguard Worker    @internalcode
359*635a8641SAndroid Build Coastguard Worker    def load(self, environment, name, globals=None):
360*635a8641SAndroid Build Coastguard Worker        loader, local_name = self.get_loader(name)
361*635a8641SAndroid Build Coastguard Worker        try:
362*635a8641SAndroid Build Coastguard Worker            return loader.load(environment, local_name, globals)
363*635a8641SAndroid Build Coastguard Worker        except TemplateNotFound:
364*635a8641SAndroid Build Coastguard Worker            # re-raise the exception with the correct filename here.
365*635a8641SAndroid Build Coastguard Worker            # (the one that includes the prefix)
366*635a8641SAndroid Build Coastguard Worker            raise TemplateNotFound(name)
367*635a8641SAndroid Build Coastguard Worker
368*635a8641SAndroid Build Coastguard Worker    def list_templates(self):
369*635a8641SAndroid Build Coastguard Worker        result = []
370*635a8641SAndroid Build Coastguard Worker        for prefix, loader in iteritems(self.mapping):
371*635a8641SAndroid Build Coastguard Worker            for template in loader.list_templates():
372*635a8641SAndroid Build Coastguard Worker                result.append(prefix + self.delimiter + template)
373*635a8641SAndroid Build Coastguard Worker        return result
374*635a8641SAndroid Build Coastguard Worker
375*635a8641SAndroid Build Coastguard Worker
376*635a8641SAndroid Build Coastguard Workerclass ChoiceLoader(BaseLoader):
377*635a8641SAndroid Build Coastguard Worker    """This loader works like the `PrefixLoader` just that no prefix is
378*635a8641SAndroid Build Coastguard Worker    specified.  If a template could not be found by one loader the next one
379*635a8641SAndroid Build Coastguard Worker    is tried.
380*635a8641SAndroid Build Coastguard Worker
381*635a8641SAndroid Build Coastguard Worker    >>> loader = ChoiceLoader([
382*635a8641SAndroid Build Coastguard Worker    ...     FileSystemLoader('/path/to/user/templates'),
383*635a8641SAndroid Build Coastguard Worker    ...     FileSystemLoader('/path/to/system/templates')
384*635a8641SAndroid Build Coastguard Worker    ... ])
385*635a8641SAndroid Build Coastguard Worker
386*635a8641SAndroid Build Coastguard Worker    This is useful if you want to allow users to override builtin templates
387*635a8641SAndroid Build Coastguard Worker    from a different location.
388*635a8641SAndroid Build Coastguard Worker    """
389*635a8641SAndroid Build Coastguard Worker
390*635a8641SAndroid Build Coastguard Worker    def __init__(self, loaders):
391*635a8641SAndroid Build Coastguard Worker        self.loaders = loaders
392*635a8641SAndroid Build Coastguard Worker
393*635a8641SAndroid Build Coastguard Worker    def get_source(self, environment, template):
394*635a8641SAndroid Build Coastguard Worker        for loader in self.loaders:
395*635a8641SAndroid Build Coastguard Worker            try:
396*635a8641SAndroid Build Coastguard Worker                return loader.get_source(environment, template)
397*635a8641SAndroid Build Coastguard Worker            except TemplateNotFound:
398*635a8641SAndroid Build Coastguard Worker                pass
399*635a8641SAndroid Build Coastguard Worker        raise TemplateNotFound(template)
400*635a8641SAndroid Build Coastguard Worker
401*635a8641SAndroid Build Coastguard Worker    @internalcode
402*635a8641SAndroid Build Coastguard Worker    def load(self, environment, name, globals=None):
403*635a8641SAndroid Build Coastguard Worker        for loader in self.loaders:
404*635a8641SAndroid Build Coastguard Worker            try:
405*635a8641SAndroid Build Coastguard Worker                return loader.load(environment, name, globals)
406*635a8641SAndroid Build Coastguard Worker            except TemplateNotFound:
407*635a8641SAndroid Build Coastguard Worker                pass
408*635a8641SAndroid Build Coastguard Worker        raise TemplateNotFound(name)
409*635a8641SAndroid Build Coastguard Worker
410*635a8641SAndroid Build Coastguard Worker    def list_templates(self):
411*635a8641SAndroid Build Coastguard Worker        found = set()
412*635a8641SAndroid Build Coastguard Worker        for loader in self.loaders:
413*635a8641SAndroid Build Coastguard Worker            found.update(loader.list_templates())
414*635a8641SAndroid Build Coastguard Worker        return sorted(found)
415*635a8641SAndroid Build Coastguard Worker
416*635a8641SAndroid Build Coastguard Worker
417*635a8641SAndroid Build Coastguard Workerclass _TemplateModule(ModuleType):
418*635a8641SAndroid Build Coastguard Worker    """Like a normal module but with support for weak references"""
419*635a8641SAndroid Build Coastguard Worker
420*635a8641SAndroid Build Coastguard Worker
421*635a8641SAndroid Build Coastguard Workerclass ModuleLoader(BaseLoader):
422*635a8641SAndroid Build Coastguard Worker    """This loader loads templates from precompiled templates.
423*635a8641SAndroid Build Coastguard Worker
424*635a8641SAndroid Build Coastguard Worker    Example usage:
425*635a8641SAndroid Build Coastguard Worker
426*635a8641SAndroid Build Coastguard Worker    >>> loader = ChoiceLoader([
427*635a8641SAndroid Build Coastguard Worker    ...     ModuleLoader('/path/to/compiled/templates'),
428*635a8641SAndroid Build Coastguard Worker    ...     FileSystemLoader('/path/to/templates')
429*635a8641SAndroid Build Coastguard Worker    ... ])
430*635a8641SAndroid Build Coastguard Worker
431*635a8641SAndroid Build Coastguard Worker    Templates can be precompiled with :meth:`Environment.compile_templates`.
432*635a8641SAndroid Build Coastguard Worker    """
433*635a8641SAndroid Build Coastguard Worker
434*635a8641SAndroid Build Coastguard Worker    has_source_access = False
435*635a8641SAndroid Build Coastguard Worker
436*635a8641SAndroid Build Coastguard Worker    def __init__(self, path):
437*635a8641SAndroid Build Coastguard Worker        package_name = '_jinja2_module_templates_%x' % id(self)
438*635a8641SAndroid Build Coastguard Worker
439*635a8641SAndroid Build Coastguard Worker        # create a fake module that looks for the templates in the
440*635a8641SAndroid Build Coastguard Worker        # path given.
441*635a8641SAndroid Build Coastguard Worker        mod = _TemplateModule(package_name)
442*635a8641SAndroid Build Coastguard Worker        if isinstance(path, string_types):
443*635a8641SAndroid Build Coastguard Worker            path = [path]
444*635a8641SAndroid Build Coastguard Worker        else:
445*635a8641SAndroid Build Coastguard Worker            path = list(path)
446*635a8641SAndroid Build Coastguard Worker        mod.__path__ = path
447*635a8641SAndroid Build Coastguard Worker
448*635a8641SAndroid Build Coastguard Worker        sys.modules[package_name] = weakref.proxy(mod,
449*635a8641SAndroid Build Coastguard Worker            lambda x: sys.modules.pop(package_name, None))
450*635a8641SAndroid Build Coastguard Worker
451*635a8641SAndroid Build Coastguard Worker        # the only strong reference, the sys.modules entry is weak
452*635a8641SAndroid Build Coastguard Worker        # so that the garbage collector can remove it once the
453*635a8641SAndroid Build Coastguard Worker        # loader that created it goes out of business.
454*635a8641SAndroid Build Coastguard Worker        self.module = mod
455*635a8641SAndroid Build Coastguard Worker        self.package_name = package_name
456*635a8641SAndroid Build Coastguard Worker
457*635a8641SAndroid Build Coastguard Worker    @staticmethod
458*635a8641SAndroid Build Coastguard Worker    def get_template_key(name):
459*635a8641SAndroid Build Coastguard Worker        return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest()
460*635a8641SAndroid Build Coastguard Worker
461*635a8641SAndroid Build Coastguard Worker    @staticmethod
462*635a8641SAndroid Build Coastguard Worker    def get_module_filename(name):
463*635a8641SAndroid Build Coastguard Worker        return ModuleLoader.get_template_key(name) + '.py'
464*635a8641SAndroid Build Coastguard Worker
465*635a8641SAndroid Build Coastguard Worker    @internalcode
466*635a8641SAndroid Build Coastguard Worker    def load(self, environment, name, globals=None):
467*635a8641SAndroid Build Coastguard Worker        key = self.get_template_key(name)
468*635a8641SAndroid Build Coastguard Worker        module = '%s.%s' % (self.package_name, key)
469*635a8641SAndroid Build Coastguard Worker        mod = getattr(self.module, module, None)
470*635a8641SAndroid Build Coastguard Worker        if mod is None:
471*635a8641SAndroid Build Coastguard Worker            try:
472*635a8641SAndroid Build Coastguard Worker                mod = __import__(module, None, None, ['root'])
473*635a8641SAndroid Build Coastguard Worker            except ImportError:
474*635a8641SAndroid Build Coastguard Worker                raise TemplateNotFound(name)
475*635a8641SAndroid Build Coastguard Worker
476*635a8641SAndroid Build Coastguard Worker            # remove the entry from sys.modules, we only want the attribute
477*635a8641SAndroid Build Coastguard Worker            # on the module object we have stored on the loader.
478*635a8641SAndroid Build Coastguard Worker            sys.modules.pop(module, None)
479*635a8641SAndroid Build Coastguard Worker
480*635a8641SAndroid Build Coastguard Worker        return environment.template_class.from_module_dict(
481*635a8641SAndroid Build Coastguard Worker            environment, mod.__dict__, globals)
482