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