From 5861585e94a32b3032ac473804bf90c6e1363940 Mon Sep 17 00:00:00 2001 From: Sebastian Silva Date: Wed, 28 Sep 2011 00:19:33 +0000 Subject: Migrated to Flask, added JQuery sugar theme, fixed race condition --- (limited to 'websdk/genshi/template/loader.py') 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, '

$var

') + 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 -- cgit v0.9.1