Mercurial > piecrust2
comparison piecrust/templating/_inukshukext.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 | 10fd55b9ccfb |
comparison
equal
deleted
inserted
replaced
1011:c4cf3cfe2726 | 1012:576f7ebcd9c0 |
---|---|
1 import io | |
2 import re | |
3 import time | |
4 from inukshuk.ext import Extension, StatementNode | |
5 from inukshuk.ext.core import filter_make_xml_date, filter_safe | |
6 from inukshuk.lexer import ( | |
7 TOKEN_ID_STRING_SINGLE_QUOTES, TOKEN_ID_STRING_DOUBLE_QUOTES) | |
8 from pygments import highlight | |
9 from pygments.formatters import HtmlFormatter | |
10 from pygments.lexers import get_lexer_by_name, guess_lexer | |
11 from piecrust.data.paginator import Paginator | |
12 from piecrust.rendering import format_text | |
13 | |
14 | |
15 class PieCrustExtension(Extension): | |
16 def __init__(self, app): | |
17 self.app = app | |
18 | |
19 def setupEngine(self, engine): | |
20 engine.piecrust_app = self.app | |
21 engine.piecrust_cache = {} | |
22 | |
23 def getGlobals(self): | |
24 return { | |
25 'highlight_css': get_highlight_css} | |
26 | |
27 def getFilters(self): | |
28 return { | |
29 'paginate': self._paginate, | |
30 'formatwith': self._formatWith, | |
31 'markdown': lambda v: self._formatWith(v, 'markdown'), | |
32 'textile': lambda v: self._formatWith(v, 'textile'), | |
33 'nocache': add_no_cache_parameter, | |
34 'stripoutertag': strip_outer_tag, | |
35 'stripslash': strip_slash, | |
36 'atomdate': filter_make_xml_date, | |
37 'raw': filter_safe | |
38 } | |
39 | |
40 def getTests(self): | |
41 return {} | |
42 | |
43 def getStatementNodes(self): | |
44 return [ | |
45 PieCrustHighlightStatementNode, | |
46 PieCrustGeshiStatementNode, | |
47 PieCrustCacheStatementNode, | |
48 PieCrustFormatStatementNode] | |
49 | |
50 def _paginate(self, value, items_per_page=5): | |
51 ctx = self.app.env.render_ctx_stack.current_ctx | |
52 if ctx is None or ctx.page is None: | |
53 raise Exception("Can't paginate when no page has been pushed " | |
54 "on the execution stack.") | |
55 return Paginator(ctx.page, value, | |
56 sub_num=ctx.sub_num, | |
57 items_per_page=items_per_page) | |
58 | |
59 def _formatWith(self, value, format_name): | |
60 return format_text(self.app, format_name, value) | |
61 | |
62 | |
63 def add_no_cache_parameter(value, param_name='t', param_value=None): | |
64 if not param_value: | |
65 param_value = time.time() | |
66 if '?' in value: | |
67 value += '&' | |
68 else: | |
69 value += '?' | |
70 value += '%s=%s' % (param_name, param_value) | |
71 return value | |
72 | |
73 | |
74 def strip_outer_tag(value, tag=None): | |
75 tag_pattern = '[a-z]+[a-z0-9]*' | |
76 if tag is not None: | |
77 tag_pattern = re.escape(tag) | |
78 pat = r'^\<' + tag_pattern + r'\>(.*)\</' + tag_pattern + '>$' | |
79 m = re.match(pat, value) | |
80 if m: | |
81 return m.group(1) | |
82 return value | |
83 | |
84 | |
85 def strip_slash(value): | |
86 return value.rstrip('/') | |
87 | |
88 | |
89 class PieCrustFormatStatementNode(StatementNode): | |
90 name = 'pcformat' | |
91 compiler_imports = ['import io', | |
92 'from piecrust.rendering import format_text'] | |
93 | |
94 def __init__(self): | |
95 super().__init__() | |
96 self.format = None | |
97 | |
98 def parse(self, parser): | |
99 self.format = parser.expectIdentifier() | |
100 parser.skipWhitespace() | |
101 parser.expectStatementEnd() | |
102 parser.parseUntilStatement(self, ['endpcformat']) | |
103 parser.expectIdentifier('endpcformat') | |
104 | |
105 def render(self, ctx, data, out): | |
106 with io.StringIO() as tmp: | |
107 inner_out = tmp.write | |
108 for c in self.children: | |
109 c.render(ctx, data, inner_out) | |
110 | |
111 text = format_text(ctx.engine.piecrust_app, self.format, | |
112 tmp.getvalue(), exact_format=True) | |
113 out(text) | |
114 | |
115 def compile(self, ctx, out): | |
116 out.indent().write('with io.StringIO() as tmp:\n') | |
117 out.push(False) | |
118 out.indent().write('prev_out_write = out_write\n') | |
119 out.indent().write('out_write = tmp.write\n') | |
120 for c in self.children: | |
121 c.compile(ctx, out) | |
122 out.indent().write('out_write = prev_out_write\n') | |
123 out.indent().write( | |
124 'text = format_text(ctx_engine.piecrust_app, %s, tmp.getvalue(), ' | |
125 'exact_format=True)\n' % repr(self.format)) | |
126 out.indent().write('out_write(text)\n') | |
127 out.pull() | |
128 | |
129 | |
130 class PieCrustHighlightStatementNode(StatementNode): | |
131 name = 'highlight' | |
132 endname = 'endhighlight' | |
133 compiler_imports = [ | |
134 'from pygments import highlight', | |
135 'from pygments.formatters import HtmlFormatter', | |
136 'from pygments.lexers import get_lexer_by_name, guess_lexer'] | |
137 | |
138 def __init__(self): | |
139 super().__init__() | |
140 self.lang = None | |
141 | |
142 def parse(self, parser): | |
143 self.lang = parser.expectAny([TOKEN_ID_STRING_SINGLE_QUOTES, | |
144 TOKEN_ID_STRING_DOUBLE_QUOTES]) | |
145 parser.skipWhitespace() | |
146 parser.expectStatementEnd() | |
147 | |
148 parser.parseUntilStatement(self, self.endname) | |
149 parser.expectIdentifier(self.endname) | |
150 | |
151 def render(self, ctx, data, out): | |
152 with io.StringIO() as tmp: | |
153 inner_out = tmp.write | |
154 for c in self.children: | |
155 c.render(ctx, data, inner_out) | |
156 | |
157 raw_text = tmp.getvalue() | |
158 | |
159 if self.lang is None: | |
160 lexer = guess_lexer(raw_text) | |
161 else: | |
162 lexer = get_lexer_by_name(self.lang, stripall=False) | |
163 | |
164 formatter = HtmlFormatter() | |
165 code = highlight(raw_text, lexer, formatter) | |
166 out(code) | |
167 | |
168 def compile(self, ctx, out): | |
169 out.indent().write('with io.StringIO() as tmp:\n') | |
170 out.push(False) | |
171 out.indent().write('prev_out_write = out_write\n') | |
172 out.indent().write('out_write = tmp.write\n') | |
173 for c in self.children: | |
174 c.compile(ctx, out) | |
175 out.indent().write('out_write = prev_out_write\n') | |
176 out.indent().write('raw_text = tmp.getvalue()\n') | |
177 out.pull() | |
178 if self.lang is None: | |
179 out.indent().write('lexer = guess_lexer(raw_text)\n') | |
180 else: | |
181 out.indent().write( | |
182 'lexer = get_lexer_by_name(%s, stripall=False)\n' % | |
183 repr(self.lang)) | |
184 out.indent().write('formatter = HtmlFormatter()\n') | |
185 out.indent().write('code = highlight(raw_text, lexer, formatter)\n') | |
186 out.indent().write('out_write(code)\n') | |
187 | |
188 | |
189 class PieCrustGeshiStatementNode(PieCrustHighlightStatementNode): | |
190 name = 'geshi' | |
191 endname = 'endgeshi' | |
192 | |
193 | |
194 def get_highlight_css(style_name='default', class_name='.highlight'): | |
195 return HtmlFormatter(style=style_name).get_style_defs(class_name) | |
196 | |
197 | |
198 class PieCrustCacheStatementNode(StatementNode): | |
199 name = 'pccache' | |
200 compiler_imports = ['import io'] | |
201 | |
202 def __init__(self): | |
203 super().__init__() | |
204 self.cache_key = None | |
205 | |
206 def parse(self, parser): | |
207 self.cache_key = parser.expectString() | |
208 parser.skipWhitespace() | |
209 parser.expectStatementEnd() | |
210 | |
211 parser.parseUntilStatement(self, 'endpccache') | |
212 parser.expectIdentifier('endpccache') | |
213 | |
214 def render(self, ctx, data, out): | |
215 raise Exception("No implemented") | |
216 | |
217 # exc_stack = ctx.engine.piecrust_app.env.exec_info_stack | |
218 # render_ctx = exc_stack.current_page_info.render_ctx | |
219 # rdr_pass = render_ctx.current_pass_info | |
220 | |
221 # pair = ctx.engine.piecrust_cache.get(self.cache_key) | |
222 # if pair is not None: | |
223 # rdr_pass.used_source_names.update(pair[1]) | |
224 # return pair[0] | |
225 | |
226 # prev_used = rdr_pass.used_source_names.copy() | |
227 | |
228 # with io.StringIO() as tmp: | |
229 # inner_out = tmp.write | |
230 # for c in self.children: | |
231 # c.render(ctx, data, inner_out) | |
232 | |
233 # raw_text = tmp.getvalue() | |
234 | |
235 # after_used = rdr_pass.used_source_names.copy() | |
236 # used_delta = after_used.difference(prev_used) | |
237 # ctx.engine.piecrust_cache[self.cache_key] = (raw_text, used_delta) | |
238 | |
239 # return raw_text | |
240 | |
241 def compile(self, ctx, out): | |
242 out.indent().write( | |
243 'ctx_stack = ctx.engine.piecrust_app.env.render_ctx_stack\n') | |
244 out.indent().write( | |
245 'render_ctx = ctx_stack.current_ctx\n') | |
246 out.indent().write( | |
247 'rdr_pass = render_ctx.current_pass_info\n') | |
248 | |
249 pair_var = ctx.varname('pair') | |
250 out.indent().write( | |
251 '%s = ctx.engine.piecrust_cache.get(%s)\n' % | |
252 (pair_var, repr(self.cache_key))) | |
253 out.indent().write( | |
254 'if %s is not None:\n' % pair_var) | |
255 out.push().write( | |
256 'rdr_pass.used_source_names.update(%s[1])\n' % pair_var) | |
257 out.indent().write('out_write(%s[0])\n' % pair_var) | |
258 out.pull() | |
259 out.indent().write('else:\n') | |
260 | |
261 tmp_var = ctx.varname('tmp') | |
262 prev_used_var = ctx.varname('prev_used') | |
263 prev_out_write_var = ctx.varname('prev_out_write') | |
264 prev_out_write_escaped_var = ctx.varname('prev_out_write_escaped') | |
265 | |
266 out.push().write( | |
267 '%s = rdr_pass.used_source_names.copy()\n' % prev_used_var) | |
268 out.indent().write( | |
269 'with io.StringIO() as %s:\n' % tmp_var) | |
270 out.push().write( | |
271 '%s = out_write\n' % prev_out_write_var) | |
272 out.indent().write( | |
273 '%s = out_write_escaped\n' % prev_out_write_escaped_var) | |
274 out.indent().write( | |
275 'out_write = %s.write\n' % tmp_var) | |
276 out.indent().write( | |
277 'out_write_escaped = ctx.engine._getWriteEscapeFunc(out_write)\n') | |
278 for c in self.children: | |
279 c.compile(ctx, out) | |
280 | |
281 out.indent().write( | |
282 'out_write_escaped = %s\n' % prev_out_write_escaped_var) | |
283 out.indent().write( | |
284 'out_write = %s\n' % prev_out_write_var) | |
285 out.indent().write( | |
286 'raw_text = %s.getvalue()\n' % tmp_var) | |
287 out.pull() | |
288 | |
289 out.indent().write( | |
290 'after_used = rdr_pass.used_source_names.copy()\n') | |
291 out.indent().write( | |
292 'used_delta = after_used.difference(%s)\n' % prev_used_var) | |
293 out.indent().write( | |
294 'ctx.engine.piecrust_cache[%s] = (raw_text, used_delta)\n' % | |
295 repr(self.cache_key)) | |
296 out.indent().write('out_write(raw_text)\n') | |
297 out.pull() |