Index: third_party/jinja2/loaders.py |
diff --git a/third_party/jinja2/loaders.py b/third_party/jinja2/loaders.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..419a9c8c6c1566a918c041e5d28e767f85a1ff24 |
--- /dev/null |
+++ b/third_party/jinja2/loaders.py |
@@ -0,0 +1,450 @@ |
+# -*- coding: utf-8 -*- |
+""" |
+ jinja2.loaders |
+ ~~~~~~~~~~~~~~ |
+ |
+ Jinja loader classes. |
+ |
+ :copyright: (c) 2010 by the Jinja Team. |
+ :license: BSD, see LICENSE for more details. |
+""" |
+import os |
+import sys |
+import weakref |
+from types import ModuleType |
+from os import path |
+try: |
+ from hashlib import sha1 |
+except ImportError: |
+ from sha import new as sha1 |
+from jinja2.exceptions import TemplateNotFound |
+from jinja2.utils import LRUCache, open_if_exists, internalcode |
+ |
+ |
+def split_template_path(template): |
+ """Split a path into segments and perform a sanity check. If it detects |
+ '..' in the path it will raise a `TemplateNotFound` error. |
+ """ |
+ pieces = [] |
+ for piece in template.split('/'): |
+ if path.sep in piece \ |
+ or (path.altsep and path.altsep in piece) or \ |
+ piece == path.pardir: |
+ raise TemplateNotFound(template) |
+ elif piece and piece != '.': |
+ pieces.append(piece) |
+ return pieces |
+ |
+ |
+class BaseLoader(object): |
+ """Baseclass for all loaders. Subclass this and override `get_source` to |
+ implement a custom loading mechanism. The environment provides a |
+ `get_template` method that calls the loader's `load` method to get the |
+ :class:`Template` object. |
+ |
+ A very basic example for a loader that looks up templates on the file |
+ system could look like this:: |
+ |
+ from jinja2 import BaseLoader, TemplateNotFound |
+ from os.path import join, exists, getmtime |
+ |
+ class MyLoader(BaseLoader): |
+ |
+ def __init__(self, path): |
+ self.path = path |
+ |
+ def get_source(self, environment, template): |
+ path = join(self.path, template) |
+ if not exists(path): |
+ raise TemplateNotFound(template) |
+ mtime = getmtime(path) |
+ with file(path) as f: |
+ source = f.read().decode('utf-8') |
+ return source, path, lambda: mtime == getmtime(path) |
+ """ |
+ |
+ #: if set to `False` it indicates that the loader cannot provide access |
+ #: to the source of templates. |
+ #: |
+ #: .. versionadded:: 2.4 |
+ has_source_access = True |
+ |
+ def get_source(self, environment, template): |
+ """Get the template source, filename and reload helper for a template. |
+ It's passed the environment and template name and has to return a |
+ tuple in the form ``(source, filename, uptodate)`` or raise a |
+ `TemplateNotFound` error if it can't locate the template. |
+ |
+ The source part of the returned tuple must be the source of the |
+ template as unicode string or a ASCII bytestring. The filename should |
+ be the name of the file on the filesystem if it was loaded from there, |
+ otherwise `None`. The filename is used by python for the tracebacks |
+ if no loader extension is used. |
+ |
+ The last item in the tuple is the `uptodate` function. If auto |
+ reloading is enabled it's always called to check if the template |
+ changed. No arguments are passed so the function must store the |
+ old state somewhere (for example in a closure). If it returns `False` |
+ the template will be reloaded. |
+ """ |
+ if not self.has_source_access: |
+ raise RuntimeError('%s cannot provide access to the source' % |
+ self.__class__.__name__) |
+ raise TemplateNotFound(template) |
+ |
+ def list_templates(self): |
+ """Iterates over all templates. If the loader does not support that |
+ it should raise a :exc:`TypeError` which is the default behavior. |
+ """ |
+ raise TypeError('this loader cannot iterate over all templates') |
+ |
+ @internalcode |
+ def load(self, environment, name, globals=None): |
+ """Loads a template. This method looks up the template in the cache |
+ or loads one by calling :meth:`get_source`. Subclasses should not |
+ override this method as loaders working on collections of other |
+ loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`) |
+ will not call this method but `get_source` directly. |
+ """ |
+ code = None |
+ if globals is None: |
+ globals = {} |
+ |
+ # first we try to get the source for this template together |
+ # with the filename and the uptodate function. |
+ source, filename, uptodate = self.get_source(environment, name) |
+ |
+ # try to load the code from the bytecode cache if there is a |
+ # bytecode cache configured. |
+ bcc = environment.bytecode_cache |
+ if bcc is not None: |
+ bucket = bcc.get_bucket(environment, name, filename, source) |
+ code = bucket.code |
+ |
+ # if we don't have code so far (not cached, no longer up to |
+ # date) etc. we compile the template |
+ if code is None: |
+ code = environment.compile(source, name, filename) |
+ |
+ # if the bytecode cache is available and the bucket doesn't |
+ # have a code so far, we give the bucket the new code and put |
+ # it back to the bytecode cache. |
+ if bcc is not None and bucket.code is None: |
+ bucket.code = code |
+ bcc.set_bucket(bucket) |
+ |
+ return environment.template_class.from_code(environment, code, |
+ globals, uptodate) |
+ |
+ |
+class FileSystemLoader(BaseLoader): |
+ """Loads templates from the file system. This loader can find templates |
+ in folders on the file system and is the preferred way to load them. |
+ |
+ The loader takes the path to the templates as string, or if multiple |
+ locations are wanted a list of them which is then looked up in the |
+ given order: |
+ |
+ >>> loader = FileSystemLoader('/path/to/templates') |
+ >>> loader = FileSystemLoader(['/path/to/templates', '/other/path']) |
+ |
+ Per default the template encoding is ``'utf-8'`` which can be changed |
+ by setting the `encoding` parameter to something else. |
+ """ |
+ |
+ def __init__(self, searchpath, encoding='utf-8'): |
+ if isinstance(searchpath, basestring): |
+ searchpath = [searchpath] |
+ self.searchpath = list(searchpath) |
+ self.encoding = encoding |
+ |
+ def get_source(self, environment, template): |
+ pieces = split_template_path(template) |
+ for searchpath in self.searchpath: |
+ filename = path.join(searchpath, *pieces) |
+ f = open_if_exists(filename) |
+ if f is None: |
+ continue |
+ try: |
+ contents = f.read().decode(self.encoding) |
+ finally: |
+ f.close() |
+ |
+ mtime = path.getmtime(filename) |
+ def uptodate(): |
+ try: |
+ return path.getmtime(filename) == mtime |
+ except OSError: |
+ return False |
+ return contents, filename, uptodate |
+ raise TemplateNotFound(template) |
+ |
+ def list_templates(self): |
+ found = set() |
+ for searchpath in self.searchpath: |
+ for dirpath, dirnames, filenames in os.walk(searchpath): |
+ for filename in filenames: |
+ template = os.path.join(dirpath, filename) \ |
+ [len(searchpath):].strip(os.path.sep) \ |
+ .replace(os.path.sep, '/') |
+ if template[:2] == './': |
+ template = template[2:] |
+ if template not in found: |
+ found.add(template) |
+ return sorted(found) |
+ |
+ |
+class PackageLoader(BaseLoader): |
+ """Load templates from python eggs or packages. It is constructed with |
+ the name of the python package and the path to the templates in that |
+ package:: |
+ |
+ loader = PackageLoader('mypackage', 'views') |
+ |
+ If the package path is not given, ``'templates'`` is assumed. |
+ |
+ Per default the template encoding is ``'utf-8'`` which can be changed |
+ by setting the `encoding` parameter to something else. Due to the nature |
+ of eggs it's only possible to reload templates if the package was loaded |
+ from the file system and not a zip file. |
+ """ |
+ |
+ def __init__(self, package_name, package_path='templates', |
+ encoding='utf-8'): |
+ from pkg_resources import DefaultProvider, ResourceManager, \ |
+ get_provider |
+ provider = get_provider(package_name) |
+ self.encoding = encoding |
+ self.manager = ResourceManager() |
+ self.filesystem_bound = isinstance(provider, DefaultProvider) |
+ self.provider = provider |
+ self.package_path = package_path |
+ |
+ def get_source(self, environment, template): |
+ pieces = split_template_path(template) |
+ p = '/'.join((self.package_path,) + tuple(pieces)) |
+ if not self.provider.has_resource(p): |
+ raise TemplateNotFound(template) |
+ |
+ filename = uptodate = None |
+ if self.filesystem_bound: |
+ filename = self.provider.get_resource_filename(self.manager, p) |
+ mtime = path.getmtime(filename) |
+ def uptodate(): |
+ try: |
+ return path.getmtime(filename) == mtime |
+ except OSError: |
+ return False |
+ |
+ source = self.provider.get_resource_string(self.manager, p) |
+ return source.decode(self.encoding), filename, uptodate |
+ |
+ def list_templates(self): |
+ path = self.package_path |
+ if path[:2] == './': |
+ path = path[2:] |
+ elif path == '.': |
+ path = '' |
+ offset = len(path) |
+ results = [] |
+ def _walk(path): |
+ for filename in self.provider.resource_listdir(path): |
+ fullname = path + '/' + filename |
+ if self.provider.resource_isdir(fullname): |
+ _walk(fullname) |
+ else: |
+ results.append(fullname[offset:].lstrip('/')) |
+ _walk(path) |
+ results.sort() |
+ return results |
+ |
+ |
+class DictLoader(BaseLoader): |
+ """Loads a template from a python dict. It's passed a dict of unicode |
+ strings bound to template names. This loader is useful for unittesting: |
+ |
+ >>> loader = DictLoader({'index.html': 'source here'}) |
+ |
+ Because auto reloading is rarely useful this is disabled per default. |
+ """ |
+ |
+ def __init__(self, mapping): |
+ self.mapping = mapping |
+ |
+ def get_source(self, environment, template): |
+ if template in self.mapping: |
+ source = self.mapping[template] |
+ return source, None, lambda: source != self.mapping.get(template) |
+ raise TemplateNotFound(template) |
+ |
+ def list_templates(self): |
+ return sorted(self.mapping) |
+ |
+ |
+class FunctionLoader(BaseLoader): |
+ """A loader that is passed a function which does the loading. The |
+ function becomes the name of the template passed and has to return either |
+ an unicode string with the template source, a tuple in the form ``(source, |
+ filename, uptodatefunc)`` or `None` if the template does not exist. |
+ |
+ >>> def load_template(name): |
+ ... if name == 'index.html': |
+ ... return '...' |
+ ... |
+ >>> loader = FunctionLoader(load_template) |
+ |
+ The `uptodatefunc` is a function that is called if autoreload is enabled |
+ and has to return `True` if the template is still up to date. For more |
+ details have a look at :meth:`BaseLoader.get_source` which has the same |
+ return value. |
+ """ |
+ |
+ def __init__(self, load_func): |
+ self.load_func = load_func |
+ |
+ def get_source(self, environment, template): |
+ rv = self.load_func(template) |
+ if rv is None: |
+ raise TemplateNotFound(template) |
+ elif isinstance(rv, basestring): |
+ return rv, None, None |
+ return rv |
+ |
+ |
+class PrefixLoader(BaseLoader): |
+ """A loader that is passed a dict of loaders where each loader is bound |
+ to a prefix. The prefix is delimited from the template by a slash per |
+ default, which can be changed by setting the `delimiter` argument to |
+ something else:: |
+ |
+ loader = PrefixLoader({ |
+ 'app1': PackageLoader('mypackage.app1'), |
+ 'app2': PackageLoader('mypackage.app2') |
+ }) |
+ |
+ By loading ``'app1/index.html'`` the file from the app1 package is loaded, |
+ by loading ``'app2/index.html'`` the file from the second. |
+ """ |
+ |
+ def __init__(self, mapping, delimiter='/'): |
+ self.mapping = mapping |
+ self.delimiter = delimiter |
+ |
+ def get_source(self, environment, template): |
+ try: |
+ prefix, name = template.split(self.delimiter, 1) |
+ loader = self.mapping[prefix] |
+ except (ValueError, KeyError): |
+ raise TemplateNotFound(template) |
+ try: |
+ return loader.get_source(environment, name) |
+ except TemplateNotFound: |
+ # re-raise the exception with the correct fileame here. |
+ # (the one that includes the prefix) |
+ raise TemplateNotFound(template) |
+ |
+ def list_templates(self): |
+ result = [] |
+ for prefix, loader in self.mapping.iteritems(): |
+ for template in loader.list_templates(): |
+ result.append(prefix + self.delimiter + template) |
+ return result |
+ |
+ |
+class ChoiceLoader(BaseLoader): |
+ """This loader works like the `PrefixLoader` just that no prefix is |
+ specified. If a template could not be found by one loader the next one |
+ is tried. |
+ |
+ >>> loader = ChoiceLoader([ |
+ ... FileSystemLoader('/path/to/user/templates'), |
+ ... FileSystemLoader('/path/to/system/templates') |
+ ... ]) |
+ |
+ This is useful if you want to allow users to override builtin templates |
+ from a different location. |
+ """ |
+ |
+ def __init__(self, loaders): |
+ self.loaders = loaders |
+ |
+ def get_source(self, environment, template): |
+ for loader in self.loaders: |
+ try: |
+ return loader.get_source(environment, template) |
+ except TemplateNotFound: |
+ pass |
+ raise TemplateNotFound(template) |
+ |
+ def list_templates(self): |
+ found = set() |
+ for loader in self.loaders: |
+ found.update(loader.list_templates()) |
+ return sorted(found) |
+ |
+ |
+class _TemplateModule(ModuleType): |
+ """Like a normal module but with support for weak references""" |
+ |
+ |
+class ModuleLoader(BaseLoader): |
+ """This loader loads templates from precompiled templates. |
+ |
+ Example usage: |
+ |
+ >>> loader = ChoiceLoader([ |
+ ... ModuleLoader('/path/to/compiled/templates'), |
+ ... FileSystemLoader('/path/to/templates') |
+ ... ]) |
+ |
+ Templates can be precompiled with :meth:`Environment.compile_templates`. |
+ """ |
+ |
+ has_source_access = False |
+ |
+ def __init__(self, path): |
+ package_name = '_jinja2_module_templates_%x' % id(self) |
+ |
+ # create a fake module that looks for the templates in the |
+ # path given. |
+ mod = _TemplateModule(package_name) |
+ if isinstance(path, basestring): |
+ path = [path] |
+ else: |
+ path = list(path) |
+ mod.__path__ = path |
+ |
+ sys.modules[package_name] = weakref.proxy(mod, |
+ lambda x: sys.modules.pop(package_name, None)) |
+ |
+ # the only strong reference, the sys.modules entry is weak |
+ # so that the garbage collector can remove it once the |
+ # loader that created it goes out of business. |
+ self.module = mod |
+ self.package_name = package_name |
+ |
+ @staticmethod |
+ def get_template_key(name): |
+ return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest() |
+ |
+ @staticmethod |
+ def get_module_filename(name): |
+ return ModuleLoader.get_template_key(name) + '.py' |
+ |
+ @internalcode |
+ def load(self, environment, name, globals=None): |
+ key = self.get_template_key(name) |
+ module = '%s.%s' % (self.package_name, key) |
+ mod = getattr(self.module, module, None) |
+ if mod is None: |
+ try: |
+ mod = __import__(module, None, None, ['root']) |
+ except ImportError: |
+ raise TemplateNotFound(name) |
+ |
+ # remove the entry from sys.modules, we only want the attribute |
+ # on the module object we have stored on the loader. |
+ sys.modules.pop(module, None) |
+ |
+ return environment.template_class.from_module_dict( |
+ environment, mod.__dict__, globals) |