662425c17cc16d67fd8aa4184cc1ca793c74db21
[jinja2.git] / jinja2 / loaders.py
1 # -*- coding: utf-8 -*-
2 """
3     jinja2.loaders
4     ~~~~~~~~~~~~~~
5
6     Jinja loader classes.
7
8     :copyright: 2008 by Armin Ronacher.
9     :license: BSD, see LICENSE for more details.
10 """
11 from os import path
12 try:
13     from hashlib import sha1
14 except ImportError:
15     from sha import new as sha1
16 from jinja2.exceptions import TemplateNotFound
17 from jinja2.utils import LRUCache
18
19
20 def split_template_path(template):
21     """Split a path into segments and perform a sanity check.  If it detects
22     '..' in the path it will raise a `TemplateNotFound` error.
23     """
24     pieces = []
25     for piece in template.split('/'):
26         if path.sep in piece \
27            or (path.altsep and path.altsep in piece) or \
28            piece == path.pardir:
29             raise TemplateNotFound(template)
30         elif piece and piece != '.':
31             pieces.append(piece)
32     return pieces
33
34
35 class BaseLoader(object):
36     """Baseclass for all loaders.  Subclass this and override `get_source` to
37     implement a custom loading mechanism.  The environment provides a
38     `get_template` method that calls the loader's `load` method to get the
39     :class:`Template` object.
40
41     A very basic example for a loader that looks up templates on the file
42     system could look like this::
43
44         from jinja2 import BaseLoader, TemplateNotFound
45         from os.path import join, exists, getmtime
46
47         class MyLoader(BaseLoader):
48
49             def __init__(self, path):
50                 self.path = path
51
52             def get_source(self, environment, template):
53                 path = join(self.path, template)
54                 if not exists(path):
55                     raise TemplateNotFound(template)
56                 mtime = getmtime(path)
57                 with file(path) as f:
58                     source = f.read().decode('utf-8')
59                 return source, path, lambda: mtime == getmtime(path)
60     """
61
62     def get_source(self, environment, template):
63         """Get the template source, filename and reload helper for a template.
64         It's passed the environment and template name and has to return a
65         tuple in the form ``(source, filename, uptodate)`` or raise a
66         `TemplateNotFound` error if it can't locate the template.
67
68         The source part of the returned tuple must be the source of the
69         template as unicode string or a ASCII bytestring.  The filename should
70         be the name of the file on the filesystem if it was loaded from there,
71         otherwise `None`.  The filename is used by python for the tracebacks
72         if no loader extension is used.
73
74         The last item in the tuple is the `uptodate` function.  If auto
75         reloading is enabled it's always called to check if the template
76         changed.  No arguments are passed so the function must store the
77         old state somewhere (for example in a closure).  If it returns `False`
78         the template will be reloaded.
79         """
80         raise TemplateNotFound(template)
81
82     def load(self, environment, name, globals=None):
83         """Loads a template.  This method looks up the template in the cache
84         or loads one by calling :meth:`get_source`.  Subclasses should not
85         override this method as loaders working on collections of other
86         loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
87         will not call this method but `get_source` directly.
88         """
89         if globals is None:
90             globals = {}
91         source, filename, uptodate = self.get_source(environment, name)
92
93         code = bucket = None
94         if environment.bytecode_cache is not None:
95             bucket = environment.bytecode_cache.get_bucket(environment, name,
96                                                            source)
97             code = bucket.code
98
99         if code is None:
100             code = environment.compile(source, name, filename)
101
102         if bucket and bucket.code is None:
103             bucket.code = code
104             bucket.write_back()
105
106         return environment.template_class.from_code(environment, code,
107                                                     globals, uptodate)
108
109
110 class FileSystemLoader(BaseLoader):
111     """Loads templates from the file system.  This loader can find templates
112     in folders on the file system and is the preferred way to load them.
113
114     The loader takes the path to the templates as string, or if multiple
115     locations are wanted a list of them which is then looked up in the
116     given order:
117
118     >>> loader = FileSystemLoader('/path/to/templates')
119     >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
120
121     Per default the template encoding is ``'utf-8'`` which can be changed
122     by setting the `encoding` parameter to something else.
123     """
124
125     def __init__(self, searchpath, encoding='utf-8'):
126         if isinstance(searchpath, basestring):
127             searchpath = [searchpath]
128         self.searchpath = list(searchpath)
129         self.encoding = encoding
130
131     def get_source(self, environment, template):
132         pieces = split_template_path(template)
133         for searchpath in self.searchpath:
134             filename = path.join(searchpath, *pieces)
135             if not path.isfile(filename):
136                 continue
137             f = file(filename)
138             try:
139                 contents = f.read().decode(self.encoding)
140             finally:
141                 f.close()
142             old = path.getmtime(filename)
143             return contents, filename, lambda: path.getmtime(filename) == old
144         raise TemplateNotFound(template)
145
146
147 class PackageLoader(BaseLoader):
148     """Load templates from python eggs or packages.  It is constructed with
149     the name of the python package and the path to the templates in that
150     package:
151
152     >>> loader = PackageLoader('mypackage', 'views')
153
154     If the package path is not given, ``'templates'`` is assumed.
155
156     Per default the template encoding is ``'utf-8'`` which can be changed
157     by setting the `encoding` parameter to something else.  Due to the nature
158     of eggs it's only possible to reload templates if the package was loaded
159     from the file system and not a zip file.
160     """
161
162     def __init__(self, package_name, package_path='templates',
163                  encoding='utf-8'):
164         from pkg_resources import DefaultProvider, ResourceManager, get_provider
165         provider = get_provider(package_name)
166         self.encoding = encoding
167         self.manager = ResourceManager()
168         self.filesystem_bound = isinstance(provider, DefaultProvider)
169         self.provider = provider
170         self.package_path = package_path
171
172     def get_source(self, environment, template):
173         pieces = split_template_path(template)
174         p = '/'.join((self.package_path,) + tuple(pieces))
175         if not self.provider.has_resource(p):
176             raise TemplateNotFound(template)
177
178         filename = uptodate = None
179         if self.filesystem_bound:
180             filename = self.provider.get_resource_filename(self.manager, p)
181             mtime = path.getmtime(filename)
182             def uptodate():
183                 return path.getmtime(filename) == mtime
184
185         source = self.provider.get_resource_string(self.manager, p)
186         return source.decode(self.encoding), filename, uptodate
187
188
189 class DictLoader(BaseLoader):
190     """Loads a template from a python dict.  It's passed a dict of unicode
191     strings bound to template names.  This loader is useful for unittesting:
192
193     >>> loader = DictLoader({'index.html': 'source here'})
194
195     Because auto reloading is rarely useful this is disabled per default.
196     """
197
198     def __init__(self, mapping):
199         self.mapping = mapping
200
201     def get_source(self, environment, template):
202         if template in self.mapping:
203             source = self.mapping[template]
204             return source, None, lambda: source != self.mapping[template]
205         raise TemplateNotFound(template)
206
207
208 class FunctionLoader(BaseLoader):
209     """A loader that is passed a function which does the loading.  The
210     function becomes the name of the template passed and has to return either
211     an unicode string with the template source, a tuple in the form ``(source,
212     filename, uptodatefunc)`` or `None` if the template does not exist.
213
214     >>> def load_template(name):
215     ...     if name == 'index.html'
216     ...         return '...'
217     ...
218     >>> loader = FunctionLoader(load_template)
219
220     The `uptodatefunc` is a function that is called if autoreload is enabled
221     and has to return `True` if the template is still up to date.  For more
222     details have a look at :meth:`BaseLoader.get_source` which has the same
223     return value.
224     """
225
226     def __init__(self, load_func):
227         self.load_func = load_func
228
229     def get_source(self, environment, template):
230         rv = self.load_func(template)
231         if rv is None:
232             raise TemplateNotFound(template)
233         elif isinstance(rv, basestring):
234             return rv, None, None
235         return rv
236
237
238 class PrefixLoader(BaseLoader):
239     """A loader that is passed a dict of loaders where each loader is bound
240     to a prefix.  The prefix is delimited from the template by a slash per
241     default, which can be changed by setting the `delimiter` argument to
242     something else.
243
244     >>> loader = PrefixLoader({
245     ...     'app1':     PackageLoader('mypackage.app1'),
246     ...     'app2':     PackageLoader('mypackage.app2')
247     ... })
248
249     By loading ``'app1/index.html'`` the file from the app1 package is loaded,
250     by loading ``'app2/index.html'`` the file from the second.
251     """
252
253     def __init__(self, mapping, delimiter='/'):
254         self.mapping = mapping
255         self.delimiter = delimiter
256
257     def get_source(self, environment, template):
258         try:
259             prefix, template = template.split(self.delimiter, 1)
260             loader = self.mapping[prefix]
261         except (ValueError, KeyError):
262             raise TemplateNotFound(template)
263         return loader.get_source(environment, template)
264
265
266 class ChoiceLoader(BaseLoader):
267     """This loader works like the `PrefixLoader` just that no prefix is
268     specified.  If a template could not be found by one loader the next one
269     is tried.
270
271     >>> loader = ChoiceLoader([
272     ...     FileSystemLoader('/path/to/user/templates'),
273     ...     PackageLoader('mypackage')
274     ... ])
275
276     This is useful if you want to allow users to override builtin templates
277     from a different location.
278     """
279
280     def __init__(self, loaders):
281         self.loaders = loaders
282
283     def get_source(self, environment, template):
284         for loader in self.loaders:
285             try:
286                 return loader.get_source(environment, template)
287             except TemplateNotFound:
288                 pass
289         raise TemplateNotFound(template)