diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/piecrust/templating/jinja/environment.py	Sat Apr 29 21:42:22 2017 -0700
@@ -0,0 +1,204 @@
+import re
+import time
+import email.utils
+import hashlib
+import strict_rfc3339
+from jinja2 import Environment
+from .extensions import get_highlight_css
+from piecrust.data.paginator import Paginator
+from piecrust.rendering import format_text
+from piecrust.uriutil import multi_replace
+
+
+class PieCrustEnvironment(Environment):
+    def __init__(self, app, *args, **kwargs):
+        self.app = app
+
+        # Before we create the base Environement, let's figure out the options
+        # we want to pass to it.
+        twig_compatibility_mode = app.config.get('jinja/twig_compatibility')
+
+        # Disable auto-reload when we're baking.
+        if app.config.get('baker/is_baking'):
+            kwargs.setdefault('auto_reload', False)
+
+        # Let the user override most Jinja options via the site config.
+        for name in ['block_start_string', 'block_end_string',
+                     'variable_start_string', 'variable_end_string',
+                     'comment_start_string', 'comment_end_string',
+                     'line_statement_prefix', 'line_comment_prefix',
+                     'trim_blocks', 'lstrip_blocks',
+                     'newline_sequence', 'keep_trailing_newline']:
+            val = app.config.get('jinja/' + name)
+            if val is not None:
+                kwargs.setdefault(name, val)
+
+        # Twig trims blocks.
+        if twig_compatibility_mode is True:
+            kwargs['trim_blocks'] = True
+
+        # All good! Create the Environment.
+        super(PieCrustEnvironment, self).__init__(*args, **kwargs)
+
+        # Now add globals and filters.
+        self.globals.update({
+            'now': get_now_date(),
+            'fail': raise_exception,
+            'highlight_css': get_highlight_css})
+
+        self.filters.update({
+            'keys': get_dict_keys,
+            'values': get_dict_values,
+            'paginate': self._paginate,
+            'formatwith': self._formatWith,
+            'markdown': lambda v: self._formatWith(v, 'markdown'),
+            'textile': lambda v: self._formatWith(v, 'textile'),
+            'nocache': add_no_cache_parameter,
+            'wordcount': get_word_count,
+            'stripoutertag': strip_outer_tag,
+            'stripslash': strip_slash,
+            'titlecase': title_case,
+            'md5': make_md5,
+            'atomdate': get_xml_date,
+            'xmldate': get_xml_date,
+            'emaildate': get_email_date,
+            'date': get_date})
+
+        # Backwards compatibility with Twig.
+        if twig_compatibility_mode is True:
+            self.filters['raw'] = self.filters['safe']
+            self.globals['pcfail'] = raise_exception
+
+    def _paginate(self, value, items_per_page=5):
+        cpi = self.app.env.exec_info_stack.current_page_info
+        if cpi is None or cpi.page is None or cpi.render_ctx is None:
+            raise Exception("Can't paginate when no page has been pushed "
+                            "on the execution stack.")
+        return Paginator(cpi.page, value,
+                         page_num=cpi.render_ctx.page_num,
+                         items_per_page=items_per_page)
+
+    def _formatWith(self, value, format_name):
+        return format_text(self.app, format_name, value)
+
+
+def raise_exception(msg):
+    raise Exception(msg)
+
+
+def get_dict_keys(value):
+    if isinstance(value, list):
+        return [i[0] for i in value]
+    return value.keys()
+
+
+def get_dict_values(value):
+    if isinstance(value, list):
+        return [i[1] for i in value]
+    return value.values()
+
+
+def add_no_cache_parameter(value, param_name='t', param_value=None):
+    if not param_value:
+        param_value = time.time()
+    if '?' in value:
+        value += '&'
+    else:
+        value += '?'
+    value += '%s=%s' % (param_name, param_value)
+    return value
+
+
+def get_word_count(value):
+    return len(value.split())
+
+
+def strip_outer_tag(value, tag=None):
+    tag_pattern = '[a-z]+[a-z0-9]*'
+    if tag is not None:
+        tag_pattern = re.escape(tag)
+    pat = r'^\<' + tag_pattern + r'\>(.*)\</' + tag_pattern + '>$'
+    m = re.match(pat, value)
+    if m:
+        return m.group(1)
+    return value
+
+
+def strip_slash(value):
+    return value.rstrip('/')
+
+
+def title_case(value):
+    return value.title()
+
+
+def make_md5(value):
+    return hashlib.md5(value.lower().encode('utf8')).hexdigest()
+
+
+def get_xml_date(value):
+    """ Formats timestamps like 1985-04-12T23:20:50.52Z
+    """
+    if value == 'now':
+        value = time.time()
+    return strict_rfc3339.timestamp_to_rfc3339_localoffset(int(value))
+
+
+def get_email_date(value, localtime=False):
+    """ Formats timestamps like Fri, 09 Nov 2001 01:08:47 -0000
+    """
+    if value == 'now':
+        value = time.time()
+    return email.utils.formatdate(value, localtime=localtime)
+
+
+def get_now_date():
+    return time.time()
+
+
+def get_date(value, fmt):
+    if value == 'now':
+        value = time.time()
+    if '%' not in fmt:
+        suggest = php_format_to_strftime_format(fmt)
+        if suggest != fmt:
+            suggest_message = ("You probably want a format that looks "
+                               "like: '%s'." % suggest)
+        else:
+            suggest_message = ("We can't suggest a proper date format "
+                               "for you right now, though.")
+        raise Exception("Got incorrect date format: '%s\n"
+                        "PieCrust 1 date formats won't work in PieCrust 2. "
+                        "%s\n"
+                        "Please check the `strftime` formatting page here: "
+                        "https://docs.python.org/3/library/datetime.html"
+                        "#strftime-and-strptime-behavior" %
+                        (fmt, suggest_message))
+    return time.strftime(fmt, time.localtime(value))
+
+
+def php_format_to_strftime_format(fmt):
+    replacements = {
+        'd': '%d',
+        'D': '%a',
+        'j': '%d',
+        'l': '%A',
+        'w': '%w',
+        'z': '%j',
+        'W': '%W',
+        'F': '%B',
+        'm': '%m',
+        'M': '%b',
+        'n': '%m',
+        'y': '%Y',
+        'Y': '%y',
+        'g': '%I',
+        'G': '%H',
+        'h': '%I',
+        'H': '%H',
+        'i': '%M',
+        's': '%S',
+        'e': '%Z',
+        'O': '%z',
+        'c': '%Y-%m-%dT%H:%M:%SZ'}
+    return multi_replace(fmt, replacements)