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 |