view piecrust/templating/_inukshukext.py @ 1137:10fd55b9ccfb

templating: Fix Inukshuk `paginate` function.
author Ludovic Chabant <ludovic@chabant.com>
date Tue, 24 Apr 2018 21:27:46 -0700
parents 576f7ebcd9c0
children
line wrap: on
line source

import io
import re
import time
from inukshuk.ext import Extension, StatementNode
from inukshuk.ext.core import filter_make_xml_date, filter_safe
from inukshuk.lexer import (
    TOKEN_ID_STRING_SINGLE_QUOTES, TOKEN_ID_STRING_DOUBLE_QUOTES)
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import get_lexer_by_name, guess_lexer
from piecrust.data.paginator import Paginator
from piecrust.rendering import format_text


class PieCrustExtension(Extension):
    def __init__(self, app):
        self.app = app

    def setupEngine(self, engine):
        engine.piecrust_app = self.app
        engine.piecrust_cache = {}

    def getGlobals(self):
        return {
            'highlight_css': get_highlight_css}

    def getFilters(self):
        return {
            '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,
            'stripoutertag': strip_outer_tag,
            'stripslash': strip_slash,
            'atomdate': filter_make_xml_date,
            'raw': filter_safe
        }

    def getTests(self):
        return {}

    def getStatementNodes(self):
        return [
            PieCrustHighlightStatementNode,
            PieCrustGeshiStatementNode,
            PieCrustCacheStatementNode,
            PieCrustFormatStatementNode]

    def _paginate(self, value, items_per_page=5):
        ctx = self.app.env.render_ctx_stack.current_ctx
        if ctx is None or ctx.page is None:
            raise Exception("Can't paginate when no page has been pushed "
                            "on the execution stack.")
        return Paginator(value, ctx.page,
                         sub_num=ctx.sub_num,
                         items_per_page=items_per_page)

    def _formatWith(self, value, format_name):
        return format_text(self.app, format_name, value)


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


class PieCrustFormatStatementNode(StatementNode):
    name = 'pcformat'
    compiler_imports = ['import io',
                        'from piecrust.rendering import format_text']

    def __init__(self):
        super().__init__()
        self.format = None

    def parse(self, parser):
        self.format = parser.expectIdentifier()
        parser.skipWhitespace()
        parser.expectStatementEnd()
        parser.parseUntilStatement(self, ['endpcformat'])
        parser.expectIdentifier('endpcformat')

    def render(self, ctx, data, out):
        with io.StringIO() as tmp:
            inner_out = tmp.write
            for c in self.children:
                c.render(ctx, data, inner_out)

            text = format_text(ctx.engine.piecrust_app, self.format,
                               tmp.getvalue(), exact_format=True)
            out(text)

    def compile(self, ctx, out):
        out.indent().write('with io.StringIO() as tmp:\n')
        out.push(False)
        out.indent().write('prev_out_write = out_write\n')
        out.indent().write('out_write = tmp.write\n')
        for c in self.children:
            c.compile(ctx, out)
        out.indent().write('out_write = prev_out_write\n')
        out.indent().write(
            'text = format_text(ctx_engine.piecrust_app, %s, tmp.getvalue(), '
            'exact_format=True)\n' % repr(self.format))
        out.indent().write('out_write(text)\n')
        out.pull()


class PieCrustHighlightStatementNode(StatementNode):
    name = 'highlight'
    endname = 'endhighlight'
    compiler_imports = [
        'from pygments import highlight',
        'from pygments.formatters import HtmlFormatter',
        'from pygments.lexers import get_lexer_by_name, guess_lexer']

    def __init__(self):
        super().__init__()
        self.lang = None

    def parse(self, parser):
        self.lang = parser.expectAny([TOKEN_ID_STRING_SINGLE_QUOTES,
                                      TOKEN_ID_STRING_DOUBLE_QUOTES])
        parser.skipWhitespace()
        parser.expectStatementEnd()

        parser.parseUntilStatement(self, self.endname)
        parser.expectIdentifier(self.endname)

    def render(self, ctx, data, out):
        with io.StringIO() as tmp:
            inner_out = tmp.write
            for c in self.children:
                c.render(ctx, data, inner_out)

            raw_text = tmp.getvalue()

        if self.lang is None:
            lexer = guess_lexer(raw_text)
        else:
            lexer = get_lexer_by_name(self.lang, stripall=False)

        formatter = HtmlFormatter()
        code = highlight(raw_text, lexer, formatter)
        out(code)

    def compile(self, ctx, out):
        out.indent().write('with io.StringIO() as tmp:\n')
        out.push(False)
        out.indent().write('prev_out_write = out_write\n')
        out.indent().write('out_write = tmp.write\n')
        for c in self.children:
            c.compile(ctx, out)
        out.indent().write('out_write = prev_out_write\n')
        out.indent().write('raw_text = tmp.getvalue()\n')
        out.pull()
        if self.lang is None:
            out.indent().write('lexer = guess_lexer(raw_text)\n')
        else:
            out.indent().write(
                'lexer = get_lexer_by_name(%s, stripall=False)\n' %
                repr(self.lang))
        out.indent().write('formatter = HtmlFormatter()\n')
        out.indent().write('code = highlight(raw_text, lexer, formatter)\n')
        out.indent().write('out_write(code)\n')


class PieCrustGeshiStatementNode(PieCrustHighlightStatementNode):
    name = 'geshi'
    endname = 'endgeshi'


def get_highlight_css(style_name='default', class_name='.highlight'):
    return HtmlFormatter(style=style_name).get_style_defs(class_name)


class PieCrustCacheStatementNode(StatementNode):
    name = 'pccache'
    compiler_imports = ['import io']

    def __init__(self):
        super().__init__()
        self.cache_key = None

    def parse(self, parser):
        self.cache_key = parser.expectString()
        parser.skipWhitespace()
        parser.expectStatementEnd()

        parser.parseUntilStatement(self, 'endpccache')
        parser.expectIdentifier('endpccache')

    def render(self, ctx, data, out):
        raise Exception("No implemented")

        # exc_stack = ctx.engine.piecrust_app.env.exec_info_stack
        # render_ctx = exc_stack.current_page_info.render_ctx
        # rdr_pass = render_ctx.current_pass_info

        # pair = ctx.engine.piecrust_cache.get(self.cache_key)
        # if pair is not None:
        #     rdr_pass.used_source_names.update(pair[1])
        #     return pair[0]

        # prev_used = rdr_pass.used_source_names.copy()

        # with io.StringIO() as tmp:
        #     inner_out = tmp.write
        #     for c in self.children:
        #         c.render(ctx, data, inner_out)

        #     raw_text = tmp.getvalue()

        # after_used = rdr_pass.used_source_names.copy()
        # used_delta = after_used.difference(prev_used)
        # ctx.engine.piecrust_cache[self.cache_key] = (raw_text, used_delta)

        # return raw_text

    def compile(self, ctx, out):
        out.indent().write(
            'ctx_stack = ctx.engine.piecrust_app.env.render_ctx_stack\n')
        out.indent().write(
            'render_ctx = ctx_stack.current_ctx\n')
        out.indent().write(
            'rdr_pass = render_ctx.current_pass_info\n')

        pair_var = ctx.varname('pair')
        out.indent().write(
            '%s = ctx.engine.piecrust_cache.get(%s)\n' %
            (pair_var, repr(self.cache_key)))
        out.indent().write(
            'if %s is not None:\n' % pair_var)
        out.push().write(
            'rdr_pass.used_source_names.update(%s[1])\n' % pair_var)
        out.indent().write('out_write(%s[0])\n' % pair_var)
        out.pull()
        out.indent().write('else:\n')

        tmp_var = ctx.varname('tmp')
        prev_used_var = ctx.varname('prev_used')
        prev_out_write_var = ctx.varname('prev_out_write')
        prev_out_write_escaped_var = ctx.varname('prev_out_write_escaped')

        out.push().write(
            '%s = rdr_pass.used_source_names.copy()\n' % prev_used_var)
        out.indent().write(
            'with io.StringIO() as %s:\n' % tmp_var)
        out.push().write(
            '%s = out_write\n' % prev_out_write_var)
        out.indent().write(
            '%s = out_write_escaped\n' % prev_out_write_escaped_var)
        out.indent().write(
            'out_write = %s.write\n' % tmp_var)
        out.indent().write(
            'out_write_escaped = ctx.engine._getWriteEscapeFunc(out_write)\n')
        for c in self.children:
            c.compile(ctx, out)

        out.indent().write(
            'out_write_escaped = %s\n' % prev_out_write_escaped_var)
        out.indent().write(
            'out_write = %s\n' % prev_out_write_var)
        out.indent().write(
            'raw_text = %s.getvalue()\n' % tmp_var)
        out.pull()

        out.indent().write(
            'after_used = rdr_pass.used_source_names.copy()\n')
        out.indent().write(
            'used_delta = after_used.difference(%s)\n' % prev_used_var)
        out.indent().write(
            'ctx.engine.piecrust_cache[%s] = (raw_text, used_delta)\n' %
            repr(self.cache_key))
        out.indent().write('out_write(raw_text)\n')
        out.pull()