Mercurial > piecrust2
comparison piecrust/templating/jinjaengine.py @ 65:071cc99b1779
Jinja templating now has `spaceless`, `|keys` and `|values`.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Fri, 29 Aug 2014 08:06:43 -0700 |
parents | e3e3de44377c |
children | e771c202583a |
comparison
equal
deleted
inserted
replaced
64:9ae3237365eb | 65:071cc99b1779 |
---|---|
3 import logging | 3 import logging |
4 import strict_rfc3339 | 4 import strict_rfc3339 |
5 from jinja2 import Environment, FileSystemLoader, TemplateNotFound | 5 from jinja2 import Environment, FileSystemLoader, TemplateNotFound |
6 from jinja2.exceptions import TemplateSyntaxError | 6 from jinja2.exceptions import TemplateSyntaxError |
7 from jinja2.ext import Extension, Markup | 7 from jinja2.ext import Extension, Markup |
8 from jinja2.lexer import Token, describe_token | |
8 from jinja2.nodes import CallBlock, Const | 9 from jinja2.nodes import CallBlock, Const |
10 from compressinja.html import HtmlCompressor, StreamProcessContext | |
9 from pygments import highlight | 11 from pygments import highlight |
10 from pygments.formatters import HtmlFormatter | 12 from pygments.formatters import HtmlFormatter |
11 from pygments.lexers import get_lexer_by_name, guess_lexer | 13 from pygments.lexers import get_lexer_by_name, guess_lexer |
12 from piecrust.data.paginator import Paginator | 14 from piecrust.data.paginator import Paginator |
13 from piecrust.rendering import format_text | 15 from piecrust.rendering import format_text |
67 logger.debug("Creating Jinja environment with folders: %s" % | 69 logger.debug("Creating Jinja environment with folders: %s" % |
68 self.app.templates_dirs) | 70 self.app.templates_dirs) |
69 loader = FileSystemLoader(self.app.templates_dirs) | 71 loader = FileSystemLoader(self.app.templates_dirs) |
70 extensions = [ | 72 extensions = [ |
71 PieCrustHighlightExtension, | 73 PieCrustHighlightExtension, |
72 PieCrustCacheExtension] | 74 PieCrustCacheExtension, |
75 PieCrustSpacelessExtension] | |
73 if autoescape: | 76 if autoescape: |
74 extensions.append('jinja2.ext.autoescape') | 77 extensions.append('jinja2.ext.autoescape') |
75 self.env = PieCrustEnvironment( | 78 self.env = PieCrustEnvironment( |
76 self.app, | 79 self.app, |
77 loader=loader, | 80 loader=loader, |
84 self.app = app | 87 self.app = app |
85 self.auto_reload = True | 88 self.auto_reload = True |
86 self.globals.update({ | 89 self.globals.update({ |
87 'fail': raise_exception}) | 90 'fail': raise_exception}) |
88 self.filters.update({ | 91 self.filters.update({ |
92 'keys': get_dict_keys, | |
93 'values': get_dict_values, | |
89 'paginate': self._paginate, | 94 'paginate': self._paginate, |
90 'formatwith': self._formatWith, | 95 'formatwith': self._formatWith, |
91 'markdown': lambda v: self._formatWith(v, 'markdown'), | 96 'markdown': lambda v: self._formatWith(v, 'markdown'), |
92 'textile': lambda v: self._formatWith(v, 'textile'), | 97 'textile': lambda v: self._formatWith(v, 'textile'), |
93 'nocache': add_no_cache_parameter, | 98 'nocache': add_no_cache_parameter, |
138 | 143 |
139 def raise_exception(msg): | 144 def raise_exception(msg): |
140 raise Exception(msg) | 145 raise Exception(msg) |
141 | 146 |
142 | 147 |
148 def get_dict_keys(value): | |
149 if isinstance(value, list): | |
150 return [i[0] for i in value] | |
151 return value.keys() | |
152 | |
153 | |
154 def get_dict_values(value): | |
155 if isinstance(value, list): | |
156 return [i[1] for i in value] | |
157 return value.values() | |
158 | |
159 | |
143 def add_no_cache_parameter(value, param_name='t', param_value=None): | 160 def add_no_cache_parameter(value, param_name='t', param_value=None): |
144 if not param_value: | 161 if not param_value: |
145 param_value = time.time() | 162 param_value = time.time() |
146 if '?' in value: | 163 if '?' in value: |
147 value += '&' | 164 value += '&' |
249 code = highlight(Markup(body.rstrip()).unescape(), lexer, formatter) | 266 code = highlight(Markup(body.rstrip()).unescape(), lexer, formatter) |
250 return code | 267 return code |
251 | 268 |
252 | 269 |
253 class PieCrustCacheExtension(Extension): | 270 class PieCrustCacheExtension(Extension): |
254 tags = set(['pccache']) | 271 tags = set(['pccache', 'cache']) |
255 | 272 |
256 def __init__(self, environment): | 273 def __init__(self, environment): |
257 super(PieCrustCacheExtension, self).__init__(environment) | 274 super(PieCrustCacheExtension, self).__init__(environment) |
258 | 275 |
259 environment.extend( | 276 environment.extend( |
271 # now we parse a single expression that is used as cache key. | 288 # now we parse a single expression that is used as cache key. |
272 args = [parser.parse_expression()] | 289 args = [parser.parse_expression()] |
273 | 290 |
274 # now we parse the body of the cache block up to `endpccache` and | 291 # now we parse the body of the cache block up to `endpccache` and |
275 # drop the needle (which would always be `endpccache` in that case) | 292 # drop the needle (which would always be `endpccache` in that case) |
276 body = parser.parse_statements(['name:endpccache'], drop_needle=True) | 293 body = parser.parse_statements(['name:endpccache', 'name:endcache'], |
294 drop_needle=True) | |
277 | 295 |
278 # now return a `CallBlock` node that calls our _cache_support | 296 # now return a `CallBlock` node that calls our _cache_support |
279 # helper method on this extension. | 297 # helper method on this extension. |
280 return CallBlock(self.call_method('_cache_support', args), | 298 return CallBlock(self.call_method('_cache_support', args), |
281 [], [], body).set_lineno(lineno) | 299 [], [], body).set_lineno(lineno) |
290 if rv is not None: | 308 if rv is not None: |
291 return rv | 309 return rv |
292 rv = caller() | 310 rv = caller() |
293 self.environment.piecrust_cache[key] = rv | 311 self.environment.piecrust_cache[key] = rv |
294 return rv | 312 return rv |
313 | |
314 | |
315 class PieCrustSpacelessExtension(HtmlCompressor): | |
316 """ A re-implementation of `SelectiveHtmlCompressor` so that we can | |
317 both use `strip` or `spaceless` in templates. | |
318 """ | |
319 def filter_stream(self, stream): | |
320 ctx = StreamProcessContext(stream) | |
321 strip_depth = 0 | |
322 while 1: | |
323 if stream.current.type == 'block_begin': | |
324 for tk in ['strip', 'spaceless']: | |
325 change = self._processToken(ctx, stream, tk) | |
326 if change != 0: | |
327 strip_depth += change | |
328 if strip_depth < 0: | |
329 ctx.fail('Unexpected tag end%s' % tk) | |
330 break | |
331 if strip_depth > 0 and stream.current.type == 'data': | |
332 ctx.token = stream.current | |
333 value = self.normalize(ctx) | |
334 yield Token(stream.current.lineno, 'data', value) | |
335 else: | |
336 yield stream.current | |
337 next(stream) | |
338 | |
339 def _processToken(self, ctx, stream, test_token): | |
340 change = 0 | |
341 if (stream.look().test('name:%s' % test_token) or | |
342 stream.look().test('name:end%s' % test_token)): | |
343 stream.skip() | |
344 if stream.current.value == test_token: | |
345 change = 1 | |
346 else: | |
347 change = -1 | |
348 stream.skip() | |
349 if stream.current.type != 'block_end': | |
350 ctx.fail('expected end of block, got %s' % | |
351 describe_token(stream.current)) | |
352 stream.skip() | |
353 return change | |
295 | 354 |
296 | 355 |
297 def php_format_to_strftime_format(fmt): | 356 def php_format_to_strftime_format(fmt): |
298 replacements = { | 357 replacements = { |
299 'd': '%d', | 358 'd': '%d', |