changeset 714:fc6d9d2a3527 trunk

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.
author cmlenz
date Tue, 08 Apr 2008 19:49:39 +0000
parents 5420fe9d99a9
children b5bd8c109209
files ChangeLog doc/upgrade.txt genshi/template/base.py genshi/template/interpolation.py genshi/template/loader.py genshi/template/markup.py genshi/template/tests/interpolation.py genshi/template/tests/loader.py genshi/template/tests/markup.py genshi/template/tests/text.py genshi/template/text.py
diffstat 11 files changed, 64 insertions(+), 70 deletions(-) [+]
line wrap: on
line diff
--- 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
--- a/doc/upgrade.txt
+++ b/doc/upgrade.txt
@@ -70,6 +70,15 @@
 
   Markup('<b>%s</b>') % 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
--- 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
--- 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 = []
--- 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)
--- 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:
--- 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
--- 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("""<html>
--- 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
--- 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
--- a/genshi/template/text.py
+++ b/genshi/template/text.py
@@ -130,11 +130,11 @@
     _DIRECTIVE_RE = r'((?<!\\)%s\s*(\w+)\s*(.*?)\s*%s|(?<!\\)%s.*?%s)'
     _ESCAPE_RE = r'\\\n|\\(\\)|\\(%s)|\\(%s)'
 
-    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=False,
                  delims=('{%', '%}', '{#', '#}')):
         self.delimiters = delims
-        Template.__init__(self, source, basedir=basedir, filename=filename,
+        Template.__init__(self, source, filepath=filepath, filename=filename,
                           loader=loader, encoding=encoding, lookup=lookup)
 
     def _get_delims(self):
@@ -178,8 +178,7 @@
             start, end = mo.span(1)
             if start > 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))
 
Copyright (C) 2012-2017 Edgewall Software