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