1
# -*- coding: utf-8 -*-
3
# Copyright (C) 2006-2007 Edgewall Software
6
# This software is licensed as described in the file COPYING, which
7
# you should have received as part of this distribution. The terms
8
# are also available at http://genshi.edgewall.org/wiki/License.
10
# This software consists of voluntary contributions made by many
11
# individuals. For the exact contribution history, see the revision
12
# history and logs, available at http://genshi.edgewall.org/log/.
14
"""Template loading and caching."""
20
import dummy_threading as threading
22
from genshi.template.base import TemplateError
23
from genshi.util import LRUCache
25
__all__ = ['TemplateLoader', 'TemplateNotFound']
26
__docformat__ = 'restructuredtext en'
29
class TemplateNotFound(TemplateError):
30
"""Exception raised when a specific template file could not be found."""
32
def __init__(self, name, search_path):
33
"""Create the exception.
35
:param name: the filename of the template
36
:param search_path: the search path used to lookup the template
38
TemplateError.__init__(self, 'Template "%s" not found' % name)
39
self.search_path = search_path
42
class TemplateLoader(object):
43
"""Responsible for loading templates from files on the specified search
47
>>> fd, path = tempfile.mkstemp(suffix='.html', prefix='template')
48
>>> os.write(fd, '<p>$var</p>')
52
The template loader accepts a list of directory paths that are then used
53
when searching for template files, in the given order:
55
>>> loader = TemplateLoader([os.path.dirname(path)])
57
The `load()` method first checks the template cache whether the requested
58
template has already been loaded. If not, it attempts to locate the
59
template file, and returns the corresponding `Template` object:
61
>>> from genshi.template import MarkupTemplate
62
>>> template = loader.load(os.path.basename(path))
63
>>> isinstance(template, MarkupTemplate)
66
Template instances are cached: requesting a template with the same name
67
results in the same instance being returned:
69
>>> loader.load(os.path.basename(path)) is template
74
def __init__(self, search_path=None, auto_reload=False,
75
default_encoding=None, max_cache_size=25, default_class=None,
76
variable_lookup='lenient', callback=None):
77
"""Create the template laoder.
79
:param search_path: a list of absolute path names that should be
80
searched for template files, or a string containing
81
a single absolute path
82
:param auto_reload: whether to check the last modification time of
83
template files, and reload them if they have changed
84
:param default_encoding: the default encoding to assume when loading
85
templates; defaults to UTF-8
86
:param max_cache_size: the maximum number of templates to keep in the
88
:param default_class: the default `Template` subclass to use when
89
instantiating templates
90
:param variable_lookup: the variable lookup mechanism; either "lenient"
91
(the default), "strict", or a custom lookup
93
:param callback: (optional) a callback function that is invoked after a
94
template was initialized by this loader; the function
95
is passed the template object as only argument. This
96
callback can be used for example to add any desired
97
filters to the template
98
:see: `LenientLookup`, `StrictLookup`
100
from genshi.template.markup import MarkupTemplate
102
self.search_path = search_path
103
if self.search_path is None:
104
self.search_path = []
105
elif isinstance(self.search_path, basestring):
106
self.search_path = [self.search_path]
107
self.auto_reload = auto_reload
108
self.default_encoding = default_encoding
109
self.default_class = default_class or MarkupTemplate
110
self.variable_lookup = variable_lookup
111
if callback is not None and not callable(callback):
112
raise TypeError('The "callback" parameter needs to be callable')
113
self.callback = callback
114
self._cache = LRUCache(max_cache_size)
116
self._lock = threading.Lock()
118
def load(self, filename, relative_to=None, cls=None, encoding=None):
119
"""Load the template with the given name.
121
If the `filename` parameter is relative, this method searches the search
122
path trying to locate a template matching the given name. If the file
123
name is an absolute path, the search path is ignored.
125
If the requested template is not found, a `TemplateNotFound` exception
126
is raised. Otherwise, a `Template` object is returned that represents
129
Template instances are cached to avoid having to parse the same
130
template file more than once. Thus, subsequent calls of this method
131
with the same template file name will return the same `Template`
132
object (unless the ``auto_reload`` option is enabled and the file was
133
changed since the last parse.)
135
If the `relative_to` parameter is provided, the `filename` is
136
interpreted as being relative to that path.
138
:param filename: the relative path of the template file to load
139
:param relative_to: the filename of the template from which the new
140
template is being loaded, or ``None`` if the
141
template is being loaded directly
142
:param cls: the class of the template object to instantiate
143
:param encoding: the encoding of the template to load; defaults to the
144
``default_encoding`` of the loader instance
145
:return: the loaded `Template` instance
146
:raises TemplateNotFound: if a template with the given name could not be
150
cls = self.default_class
152
encoding = self.default_encoding
153
if relative_to and not os.path.isabs(relative_to):
154
filename = os.path.join(os.path.dirname(relative_to), filename)
155
filename = os.path.normpath(filename)
159
# First check the cache to avoid reparsing the same file
161
tmpl = self._cache[filename]
162
if not self.auto_reload or \
163
os.path.getmtime(tmpl.filepath) == self._mtime[filename]:
168
search_path = self.search_path
171
if os.path.isabs(filename):
172
# Bypass the search path if the requested filename is absolute
173
search_path = [os.path.dirname(filename)]
176
elif relative_to and os.path.isabs(relative_to):
177
# Make sure that the directory containing the including
178
# template is on the search path
179
dirname = os.path.dirname(relative_to)
180
if dirname not in search_path:
181
search_path = search_path + [dirname]
184
elif not search_path:
185
# Uh oh, don't know where to look for the template
186
raise TemplateError('Search path for templates not configured')
188
for dirname in search_path:
189
filepath = os.path.join(dirname, filename)
191
fileobj = open(filepath, 'U')
194
# If the filename of either the included or the
195
# including template is absolute, make sure the
196
# included template gets an absolute path, too,
197
# so that nested include work properly without a
199
filename = os.path.join(dirname, filename)
201
tmpl = cls(fileobj, basedir=dirname, filename=filename,
202
loader=self, lookup=self.variable_lookup,
206
self._cache[filename] = tmpl
207
self._mtime[filename] = os.path.getmtime(filepath)
214
raise TemplateNotFound(filename, search_path)