blob: e964fdcf7964092480d19fc85bfc4427c9b3723e [file] [log] [blame]
Armin Ronacher07bc6842008-03-31 14:18:49 +02001# -*- coding: utf-8 -*-
2"""
Armin Ronacherbcb7c532008-04-11 16:30:34 +02003 jinja2.loaders
4 ~~~~~~~~~~~~~~
Armin Ronacher07bc6842008-03-31 14:18:49 +02005
6 Jinja loader classes.
7
Armin Ronacherbcb7c532008-04-11 16:30:34 +02008 :copyright: 2008 by Armin Ronacher.
Armin Ronacher07bc6842008-03-31 14:18:49 +02009 :license: BSD, see LICENSE for more details.
10"""
Armin Ronacher57c9b6d2008-09-13 19:19:22 +020011from os import path
Armin Ronacher63fd7982008-06-20 18:47:56 +020012try:
13 from hashlib import sha1
14except ImportError:
15 from sha import new as sha1
Armin Ronacherbcb7c532008-04-11 16:30:34 +020016from jinja2.exceptions import TemplateNotFound
Armin Ronacher814f6c22008-04-17 15:52:23 +020017from jinja2.utils import LRUCache
Armin Ronacherbcb7c532008-04-11 16:30:34 +020018
19
Armin Ronacher9a822052008-04-17 18:44:07 +020020def 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)
Armin Ronacher58f351d2008-05-28 21:30:14 +020030 elif piece and piece != '.':
Armin Ronacher9a822052008-04-17 18:44:07 +020031 pieces.append(piece)
32 return pieces
33
34
Armin Ronacherbcb7c532008-04-11 16:30:34 +020035class BaseLoader(object):
Armin Ronacher203bfcb2008-04-24 21:54:44 +020036 """Baseclass for all loaders. Subclass this and override `get_source` to
Armin Ronacherd1342312008-04-28 12:20:12 +020037 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.
Armin Ronacher814f6c22008-04-17 15:52:23 +020040
Armin Ronacherd1342312008-04-28 12:20:12 +020041 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
Armin Ronacher63fd7982008-06-20 18:47:56 +020049 def __init__(self, path):
Armin Ronacherd1342312008-04-28 12:20:12 +020050 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')
Armin Ronacher4dc95782008-05-04 01:11:14 +020059 return source, path, lambda: mtime == getmtime(path)
Armin Ronacher814f6c22008-04-17 15:52:23 +020060 """
61
Armin Ronacherbcb7c532008-04-11 16:30:34 +020062 def get_source(self, environment, template):
Armin Ronacher814f6c22008-04-17 15:52:23 +020063 """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)
Armin Ronacherbcb7c532008-04-11 16:30:34 +020081
Armin Ronacherba3757b2008-04-16 19:43:16 +020082 def load(self, environment, name, globals=None):
Armin Ronacherd1342312008-04-28 12:20:12 +020083 """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.
Armin Ronacher814f6c22008-04-17 15:52:23 +020088 """
89 if globals is None:
90 globals = {}
Armin Ronacher814f6c22008-04-17 15:52:23 +020091 source, filename, uptodate = self.get_source(environment, name)
Armin Ronacher981cbf62008-05-13 09:12:27 +020092 code = environment.compile(source, name, filename)
Armin Ronacher7259c762008-04-30 13:03:59 +020093 return environment.template_class.from_code(environment, code,
94 globals, uptodate)
Armin Ronacherbcb7c532008-04-11 16:30:34 +020095
96
97class FileSystemLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +020098 """Loads templates from the file system. This loader can find templates
99 in folders on the file system and is the preferred way to load them.
100
101 The loader takes the path to the templates as string, or if multiple
102 locations are wanted a list of them which is then looked up in the
103 given order:
104
105 >>> loader = FileSystemLoader('/path/to/templates')
106 >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
107
108 Per default the template encoding is ``'utf-8'`` which can be changed
109 by setting the `encoding` parameter to something else.
110 """
Armin Ronacherbcb7c532008-04-11 16:30:34 +0200111
Armin Ronacher7259c762008-04-30 13:03:59 +0200112 def __init__(self, searchpath, encoding='utf-8'):
Armin Ronacher814f6c22008-04-17 15:52:23 +0200113 if isinstance(searchpath, basestring):
114 searchpath = [searchpath]
Armin Ronacherd1342312008-04-28 12:20:12 +0200115 self.searchpath = list(searchpath)
Armin Ronacherbcb7c532008-04-11 16:30:34 +0200116 self.encoding = encoding
117
118 def get_source(self, environment, template):
Armin Ronacher9a822052008-04-17 18:44:07 +0200119 pieces = split_template_path(template)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200120 for searchpath in self.searchpath:
121 filename = path.join(searchpath, *pieces)
Armin Ronacher9a822052008-04-17 18:44:07 +0200122 if not path.isfile(filename):
123 continue
124 f = file(filename)
125 try:
126 contents = f.read().decode(self.encoding)
127 finally:
128 f.close()
129 old = path.getmtime(filename)
Armin Ronacher4dc95782008-05-04 01:11:14 +0200130 return contents, filename, lambda: path.getmtime(filename) == old
Armin Ronacher814f6c22008-04-17 15:52:23 +0200131 raise TemplateNotFound(template)
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200132
133
Armin Ronacher9a822052008-04-17 18:44:07 +0200134class PackageLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200135 """Load templates from python eggs or packages. It is constructed with
136 the name of the python package and the path to the templates in that
137 package:
Armin Ronacher9a822052008-04-17 18:44:07 +0200138
Armin Ronacherd1342312008-04-28 12:20:12 +0200139 >>> loader = PackageLoader('mypackage', 'views')
140
141 If the package path is not given, ``'templates'`` is assumed.
142
143 Per default the template encoding is ``'utf-8'`` which can be changed
144 by setting the `encoding` parameter to something else. Due to the nature
145 of eggs it's only possible to reload templates if the package was loaded
146 from the file system and not a zip file.
147 """
148
149 def __init__(self, package_name, package_path='templates',
Armin Ronacher7259c762008-04-30 13:03:59 +0200150 encoding='utf-8'):
Armin Ronacherd1342312008-04-28 12:20:12 +0200151 from pkg_resources import DefaultProvider, ResourceManager, get_provider
152 provider = get_provider(package_name)
153 self.encoding = encoding
154 self.manager = ResourceManager()
155 self.filesystem_bound = isinstance(provider, DefaultProvider)
156 self.provider = provider
Armin Ronacher9a822052008-04-17 18:44:07 +0200157 self.package_path = package_path
158
159 def get_source(self, environment, template):
Armin Ronacher963f97d2008-04-25 11:44:59 +0200160 pieces = split_template_path(template)
Armin Ronacherd1342312008-04-28 12:20:12 +0200161 p = '/'.join((self.package_path,) + tuple(pieces))
162 if not self.provider.has_resource(p):
Armin Ronacher9a822052008-04-17 18:44:07 +0200163 raise TemplateNotFound(template)
Armin Ronacherd1342312008-04-28 12:20:12 +0200164
165 filename = uptodate = None
166 if self.filesystem_bound:
167 filename = self.provider.get_resource_filename(self.manager, p)
168 mtime = path.getmtime(filename)
169 def uptodate():
Armin Ronacher4dc95782008-05-04 01:11:14 +0200170 return path.getmtime(filename) == mtime
Armin Ronacherd1342312008-04-28 12:20:12 +0200171
172 source = self.provider.get_resource_string(self.manager, p)
173 return source.decode(self.encoding), filename, uptodate
Armin Ronacher9a822052008-04-17 18:44:07 +0200174
175
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200176class DictLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200177 """Loads a template from a python dict. It's passed a dict of unicode
178 strings bound to template names. This loader is useful for unittesting:
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200179
Armin Ronacherd1342312008-04-28 12:20:12 +0200180 >>> loader = DictLoader({'index.html': 'source here'})
181
182 Because auto reloading is rarely useful this is disabled per default.
183 """
184
Armin Ronacher7259c762008-04-30 13:03:59 +0200185 def __init__(self, mapping):
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200186 self.mapping = mapping
187
188 def get_source(self, environment, template):
189 if template in self.mapping:
Armin Ronacherd1342312008-04-28 12:20:12 +0200190 source = self.mapping[template]
191 return source, None, lambda: source != self.mapping[template]
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200192 raise TemplateNotFound(template)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200193
194
195class FunctionLoader(BaseLoader):
196 """A loader that is passed a function which does the loading. The
Armin Ronacher963f97d2008-04-25 11:44:59 +0200197 function becomes the name of the template passed and has to return either
198 an unicode string with the template source, a tuple in the form ``(source,
199 filename, uptodatefunc)`` or `None` if the template does not exist.
Armin Ronacherd1342312008-04-28 12:20:12 +0200200
201 >>> def load_template(name):
202 ... if name == 'index.html'
203 ... return '...'
204 ...
205 >>> loader = FunctionLoader(load_template)
206
207 The `uptodatefunc` is a function that is called if autoreload is enabled
208 and has to return `True` if the template is still up to date. For more
209 details have a look at :meth:`BaseLoader.get_source` which has the same
210 return value.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200211 """
212
Armin Ronacher7259c762008-04-30 13:03:59 +0200213 def __init__(self, load_func):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200214 self.load_func = load_func
215
216 def get_source(self, environment, template):
Armin Ronacher963f97d2008-04-25 11:44:59 +0200217 rv = self.load_func(template)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200218 if rv is None:
219 raise TemplateNotFound(template)
Armin Ronacher963f97d2008-04-25 11:44:59 +0200220 elif isinstance(rv, basestring):
221 return rv, None, None
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200222 return rv
223
224
225class PrefixLoader(BaseLoader):
226 """A loader that is passed a dict of loaders where each loader is bound
Armin Ronacher7259c762008-04-30 13:03:59 +0200227 to a prefix. The prefix is delimited from the template by a slash per
228 default, which can be changed by setting the `delimiter` argument to
229 something else.
Armin Ronacherd1342312008-04-28 12:20:12 +0200230
231 >>> loader = PrefixLoader({
232 ... 'app1': PackageLoader('mypackage.app1'),
233 ... 'app2': PackageLoader('mypackage.app2')
234 ... })
235
236 By loading ``'app1/index.html'`` the file from the app1 package is loaded,
237 by loading ``'app2/index.html'`` the file from the second.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200238 """
239
Armin Ronacher7259c762008-04-30 13:03:59 +0200240 def __init__(self, mapping, delimiter='/'):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200241 self.mapping = mapping
242 self.delimiter = delimiter
243
244 def get_source(self, environment, template):
245 try:
246 prefix, template = template.split(self.delimiter, 1)
247 loader = self.mapping[prefix]
248 except (ValueError, KeyError):
249 raise TemplateNotFound(template)
250 return loader.get_source(environment, template)
251
252
253class ChoiceLoader(BaseLoader):
254 """This loader works like the `PrefixLoader` just that no prefix is
255 specified. If a template could not be found by one loader the next one
Armin Ronacher7259c762008-04-30 13:03:59 +0200256 is tried.
Armin Ronacherd1342312008-04-28 12:20:12 +0200257
258 >>> loader = ChoiceLoader([
259 ... FileSystemLoader('/path/to/user/templates'),
Armin Ronacherabd36572008-06-27 08:45:19 +0200260 ... PackageLoader('mypackage')
Armin Ronacher61a5a242008-05-26 12:07:44 +0200261 ... ])
Armin Ronacherd1342312008-04-28 12:20:12 +0200262
263 This is useful if you want to allow users to override builtin templates
264 from a different location.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200265 """
266
Armin Ronacher7259c762008-04-30 13:03:59 +0200267 def __init__(self, loaders):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200268 self.loaders = loaders
269
270 def get_source(self, environment, template):
271 for loader in self.loaders:
272 try:
273 return loader.get_source(environment, template)
274 except TemplateNotFound:
275 pass
276 raise TemplateNotFound(template)