Mercurial > piecrust2
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 1011:c4cf3cfe2726 | 1012:576f7ebcd9c0 |
|---|---|
| 1 import io | |
| 2 import os.path | |
| 3 import time | |
| 4 import logging | |
| 5 from inukshuk.parser import ParserError | |
| 6 from piecrust.templating.base import ( | |
| 7 TemplateEngine, TemplatingError, TemplateNotFoundError) | |
| 8 | |
| 9 | |
| 10 logger = logging.getLogger(__name__) | |
| 11 | |
| 12 _profile = True | |
| 13 | |
| 14 | |
| 15 class InukshukTemplateEngine(TemplateEngine): | |
| 16 ENGINE_NAMES = ['inukshuk', 'inuk'] | |
| 17 EXTENSIONS = ['html', 'inuk'] | |
| 18 | |
| 19 def __init__(self): | |
| 20 self.engine = None | |
| 21 self.pc_cache = {} | |
| 22 self._seg_loader = None | |
| 23 self._buf = io.StringIO() | |
| 24 self._buf.truncate(2048) | |
| 25 if _profile: | |
| 26 self._renderTemplate = self._renderTemplateProf | |
| 27 else: | |
| 28 self._renderTemplate = self._renderTemplateNoProf | |
| 29 | |
| 30 def populateCache(self): | |
| 31 self._ensureLoaded() | |
| 32 | |
| 33 used_names = set() | |
| 34 | |
| 35 def _filter_names(name): | |
| 36 if name in used_names: | |
| 37 return False | |
| 38 used_names.add(name) | |
| 39 return True | |
| 40 | |
| 41 self.engine.cacheAllTemplates(cache_condition=_filter_names) | |
| 42 | |
| 43 def renderSegment(self, path, segment, data): | |
| 44 if not _string_needs_render(segment.content): | |
| 45 return segment.content, False | |
| 46 | |
| 47 self._ensureLoaded() | |
| 48 | |
| 49 tpl_name = os.path.relpath(path, self.app.root_dir) | |
| 50 try: | |
| 51 self._seg_loader.templates[tpl_name] = segment.content | |
| 52 tpl = self.engine.getTemplate(tpl_name, memmodule=True) | |
| 53 return self._renderTemplate(tpl, data), True | |
| 54 except ParserError as pe: | |
| 55 raise TemplatingError(pe.message, path, pe.line_num) | |
| 56 | |
| 57 def renderFile(self, paths, data): | |
| 58 self._ensureLoaded() | |
| 59 | |
| 60 tpl = None | |
| 61 rendered_path = None | |
| 62 for p in paths: | |
| 63 try: | |
| 64 tpl = self.engine.getTemplate(p) | |
| 65 rendered_path = p | |
| 66 break | |
| 67 except Exception: | |
| 68 pass | |
| 69 | |
| 70 if tpl is None: | |
| 71 raise TemplateNotFoundError() | |
| 72 | |
| 73 try: | |
| 74 return self._renderTemplate(tpl, data) | |
| 75 except ParserError as pe: | |
| 76 raise TemplatingError(pe.message, rendered_path, pe.line_num) | |
| 77 | |
| 78 def _renderTemplateNoProf(self, tpl, data): | |
| 79 return tpl.render(data) | |
| 80 | |
| 81 def _renderTemplateIntoNoProf(self, tpl, data): | |
| 82 buf = self._buf | |
| 83 buf.seek(0) | |
| 84 tpl.renderInto(data, buf) | |
| 85 buf.flush() | |
| 86 size = buf.tell() | |
| 87 buf.seek(0) | |
| 88 return buf.read(size) | |
| 89 | |
| 90 def _renderTemplateProf(self, tpl, data): | |
| 91 stats = self.app.env.stats | |
| 92 | |
| 93 # Code copied from Inukshuk, but with an `out_write` method that | |
| 94 # wraps a timer scope. | |
| 95 out = [] | |
| 96 | |
| 97 def out_write(s): | |
| 98 start = time.perf_counter() | |
| 99 out.append(s) | |
| 100 stats.stepTimerSince('Inukshuk_outWrite', start) | |
| 101 | |
| 102 tpl._renderWithContext(None, data, out_write) | |
| 103 return ''.join(out) | |
| 104 | |
| 105 def _ensureLoaded(self): | |
| 106 if self.engine is not None: | |
| 107 return | |
| 108 | |
| 109 from inukshuk.engine import Engine | |
| 110 from inukshuk.loader import ( | |
| 111 StringsLoader, FileSystemLoader, CompositeLoader) | |
| 112 from ._inukshukext import PieCrustExtension | |
| 113 | |
| 114 self._seg_loader = StringsLoader() | |
| 115 loader = CompositeLoader([ | |
| 116 self._seg_loader, | |
| 117 FileSystemLoader(self.app.templates_dirs)]) | |
| 118 self.engine = Engine(loader) | |
| 119 self.engine.autoescape = True | |
| 120 self.engine.extensions.append(PieCrustExtension(self.app)) | |
| 121 self.engine.compile_templates = True | |
| 122 self.engine.compile_cache_dir = os.path.join( | |
| 123 self.app.cache_dir, 'inuk') | |
| 124 | |
| 125 if _profile: | |
| 126 # If we're profiling, monkeypatch all the appropriate methods | |
| 127 # from the Inukshuk API. | |
| 128 stats = self.app.env.stats | |
| 129 | |
| 130 import inukshuk.rendering | |
| 131 | |
| 132 afe = inukshuk.rendering._attr_first_access | |
| 133 | |
| 134 def wafe(ctx, data, prop_name): | |
| 135 with stats.timerScope('Inukshuk_query'): | |
| 136 return afe(ctx, data, prop_name) | |
| 137 | |
| 138 inukshuk.rendering._attr_first_access = wafe | |
| 139 | |
| 140 afer = inukshuk.rendering._attr_first_access_root | |
| 141 | |
| 142 def wafer(ctx, ctx_locals, data, ctx_globals, prop_name): | |
| 143 with stats.timerScope('Inukshuk_query'): | |
| 144 return afer(ctx, ctx_locals, data, ctx_globals, prop_name) | |
| 145 | |
| 146 inukshuk.rendering._attr_first_access_root = wafer | |
| 147 | |
| 148 i = inukshuk.rendering.RenderContext.invoke | |
| 149 | |
| 150 def wi(ctx, data, out, data_func, *args, **kwargs): | |
| 151 with stats.timerScope('Inukshuk_invoke'): | |
| 152 return i(ctx, data, out, data_func, *args, **kwargs) | |
| 153 | |
| 154 inukshuk.rendering.RenderContext.invoke = wi | |
| 155 | |
| 156 import inukshuk.template | |
| 157 | |
| 158 cc = inukshuk.template.Template._compileContent | |
| 159 | |
| 160 def wcc(tpl, force_compiled=False): | |
| 161 with stats.timerScope('Inukshuk_templateCompileContent'): | |
| 162 return cc(tpl, force_compiled) | |
| 163 | |
| 164 inukshuk.template.Template._compileContent = wcc | |
| 165 | |
| 166 dr = inukshuk.template.Template._doRender | |
| 167 | |
| 168 def wdr(tpl, ctx, data, out): | |
| 169 with stats.timerScope('Inukshuk_templateDoRender'): | |
| 170 return dr(tpl, ctx, data, out) | |
| 171 | |
| 172 inukshuk.template.Template._doRender = wdr | |
| 173 | |
| 174 stats.registerTimer('Inukshuk_query') | |
| 175 stats.registerTimer('Inukshuk_invoke') | |
| 176 stats.registerTimer('Inukshuk_templateDoRender') | |
| 177 stats.registerTimer('Inukshuk_templateCompileContent') | |
| 178 stats.registerTimer('Inukshuk_outWrite') | |
| 179 | |
| 180 try: | |
| 181 os.makedirs(self.engine.compile_cache_dir) | |
| 182 except OSError: | |
| 183 pass | |
| 184 | |
| 185 | |
| 186 def _string_needs_render(txt): | |
| 187 index = txt.find('{') | |
| 188 while index >= 0: | |
| 189 ch = txt[index + 1] | |
| 190 if ch == '{' or ch == '%': | |
| 191 return True | |
| 192 index = txt.find('{', index + 1) | |
| 193 return False |
