comparison piecrust/templating/jinja/environment.py @ 851:2c7e57d80bba

optimize: Don't load Jinja unless we need to.
author Ludovic Chabant <ludovic@chabant.com>
date Sat, 29 Apr 2017 21:42:22 -0700
parents
children 08e02c2a2a1a
comparison
equal deleted inserted replaced
850:370e74941d32 851:2c7e57d80bba
1 import re
2 import time
3 import email.utils
4 import hashlib
5 import strict_rfc3339
6 from jinja2 import Environment
7 from .extensions import get_highlight_css
8 from piecrust.data.paginator import Paginator
9 from piecrust.rendering import format_text
10 from piecrust.uriutil import multi_replace
11
12
13 class PieCrustEnvironment(Environment):
14 def __init__(self, app, *args, **kwargs):
15 self.app = app
16
17 # Before we create the base Environement, let's figure out the options
18 # we want to pass to it.
19 twig_compatibility_mode = app.config.get('jinja/twig_compatibility')
20
21 # Disable auto-reload when we're baking.
22 if app.config.get('baker/is_baking'):
23 kwargs.setdefault('auto_reload', False)
24
25 # Let the user override most Jinja options via the site config.
26 for name in ['block_start_string', 'block_end_string',
27 'variable_start_string', 'variable_end_string',
28 'comment_start_string', 'comment_end_string',
29 'line_statement_prefix', 'line_comment_prefix',
30 'trim_blocks', 'lstrip_blocks',
31 'newline_sequence', 'keep_trailing_newline']:
32 val = app.config.get('jinja/' + name)
33 if val is not None:
34 kwargs.setdefault(name, val)
35
36 # Twig trims blocks.
37 if twig_compatibility_mode is True:
38 kwargs['trim_blocks'] = True
39
40 # All good! Create the Environment.
41 super(PieCrustEnvironment, self).__init__(*args, **kwargs)
42
43 # Now add globals and filters.
44 self.globals.update({
45 'now': get_now_date(),
46 'fail': raise_exception,
47 'highlight_css': get_highlight_css})
48
49 self.filters.update({
50 'keys': get_dict_keys,
51 'values': get_dict_values,
52 'paginate': self._paginate,
53 'formatwith': self._formatWith,
54 'markdown': lambda v: self._formatWith(v, 'markdown'),
55 'textile': lambda v: self._formatWith(v, 'textile'),
56 'nocache': add_no_cache_parameter,
57 'wordcount': get_word_count,
58 'stripoutertag': strip_outer_tag,
59 'stripslash': strip_slash,
60 'titlecase': title_case,
61 'md5': make_md5,
62 'atomdate': get_xml_date,
63 'xmldate': get_xml_date,
64 'emaildate': get_email_date,
65 'date': get_date})
66
67 # Backwards compatibility with Twig.
68 if twig_compatibility_mode is True:
69 self.filters['raw'] = self.filters['safe']
70 self.globals['pcfail'] = raise_exception
71
72 def _paginate(self, value, items_per_page=5):
73 cpi = self.app.env.exec_info_stack.current_page_info
74 if cpi is None or cpi.page is None or cpi.render_ctx is None:
75 raise Exception("Can't paginate when no page has been pushed "
76 "on the execution stack.")
77 return Paginator(cpi.page, value,
78 page_num=cpi.render_ctx.page_num,
79 items_per_page=items_per_page)
80
81 def _formatWith(self, value, format_name):
82 return format_text(self.app, format_name, value)
83
84
85 def raise_exception(msg):
86 raise Exception(msg)
87
88
89 def get_dict_keys(value):
90 if isinstance(value, list):
91 return [i[0] for i in value]
92 return value.keys()
93
94
95 def get_dict_values(value):
96 if isinstance(value, list):
97 return [i[1] for i in value]
98 return value.values()
99
100
101 def add_no_cache_parameter(value, param_name='t', param_value=None):
102 if not param_value:
103 param_value = time.time()
104 if '?' in value:
105 value += '&'
106 else:
107 value += '?'
108 value += '%s=%s' % (param_name, param_value)
109 return value
110
111
112 def get_word_count(value):
113 return len(value.split())
114
115
116 def strip_outer_tag(value, tag=None):
117 tag_pattern = '[a-z]+[a-z0-9]*'
118 if tag is not None:
119 tag_pattern = re.escape(tag)
120 pat = r'^\<' + tag_pattern + r'\>(.*)\</' + tag_pattern + '>$'
121 m = re.match(pat, value)
122 if m:
123 return m.group(1)
124 return value
125
126
127 def strip_slash(value):
128 return value.rstrip('/')
129
130
131 def title_case(value):
132 return value.title()
133
134
135 def make_md5(value):
136 return hashlib.md5(value.lower().encode('utf8')).hexdigest()
137
138
139 def get_xml_date(value):
140 """ Formats timestamps like 1985-04-12T23:20:50.52Z
141 """
142 if value == 'now':
143 value = time.time()
144 return strict_rfc3339.timestamp_to_rfc3339_localoffset(int(value))
145
146
147 def get_email_date(value, localtime=False):
148 """ Formats timestamps like Fri, 09 Nov 2001 01:08:47 -0000
149 """
150 if value == 'now':
151 value = time.time()
152 return email.utils.formatdate(value, localtime=localtime)
153
154
155 def get_now_date():
156 return time.time()
157
158
159 def get_date(value, fmt):
160 if value == 'now':
161 value = time.time()
162 if '%' not in fmt:
163 suggest = php_format_to_strftime_format(fmt)
164 if suggest != fmt:
165 suggest_message = ("You probably want a format that looks "
166 "like: '%s'." % suggest)
167 else:
168 suggest_message = ("We can't suggest a proper date format "
169 "for you right now, though.")
170 raise Exception("Got incorrect date format: '%s\n"
171 "PieCrust 1 date formats won't work in PieCrust 2. "
172 "%s\n"
173 "Please check the `strftime` formatting page here: "
174 "https://docs.python.org/3/library/datetime.html"
175 "#strftime-and-strptime-behavior" %
176 (fmt, suggest_message))
177 return time.strftime(fmt, time.localtime(value))
178
179
180 def php_format_to_strftime_format(fmt):
181 replacements = {
182 'd': '%d',
183 'D': '%a',
184 'j': '%d',
185 'l': '%A',
186 'w': '%w',
187 'z': '%j',
188 'W': '%W',
189 'F': '%B',
190 'm': '%m',
191 'M': '%b',
192 'n': '%m',
193 'y': '%Y',
194 'Y': '%y',
195 'g': '%I',
196 'G': '%H',
197 'h': '%I',
198 'H': '%H',
199 'i': '%M',
200 's': '%S',
201 'e': '%Z',
202 'O': '%z',
203 'c': '%Y-%m-%dT%H:%M:%SZ'}
204 return multi_replace(fmt, replacements)