Mercurial > piecrust2
diff piecrust/templating/inukshukengine.py @ 1012:576f7ebcd9c0
templating: Add Inukshuk template engine.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Mon, 27 Nov 2017 23:11:15 -0800 |
parents | |
children | 6370ab74b2d5 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/piecrust/templating/inukshukengine.py Mon Nov 27 23:11:15 2017 -0800 @@ -0,0 +1,193 @@ +import io +import os.path +import time +import logging +from inukshuk.parser import ParserError +from piecrust.templating.base import ( + TemplateEngine, TemplatingError, TemplateNotFoundError) + + +logger = logging.getLogger(__name__) + +_profile = True + + +class InukshukTemplateEngine(TemplateEngine): + ENGINE_NAMES = ['inukshuk', 'inuk'] + EXTENSIONS = ['html', 'inuk'] + + def __init__(self): + self.engine = None + self.pc_cache = {} + self._seg_loader = None + self._buf = io.StringIO() + self._buf.truncate(2048) + if _profile: + self._renderTemplate = self._renderTemplateProf + else: + self._renderTemplate = self._renderTemplateNoProf + + def populateCache(self): + self._ensureLoaded() + + used_names = set() + + def _filter_names(name): + if name in used_names: + return False + used_names.add(name) + return True + + self.engine.cacheAllTemplates(cache_condition=_filter_names) + + def renderSegment(self, path, segment, data): + if not _string_needs_render(segment.content): + return segment.content, False + + self._ensureLoaded() + + tpl_name = os.path.relpath(path, self.app.root_dir) + try: + self._seg_loader.templates[tpl_name] = segment.content + tpl = self.engine.getTemplate(tpl_name, memmodule=True) + return self._renderTemplate(tpl, data), True + except ParserError as pe: + raise TemplatingError(pe.message, path, pe.line_num) + + def renderFile(self, paths, data): + self._ensureLoaded() + + tpl = None + rendered_path = None + for p in paths: + try: + tpl = self.engine.getTemplate(p) + rendered_path = p + break + except Exception: + pass + + if tpl is None: + raise TemplateNotFoundError() + + try: + return self._renderTemplate(tpl, data) + except ParserError as pe: + raise TemplatingError(pe.message, rendered_path, pe.line_num) + + def _renderTemplateNoProf(self, tpl, data): + return tpl.render(data) + + def _renderTemplateIntoNoProf(self, tpl, data): + buf = self._buf + buf.seek(0) + tpl.renderInto(data, buf) + buf.flush() + size = buf.tell() + buf.seek(0) + return buf.read(size) + + def _renderTemplateProf(self, tpl, data): + stats = self.app.env.stats + + # Code copied from Inukshuk, but with an `out_write` method that + # wraps a timer scope. + out = [] + + def out_write(s): + start = time.perf_counter() + out.append(s) + stats.stepTimerSince('Inukshuk_outWrite', start) + + tpl._renderWithContext(None, data, out_write) + return ''.join(out) + + def _ensureLoaded(self): + if self.engine is not None: + return + + from inukshuk.engine import Engine + from inukshuk.loader import ( + StringsLoader, FileSystemLoader, CompositeLoader) + from ._inukshukext import PieCrustExtension + + self._seg_loader = StringsLoader() + loader = CompositeLoader([ + self._seg_loader, + FileSystemLoader(self.app.templates_dirs)]) + self.engine = Engine(loader) + self.engine.autoescape = True + self.engine.extensions.append(PieCrustExtension(self.app)) + self.engine.compile_templates = True + self.engine.compile_cache_dir = os.path.join( + self.app.cache_dir, 'inuk') + + if _profile: + # If we're profiling, monkeypatch all the appropriate methods + # from the Inukshuk API. + stats = self.app.env.stats + + import inukshuk.rendering + + afe = inukshuk.rendering._attr_first_access + + def wafe(ctx, data, prop_name): + with stats.timerScope('Inukshuk_query'): + return afe(ctx, data, prop_name) + + inukshuk.rendering._attr_first_access = wafe + + afer = inukshuk.rendering._attr_first_access_root + + def wafer(ctx, ctx_locals, data, ctx_globals, prop_name): + with stats.timerScope('Inukshuk_query'): + return afer(ctx, ctx_locals, data, ctx_globals, prop_name) + + inukshuk.rendering._attr_first_access_root = wafer + + i = inukshuk.rendering.RenderContext.invoke + + def wi(ctx, data, out, data_func, *args, **kwargs): + with stats.timerScope('Inukshuk_invoke'): + return i(ctx, data, out, data_func, *args, **kwargs) + + inukshuk.rendering.RenderContext.invoke = wi + + import inukshuk.template + + cc = inukshuk.template.Template._compileContent + + def wcc(tpl, force_compiled=False): + with stats.timerScope('Inukshuk_templateCompileContent'): + return cc(tpl, force_compiled) + + inukshuk.template.Template._compileContent = wcc + + dr = inukshuk.template.Template._doRender + + def wdr(tpl, ctx, data, out): + with stats.timerScope('Inukshuk_templateDoRender'): + return dr(tpl, ctx, data, out) + + inukshuk.template.Template._doRender = wdr + + stats.registerTimer('Inukshuk_query') + stats.registerTimer('Inukshuk_invoke') + stats.registerTimer('Inukshuk_templateDoRender') + stats.registerTimer('Inukshuk_templateCompileContent') + stats.registerTimer('Inukshuk_outWrite') + + try: + os.makedirs(self.engine.compile_cache_dir) + except OSError: + pass + + +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