| 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)
|
|
|