- add sources.
[platform/framework/web/crosswalk.git] / src / third_party / jinja2 / loaders.py
1 # -*- coding: utf-8 -*-
2 """
3     jinja2.loaders
4     ~~~~~~~~~~~~~~
5
6     Jinja loader classes.
7
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
20
21
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
35
36
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.
42
43     A very basic example for a loader that looks up templates on the file
44     system could look like this::
45
46         from jinja2 import BaseLoader, TemplateNotFound
47         from os.path import join, exists, getmtime
48
49         class MyLoader(BaseLoader):
50
51             def __init__(self, path):
52                 self.path = path
53
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     """
63
64     #: if set to `False` it indicates that the loader cannot provide access
65     #: to the source of templates.
66     #:
67     #: .. versionadded:: 2.4
68     has_source_access = True
69
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.
75
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.
81
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)
92
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')
98
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.
106         """
107         code = None
108         if globals is None:
109             globals = {}
110
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)
114
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
121
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)
126
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)
133
134         return environment.template_class.from_code(environment, code,
135                                                     globals, uptodate)
136
137
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.
141
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:
145
146     >>> loader = FileSystemLoader('/path/to/templates')
147     >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
148
149     Per default the template encoding is ``'utf-8'`` which can be changed
150     by setting the `encoding` parameter to something else.
151     """
152
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
158
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()
170
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)
179
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)
193
194
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::
199
200         loader = PackageLoader('mypackage', 'views')
201
202     If the package path is not given, ``'templates'`` is assumed.
203
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.
208     """
209
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
220
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)
226
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
236
237         source = self.provider.get_resource_string(self.manager, p)
238         return source.decode(self.encoding), filename, uptodate
239
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
258
259
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:
263
264     >>> loader = DictLoader({'index.html': 'source here'})
265
266     Because auto reloading is rarely useful this is disabled per default.
267     """
268
269     def __init__(self, mapping):
270         self.mapping = mapping
271
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)
277
278     def list_templates(self):
279         return sorted(self.mapping)
280
281
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.
287
288     >>> def load_template(name):
289     ...     if name == 'index.html':
290     ...         return '...'
291     ...
292     >>> loader = FunctionLoader(load_template)
293
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.
298     """
299
300     def __init__(self, load_func):
301         self.load_func = load_func
302
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
310
311
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::
317
318         loader = PrefixLoader({
319             'app1':     PackageLoader('mypackage.app1'),
320             'app2':     PackageLoader('mypackage.app2')
321         })
322
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.
325     """
326
327     def __init__(self, mapping, delimiter='/'):
328         self.mapping = mapping
329         self.delimiter = delimiter
330
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
338
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)
347
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)
357
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
364
365
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.
370
371     >>> loader = ChoiceLoader([
372     ...     FileSystemLoader('/path/to/user/templates'),
373     ...     FileSystemLoader('/path/to/system/templates')
374     ... ])
375
376     This is useful if you want to allow users to override builtin templates
377     from a different location.
378     """
379
380     def __init__(self, loaders):
381         self.loaders = loaders
382
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)
390
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)
399
400     def list_templates(self):
401         found = set()
402         for loader in self.loaders:
403             found.update(loader.list_templates())
404         return sorted(found)
405
406
407 class _TemplateModule(ModuleType):
408     """Like a normal module but with support for weak references"""
409
410
411 class ModuleLoader(BaseLoader):
412     """This loader loads templates from precompiled templates.
413
414     Example usage:
415
416     >>> loader = ChoiceLoader([
417     ...     ModuleLoader('/path/to/compiled/templates'),
418     ...     FileSystemLoader('/path/to/templates')
419     ... ])
420
421     Templates can be precompiled with :meth:`Environment.compile_templates`.
422     """
423
424     has_source_access = False
425
426     def __init__(self, path):
427         package_name = '_jinja2_module_templates_%x' % id(self)
428
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
437
438         sys.modules[package_name] = weakref.proxy(mod,
439             lambda x: sys.modules.pop(package_name, None))
440
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
446
447     @staticmethod
448     def get_template_key(name):
449         return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest()
450
451     @staticmethod
452     def get_module_filename(name):
453         return ModuleLoader.get_template_key(name) + '.py'
454
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)
465
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)
469
470         return environment.template_class.from_module_dict(
471             environment, mod.__dict__, globals)