view genshi/template/inline.py @ 355:94639584725a experimental-inline

inline branch: Merged [430:434/trunk].
author cmlenz
date Mon, 13 Nov 2006 18:16:57 +0000
parents 47e6c0100514
children cc04c70d1bbd
line wrap: on
line source
# -*- coding: utf-8 -*-
#
# Copyright (C) 2006 Edgewall Software
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://genshi.edgewall.org/wiki/License.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://genshi.edgewall.org/log/.

import compiler
import imp

from genshi.core import Attrs, Stream, _ensure, START, END, TEXT
from genshi.template.core import EXPR, SUB
from genshi.template.directives import *


class CodeWriter(object):

    def __init__(self):
        self.indent = 0

    def __call__(self, line='', *args):
        if not line:
            return ''
        if args:
            line %= args
        return ' ' * self.indent + line

    def shift(self):
        self.indent += 4

    def unshift(self):
        self.indent -= 4


def _expand(obj, pos):
    if obj is not None:
        # First check for a string, otherwise the iterable test below
        # succeeds, and the string will be chopped up into individual
        # characters
        if isinstance(obj, basestring):
            yield TEXT, obj, pos
        elif hasattr(obj, '__iter__'):
            for event in _ensure(obj):
                yield event
        else:
            yield TEXT, unicode(obj), pos

def _expand_text(obj):
    if obj is not None:
        # First check for a string, otherwise the iterable test below
        # succeeds, and the string will be chopped up into individual
        # characters
        if isinstance(obj, basestring):
            yield obj
        elif hasattr(obj, '__iter__'):
            for event in _ensure(obj):
                if event[0] is TEXT:
                    yield event[1]
        else:
            yield unicode(obj)

def _assign(ast):
    buf = []
    def _build(node, indices):
        if isinstance(node, (compiler.ast.AssTuple, compiler.ast.Tuple)):
            for idx, child in enumerate(node.nodes):
                _build(child, indices + (idx,))
        elif isinstance(node, (compiler.ast.AssName, compiler.ast.Name)):
            buf.append('"%s": v%s' % (node.name, ''.join(['[%s]' % i for i in indices])))
    _build(ast, ())
    return '{%s}' % ', '.join(buf)

def inline(template):
    w = CodeWriter()

    yield w('from genshi.core import Attrs, QName')
    yield w('from genshi.core import START, START_NS, END, END_NS, DOCTYPE, TEXT')
    yield w('from genshi.path import Path')
    yield w('from genshi.template.eval import Expression')
    yield w('from genshi.template.inline import _expand, _expand_text')
    yield w()

    def _predecl_vars(stream):
        for kind, data, pos in stream:

            if kind is START:
                tagname, attrs = data
                if tagname not in p_qnames:
                    qi[0] += 1
                    yield w('Q%d = %r', qi[0], tagname)
                    p_qnames[tagname] = qi[0]

                sattrs = Attrs([(n, v) for n, v in attrs
                                if isinstance(v, basestring)])
                for name, val in [(n, v) for n, v in attrs
                                  if not isinstance(v, basestring)]:
                    for subkind, subdata, subpos in val:
                        if subkind is EXPR:
                            if subdata not in p_exprs:
                                ei[0] += 1
                                yield w('E%d = %r', ei[0], subdata)
                                p_exprs[subdata] = ei[0]

                if tuple(sattrs) not in p_attrs:
                    ai[0] += 1
                    yield w('A%d = %r', ai[0], sattrs)
                    p_attrs[tuple(sattrs)] = ai[0]

            elif kind is EXPR:
                if data not in p_exprs:
                    ei[0] += 1
                    yield w('E%d = %r', ei[0], data)
                    p_exprs[data] = ei[0]

            elif kind is SUB:
                directives, substream = data
                for directive in directives:

                    if directive.expr:
                        if directive.expr not in p_exprs:
                            ei[0] += 1
                            yield w('E%d = %r', ei[0], directive.expr)
                            p_exprs[directive.expr] = ei[0]

                    elif hasattr(directive, 'vars'):
                        for _, expr in directive.vars:
                            if expr not in p_exprs:
                                ei[0] += 1
                                yield w('E%d = %r', ei[0], expr)
                                p_exprs[expr] = ei[0]

                    elif hasattr(directive, 'path') and directive.path:
                        yield w('P%d = %r', pi[0], directive.path)

                for line in _predecl_vars(substream):
                    yield line

    def _predecl_funcs(stream):
        for kind, data, pos in stream:
            if kind is SUB:
                directives, substream = data
                for idx, directive in enumerate(directives):
                    if isinstance(directive, DefDirective):
                        defs.append(directive.name)
                        yield w('def %s:', directive.signature)
                        w.shift()
                        args = ['%r: %s' % (name, name) for name
                                in directive.args]
                        yield w('ctxt.push({%s})', ', '.join(args))
                        for line in _apply(directives[idx + 1:], substream):
                            yield line
                        yield w('ctxt.pop()')
                        w.unshift()

    # Recursively apply directives
    def _apply(directives, stream):
        if not directives:
            for line in _generate(stream):
                yield line
            return

        directive = directives[0]
        directives = directives[1:]

        if isinstance(directive, DefDirective):
            return

        yield w()
        yield w('# Applying %r', directive)

        if isinstance(directive, DefDirective):
            pass

        elif isinstance(directive, ForDirective):
            ei[0] += 1
            yield w('for v in E%d.evaluate(ctxt):', p_exprs[directive.expr])
            w.shift()
            yield w('ctxt.push(%s)', _assign(directive.target))
            for line in _apply(directives, stream):
                yield line
            yield w('ctxt.pop()')
            w.unshift()

        elif isinstance(directive, IfDirective):
            ei[0] += 1
            yield w('if E%d.evaluate(ctxt):', p_exprs[directive.expr])
            w.shift()
            for line in _apply(directives, stream):
                yield line
            w.unshift()

        elif isinstance(directive, WithDirective):
            for targets, expr in directive.vars:
                ei[0] += 1
                yield w('v = E%d.evaluate(ctxt)', p_exprs[directive.expr])
                for node, _ in targets:
                    yield w('ctxt.push(%s)', _assign(node))
            for line in _apply(directives, stream):
                yield line
            yield w('ctxt.pop()')

        elif isinstance(directive, StripDirective):
            yield w('if E%d.evaluate(ctxt):', p_exprs[directive.expr])
            w.shift()
            lines = _apply(directives, stream)
            previous = lines.next()
            for line in lines:
                yield previous
                previous = line
            w.unshift()
            yield w('else:')
            w.shift()
            for line in _apply(directives, stream):
                yield line
            w.unshift()

        else:
            raise NotImplementedError

        yield w()

    # Generate code for the given template stream
    def _generate(stream):
        for kind, data, pos in stream:

            if kind is EXPR:
                yield w('for e in _expand(E%d.evaluate(ctxt), (f, %d, %d)): yield e',
                        p_exprs[data], *pos[1:])

            elif kind is START:
                tagname, attrs = data
                qn = p_qnames[tagname]

                sattrs = Attrs([(n, v) for n, v in attrs
                                if isinstance(v, basestring)])
                at = p_attrs[tuple(sattrs)]
                if filter(None, [not isinstance(v, basestring) for n,v in attrs]):
                    yield w('a = []')
                    for name, value in [(n, v) for n, v in attrs
                                        if not isinstance(v, basestring)]:
                        parts = []
                        for subkind, subdata, subpos in value:
                            if subkind is EXPR:
                                parts.append('list(_expand_text(E%d.evaluate(ctxt)))' %
                                             p_exprs[subdata])
                            elif subkind is TEXT:
                                parts.append('[%r]' % subdata)
                        yield w('v = [v for v in %s if v is not None]',
                                ' + '.join(parts))
                        yield w('if v:')
                        w.shift()
                        yield w('a.append((%r, "".join(v)))', name)
                        w.unshift()
                    yield w('yield START, (Q%d, A%d | a), (f, %d, %d)', qn, at,
                            *pos[1:])
                else:
                    yield w('yield START, (Q%d, A%d), (f, %d, %d)', qn, at, *pos[1:])

            elif kind is END:
                yield w('yield END, Q%d, (f, %d, %d)', p_qnames[data], *pos[1:])

            elif kind is SUB:
                directives, substream = data
                for line in _apply(directives, substream):
                    yield line

            else:
                yield w('yield %s, %r, (f, %d, %d)', kind, data, *pos[1:])

    p_attrs, p_qnames, p_exprs = {}, {}, {}
    ai, qi, ei, pi = [0], [0], [0], [0]
    defs = []

    yield w('FILE = %r', template.filename)
    yield w()

    yield '# predeclare qnames, attributes, and expressions'
    lines = list(_predecl_vars(template.stream))
    lines.sort()
    for line in lines:
        yield line
    yield w()

    yield w('def generate(ctxt, f=FILE):')
    yield w()
    w.shift()

    # Define macro functions
    for line in _predecl_funcs(template.stream):
        yield line
    yield w()
    yield w('ctxt.push({%s})', ', '.join(['%r: %s' % (n, n) for n in defs]))
    yield w()

    ei, pi = [0], [0]
    for line in _generate(template.stream):
        yield line


if __name__ == '__main__':
    import timeit
    from genshi.template import Context, MarkupTemplate

    text = """<!DOCTYPE html
    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
      lang="en">
 <body>
    <h1 py:def="sayhi(name='world')" py:strip="">
      Hello, $name!
    </h1>
    ${sayhi()}
    <ul py:if="items">
      <li py:for="idx, item in enumerate(items)">
        <span py:replace="item + 1">NUM</span>
      </li>
    </ul>
 </body>
</html>"""

    ctxt = Context(hello='world', items=range(10))
    tmpl = MarkupTemplate(text)

    print 'Generated source:'
    for idx, line in enumerate(inline(tmpl)):
        print '%3d  %s' % (idx + 1, line)

    print
    print 'Interpreted template:'
    print tmpl.generate(ctxt)

    print
    print 'Executed module:'
    module = tmpl.compile()
    print Stream(module.generate(ctxt))

    print
    print
    t = timeit.Timer('list(tmpl.generate(**data))', '''
from genshi.template import Context, MarkupTemplate
data = dict(hello='world', items=range(10))
tmpl = MarkupTemplate("""%s""")''' % text)
    print 'Interpreted: %.2f msec/pass' % (1000 * t.timeit(number=10000) / 10000)
    print

    t = timeit.Timer('list(module.generate(Context(**data)))', '''
from genshi.core import Stream
from genshi.template import Context, MarkupTemplate
data = dict(hello='world', items=range(10))
tmpl = MarkupTemplate("""%s""")
module = tmpl.compile()''' % text)
    print 'Compiled: %.2f msec/pass' % (1000 * t.timeit(number=10000) / 10000)
    print
Copyright (C) 2012-2017 Edgewall Software