~ubuntu-branches/ubuntu/raring/genshi/raring-proposed

« back to all changes in this revision

Viewing changes to genshi/template/loader.py

  • Committer: Bazaar Package Importer
  • Author(s): Arnaud Fontaine
  • Date: 2007-04-16 17:49:03 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20070416174903-x2p3n9g890v18d0m
Tags: 0.4-1
* New upstream release.
* Remove useless python-markup transition package.
* Add Provides against python-markup.
* Add doc-base.
* Add depends against python-xml.
* Add suggests to python-setuptools.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
#
 
3
# Copyright (C) 2006-2007 Edgewall Software
 
4
# All rights reserved.
 
5
#
 
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.
 
9
#
 
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/.
 
13
 
 
14
"""Template loading and caching."""
 
15
 
 
16
import os
 
17
try:
 
18
    import threading
 
19
except ImportError:
 
20
    import dummy_threading as threading
 
21
 
 
22
from genshi.template.base import TemplateError
 
23
from genshi.util import LRUCache
 
24
 
 
25
__all__ = ['TemplateLoader', 'TemplateNotFound']
 
26
__docformat__ = 'restructuredtext en'
 
27
 
 
28
 
 
29
class TemplateNotFound(TemplateError):
 
30
    """Exception raised when a specific template file could not be found."""
 
31
 
 
32
    def __init__(self, name, search_path):
 
33
        """Create the exception.
 
34
        
 
35
        :param name: the filename of the template
 
36
        :param search_path: the search path used to lookup the template
 
37
        """
 
38
        TemplateError.__init__(self, 'Template "%s" not found' % name)
 
39
        self.search_path = search_path
 
40
 
 
41
 
 
42
class TemplateLoader(object):
 
43
    """Responsible for loading templates from files on the specified search
 
44
    path.
 
45
    
 
46
    >>> import tempfile
 
47
    >>> fd, path = tempfile.mkstemp(suffix='.html', prefix='template')
 
48
    >>> os.write(fd, '<p>$var</p>')
 
49
    11
 
50
    >>> os.close(fd)
 
51
    
 
52
    The template loader accepts a list of directory paths that are then used
 
53
    when searching for template files, in the given order:
 
54
    
 
55
    >>> loader = TemplateLoader([os.path.dirname(path)])
 
56
    
 
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:
 
60
    
 
61
    >>> from genshi.template import MarkupTemplate
 
62
    >>> template = loader.load(os.path.basename(path))
 
63
    >>> isinstance(template, MarkupTemplate)
 
64
    True
 
65
    
 
66
    Template instances are cached: requesting a template with the same name
 
67
    results in the same instance being returned:
 
68
    
 
69
    >>> loader.load(os.path.basename(path)) is template
 
70
    True
 
71
    
 
72
    >>> os.remove(path)
 
73
    """
 
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.
 
78
        
 
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
 
87
                               cache
 
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
 
92
                                class
 
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`
 
99
        """
 
100
        from genshi.template.markup import MarkupTemplate
 
101
 
 
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)
 
115
        self._mtime = {}
 
116
        self._lock = threading.Lock()
 
117
 
 
118
    def load(self, filename, relative_to=None, cls=None, encoding=None):
 
119
        """Load the template with the given name.
 
120
        
 
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.
 
124
        
 
125
        If the requested template is not found, a `TemplateNotFound` exception
 
126
        is raised. Otherwise, a `Template` object is returned that represents
 
127
        the parsed template.
 
128
        
 
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.)
 
134
        
 
135
        If the `relative_to` parameter is provided, the `filename` is
 
136
        interpreted as being relative to that path.
 
137
        
 
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
 
147
                                  found
 
148
        """
 
149
        if cls is None:
 
150
            cls = self.default_class
 
151
        if encoding is None:
 
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)
 
156
 
 
157
        self._lock.acquire()
 
158
        try:
 
159
            # First check the cache to avoid reparsing the same file
 
160
            try:
 
161
                tmpl = self._cache[filename]
 
162
                if not self.auto_reload or \
 
163
                        os.path.getmtime(tmpl.filepath) == self._mtime[filename]:
 
164
                    return tmpl
 
165
            except KeyError:
 
166
                pass
 
167
 
 
168
            search_path = self.search_path
 
169
            isabs = False
 
170
 
 
171
            if os.path.isabs(filename):
 
172
                # Bypass the search path if the requested filename is absolute
 
173
                search_path = [os.path.dirname(filename)]
 
174
                isabs = True
 
175
 
 
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]
 
182
                isabs = True
 
183
 
 
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')
 
187
 
 
188
            for dirname in search_path:
 
189
                filepath = os.path.join(dirname, filename)
 
190
                try:
 
191
                    fileobj = open(filepath, 'U')
 
192
                    try:
 
193
                        if isabs:
 
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
 
198
                            # search path
 
199
                            filename = os.path.join(dirname, filename)
 
200
                            dirname = ''
 
201
                        tmpl = cls(fileobj, basedir=dirname, filename=filename,
 
202
                                   loader=self, lookup=self.variable_lookup,
 
203
                                   encoding=encoding)
 
204
                        if self.callback:
 
205
                            self.callback(tmpl)
 
206
                        self._cache[filename] = tmpl
 
207
                        self._mtime[filename] = os.path.getmtime(filepath)
 
208
                    finally:
 
209
                        fileobj.close()
 
210
                    return tmpl
 
211
                except IOError:
 
212
                    continue
 
213
 
 
214
            raise TemplateNotFound(filename, search_path)
 
215
 
 
216
        finally:
 
217
            self._lock.release()