blob: dc3ccfb709cf257e1915d5df539a5546a9966251 [file] [log] [blame]
Armin Ronacher07bc6842008-03-31 14:18:49 +02001# -*- coding: utf-8 -*-
2"""
Armin Ronacherbcb7c532008-04-11 16:30:34 +02003 jinja2.loaders
4 ~~~~~~~~~~~~~~
Armin Ronacher07bc6842008-03-31 14:18:49 +02005
6 Jinja loader classes.
7
Armin Ronacherbcb7c532008-04-11 16:30:34 +02008 :copyright: 2008 by Armin Ronacher.
Armin Ronacher07bc6842008-03-31 14:18:49 +02009 :license: BSD, see LICENSE for more details.
10"""
Armin Ronacherbcb7c532008-04-11 16:30:34 +020011from os import path
12from jinja2.exceptions import TemplateNotFound
13from jinja2.environment import Template
Armin Ronacher814f6c22008-04-17 15:52:23 +020014from jinja2.utils import LRUCache
Armin Ronacherbcb7c532008-04-11 16:30:34 +020015
16
Armin Ronacher9a822052008-04-17 18:44:07 +020017def split_template_path(template):
18 """Split a path into segments and perform a sanity check. If it detects
19 '..' in the path it will raise a `TemplateNotFound` error.
20 """
21 pieces = []
22 for piece in template.split('/'):
23 if path.sep in piece \
24 or (path.altsep and path.altsep in piece) or \
25 piece == path.pardir:
26 raise TemplateNotFound(template)
27 elif piece != '.':
28 pieces.append(piece)
29 return pieces
30
31
Armin Ronacherbcb7c532008-04-11 16:30:34 +020032class BaseLoader(object):
Armin Ronacher814f6c22008-04-17 15:52:23 +020033 """
34 Baseclass for all loaders. Subclass this and override `get_source` to
35 implement a custom loading mechanism.
36
37 The environment provides a `get_template` method that will automatically
38 call the loader bound to an environment.
39 """
40
41 def __init__(self, cache_size=50, auto_reload=True):
Armin Ronacherf41d1392008-04-18 16:41:52 +020042 if cache_size == 0:
Armin Ronacher814f6c22008-04-17 15:52:23 +020043 self.cache = None
Armin Ronacherf41d1392008-04-18 16:41:52 +020044 elif cache_size < 0:
45 self.cache = {}
46 else:
47 self.cache = LRUCache(cache_size)
Armin Ronacher814f6c22008-04-17 15:52:23 +020048 self.auto_reload = auto_reload
Armin Ronacherbcb7c532008-04-11 16:30:34 +020049
50 def get_source(self, environment, template):
Armin Ronacher814f6c22008-04-17 15:52:23 +020051 """Get the template source, filename and reload helper for a template.
52 It's passed the environment and template name and has to return a
53 tuple in the form ``(source, filename, uptodate)`` or raise a
54 `TemplateNotFound` error if it can't locate the template.
55
56 The source part of the returned tuple must be the source of the
57 template as unicode string or a ASCII bytestring. The filename should
58 be the name of the file on the filesystem if it was loaded from there,
59 otherwise `None`. The filename is used by python for the tracebacks
60 if no loader extension is used.
61
62 The last item in the tuple is the `uptodate` function. If auto
63 reloading is enabled it's always called to check if the template
64 changed. No arguments are passed so the function must store the
65 old state somewhere (for example in a closure). If it returns `False`
66 the template will be reloaded.
67 """
68 raise TemplateNotFound(template)
Armin Ronacherbcb7c532008-04-11 16:30:34 +020069
Armin Ronacherba3757b2008-04-16 19:43:16 +020070 def load(self, environment, name, globals=None):
Armin Ronacher814f6c22008-04-17 15:52:23 +020071 """Loads a template. This method should not be overriden by
72 subclasses unless `get_source` doesn't provide enough flexibility.
73 """
74 if globals is None:
75 globals = {}
76
77 if self.cache is not None:
78 template = self.cache.get(name)
79 if template is not None and (not self.auto_reload or \
Armin Ronacher9a822052008-04-17 18:44:07 +020080 template.is_up_to_date):
Armin Ronacher814f6c22008-04-17 15:52:23 +020081 return template
82
83 source, filename, uptodate = self.get_source(environment, name)
84 code = environment.compile(source, name, filename, globals)
85 template = Template(environment, code, globals, uptodate)
86 if self.cache is not None:
87 self.cache[name] = template
88 return template
Armin Ronacherbcb7c532008-04-11 16:30:34 +020089
90
91class FileSystemLoader(BaseLoader):
Armin Ronacher814f6c22008-04-17 15:52:23 +020092 """Loads templates from the file system."""
Armin Ronacherbcb7c532008-04-11 16:30:34 +020093
Armin Ronacher814f6c22008-04-17 15:52:23 +020094 def __init__(self, searchpath, encoding='utf-8', cache_size=50,
95 auto_reload=True):
96 BaseLoader.__init__(self, cache_size, auto_reload)
97 if isinstance(searchpath, basestring):
98 searchpath = [searchpath]
99 self.searchpath = searchpath
Armin Ronacherbcb7c532008-04-11 16:30:34 +0200100 self.encoding = encoding
101
102 def get_source(self, environment, template):
Armin Ronacher9a822052008-04-17 18:44:07 +0200103 pieces = split_template_path(template)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200104 for searchpath in self.searchpath:
105 filename = path.join(searchpath, *pieces)
Armin Ronacher9a822052008-04-17 18:44:07 +0200106 if not path.isfile(filename):
107 continue
108 f = file(filename)
109 try:
110 contents = f.read().decode(self.encoding)
111 finally:
112 f.close()
113 old = path.getmtime(filename)
114 return contents, filename, lambda: path.getmtime(filename) != old
Armin Ronacher814f6c22008-04-17 15:52:23 +0200115 raise TemplateNotFound(template)
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200116
117
Armin Ronacher9a822052008-04-17 18:44:07 +0200118class PackageLoader(BaseLoader):
119 """Load templates from python eggs."""
120
121 def __init__(self, package_name, package_path, charset='utf-8',
122 cache_size=50, auto_reload=True):
123 BaseLoader.__init__(self, cache_size, auto_reload)
124 import pkg_resources
125 self._pkg = pkg_resources
126 self.package_name = package_name
127 self.package_path = package_path
128
129 def get_source(self, environment, template):
130 path = '/'.join(split_template_path(template))
131 if not self._pkg.resource_exists(self.package_name, path):
132 raise TemplateNotFound(template)
133 return self._pkg.resource_string(self.package_name, path), None, None
134
135
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200136class DictLoader(BaseLoader):
Armin Ronacher814f6c22008-04-17 15:52:23 +0200137 """Loads a template from a python dict. Used for unittests mostly."""
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200138
Armin Ronacherf41d1392008-04-18 16:41:52 +0200139 def __init__(self, mapping, cache_size=50):
140 BaseLoader.__init__(self, cache_size, False)
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200141 self.mapping = mapping
142
143 def get_source(self, environment, template):
144 if template in self.mapping:
Armin Ronacher814f6c22008-04-17 15:52:23 +0200145 return self.mapping[template], None, None
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200146 raise TemplateNotFound(template)