blob: 2bcb30d0872797e3f1d6d1123a6a471ee3a3112f [file] [log] [blame]
# -*- coding: utf-8 -*-
"""
jinja2.loaders
~~~~~~~~~~~~~~
Jinja loader classes.
:copyright: 2008 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from os import path
from jinja2.exceptions import TemplateNotFound
from jinja2.environment import template_from_code
from jinja2.utils import LRUCache
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 != '.':
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 will automatically
call the loader bound to an environment.
"""
def __init__(self, cache_size=50, auto_reload=True):
if cache_size == 0:
self.cache = None
elif cache_size < 0:
self.cache = {}
else:
self.cache = LRUCache(cache_size)
self.auto_reload = auto_reload
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.
"""
raise TemplateNotFound(template)
def load(self, environment, name, globals=None):
"""Loads a template. This method should not be overriden by
subclasses unless `get_source` doesn't provide enough flexibility.
"""
if globals is None:
globals = {}
if self.cache is not None:
template = self.cache.get(name)
if template is not None and (not self.auto_reload or \
template.is_up_to_date):
return template
source, filename, uptodate = self.get_source(environment, name)
code = environment.compile(source, name, filename, globals)
template = template_from_code(environment, code, globals, uptodate)
if self.cache is not None:
self.cache[name] = template
return template
class FileSystemLoader(BaseLoader):
"""Loads templates from the file system."""
def __init__(self, searchpath, encoding='utf-8', cache_size=50,
auto_reload=True):
BaseLoader.__init__(self, cache_size, auto_reload)
if isinstance(searchpath, basestring):
searchpath = [searchpath]
self.searchpath = 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)
if not path.isfile(filename):
continue
f = file(filename)
try:
contents = f.read().decode(self.encoding)
finally:
f.close()
old = path.getmtime(filename)
return contents, filename, lambda: path.getmtime(filename) != old
raise TemplateNotFound(template)
class PackageLoader(BaseLoader):
"""Load templates from python eggs."""
def __init__(self, package_name, package_path, charset='utf-8',
cache_size=50, auto_reload=True):
BaseLoader.__init__(self, cache_size, auto_reload)
import pkg_resources
self._pkg = pkg_resources
self.package_name = package_name
self.package_path = package_path
def get_source(self, environment, template):
pieces = split_template_path(template)
path = '/'.join((self.package_path,) + tuple(pieces))
if not self._pkg.resource_exists(self.package_name, path):
raise TemplateNotFound(template)
return self._pkg.resource_string(self.package_name, path), None, None
class DictLoader(BaseLoader):
"""Loads a template from a python dict. Used for unittests mostly."""
def __init__(self, mapping, cache_size=50):
BaseLoader.__init__(self, cache_size, False)
self.mapping = mapping
def get_source(self, environment, template):
if template in self.mapping:
return self.mapping[template], None, None
raise TemplateNotFound(template)
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 __init__(self, load_func, cache_size=50, auto_reload=True):
BaseLoader.__init__(self, cache_size, auto_reload)
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 caching is independent of the actual loaders so the
per loader cache settings are ignored. The prefix is delimited from the
template by a slash.
"""
def __init__(self, mapping, delimiter='/', cache_size=50,
auto_reload=True):
BaseLoader.__init__(self, cache_size, auto_reload)
self.mapping = mapping
self.delimiter = delimiter
def get_source(self, environment, template):
try:
prefix, template = template.split(self.delimiter, 1)
loader = self.mapping[prefix]
except (ValueError, KeyError):
raise TemplateNotFound(template)
return loader.get_source(environment, template)
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. Like for the `PrefixLoader` the cache settings of the actual
loaders don't matter as the choice loader does the caching.
"""
def __init__(self, loaders, cache_size=50, auto_reload=True):
BaseLoader.__init__(self, cache_size, auto_reload)
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)