view genshi/template/inline.py @ 903:95d62e239f60 experimental-inline

inline branch: support for a couple more directives
author cmlenz
date Mon, 26 Apr 2010 17:09:08 +0000
parents 09cc3627654c
children bf20de18289c
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 imp

from genshi.core import Attrs, Stream, _ensure, START, END, TEXT
from genshi.template.astutil import _ast
from genshi.template.base import EXEC, 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 isinstance(obj, (int, float, long)):
            yield TEXT, unicode(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:
        if isinstance(obj, basestring):
            return [obj]
        elif isinstance(obj, (int, float, long)):
            return [unicode(result)]
        elif hasattr(obj, '__iter__'):
            return [e[1] for e in _ensure(obj) if e[0] is TEXT]
        else:
            return [unicode(obj)]
    return []


def _assign(ast):
    buf = []
    def _build(node, indices):
        if isinstance(node, _ast.Tuple):
            for idx, elt in enumerate(node.elts):
                _build(elt, indices + (idx,))
        elif isinstance(node, _ast.Name):
            buf.append('%r: v%s' % (node.id, ''.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_CDATA, START_NS, END, '
                                    'END_CDATA, END_NS, DOCTYPE, TEXT')
    yield w('from genshi.path import Path')
    yield w('from genshi.template.eval import Expression, Suite')
    yield w('from genshi.template.inline import _expand, _expand_text')
    yield w()

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

            if kind is START:
                tagname, attrs = data
                yield 'Q', tagname, tagname

                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)]:
                    yield 'Q', name, name
                    for subkind, subdata, subpos in val:
                        if subkind is EXPR:
                            yield 'E', subdata, subdata

                yield 'A', tuple(sattrs), sattrs

            elif kind is EXPR:
                yield 'E', data, data

            elif kind is EXEC:
                yield 'S', data, data

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

                    if directive.expr:
                        yield 'E', directive.expr, directive.expr

                    elif hasattr(directive, 'vars'):
                        for _, expr in directive.vars:
                            yield 'E', expr, expr

                    elif hasattr(directive, 'path') and directive.path:
                        yield 'P', directive.path, directive.path

                for line in _declare_vars(substream):
                    yield line

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

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

        d = directives[0]
        rest = directives[1:]

        if isinstance(d, DefDirective):
            return # already added

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

        if isinstance(d, ForDirective):
            yield w('for v in e[%d].evaluate(ctxt):', index['E'][d.expr])
            w.shift()
            yield w('push(%s)', _assign(d.target))
            for line in _apply(rest, stream):
                yield line
            yield w('pop()')
            w.unshift()

        elif isinstance(d, IfDirective):
            yield w('if e[%d].evaluate(ctxt):', index['E'][d.expr])
            w.shift()
            for line in _apply(rest, stream):
                yield line
            w.unshift()

        elif isinstance(d, StripDirective):
            if not d.expr:
                stream = stream[1:-2]
                for line in _apply(rest, stream):
                    yield line
            else:
                yield w('strip.append(e[%d].evaluate(ctxt))',
                        index['E'][d.expr])
                yield w('if not strip[-1]:')
                w.shift()
                for line in _generate([stream[0]]):
                    yield line
                w.unshift()
                for line in _apply(rest, stream[1:-2]):
                    yield line
                yield w('if not strip[-1]:')
                w.shift()
                for line in _generate([stream[-1]]):
                    yield line
                w.unshift()
                yield w('strip.pop(-1)')

        elif isinstance(d, WithDirective):
            yield w('push({%s})' % ','.join([
                '%r: e[%d].evaluate(ctxt)' % (
                    name[0][0].id,
                    index['E'][expr]
                ) for name, expr in d.vars
            ]))
            for line in _apply(rest, stream):
                yield line
            yield w('pop()')

        elif isinstance(d, ContentDirective):
            for line in _generate([stream[0]]):
                yield line
            yield w('for v in e[%d].evaluate(ctxt): yield v', index['E'][d.expr])
            for line in _generate([stream[-1]]):
                yield line

        elif isinstance(d, ReplaceDirective):
            yield w('for v in e[%d].evaluate(ctxt): yield v', index['E'][d.expr])

        else:
            raise NotImplementedError, '%r directive not supported' % d.tagname

        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 evt in _expand(e[%d].evaluate(ctxt), (f, %d, %d)): yield evt',
                        index['E'][data], *pos[1:])

            elif kind is EXEC:
                yield w('s[%d].execute(ctxt)', index['S'][data])

            elif kind is START:
                tagname, attrs = data
                qn = index['Q'][tagname]

                sattrs = Attrs([(n, v) for n, v in attrs
                                if isinstance(v, basestring)])
                at = index['A'][tuple(sattrs)]
                if filter(None, [not isinstance(v, basestring) for n,v in attrs]):
                    yield w('at = [(an, "".join(av)) for an, av in ([')
                    w.shift()
                    for name, value in [(n, v) for n, v in attrs
                                        if not isinstance(v, basestring)]:
                        values = []
                        for subkind, subdata, subpos in value:
                            if subkind is EXPR:
                                values.append('_expand_text(e[%d].evaluate(ctxt))' %
                                             index['E'][subdata])
                            elif subkind is TEXT:
                                values.append('[%r]' % subdata)
                        yield w('(q[%d], [v for v in %s if v is not None]),' % (
                            index['Q'][name], ' + '.join(values)
                        ))
                    w.unshift()
                    yield w(']) if av]')
                    yield w('yield START, (q[%d], a[%d] | at), (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)', index['Q'][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:])

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

    yield '# Create qnames, attributes, expressions, and suite objects'
    index, counter, values = {}, {}, {}
    for prefix, key, value in _declare_vars(template.stream):
        if not prefix in counter:
            counter[prefix] = 0
        if key not in index.get(prefix, ()):
            index.setdefault(prefix, {})[key] = counter[prefix]
            counter[prefix] += 1
            values.setdefault(prefix, []).append(value)
    for prefix in sorted(values.keys()):
        yield w('_%s = (', prefix)
        for value in values[prefix]:
            yield w('      ' + repr(value) + ',')
        yield w(')')
    yield w()

    yield w('def generate(ctxt, %s):',
            ', '.join(['f=_F'] + ['%s=_%s' % (n.lower(), n) for n in index]))
    w.shift()
    yield w('push = ctxt.push; pop = ctxt.pop')
    yield w('strip = []')
    yield w()

    # Define macro functions
    defs = []
    for line in _declare_functions(template.stream, names=defs):
        yield line
    if defs:
        yield w()
        yield w('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 = """<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:py="http://genshi.edgewall.org/"
      lang="en">
 <?python
    def foo(x):
        return x*x
 ?>
 <body>
    <h1 py:def="sayhi(name='world')" py:strip="1">
      Hello, $name!
    </h1>
    ${sayhi()}
    <ul py:if="items">
      <li py:for="idx, item in enumerate(items)"
          class="${idx % 2 and 'odd' or 'even'}"
          py:with="num=item + 1">
        <span py:replace="num">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).render('html')

    print
    print 'Executed module:'
    tmpl.compile()
    print tmpl.generate(ctxt).render('html')

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

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