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