Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/websdk/genshi/template/loader.py
diff options
context:
space:
mode:
Diffstat (limited to 'websdk/genshi/template/loader.py')
-rw-r--r--websdk/genshi/template/loader.py344
1 files changed, 344 insertions, 0 deletions
diff --git a/websdk/genshi/template/loader.py b/websdk/genshi/template/loader.py
new file mode 100644
index 0000000..0e7cda7
--- /dev/null
+++ b/websdk/genshi/template/loader.py
@@ -0,0 +1,344 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006-2010 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://genshi.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://genshi.edgewall.org/log/.
+
+"""Template loading and caching."""
+
+import os
+try:
+ import threading
+except ImportError:
+ import dummy_threading as threading
+
+from genshi.template.base import TemplateError
+from genshi.util import LRUCache
+
+__all__ = ['TemplateLoader', 'TemplateNotFound', 'directory', 'package',
+ 'prefixed']
+__docformat__ = 'restructuredtext en'
+
+
+class TemplateNotFound(TemplateError):
+ """Exception raised when a specific template file could not be found."""
+
+ def __init__(self, name, search_path):
+ """Create the exception.
+
+ :param name: the filename of the template
+ :param search_path: the search path used to lookup the template
+ """
+ TemplateError.__init__(self, 'Template "%s" not found' % name)
+ self.search_path = search_path
+
+
+class TemplateLoader(object):
+ """Responsible for loading templates from files on the specified search
+ path.
+
+ >>> import tempfile
+ >>> fd, path = tempfile.mkstemp(suffix='.html', prefix='template')
+ >>> os.write(fd, '<p>$var</p>')
+ 11
+ >>> os.close(fd)
+
+ The template loader accepts a list of directory paths that are then used
+ when searching for template files, in the given order:
+
+ >>> loader = TemplateLoader([os.path.dirname(path)])
+
+ The `load()` method first checks the template cache whether the requested
+ template has already been loaded. If not, it attempts to locate the
+ template file, and returns the corresponding `Template` object:
+
+ >>> from genshi.template import MarkupTemplate
+ >>> template = loader.load(os.path.basename(path))
+ >>> isinstance(template, MarkupTemplate)
+ True
+
+ Template instances are cached: requesting a template with the same name
+ results in the same instance being returned:
+
+ >>> loader.load(os.path.basename(path)) is template
+ True
+
+ The `auto_reload` option can be used to control whether a template should
+ be automatically reloaded when the file it was loaded from has been
+ changed. Disable this automatic reloading to improve performance.
+
+ >>> os.remove(path)
+ """
+ def __init__(self, search_path=None, auto_reload=False,
+ default_encoding=None, max_cache_size=25, default_class=None,
+ variable_lookup='strict', allow_exec=True, callback=None):
+ """Create the template laoder.
+
+ :param search_path: a list of absolute path names that should be
+ searched for template files, or a string containing
+ a single absolute path; alternatively, any item on
+ the list may be a ''load function'' that is passed
+ a filename and returns a file-like object and some
+ metadata
+ :param auto_reload: whether to check the last modification time of
+ template files, and reload them if they have changed
+ :param default_encoding: the default encoding to assume when loading
+ templates; defaults to UTF-8
+ :param max_cache_size: the maximum number of templates to keep in the
+ cache
+ :param default_class: the default `Template` subclass to use when
+ instantiating templates
+ :param variable_lookup: the variable lookup mechanism; either "strict"
+ (the default), "lenient", or a custom lookup
+ class
+ :param allow_exec: whether to allow Python code blocks in templates
+ :param callback: (optional) a callback function that is invoked after a
+ template was initialized by this loader; the function
+ is passed the template object as only argument. This
+ callback can be used for example to add any desired
+ filters to the template
+ :see: `LenientLookup`, `StrictLookup`
+
+ :note: Changed in 0.5: Added the `allow_exec` argument
+ """
+ from genshi.template.markup import MarkupTemplate
+
+ self.search_path = search_path
+ if self.search_path is None:
+ self.search_path = []
+ elif not isinstance(self.search_path, (list, tuple)):
+ self.search_path = [self.search_path]
+
+ self.auto_reload = auto_reload
+ """Whether templates should be reloaded when the underlying file is
+ changed"""
+
+ self.default_encoding = default_encoding
+ self.default_class = default_class or MarkupTemplate
+ self.variable_lookup = variable_lookup
+ self.allow_exec = allow_exec
+ if callback is not None and not hasattr(callback, '__call__'):
+ raise TypeError('The "callback" parameter needs to be callable')
+ self.callback = callback
+ self._cache = LRUCache(max_cache_size)
+ self._uptodate = {}
+ self._lock = threading.RLock()
+
+ def __getstate__(self):
+ state = self.__dict__.copy()
+ state['_lock'] = None
+ return state
+
+ def __setstate__(self, state):
+ self.__dict__ = state
+ self._lock = threading.RLock()
+
+ def load(self, filename, relative_to=None, cls=None, encoding=None):
+ """Load the template with the given name.
+
+ If the `filename` parameter is relative, this method searches the
+ search path trying to locate a template matching the given name. If the
+ file name is an absolute path, the search path is ignored.
+
+ If the requested template is not found, a `TemplateNotFound` exception
+ is raised. Otherwise, a `Template` object is returned that represents
+ the parsed template.
+
+ Template instances are cached to avoid having to parse the same
+ template file more than once. Thus, subsequent calls of this method
+ with the same template file name will return the same `Template`
+ object (unless the ``auto_reload`` option is enabled and the file was
+ changed since the last parse.)
+
+ If the `relative_to` parameter is provided, the `filename` is
+ interpreted as being relative to that path.
+
+ :param filename: the relative path of the template file to load
+ :param relative_to: the filename of the template from which the new
+ template is being loaded, or ``None`` if the
+ template is being loaded directly
+ :param cls: the class of the template object to instantiate
+ :param encoding: the encoding of the template to load; defaults to the
+ ``default_encoding`` of the loader instance
+ :return: the loaded `Template` instance
+ :raises TemplateNotFound: if a template with the given name could not
+ be found
+ """
+ if cls is None:
+ cls = self.default_class
+ search_path = self.search_path
+
+ # Make the filename relative to the template file its being loaded
+ # from, but only if that file is specified as a relative path, or no
+ # search path has been set up
+ if relative_to and (not search_path or not os.path.isabs(relative_to)):
+ filename = os.path.join(os.path.dirname(relative_to), filename)
+
+ filename = os.path.normpath(filename)
+ cachekey = filename
+
+ self._lock.acquire()
+ try:
+ # First check the cache to avoid reparsing the same file
+ try:
+ tmpl = self._cache[cachekey]
+ if not self.auto_reload:
+ return tmpl
+ uptodate = self._uptodate[cachekey]
+ if uptodate is not None and uptodate():
+ return tmpl
+ except (KeyError, OSError):
+ pass
+
+ isabs = False
+
+ if os.path.isabs(filename):
+ # Bypass the search path if the requested filename is absolute
+ search_path = [os.path.dirname(filename)]
+ isabs = True
+
+ elif relative_to and os.path.isabs(relative_to):
+ # Make sure that the directory containing the including
+ # template is on the search path
+ dirname = os.path.dirname(relative_to)
+ if dirname not in search_path:
+ search_path = list(search_path) + [dirname]
+ isabs = True
+
+ elif not search_path:
+ # Uh oh, don't know where to look for the template
+ raise TemplateError('Search path for templates not configured')
+
+ for loadfunc in search_path:
+ if isinstance(loadfunc, basestring):
+ loadfunc = directory(loadfunc)
+ try:
+ filepath, filename, fileobj, uptodate = loadfunc(filename)
+ except IOError:
+ continue
+ else:
+ try:
+ if isabs:
+ # If the filename of either the included or the
+ # including template is absolute, make sure the
+ # included template gets an absolute path, too,
+ # so that nested includes work properly without a
+ # search path
+ filename = filepath
+ tmpl = self._instantiate(cls, fileobj, filepath,
+ filename, encoding=encoding)
+ if self.callback:
+ self.callback(tmpl)
+ self._cache[cachekey] = tmpl
+ self._uptodate[cachekey] = uptodate
+ finally:
+ if hasattr(fileobj, 'close'):
+ fileobj.close()
+ return tmpl
+
+ raise TemplateNotFound(filename, search_path)
+
+ finally:
+ self._lock.release()
+
+ def _instantiate(self, cls, fileobj, filepath, filename, encoding=None):
+ """Instantiate and return the `Template` object based on the given
+ class and parameters.
+
+ This function is intended for subclasses to override if they need to
+ implement special template instantiation logic. Code that just uses
+ the `TemplateLoader` should use the `load` method instead.
+
+ :param cls: the class of the template object to instantiate
+ :param fileobj: a readable file-like object containing the template
+ source
+ :param filepath: the absolute path to the template file
+ :param filename: the path to the template file relative to the search
+ path
+ :param encoding: the encoding of the template to load; defaults to the
+ ``default_encoding`` of the loader instance
+ :return: the loaded `Template` instance
+ :rtype: `Template`
+ """
+ if encoding is None:
+ encoding = self.default_encoding
+ return cls(fileobj, filepath=filepath, filename=filename, loader=self,
+ encoding=encoding, lookup=self.variable_lookup,
+ allow_exec=self.allow_exec)
+
+ @staticmethod
+ def directory(path):
+ """Loader factory for loading templates from a local directory.
+
+ :param path: the path to the local directory containing the templates
+ :return: the loader function to load templates from the given directory
+ :rtype: ``function``
+ """
+ def _load_from_directory(filename):
+ filepath = os.path.join(path, filename)
+ fileobj = open(filepath, 'U')
+ mtime = os.path.getmtime(filepath)
+ def _uptodate():
+ return mtime == os.path.getmtime(filepath)
+ return filepath, filename, fileobj, _uptodate
+ return _load_from_directory
+
+ @staticmethod
+ def package(name, path):
+ """Loader factory for loading templates from egg package data.
+
+ :param name: the name of the package containing the resources
+ :param path: the path inside the package data
+ :return: the loader function to load templates from the given package
+ :rtype: ``function``
+ """
+ from pkg_resources import resource_stream
+ def _load_from_package(filename):
+ filepath = os.path.join(path, filename)
+ return filepath, filename, resource_stream(name, filepath), None
+ return _load_from_package
+
+ @staticmethod
+ def prefixed(**delegates):
+ """Factory for a load function that delegates to other loaders
+ depending on the prefix of the requested template path.
+
+ The prefix is stripped from the filename when passing on the load
+ request to the delegate.
+
+ >>> load = prefixed(
+ ... app1 = lambda filename: ('app1', filename, None, None),
+ ... app2 = lambda filename: ('app2', filename, None, None)
+ ... )
+ >>> print(load('app1/foo.html'))
+ ('app1', 'app1/foo.html', None, None)
+ >>> print(load('app2/bar.html'))
+ ('app2', 'app2/bar.html', None, None)
+
+ :param delegates: mapping of path prefixes to loader functions
+ :return: the loader function
+ :rtype: ``function``
+ """
+ def _dispatch_by_prefix(filename):
+ for prefix, delegate in delegates.items():
+ if filename.startswith(prefix):
+ if isinstance(delegate, basestring):
+ delegate = directory(delegate)
+ filepath, _, fileobj, uptodate = delegate(
+ filename[len(prefix):].lstrip('/\\')
+ )
+ return filepath, filename, fileobj, uptodate
+ raise TemplateNotFound(filename, list(delegates.keys()))
+ return _dispatch_by_prefix
+
+
+directory = TemplateLoader.directory
+package = TemplateLoader.package
+prefixed = TemplateLoader.prefixed