# HG changeset patch # User Ludovic Chabant # Date 1436243560 25200 # Node ID 96d363e2da4bc80bf5335669af03fda656d579ff # Parent 8351a77e13f53da76cc397de31e6163d0c938467 templating: Let Jinja2 cache the parsed template for page contents. diff -r 8351a77e13f5 -r 96d363e2da4b piecrust/page.py --- a/piecrust/page.py Tue Jul 07 20:19:54 2015 -0700 +++ b/piecrust/page.py Mon Jul 06 21:32:40 2015 -0700 @@ -199,19 +199,18 @@ class ContentSegment(object): debug_render_func = 'debug_render' - def __init__(self, content=None, fmt=None): + def __init__(self): self.parts = [] - if content is not None: - self.parts.append(ContentSegmentPart(content, fmt)) def debug_render(self): return '\n'.join([p.content for p in self.parts]) class ContentSegmentPart(object): - def __init__(self, content, fmt=None, line=-1): + def __init__(self, content, fmt=None, offset=-1, line=-1): self.content = content self.fmt = fmt + self.offset = offset self.line = line def __str__(self): @@ -223,7 +222,8 @@ for key, seg_data in data.items(): seg = ContentSegment() for p_data in seg_data: - part = ContentSegmentPart(p_data['c'], p_data['f'], p_data['l']) + part = ContentSegmentPart(p_data['c'], p_data['f'], p_data['o'], + p_data['l']) seg.parts.append(part) segments[key] = seg return segments @@ -234,7 +234,8 @@ for key, seg in segments.items(): seg_data = [] for part in seg.parts: - p_data = {'c': part.content, 'f': part.fmt, 'l': part.line} + p_data = {'c': part.content, 'f': part.fmt, 'o': part.offset, + 'l': part.line} seg_data.append(p_data) data[key] = seg_data return data @@ -357,7 +358,8 @@ # First part, before the first format change. part_text = raw[start:matches[0].start()] parts.append( - ContentSegmentPart(part_text, first_part_fmt, line_offset)) + ContentSegmentPart(part_text, first_part_fmt, start, + line_offset)) line_offset += _count_lines(part_text) for i in range(1, num_matches): @@ -366,17 +368,20 @@ part_text = raw[m1.end() + 1:m2.start()] parts.append( ContentSegmentPart( - part_text, m1.group('fmt'), line_offset)) + part_text, m1.group('fmt'), m1.end() + 1, + line_offset)) line_offset += _count_lines(part_text) lastm = matches[-1] part_text = raw[lastm.end() + 1:end] parts.append(ContentSegmentPart( - part_text, lastm.group('fmt'), line_offset)) + part_text, lastm.group('fmt'), lastm.end() + 1, + line_offset)) return parts, line_offset else: part_text = raw[start:end] - parts = [ContentSegmentPart(part_text, first_part_fmt, line_offset)] + parts = [ContentSegmentPart(part_text, first_part_fmt, start, + line_offset)] return parts, line_offset diff -r 8351a77e13f5 -r 96d363e2da4b piecrust/rendering.py --- a/piecrust/rendering.py Tue Jul 07 20:19:54 2015 -0700 +++ b/piecrust/rendering.py Mon Jul 06 21:32:40 2015 -0700 @@ -310,9 +310,8 @@ part_format = seg_part.fmt or format_name try: with app.env.timerScope(engine.__class__.__name__): - part_text = engine.renderString( - seg_part.content, page_data, - filename=page.path) + part_text = engine.renderSegmentPart( + page.path, seg_part, page_data) except TemplatingError as err: err.lineno += seg_part.line raise err diff -r 8351a77e13f5 -r 96d363e2da4b piecrust/templating/base.py --- a/piecrust/templating/base.py Tue Jul 07 20:19:54 2015 -0700 +++ b/piecrust/templating/base.py Mon Jul 06 21:32:40 2015 -0700 @@ -27,7 +27,7 @@ def initialize(self, app): self.app = app - def renderString(self, txt, data, filename=None, line_offset=0): + def renderSegmentPart(self, path, seg_part, data): raise NotImplementedError() def renderFile(self, paths, data): diff -r 8351a77e13f5 -r 96d363e2da4b piecrust/templating/jinjaengine.py --- 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 diff -r 8351a77e13f5 -r 96d363e2da4b piecrust/templating/pystacheengine.py --- a/piecrust/templating/pystacheengine.py Tue Jul 07 20:19:54 2015 -0700 +++ b/piecrust/templating/pystacheengine.py Mon Jul 06 21:32:40 2015 -0700 @@ -16,14 +16,14 @@ def __init__(self): self.renderer = None - def renderString(self, txt, data, filename=None): + def renderSegmentPart(self, path, seg_part, data): self._ensureLoaded() try: - return self.renderer.render(txt, data) + return self.renderer.render(seg_part.content, data) except pystache.common.TemplateNotFoundError as ex: raise TemplateNotFoundError() from ex except pystache.common.PystacheError as ex: - raise TemplatingError(str(ex), filename) from ex + raise TemplatingError(str(ex), path) from ex def renderFile(self, paths, data): self._ensureLoaded()