allow history to work in webkit browsers
[gae-samples.git] / python27 / guestbook / jinja2 / loaders.py
blob419a9c8c6c1566a918c041e5d28e767f85a1ff24
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 try:
17 from hashlib import sha1
18 except ImportError:
19 from sha import new as sha1
20 from jinja2.exceptions import TemplateNotFound
21 from jinja2.utils import LRUCache, open_if_exists, internalcode
24 def split_template_path(template):
25 """Split a path into segments and perform a sanity check. If it detects
26 '..' in the path it will raise a `TemplateNotFound` error.
27 """
28 pieces = []
29 for piece in template.split('/'):
30 if path.sep in piece \
31 or (path.altsep and path.altsep in piece) or \
32 piece == path.pardir:
33 raise TemplateNotFound(template)
34 elif piece and piece != '.':
35 pieces.append(piece)
36 return pieces
39 class BaseLoader(object):
40 """Baseclass for all loaders. Subclass this and override `get_source` to
41 implement a custom loading mechanism. The environment provides a
42 `get_template` method that calls the loader's `load` method to get the
43 :class:`Template` object.
45 A very basic example for a loader that looks up templates on the file
46 system could look like this::
48 from jinja2 import BaseLoader, TemplateNotFound
49 from os.path import join, exists, getmtime
51 class MyLoader(BaseLoader):
53 def __init__(self, path):
54 self.path = path
56 def get_source(self, environment, template):
57 path = join(self.path, template)
58 if not exists(path):
59 raise TemplateNotFound(template)
60 mtime = getmtime(path)
61 with file(path) as f:
62 source = f.read().decode('utf-8')
63 return source, path, lambda: mtime == getmtime(path)
64 """
66 #: if set to `False` it indicates that the loader cannot provide access
67 #: to the source of templates.
69 #: .. versionadded:: 2.4
70 has_source_access = True
72 def get_source(self, environment, template):
73 """Get the template source, filename and reload helper for a template.
74 It's passed the environment and template name and has to return a
75 tuple in the form ``(source, filename, uptodate)`` or raise a
76 `TemplateNotFound` error if it can't locate the template.
78 The source part of the returned tuple must be the source of the
79 template as unicode string or a ASCII bytestring. The filename should
80 be the name of the file on the filesystem if it was loaded from there,
81 otherwise `None`. The filename is used by python for the tracebacks
82 if no loader extension is used.
84 The last item in the tuple is the `uptodate` function. If auto
85 reloading is enabled it's always called to check if the template
86 changed. No arguments are passed so the function must store the
87 old state somewhere (for example in a closure). If it returns `False`
88 the template will be reloaded.
89 """
90 if not self.has_source_access:
91 raise RuntimeError('%s cannot provide access to the source' %
92 self.__class__.__name__)
93 raise TemplateNotFound(template)
95 def list_templates(self):
96 """Iterates over all templates. If the loader does not support that
97 it should raise a :exc:`TypeError` which is the default behavior.
98 """
99 raise TypeError('this loader cannot iterate over all templates')
101 @internalcode
102 def load(self, environment, name, globals=None):
103 """Loads a template. This method looks up the template in the cache
104 or loads one by calling :meth:`get_source`. Subclasses should not
105 override this method as loaders working on collections of other
106 loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
107 will not call this method but `get_source` directly.
109 code = None
110 if globals is None:
111 globals = {}
113 # first we try to get the source for this template together
114 # with the filename and the uptodate function.
115 source, filename, uptodate = self.get_source(environment, name)
117 # try to load the code from the bytecode cache if there is a
118 # bytecode cache configured.
119 bcc = environment.bytecode_cache
120 if bcc is not None:
121 bucket = bcc.get_bucket(environment, name, filename, source)
122 code = bucket.code
124 # if we don't have code so far (not cached, no longer up to
125 # date) etc. we compile the template
126 if code is None:
127 code = environment.compile(source, name, filename)
129 # if the bytecode cache is available and the bucket doesn't
130 # have a code so far, we give the bucket the new code and put
131 # it back to the bytecode cache.
132 if bcc is not None and bucket.code is None:
133 bucket.code = code
134 bcc.set_bucket(bucket)
136 return environment.template_class.from_code(environment, code,
137 globals, uptodate)
140 class FileSystemLoader(BaseLoader):
141 """Loads templates from the file system. This loader can find templates
142 in folders on the file system and is the preferred way to load them.
144 The loader takes the path to the templates as string, or if multiple
145 locations are wanted a list of them which is then looked up in the
146 given order:
148 >>> loader = FileSystemLoader('/path/to/templates')
149 >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
151 Per default the template encoding is ``'utf-8'`` which can be changed
152 by setting the `encoding` parameter to something else.
155 def __init__(self, searchpath, encoding='utf-8'):
156 if isinstance(searchpath, basestring):
157 searchpath = [searchpath]
158 self.searchpath = list(searchpath)
159 self.encoding = encoding
161 def get_source(self, environment, template):
162 pieces = split_template_path(template)
163 for searchpath in self.searchpath:
164 filename = path.join(searchpath, *pieces)
165 f = open_if_exists(filename)
166 if f is None:
167 continue
168 try:
169 contents = f.read().decode(self.encoding)
170 finally:
171 f.close()
173 mtime = path.getmtime(filename)
174 def uptodate():
175 try:
176 return path.getmtime(filename) == mtime
177 except OSError:
178 return False
179 return contents, filename, uptodate
180 raise TemplateNotFound(template)
182 def list_templates(self):
183 found = set()
184 for searchpath in self.searchpath:
185 for dirpath, dirnames, filenames in os.walk(searchpath):
186 for filename in filenames:
187 template = os.path.join(dirpath, filename) \
188 [len(searchpath):].strip(os.path.sep) \
189 .replace(os.path.sep, '/')
190 if template[:2] == './':
191 template = template[2:]
192 if template not in found:
193 found.add(template)
194 return sorted(found)
197 class PackageLoader(BaseLoader):
198 """Load templates from python eggs or packages. It is constructed with
199 the name of the python package and the path to the templates in that
200 package::
202 loader = PackageLoader('mypackage', 'views')
204 If the package path is not given, ``'templates'`` is assumed.
206 Per default the template encoding is ``'utf-8'`` which can be changed
207 by setting the `encoding` parameter to something else. Due to the nature
208 of eggs it's only possible to reload templates if the package was loaded
209 from the file system and not a zip file.
212 def __init__(self, package_name, package_path='templates',
213 encoding='utf-8'):
214 from pkg_resources import DefaultProvider, ResourceManager, \
215 get_provider
216 provider = get_provider(package_name)
217 self.encoding = encoding
218 self.manager = ResourceManager()
219 self.filesystem_bound = isinstance(provider, DefaultProvider)
220 self.provider = provider
221 self.package_path = package_path
223 def get_source(self, environment, template):
224 pieces = split_template_path(template)
225 p = '/'.join((self.package_path,) + tuple(pieces))
226 if not self.provider.has_resource(p):
227 raise TemplateNotFound(template)
229 filename = uptodate = None
230 if self.filesystem_bound:
231 filename = self.provider.get_resource_filename(self.manager, p)
232 mtime = path.getmtime(filename)
233 def uptodate():
234 try:
235 return path.getmtime(filename) == mtime
236 except OSError:
237 return False
239 source = self.provider.get_resource_string(self.manager, p)
240 return source.decode(self.encoding), filename, uptodate
242 def list_templates(self):
243 path = self.package_path
244 if path[:2] == './':
245 path = path[2:]
246 elif path == '.':
247 path = ''
248 offset = len(path)
249 results = []
250 def _walk(path):
251 for filename in self.provider.resource_listdir(path):
252 fullname = path + '/' + filename
253 if self.provider.resource_isdir(fullname):
254 _walk(fullname)
255 else:
256 results.append(fullname[offset:].lstrip('/'))
257 _walk(path)
258 results.sort()
259 return results
262 class DictLoader(BaseLoader):
263 """Loads a template from a python dict. It's passed a dict of unicode
264 strings bound to template names. This loader is useful for unittesting:
266 >>> loader = DictLoader({'index.html': 'source here'})
268 Because auto reloading is rarely useful this is disabled per default.
271 def __init__(self, mapping):
272 self.mapping = mapping
274 def get_source(self, environment, template):
275 if template in self.mapping:
276 source = self.mapping[template]
277 return source, None, lambda: source != self.mapping.get(template)
278 raise TemplateNotFound(template)
280 def list_templates(self):
281 return sorted(self.mapping)
284 class FunctionLoader(BaseLoader):
285 """A loader that is passed a function which does the loading. The
286 function becomes the name of the template passed and has to return either
287 an unicode string with the template source, a tuple in the form ``(source,
288 filename, uptodatefunc)`` or `None` if the template does not exist.
290 >>> def load_template(name):
291 ... if name == 'index.html':
292 ... return '...'
294 >>> loader = FunctionLoader(load_template)
296 The `uptodatefunc` is a function that is called if autoreload is enabled
297 and has to return `True` if the template is still up to date. For more
298 details have a look at :meth:`BaseLoader.get_source` which has the same
299 return value.
302 def __init__(self, load_func):
303 self.load_func = load_func
305 def get_source(self, environment, template):
306 rv = self.load_func(template)
307 if rv is None:
308 raise TemplateNotFound(template)
309 elif isinstance(rv, basestring):
310 return rv, None, None
311 return rv
314 class PrefixLoader(BaseLoader):
315 """A loader that is passed a dict of loaders where each loader is bound
316 to a prefix. The prefix is delimited from the template by a slash per
317 default, which can be changed by setting the `delimiter` argument to
318 something else::
320 loader = PrefixLoader({
321 'app1': PackageLoader('mypackage.app1'),
322 'app2': PackageLoader('mypackage.app2')
325 By loading ``'app1/index.html'`` the file from the app1 package is loaded,
326 by loading ``'app2/index.html'`` the file from the second.
329 def __init__(self, mapping, delimiter='/'):
330 self.mapping = mapping
331 self.delimiter = delimiter
333 def get_source(self, environment, template):
334 try:
335 prefix, name = template.split(self.delimiter, 1)
336 loader = self.mapping[prefix]
337 except (ValueError, KeyError):
338 raise TemplateNotFound(template)
339 try:
340 return loader.get_source(environment, name)
341 except TemplateNotFound:
342 # re-raise the exception with the correct fileame here.
343 # (the one that includes the prefix)
344 raise TemplateNotFound(template)
346 def list_templates(self):
347 result = []
348 for prefix, loader in self.mapping.iteritems():
349 for template in loader.list_templates():
350 result.append(prefix + self.delimiter + template)
351 return result
354 class ChoiceLoader(BaseLoader):
355 """This loader works like the `PrefixLoader` just that no prefix is
356 specified. If a template could not be found by one loader the next one
357 is tried.
359 >>> loader = ChoiceLoader([
360 ... FileSystemLoader('/path/to/user/templates'),
361 ... FileSystemLoader('/path/to/system/templates')
362 ... ])
364 This is useful if you want to allow users to override builtin templates
365 from a different location.
368 def __init__(self, loaders):
369 self.loaders = loaders
371 def get_source(self, environment, template):
372 for loader in self.loaders:
373 try:
374 return loader.get_source(environment, template)
375 except TemplateNotFound:
376 pass
377 raise TemplateNotFound(template)
379 def list_templates(self):
380 found = set()
381 for loader in self.loaders:
382 found.update(loader.list_templates())
383 return sorted(found)
386 class _TemplateModule(ModuleType):
387 """Like a normal module but with support for weak references"""
390 class ModuleLoader(BaseLoader):
391 """This loader loads templates from precompiled templates.
393 Example usage:
395 >>> loader = ChoiceLoader([
396 ... ModuleLoader('/path/to/compiled/templates'),
397 ... FileSystemLoader('/path/to/templates')
398 ... ])
400 Templates can be precompiled with :meth:`Environment.compile_templates`.
403 has_source_access = False
405 def __init__(self, path):
406 package_name = '_jinja2_module_templates_%x' % id(self)
408 # create a fake module that looks for the templates in the
409 # path given.
410 mod = _TemplateModule(package_name)
411 if isinstance(path, basestring):
412 path = [path]
413 else:
414 path = list(path)
415 mod.__path__ = path
417 sys.modules[package_name] = weakref.proxy(mod,
418 lambda x: sys.modules.pop(package_name, None))
420 # the only strong reference, the sys.modules entry is weak
421 # so that the garbage collector can remove it once the
422 # loader that created it goes out of business.
423 self.module = mod
424 self.package_name = package_name
426 @staticmethod
427 def get_template_key(name):
428 return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest()
430 @staticmethod
431 def get_module_filename(name):
432 return ModuleLoader.get_template_key(name) + '.py'
434 @internalcode
435 def load(self, environment, name, globals=None):
436 key = self.get_template_key(name)
437 module = '%s.%s' % (self.package_name, key)
438 mod = getattr(self.module, module, None)
439 if mod is None:
440 try:
441 mod = __import__(module, None, None, ['root'])
442 except ImportError:
443 raise TemplateNotFound(name)
445 # remove the entry from sys.modules, we only want the attribute
446 # on the module object we have stored on the loader.
447 sys.modules.pop(module, None)
449 return environment.template_class.from_module_dict(
450 environment, mod.__dict__, globals)