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()