# HG changeset patch # User cmlenz # Date 1207684179 0 # Node ID fc6d9d2a35275f6dbda1a157a50cf9ddc1661270 # Parent 5420fe9d99a95d0b947ac7bb558a6cb6ad2b5864 The `Template` class and its subclasses, as well as the interpolation API, now take an `filepath` parameter instead of `basedir`. Closes #207. Thanks to Waldemar Kornewald for the patch. diff --git a/ChangeLog b/ChangeLog --- a/ChangeLog +++ b/ChangeLog @@ -56,7 +56,7 @@ including) the matching template itself are applied to the matched content, whereas the match templates declared after the matching template are only applied to the generated content (ticket #186). - * The `TemplateLoader` class now provides an `instantiate()` method that can + * The `TemplateLoader` class now provides an `_instantiate()` method that can be overridden by subclasses to implement advanced template instantiation logic (ticket #204). * The search path of the `TemplateLoader` class can now contain ''load @@ -77,6 +77,8 @@ positional arguments that would be substituted was removed. Thus the `Markup` class now supports the same arguments as that of its `unicode` base class (ticket #211). + * The `Template` class and its subclasses, as well as the interpolation API, + now take an `filepath` parameter instead of `basedir` (ticket #207). Version 0.4.4 diff --git a/doc/upgrade.txt b/doc/upgrade.txt --- a/doc/upgrade.txt +++ b/doc/upgrade.txt @@ -70,6 +70,15 @@ Markup('%s') % name +``Template`` Constructor +------------------------ + +The constructor of the ``Template`` class and its subclasses has changed +slightly: instead of the optional ``basedir`` parameter, it now expects +an (also optional) ``filepath`` parameter, which specifies the absolute +path to the template. You probably aren't using those constructors +directly, anyway, but using the ``TemplateLoader`` API instead. + ------------------------------------ Upgrading from Genshi 0.3.x to 0.4.x diff --git a/genshi/template/base.py b/genshi/template/base.py --- a/genshi/template/base.py +++ b/genshi/template/base.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2006-2007 Edgewall Software +# Copyright (C) 2006-2008 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which @@ -338,19 +338,16 @@ serializer = None _number_conv = unicode # function used to convert numbers to event data - def __init__(self, source, basedir=None, filename=None, loader=None, + def __init__(self, source, filepath=None, filename=None, loader=None, encoding=None, lookup='strict', allow_exec=True): """Initialize a template from either a string, a file-like object, or an already parsed markup stream. :param source: a string, file-like object, or markup stream to read the template from - :param basedir: the base directory containing the template file; when - loaded from a `TemplateLoader`, this will be the - directory on the template search path in which the - template was found - :param filename: the name of the template file, relative to the given - base directory + :param filepath: the absolute path to the template file + :param filename: the path to the template file relative to the search + path :param loader: the `TemplateLoader` to use for loading included templates :param encoding: the encoding of the `source` @@ -361,12 +358,8 @@ :note: Changed in 0.5: Added the `allow_exec` argument """ - self.basedir = basedir + self.filepath = filepath or filename self.filename = filename - if basedir and filename: - self.filepath = os.path.join(basedir, filename) - else: - self.filepath = filename self.loader = loader self.lookup = lookup self.allow_exec = allow_exec diff --git a/genshi/template/interpolation.py b/genshi/template/interpolation.py --- a/genshi/template/interpolation.py +++ b/genshi/template/interpolation.py @@ -30,8 +30,7 @@ NAMECHARS = NAMESTART + '.0123456789' PREFIX = '$' -def interpolate(text, basedir=None, filename=None, lineno=-1, offset=0, - lookup='strict'): +def interpolate(text, filepath=None, lineno=-1, offset=0, lookup='strict'): """Parse the given string and extract expressions. This function is a generator that yields `TEXT` events for literal strings, @@ -45,9 +44,8 @@ TEXT u'bar' :param text: the text to parse - :param basedir: base directory of the file in which the text was found - (optional) - :param filename: basename of the file in which the text was found (optional) + :param filepath: absolute path to the file in which the text was found + (optional) :param lineno: the line number at which the text was found (optional) :param offset: the column number at which the text starts in the source (optional) @@ -57,9 +55,6 @@ :raise TemplateSyntaxError: when a syntax error in an expression is encountered """ - filepath = filename - if filepath and basedir: - filepath = os.path.join(basedir, filepath) pos = [filepath, lineno, offset] textbuf = [] diff --git a/genshi/template/loader.py b/genshi/template/loader.py --- a/genshi/template/loader.py +++ b/genshi/template/loader.py @@ -127,7 +127,7 @@ raise TypeError('The "callback" parameter needs to be callable') self.callback = callback self._cache = LRUCache(max_cache_size) - self._mtime = {} + self._uptodate = {} self._lock = threading.RLock() def load(self, filename, relative_to=None, cls=None, encoding=None): @@ -175,8 +175,8 @@ tmpl = self._cache[cachekey] if not self.auto_reload: return tmpl - mtime = self._mtime[cachekey] - if mtime and mtime == os.path.getmtime(tmpl.filepath): + uptodate = self._uptodate[cachekey] + if uptodate is not None and uptodate(): return tmpl except KeyError, OSError: pass @@ -205,7 +205,7 @@ if isinstance(loadfunc, basestring): loadfunc = directory(loadfunc) try: - dirname, filename, fileobj, mtime = loadfunc(filename) + filepath, filename, fileobj, uptodate = loadfunc(filename) except IOError: continue else: @@ -216,14 +216,13 @@ # included template gets an absolute path, too, # so that nested includes work properly without a # search path - filename = os.path.join(dirname, filename) - dirname = '' - tmpl = self.instantiate(cls, fileobj, dirname, - filename, encoding=encoding) + filename = filepath + tmpl = self._instantiate(cls, fileobj, filepath, + filename, encoding=encoding) if self.callback: self.callback(tmpl) self._cache[cachekey] = tmpl - self._mtime[cachekey] = mtime + self._uptodate[cachekey] = uptodate finally: if hasattr(fileobj, 'close'): fileobj.close() @@ -234,7 +233,7 @@ finally: self._lock.release() - def instantiate(self, cls, fileobj, dirname, filename, encoding=None): + def _instantiate(self, cls, fileobj, filepath, filename, encoding=None): """Instantiate and return the `Template` object based on the given class and parameters. @@ -245,10 +244,9 @@ :param cls: the class of the template object to instantiate :param fileobj: a readable file-like object containing the template source - :param dirname: the name of the base directory containing the template - file - :param filename: the name of the template file, relative to the given - base directory + :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 @@ -256,7 +254,7 @@ """ if encoding is None: encoding = self.default_encoding - return cls(fileobj, basedir=dirname, filename=filename, loader=self, + return cls(fileobj, filepath=filepath, filename=filename, loader=self, encoding=encoding, lookup=self.variable_lookup, allow_exec=self.allow_exec) @@ -270,7 +268,10 @@ def _load_from_directory(filename): filepath = os.path.join(path, filename) fileobj = open(filepath, 'U') - return path, filename, fileobj, os.path.getmtime(filepath) + mtime = os.path.getmtime(filepath) + def _uptodate(): + return mtime == os.path.getmtime(filepath) + return filepath, filename, fileobj, _uptodate return _load_from_directory directory = staticmethod(directory) @@ -285,7 +286,7 @@ from pkg_resources import resource_stream def _load_from_package(filename): filepath = os.path.join(path, filename) - return path, filename, resource_stream(name, filepath), None + return filepath, filename, resource_stream(name, filepath), None return _load_from_package package = staticmethod(package) @@ -301,10 +302,10 @@ ... app2 = lambda filename: ('app2', filename, None, None) ... ) >>> print load('app1/foo.html') - ('', 'app1/foo.html', None, None) + ('app1', 'app1/foo.html', None, None) >>> print load('app2/bar.html') - ('', 'app2/bar.html', None, None) - + ('app2', 'app2/bar.html', None, None) + :param delegates: mapping of path prefixes to loader functions :return: the loader function :rtype: ``function`` @@ -314,11 +315,10 @@ if filename.startswith(prefix): if isinstance(delegate, basestring): delegate = directory(delegate) - path, _, fileobj, mtime = delegate( + filepath, _, fileobj, uptodate = delegate( filename[len(prefix):].lstrip('/\\') ) - dirname = path[len(prefix):].rstrip('/\\') - return dirname, filename, fileobj, mtime + return filepath, filename, fileobj, uptodate raise TemplateNotFound(filename, delegates.keys()) return _dispatch_by_prefix prefixed = staticmethod(prefixed) diff --git a/genshi/template/markup.py b/genshi/template/markup.py --- a/genshi/template/markup.py +++ b/genshi/template/markup.py @@ -60,9 +60,9 @@ serializer = 'xml' _number_conv = Markup - def __init__(self, source, basedir=None, filename=None, loader=None, + def __init__(self, source, filepath=None, filename=None, loader=None, encoding=None, lookup='strict', allow_exec=True): - Template.__init__(self, source, basedir=basedir, filename=filename, + Template.__init__(self, source, filepath=filepath, filename=filename, loader=loader, encoding=encoding, lookup=lookup, allow_exec=allow_exec) # Make sure the include filter comes after the match filter @@ -127,8 +127,8 @@ directives.append((cls, value, ns_prefix.copy(), pos)) else: if value: - value = list(interpolate(value, self.basedir, - pos[0], pos[1], pos[2], + value = list(interpolate(value, self.filepath, + pos[1], pos[2], lookup=self.lookup)) if len(value) == 1 and value[0][0] is TEXT: value = value[0][1] @@ -210,9 +210,8 @@ stream.append((EXEC, suite, pos)) elif kind is TEXT: - for kind, data, pos in interpolate(data, self.basedir, pos[0], - pos[1], pos[2], - lookup=self.lookup): + for kind, data, pos in interpolate(data, self.filepath, pos[1], + pos[2], lookup=self.lookup): stream.append((kind, data, pos)) elif kind is COMMENT: diff --git a/genshi/template/tests/interpolation.py b/genshi/template/tests/interpolation.py --- a/genshi/template/tests/interpolation.py +++ b/genshi/template/tests/interpolation.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007 Edgewall Software +# Copyright (C) 2007-2008 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which diff --git a/genshi/template/tests/loader.py b/genshi/template/tests/loader.py --- a/genshi/template/tests/loader.py +++ b/genshi/template/tests/loader.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2006 Edgewall Software +# Copyright (C) 2006-2008 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which @@ -346,8 +346,8 @@ file3.close() loader = TemplateLoader([dir1, TemplateLoader.prefixed( - sub1 = os.path.join(dir2), - sub2 = os.path.join(dir3) + sub1 = dir2, + sub2 = dir3 )]) tmpl = loader.load('sub1/tmpl1.html') self.assertEqual(""" diff --git a/genshi/template/tests/markup.py b/genshi/template/tests/markup.py --- a/genshi/template/tests/markup.py +++ b/genshi/template/tests/markup.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2006-2007 Edgewall Software +# Copyright (C) 2006-2008 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which diff --git a/genshi/template/tests/text.py b/genshi/template/tests/text.py --- a/genshi/template/tests/text.py +++ b/genshi/template/tests/text.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2006 Edgewall Software +# Copyright (C) 2006-2008 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which diff --git a/genshi/template/text.py b/genshi/template/text.py --- a/genshi/template/text.py +++ b/genshi/template/text.py @@ -130,11 +130,11 @@ _DIRECTIVE_RE = r'((? offset: text = _escape_sub(_escape_repl, source[offset:start]) - for kind, data, pos in interpolate(text, self.basedir, - self.filename, lineno, + for kind, data, pos in interpolate(text, self.filepath, lineno, lookup=self.lookup): stream.append((kind, data, pos)) lineno += len(text.splitlines()) @@ -189,8 +188,8 @@ if command == 'include': pos = (self.filename, lineno, 0) - value = list(interpolate(value, self.basedir, self.filename, - lineno, 0, lookup=self.lookup)) + value = list(interpolate(value, self.filepath, lineno, 0, + lookup=self.lookup)) if len(value) == 1 and value[0][0] is TEXT: value = value[0][1] stream.append((INCLUDE, (value, None, []), pos)) @@ -228,8 +227,7 @@ if offset < len(source): text = _escape_sub(_escape_repl, source[offset:]) - for kind, data, pos in interpolate(text, self.basedir, - self.filename, lineno, + for kind, data, pos in interpolate(text, self.filepath, lineno, lookup=self.lookup): stream.append((kind, data, pos)) @@ -290,8 +288,7 @@ start, end = mo.span() if start > offset: text = source[offset:start] - for kind, data, pos in interpolate(text, self.basedir, - self.filename, lineno, + for kind, data, pos in interpolate(text, self.filepath, lineno, lookup=self.lookup): stream.append((kind, data, pos)) lineno += len(text.splitlines()) @@ -326,8 +323,7 @@ if offset < len(source): text = source[offset:].replace('\\#', '#') - for kind, data, pos in interpolate(text, self.basedir, - self.filename, lineno, + for kind, data, pos in interpolate(text, self.filepath, lineno, lookup=self.lookup): stream.append((kind, data, pos))