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