blob: 8b21fdcf679860f2722ee8f20baa660c3fd0a834 [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 Ronacherbcb7c532008-04-11 16:30:34 +020011from os import path
12from jinja2.exceptions import TemplateNotFound
Armin Ronacher814f6c22008-04-17 15:52:23 +020013from jinja2.utils import LRUCache
Armin Ronacherbcb7c532008-04-11 16:30:34 +020014
15
Armin Ronacher9a822052008-04-17 18:44:07 +020016def split_template_path(template):
17 """Split a path into segments and perform a sanity check. If it detects
18 '..' in the path it will raise a `TemplateNotFound` error.
19 """
20 pieces = []
21 for piece in template.split('/'):
22 if path.sep in piece \
23 or (path.altsep and path.altsep in piece) or \
24 piece == path.pardir:
25 raise TemplateNotFound(template)
26 elif piece != '.':
27 pieces.append(piece)
28 return pieces
29
30
Armin Ronacherbcb7c532008-04-11 16:30:34 +020031class BaseLoader(object):
Armin Ronacher203bfcb2008-04-24 21:54:44 +020032 """Baseclass for all loaders. Subclass this and override `get_source` to
Armin Ronacherd1342312008-04-28 12:20:12 +020033 implement a custom loading mechanism. The environment provides a
34 `get_template` method that calls the loader's `load` method to get the
35 :class:`Template` object.
Armin Ronacher814f6c22008-04-17 15:52:23 +020036
Armin Ronacherd1342312008-04-28 12:20:12 +020037 A very basic example for a loader that looks up templates on the file
38 system could look like this::
39
40 from jinja2 import BaseLoader, TemplateNotFound
41 from os.path import join, exists, getmtime
42
43 class MyLoader(BaseLoader):
44
45 def __init__(self, path, cache_size=50, auto_reload=True):
46 BaseLoader.__init__(self, cache_size, auto_reload)
47 self.path = path
48
49 def get_source(self, environment, template):
50 path = join(self.path, template)
51 if not exists(path):
52 raise TemplateNotFound(template)
53 mtime = getmtime(path)
54 with file(path) as f:
55 source = f.read().decode('utf-8')
Armin Ronacher4dc95782008-05-04 01:11:14 +020056 return source, path, lambda: mtime == getmtime(path)
Armin Ronacher814f6c22008-04-17 15:52:23 +020057 """
58
Armin Ronacherbcb7c532008-04-11 16:30:34 +020059 def get_source(self, environment, template):
Armin Ronacher814f6c22008-04-17 15:52:23 +020060 """Get the template source, filename and reload helper for a template.
61 It's passed the environment and template name and has to return a
62 tuple in the form ``(source, filename, uptodate)`` or raise a
63 `TemplateNotFound` error if it can't locate the template.
64
65 The source part of the returned tuple must be the source of the
66 template as unicode string or a ASCII bytestring. The filename should
67 be the name of the file on the filesystem if it was loaded from there,
68 otherwise `None`. The filename is used by python for the tracebacks
69 if no loader extension is used.
70
71 The last item in the tuple is the `uptodate` function. If auto
72 reloading is enabled it's always called to check if the template
73 changed. No arguments are passed so the function must store the
74 old state somewhere (for example in a closure). If it returns `False`
75 the template will be reloaded.
76 """
77 raise TemplateNotFound(template)
Armin Ronacherbcb7c532008-04-11 16:30:34 +020078
Armin Ronacherba3757b2008-04-16 19:43:16 +020079 def load(self, environment, name, globals=None):
Armin Ronacherd1342312008-04-28 12:20:12 +020080 """Loads a template. This method looks up the template in the cache
81 or loads one by calling :meth:`get_source`. Subclasses should not
82 override this method as loaders working on collections of other
83 loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
84 will not call this method but `get_source` directly.
Armin Ronacher814f6c22008-04-17 15:52:23 +020085 """
86 if globals is None:
87 globals = {}
Armin Ronacher814f6c22008-04-17 15:52:23 +020088 source, filename, uptodate = self.get_source(environment, name)
Armin Ronacher981cbf62008-05-13 09:12:27 +020089 code = environment.compile(source, name, filename)
Armin Ronacher7259c762008-04-30 13:03:59 +020090 return environment.template_class.from_code(environment, code,
91 globals, uptodate)
Armin Ronacherbcb7c532008-04-11 16:30:34 +020092
93
94class FileSystemLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +020095 """Loads templates from the file system. This loader can find templates
96 in folders on the file system and is the preferred way to load them.
97
98 The loader takes the path to the templates as string, or if multiple
99 locations are wanted a list of them which is then looked up in the
100 given order:
101
102 >>> loader = FileSystemLoader('/path/to/templates')
103 >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
104
105 Per default the template encoding is ``'utf-8'`` which can be changed
106 by setting the `encoding` parameter to something else.
107 """
Armin Ronacherbcb7c532008-04-11 16:30:34 +0200108
Armin Ronacher7259c762008-04-30 13:03:59 +0200109 def __init__(self, searchpath, encoding='utf-8'):
Armin Ronacher814f6c22008-04-17 15:52:23 +0200110 if isinstance(searchpath, basestring):
111 searchpath = [searchpath]
Armin Ronacherd1342312008-04-28 12:20:12 +0200112 self.searchpath = list(searchpath)
Armin Ronacherbcb7c532008-04-11 16:30:34 +0200113 self.encoding = encoding
114
115 def get_source(self, environment, template):
Armin Ronacher9a822052008-04-17 18:44:07 +0200116 pieces = split_template_path(template)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200117 for searchpath in self.searchpath:
118 filename = path.join(searchpath, *pieces)
Armin Ronacher9a822052008-04-17 18:44:07 +0200119 if not path.isfile(filename):
120 continue
121 f = file(filename)
122 try:
123 contents = f.read().decode(self.encoding)
124 finally:
125 f.close()
126 old = path.getmtime(filename)
Armin Ronacher4dc95782008-05-04 01:11:14 +0200127 return contents, filename, lambda: path.getmtime(filename) == old
Armin Ronacher814f6c22008-04-17 15:52:23 +0200128 raise TemplateNotFound(template)
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200129
130
Armin Ronacher9a822052008-04-17 18:44:07 +0200131class PackageLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200132 """Load templates from python eggs or packages. It is constructed with
133 the name of the python package and the path to the templates in that
134 package:
Armin Ronacher9a822052008-04-17 18:44:07 +0200135
Armin Ronacherd1342312008-04-28 12:20:12 +0200136 >>> loader = PackageLoader('mypackage', 'views')
137
138 If the package path is not given, ``'templates'`` is assumed.
139
140 Per default the template encoding is ``'utf-8'`` which can be changed
141 by setting the `encoding` parameter to something else. Due to the nature
142 of eggs it's only possible to reload templates if the package was loaded
143 from the file system and not a zip file.
144 """
145
146 def __init__(self, package_name, package_path='templates',
Armin Ronacher7259c762008-04-30 13:03:59 +0200147 encoding='utf-8'):
Armin Ronacherd1342312008-04-28 12:20:12 +0200148 from pkg_resources import DefaultProvider, ResourceManager, get_provider
149 provider = get_provider(package_name)
150 self.encoding = encoding
151 self.manager = ResourceManager()
152 self.filesystem_bound = isinstance(provider, DefaultProvider)
153 self.provider = provider
Armin Ronacher9a822052008-04-17 18:44:07 +0200154 self.package_path = package_path
155
156 def get_source(self, environment, template):
Armin Ronacher963f97d2008-04-25 11:44:59 +0200157 pieces = split_template_path(template)
Armin Ronacherd1342312008-04-28 12:20:12 +0200158 p = '/'.join((self.package_path,) + tuple(pieces))
159 if not self.provider.has_resource(p):
Armin Ronacher9a822052008-04-17 18:44:07 +0200160 raise TemplateNotFound(template)
Armin Ronacherd1342312008-04-28 12:20:12 +0200161
162 filename = uptodate = None
163 if self.filesystem_bound:
164 filename = self.provider.get_resource_filename(self.manager, p)
165 mtime = path.getmtime(filename)
166 def uptodate():
Armin Ronacher4dc95782008-05-04 01:11:14 +0200167 return path.getmtime(filename) == mtime
Armin Ronacherd1342312008-04-28 12:20:12 +0200168
169 source = self.provider.get_resource_string(self.manager, p)
170 return source.decode(self.encoding), filename, uptodate
Armin Ronacher9a822052008-04-17 18:44:07 +0200171
172
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200173class DictLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200174 """Loads a template from a python dict. It's passed a dict of unicode
175 strings bound to template names. This loader is useful for unittesting:
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200176
Armin Ronacherd1342312008-04-28 12:20:12 +0200177 >>> loader = DictLoader({'index.html': 'source here'})
178
179 Because auto reloading is rarely useful this is disabled per default.
180 """
181
Armin Ronacher7259c762008-04-30 13:03:59 +0200182 def __init__(self, mapping):
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200183 self.mapping = mapping
184
185 def get_source(self, environment, template):
186 if template in self.mapping:
Armin Ronacherd1342312008-04-28 12:20:12 +0200187 source = self.mapping[template]
188 return source, None, lambda: source != self.mapping[template]
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200189 raise TemplateNotFound(template)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200190
191
192class FunctionLoader(BaseLoader):
193 """A loader that is passed a function which does the loading. The
Armin Ronacher963f97d2008-04-25 11:44:59 +0200194 function becomes the name of the template passed and has to return either
195 an unicode string with the template source, a tuple in the form ``(source,
196 filename, uptodatefunc)`` or `None` if the template does not exist.
Armin Ronacherd1342312008-04-28 12:20:12 +0200197
198 >>> def load_template(name):
199 ... if name == 'index.html'
200 ... return '...'
201 ...
202 >>> loader = FunctionLoader(load_template)
203
204 The `uptodatefunc` is a function that is called if autoreload is enabled
205 and has to return `True` if the template is still up to date. For more
206 details have a look at :meth:`BaseLoader.get_source` which has the same
207 return value.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200208 """
209
Armin Ronacher7259c762008-04-30 13:03:59 +0200210 def __init__(self, load_func):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200211 self.load_func = load_func
212
213 def get_source(self, environment, template):
Armin Ronacher963f97d2008-04-25 11:44:59 +0200214 rv = self.load_func(template)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200215 if rv is None:
216 raise TemplateNotFound(template)
Armin Ronacher963f97d2008-04-25 11:44:59 +0200217 elif isinstance(rv, basestring):
218 return rv, None, None
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200219 return rv
220
221
222class PrefixLoader(BaseLoader):
223 """A loader that is passed a dict of loaders where each loader is bound
Armin Ronacher7259c762008-04-30 13:03:59 +0200224 to a prefix. The prefix is delimited from the template by a slash per
225 default, which can be changed by setting the `delimiter` argument to
226 something else.
Armin Ronacherd1342312008-04-28 12:20:12 +0200227
228 >>> loader = PrefixLoader({
229 ... 'app1': PackageLoader('mypackage.app1'),
230 ... 'app2': PackageLoader('mypackage.app2')
231 ... })
232
233 By loading ``'app1/index.html'`` the file from the app1 package is loaded,
234 by loading ``'app2/index.html'`` the file from the second.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200235 """
236
Armin Ronacher7259c762008-04-30 13:03:59 +0200237 def __init__(self, mapping, delimiter='/'):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200238 self.mapping = mapping
239 self.delimiter = delimiter
240
241 def get_source(self, environment, template):
242 try:
243 prefix, template = template.split(self.delimiter, 1)
244 loader = self.mapping[prefix]
245 except (ValueError, KeyError):
246 raise TemplateNotFound(template)
247 return loader.get_source(environment, template)
248
249
250class ChoiceLoader(BaseLoader):
251 """This loader works like the `PrefixLoader` just that no prefix is
252 specified. If a template could not be found by one loader the next one
Armin Ronacher7259c762008-04-30 13:03:59 +0200253 is tried.
Armin Ronacherd1342312008-04-28 12:20:12 +0200254
255 >>> loader = ChoiceLoader([
256 ... FileSystemLoader('/path/to/user/templates'),
257 ... PackageLoader('myapplication')
Armin Ronacher61a5a242008-05-26 12:07:44 +0200258 ... ])
Armin Ronacherd1342312008-04-28 12:20:12 +0200259
260 This is useful if you want to allow users to override builtin templates
261 from a different location.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200262 """
263
Armin Ronacher7259c762008-04-30 13:03:59 +0200264 def __init__(self, loaders):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200265 self.loaders = loaders
266
267 def get_source(self, environment, template):
268 for loader in self.loaders:
269 try:
270 return loader.get_source(environment, template)
271 except TemplateNotFound:
272 pass
273 raise TemplateNotFound(template)