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',