changeset 548:1cc1afc39176 trunk

Implement static includes, which improves performance a bit when auto reloading is disabled.
author cmlenz
date Fri, 29 Jun 2007 13:06:53 +0000
parents 93b2a5792cfc
children 7214c1bdb383
files ChangeLog doc/plugin.txt genshi/template/base.py genshi/template/loader.py genshi/template/tests/markup.py
diffstat 5 files changed, 94 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -13,6 +13,10 @@
    which allow matching against regular expressions.
  * Support for Python code blocks in templates can now be disabled
    (ticket #123).
+ * Includes are now processed when the template is parsed if possible, but
+   only if the template loader is not set to do automatic reloading. Included
+   templates are basically inlined into the including template, which can
+   speed up rendering of that template a bit.
 
 
 Version 0.4.3
--- a/doc/plugin.txt
+++ b/doc/plugin.txt
@@ -115,10 +115,10 @@
 files, and automatically reload them if they have been changed. Specify "yes"
 to enable this reloading (which is the default), or "no" to turn it off.
 
-.. note:: You may want to disable reloading in a production environment to gain
-          a slight (and possible even negligible) improvement in loading
-          performance, but then you'll have to manually restart the server
-          process anytime the templates are updated.
+You probably want to disable reloading in a production environment to improve
+performance of both templating loading and the processing of includes. But
+remember that you'll then have to manually restart the server process anytime
+the templates are updated.
 
 ``genshi.default_doctype``
 --------------------------
--- a/genshi/template/base.py
+++ b/genshi/template/base.py
@@ -354,6 +354,8 @@
         
         :param stream: the event stream of the template
         """
+        from genshi.template.loader import TemplateNotFound
+
         for kind, data, pos in stream:
             if kind is SUB:
                 directives = []
@@ -371,7 +373,27 @@
                         yield event
             else:
                 if kind is INCLUDE:
-                    data = data[0], list(self._prepare(data[1]))
+                    href, fallback = data
+                    if isinstance(href, basestring) and \
+                            not getattr(self.loader, 'auto_reload', True):
+                        # If the path to the included template is static, and
+                        # auto-reloading is disabled on the template loader,
+                        # the template is inlined into the stream
+                        try:
+                            tmpl = self.loader.load(href, relative_to=pos[0],
+                                                    cls=self.__class__)
+                            for event in tmpl.stream:
+                                yield event
+                        except TemplateNotFound:
+                            if fallback is None:
+                                raise
+                            for event in self._prepare(fallback):
+                                yield event
+                        continue
+                    else:
+                        # Otherwise the include is performed at run time
+                        data = href, list(self._prepare(fallback))
+
                 yield kind, data, pos
 
     def generate(self, *args, **kwargs):
--- a/genshi/template/loader.py
+++ b/genshi/template/loader.py
@@ -69,6 +69,10 @@
     >>> 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,
@@ -107,7 +111,11 @@
             self.search_path = []
         elif isinstance(self.search_path, basestring):
             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
@@ -117,7 +125,7 @@
         self.callback = callback
         self._cache = LRUCache(max_cache_size)
         self._mtime = {}
-        self._lock = threading.Lock()
+        self._lock = threading.RLock()
 
     def load(self, filename, relative_to=None, cls=None, encoding=None):
         """Load the template with the given name.
--- a/genshi/template/tests/markup.py
+++ b/genshi/template/tests/markup.py
@@ -449,6 +449,60 @@
         finally:
             shutil.rmtree(dirname)
 
+    def test_include_inlined(self):
+        dirname = tempfile.mkdtemp(suffix='genshi_test')
+        try:
+            file1 = open(os.path.join(dirname, 'tmpl1.html'), 'w')
+            try:
+                file1.write("""<div>Included</div>""")
+            finally:
+                file1.close()
+
+            file2 = open(os.path.join(dirname, 'tmpl2.html'), 'w')
+            try:
+                file2.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude"
+                                     xmlns:py="http://genshi.edgewall.org/">
+                  <xi:include href="tmpl1.html" />
+                </html>""")
+            finally:
+                file2.close()
+
+            loader = TemplateLoader([dirname], auto_reload=False)
+            tmpl = loader.load('tmpl2.html')
+            # if not inlined the following would be 5
+            self.assertEqual(7, len(tmpl.stream))
+            self.assertEqual("""<html>
+                  <div>Included</div>
+                </html>""", tmpl.generate().render())
+        finally:
+            shutil.rmtree(dirname)
+
+    def test_include_inlined_in_loop(self):
+        dirname = tempfile.mkdtemp(suffix='genshi_test')
+        try:
+            file1 = open(os.path.join(dirname, 'tmpl1.html'), 'w')
+            try:
+                file1.write("""<div>Included $idx</div>""")
+            finally:
+                file1.close()
+
+            file2 = open(os.path.join(dirname, 'tmpl2.html'), 'w')
+            try:
+                file2.write("""<html xmlns:xi="http://www.w3.org/2001/XInclude"
+                                     xmlns:py="http://genshi.edgewall.org/">
+                  <xi:include href="tmpl1.html" py:for="idx in range(3)" />
+                </html>""")
+            finally:
+                file2.close()
+
+            loader = TemplateLoader([dirname], auto_reload=False)
+            tmpl = loader.load('tmpl2.html')
+            self.assertEqual("""<html>
+                  <div>Included 0</div><div>Included 1</div><div>Included 2</div>
+                </html>""", tmpl.generate().render())
+        finally:
+            shutil.rmtree(dirname)
+
     def test_allow_exec_false(self): 
         xml = ("""<?python
           title = "A Genshi Template"
Copyright (C) 2012-2017 Edgewall Software