diff piecrust/templating/jinjaengine.py @ 454:96d363e2da4b

templating: Let Jinja2 cache the parsed template for page contents.
author Ludovic Chabant <ludovic@chabant.com>
date Mon, 06 Jul 2015 21:32:40 -0700
parents 4cdf6c2157a0
children c40b7923c474
line wrap: on
line diff
--- a/piecrust/templating/jinjaengine.py	Tue Jul 07 20:19:54 2015 -0700
+++ b/piecrust/templating/jinjaengine.py	Mon Jul 06 21:32:40 2015 -0700
@@ -33,25 +33,19 @@
     def __init__(self):
         self.env = None
 
-    def renderString(self, txt, data, filename=None):
+    def renderSegmentPart(self, path, seg_part, data):
         self._ensureLoaded()
 
-        do_render = False
-        index = txt.find('{')
-        while index >= 0:
-            ch = txt[index + 1]
-            if ch == '{' or ch == '%':
-                do_render = True
-                break
-            index = txt.find('{', index + 1)
+        if not _string_needs_render(seg_part.content):
+            return seg_part.content
 
-        if not do_render:
-            return txt
-
+        part_path = _make_segment_part_path(path, seg_part.offset)
+        self.env.loader.segment_parts_cache[part_path] = (
+                path, seg_part.content)
         try:
-            tpl = self.env.from_string(txt)
+            tpl = self.env.get_template(part_path)
         except TemplateSyntaxError as tse:
-            raise self._getTemplatingError(tse, filename=filename)
+            raise self._getTemplatingError(tse, filename=path)
         except TemplateNotFound:
             raise TemplateNotFoundError()
 
@@ -99,8 +93,8 @@
             autoescape = True
 
         logger.debug("Creating Jinja environment with folders: %s" %
-                self.app.templates_dirs)
-        loader = FileSystemLoader(self.app.templates_dirs)
+                     self.app.templates_dirs)
+        loader = PieCrustLoader(self.app.templates_dirs)
         extensions = [
                 PieCrustHighlightExtension,
                 PieCrustCacheExtension,
@@ -114,6 +108,42 @@
                 extensions=extensions)
 
 
+def _string_needs_render(txt):
+    index = txt.find('{')
+    while index >= 0:
+        ch = txt[index + 1]
+        if ch == '{' or ch == '%':
+            return True
+        index = txt.find('{', index + 1)
+    return False
+
+
+def _make_segment_part_path(path, start):
+    return '$part=%s:%d' % (path, start)
+
+
+class PieCrustLoader(FileSystemLoader):
+    def __init__(self, searchpath, encoding='utf-8'):
+        super(PieCrustLoader, self).__init__(searchpath, encoding)
+        self.segment_parts_cache = {}
+
+    def get_source(self, environment, template):
+        if template.startswith('$part='):
+            filename, seg_part = self.segment_parts_cache[template]
+
+            mtime = os.path.getmtime(filename)
+
+            def uptodate():
+                try:
+                    return os.path.getmtime(filename) == mtime
+                except OSError:
+                    return False
+
+            return seg_part, filename, uptodate
+
+        return super(PieCrustLoader, self).get_source(environment, template)
+
+
 class PieCrustEnvironment(Environment):
     def __init__(self, app, *args, **kwargs):
         self.app = app
@@ -313,7 +343,8 @@
         args = [parser.parse_expression()]
 
         # Extract optional arguments.
-        kwarg_names = {'line_numbers': 0, 'use_classes': 0, 'class': 1, 'id': 1}
+        kwarg_names = {'line_numbers': 0, 'use_classes': 0, 'class': 1,
+                       'id': 1}
         kwargs = {}
         while not parser.stream.current.test('block_end'):
             name = parser.stream.expect('name')
@@ -333,7 +364,7 @@
                          [], [], body).set_lineno(lineno)
 
     def _highlight(self, lang, line_numbers=False, use_classes=False,
-            css_class=None, css_id=None, caller=None):
+                   css_class=None, css_id=None, caller=None):
         # Try to be mostly compatible with Jinja2-highlight's settings.
         body = caller()
 
@@ -387,12 +418,12 @@
         # now we parse the body of the cache block up to `endpccache` and
         # drop the needle (which would always be `endpccache` in that case)
         body = parser.parse_statements(['name:endpccache', 'name:endcache'],
-                drop_needle=True)
+                                       drop_needle=True)
 
         # now return a `CallBlock` node that calls our _cache_support
         # helper method on this extension.
         return CallBlock(self.call_method('_cache_support', args),
-                               [], [], body).set_lineno(lineno)
+                         [], [], body).set_lineno(lineno)
 
     def _cache_support(self, name, caller):
         key = self.environment.piecrust_cache_prefix + name