cmlenz@339: # -*- coding: utf-8 -*- cmlenz@339: # cmlenz@339: # Copyright (C) 2006 Edgewall Software cmlenz@339: # All rights reserved. cmlenz@339: # cmlenz@339: # This software is licensed as described in the file COPYING, which cmlenz@339: # you should have received as part of this distribution. The terms cmlenz@339: # are also available at http://genshi.edgewall.org/wiki/License. cmlenz@339: # cmlenz@339: # This software consists of voluntary contributions made by many cmlenz@339: # individuals. For the exact contribution history, see the revision cmlenz@339: # history and logs, available at http://genshi.edgewall.org/log/. cmlenz@339: cmlenz@339: import imp cmlenz@339: cmlenz@339: from genshi.core import Attrs, Stream, _ensure, START, END, TEXT cmlenz@821: from genshi.template.astutil import _ast cmlenz@826: from genshi.template.base import EXEC, EXPR, SUB cmlenz@339: from genshi.template.directives import * cmlenz@339: cmlenz@339: cmlenz@339: class CodeWriter(object): cmlenz@339: cmlenz@339: def __init__(self): cmlenz@339: self.indent = 0 cmlenz@339: cmlenz@339: def __call__(self, line='', *args): cmlenz@339: if not line: cmlenz@339: return '' cmlenz@339: if args: cmlenz@339: line %= args cmlenz@339: return ' ' * self.indent + line cmlenz@339: cmlenz@339: def shift(self): cmlenz@339: self.indent += 4 cmlenz@339: cmlenz@339: def unshift(self): cmlenz@339: self.indent -= 4 cmlenz@339: cmlenz@339: cmlenz@339: def _expand(obj, pos): cmlenz@339: if obj is not None: cmlenz@339: # First check for a string, otherwise the iterable test below cmlenz@339: # succeeds, and the string will be chopped up into individual cmlenz@339: # characters cmlenz@339: if isinstance(obj, basestring): cmlenz@339: yield TEXT, obj, pos cmlenz@826: elif isinstance(obj, (int, float, long)): cmlenz@826: yield TEXT, unicode(obj), pos cmlenz@339: elif hasattr(obj, '__iter__'): cmlenz@339: for event in _ensure(obj): cmlenz@339: yield event cmlenz@339: else: cmlenz@339: yield TEXT, unicode(obj), pos cmlenz@339: cmlenz@826: cmlenz@339: def _expand_text(obj): cmlenz@339: if obj is not None: cmlenz@339: if isinstance(obj, basestring): cmlenz@356: return [obj] cmlenz@826: elif isinstance(obj, (int, float, long)): cmlenz@826: return [unicode(result)] cmlenz@339: elif hasattr(obj, '__iter__'): cmlenz@356: return [e[1] for e in _ensure(obj) if e[0] is TEXT] cmlenz@339: else: cmlenz@356: return [unicode(obj)] cmlenz@356: return [] cmlenz@339: cmlenz@826: cmlenz@339: def _assign(ast): cmlenz@339: buf = [] cmlenz@339: def _build(node, indices): cmlenz@821: if isinstance(node, _ast.Tuple): cmlenz@821: for idx, elt in enumerate(node.elts): cmlenz@821: _build(elt, indices + (idx,)) cmlenz@821: elif isinstance(node, _ast.Name): cmlenz@821: buf.append('%r: v%s' % (node.id, ''.join(['[%s]' % i for i in indices]))) cmlenz@339: _build(ast, ()) cmlenz@339: return '{%s}' % ', '.join(buf) cmlenz@339: cmlenz@826: cmlenz@339: def inline(template): cmlenz@339: w = CodeWriter() cmlenz@339: cmlenz@339: yield w('from genshi.core import Attrs, QName') cmlenz@826: yield w('from genshi.core import START, START_CDATA, START_NS, END, ' cmlenz@826: 'END_CDATA, END_NS, DOCTYPE, TEXT') cmlenz@339: yield w('from genshi.path import Path') cmlenz@826: yield w('from genshi.template.eval import Expression, Suite') cmlenz@339: yield w('from genshi.template.inline import _expand, _expand_text') cmlenz@342: yield w() cmlenz@339: cmlenz@826: def _declare_vars(stream): cmlenz@339: for kind, data, pos in stream: cmlenz@339: cmlenz@339: if kind is START: cmlenz@339: tagname, attrs = data cmlenz@359: yield 'Q', tagname, tagname cmlenz@339: cmlenz@348: sattrs = Attrs([(n, v) for n, v in attrs cmlenz@348: if isinstance(v, basestring)]) cmlenz@348: for name, val in [(n, v) for n, v in attrs cmlenz@348: if not isinstance(v, basestring)]: cmlenz@359: yield 'Q', name, name cmlenz@348: for subkind, subdata, subpos in val: cmlenz@348: if subkind is EXPR: cmlenz@359: yield 'E', subdata, subdata cmlenz@339: cmlenz@359: yield 'A', tuple(sattrs), sattrs cmlenz@339: cmlenz@339: elif kind is EXPR: cmlenz@359: yield 'E', data, data cmlenz@339: cmlenz@826: elif kind is EXEC: cmlenz@826: yield 'S', data, data cmlenz@826: cmlenz@339: elif kind is SUB: cmlenz@339: directives, substream = data cmlenz@339: for directive in directives: cmlenz@342: cmlenz@339: if directive.expr: cmlenz@359: yield 'E', directive.expr, directive.expr cmlenz@342: cmlenz@339: elif hasattr(directive, 'vars'): cmlenz@339: for _, expr in directive.vars: cmlenz@359: yield 'E', expr, expr cmlenz@342: cmlenz@339: elif hasattr(directive, 'path') and directive.path: cmlenz@359: yield 'P', directive.path, directive.path cmlenz@342: cmlenz@826: for line in _declare_vars(substream): cmlenz@339: yield line cmlenz@339: cmlenz@826: def _declare_functions(stream, names): cmlenz@342: for kind, data, pos in stream: cmlenz@342: if kind is SUB: cmlenz@342: directives, substream = data cmlenz@348: for idx, directive in enumerate(directives): cmlenz@342: if isinstance(directive, DefDirective): cmlenz@826: names.append(directive.name) cmlenz@342: yield w('def %s:', directive.signature) cmlenz@342: w.shift() cmlenz@342: args = ['%r: %s' % (name, name) for name cmlenz@342: in directive.args] cmlenz@826: yield w('push({%s})', ', '.join(args)) cmlenz@348: for line in _apply(directives[idx + 1:], substream): cmlenz@342: yield line cmlenz@826: yield w('pop()') cmlenz@342: w.unshift() cmlenz@339: cmlenz@339: # Recursively apply directives cmlenz@339: def _apply(directives, stream): cmlenz@339: if not directives: cmlenz@339: for line in _generate(stream): cmlenz@339: yield line cmlenz@339: return cmlenz@339: cmlenz@398: d = directives[0] cmlenz@398: rest = directives[1:] cmlenz@339: cmlenz@398: if isinstance(d, DefDirective): cmlenz@398: return # already added cmlenz@348: cmlenz@339: yield w() cmlenz@398: yield w('# Applying %r', d) cmlenz@339: cmlenz@398: if isinstance(d, ForDirective): cmlenz@398: yield w('for v in e[%d].evaluate(ctxt):', index['E'][d.expr]) cmlenz@339: w.shift() cmlenz@826: yield w('push(%s)', _assign(d.target)) cmlenz@398: for line in _apply(rest, stream): cmlenz@339: yield line cmlenz@826: yield w('pop()') cmlenz@339: w.unshift() cmlenz@339: cmlenz@398: elif isinstance(d, IfDirective): cmlenz@398: yield w('if e[%d].evaluate(ctxt):', index['E'][d.expr]) cmlenz@339: w.shift() cmlenz@398: for line in _apply(rest, stream): cmlenz@339: yield line cmlenz@339: w.unshift() cmlenz@339: cmlenz@339: else: cmlenz@339: raise NotImplementedError cmlenz@339: cmlenz@342: yield w() cmlenz@339: cmlenz@339: # Generate code for the given template stream cmlenz@339: def _generate(stream): cmlenz@339: for kind, data, pos in stream: cmlenz@339: cmlenz@339: if kind is EXPR: cmlenz@359: yield w('for evt in _expand(e[%d].evaluate(ctxt), (f, %d, %d)): yield evt', cmlenz@359: index['E'][data], *pos[1:]) cmlenz@339: cmlenz@826: elif kind is EXEC: cmlenz@826: yield w('s[%d].execute(ctxt)', index['S'][data]) cmlenz@826: cmlenz@339: elif kind is START: cmlenz@339: tagname, attrs = data cmlenz@359: qn = index['Q'][tagname] cmlenz@339: cmlenz@348: sattrs = Attrs([(n, v) for n, v in attrs cmlenz@348: if isinstance(v, basestring)]) cmlenz@359: at = index['A'][tuple(sattrs)] cmlenz@339: if filter(None, [not isinstance(v, basestring) for n,v in attrs]): cmlenz@359: yield w('at = [(an, "".join(av)) for an, av in ([') cmlenz@356: w.shift() cmlenz@348: for name, value in [(n, v) for n, v in attrs cmlenz@348: if not isinstance(v, basestring)]: cmlenz@356: values = [] cmlenz@348: for subkind, subdata, subpos in value: cmlenz@348: if subkind is EXPR: cmlenz@359: values.append('_expand_text(e[%d].evaluate(ctxt))' % cmlenz@359: index['E'][subdata]) cmlenz@348: elif subkind is TEXT: cmlenz@356: values.append('[%r]' % subdata) cmlenz@359: yield w('(q[%d], [v for v in %s if v is not None]),' % ( cmlenz@359: index['Q'][name], ' + '.join(values) cmlenz@356: )) cmlenz@356: w.unshift() cmlenz@356: yield w(']) if av]') cmlenz@359: yield w('yield START, (q[%d], a[%d] | at), (f, %d, %d)', qn, at, cmlenz@348: *pos[1:]) cmlenz@339: else: cmlenz@359: yield w('yield START, (q[%d], a[%d]), (f, %d, %d)', qn, at, *pos[1:]) cmlenz@339: cmlenz@339: elif kind is END: cmlenz@359: yield w('yield END, q[%d], (f, %d, %d)', index['Q'][data], *pos[1:]) cmlenz@339: cmlenz@339: elif kind is SUB: cmlenz@339: directives, substream = data cmlenz@339: for line in _apply(directives, substream): cmlenz@339: yield line cmlenz@339: cmlenz@339: else: cmlenz@348: yield w('yield %s, %r, (f, %d, %d)', kind, data, *pos[1:]) cmlenz@339: cmlenz@359: yield w('_F = %r', template.filename) cmlenz@348: yield w() cmlenz@342: cmlenz@826: yield '# Create qnames, attributes, expressions, and suite objects' cmlenz@359: index, counter, values = {}, {}, {} cmlenz@826: for prefix, key, value in _declare_vars(template.stream): cmlenz@359: if not prefix in counter: cmlenz@359: counter[prefix] = 0 cmlenz@359: if key not in index.get(prefix, ()): cmlenz@359: index.setdefault(prefix, {})[key] = counter[prefix] cmlenz@359: counter[prefix] += 1 cmlenz@359: values.setdefault(prefix, []).append(value) cmlenz@359: for prefix in sorted(values.keys()): cmlenz@359: yield w('_%s = (', prefix) cmlenz@359: for value in values[prefix]: cmlenz@359: yield w(' ' + repr(value) + ',') cmlenz@359: yield w(')') cmlenz@342: yield w() cmlenz@342: cmlenz@359: yield w('def generate(ctxt, %s):', cmlenz@359: ', '.join(['f=_F'] + ['%s=_%s' % (n.lower(), n) for n in index])) cmlenz@826: w.shift() cmlenz@826: yield w('push = ctxt.push; pop = ctxt.pop') cmlenz@342: yield w() cmlenz@339: cmlenz@348: # Define macro functions cmlenz@359: defs = [] cmlenz@826: for line in _declare_functions(template.stream, names=defs): cmlenz@342: yield line cmlenz@359: if defs: cmlenz@359: yield w() cmlenz@826: yield w('push({%s})', ', '.join('%r: %s' % (n, n) for n in defs)) cmlenz@359: yield w() cmlenz@342: cmlenz@339: ei, pi = [0], [0] cmlenz@339: for line in _generate(template.stream): cmlenz@339: yield line cmlenz@339: cmlenz@339: cmlenz@339: if __name__ == '__main__': cmlenz@339: import timeit cmlenz@339: from genshi.template import Context, MarkupTemplate cmlenz@339: cmlenz@826: text = """ cmlenz@826: cmlenz@339: cmlenz@348:

cmlenz@342: Hello, $name! cmlenz@348:

cmlenz@342: ${sayhi()} cmlenz@339: cmlenz@339: cmlenz@339: """ cmlenz@339: cmlenz@339: ctxt = Context(hello='world', items=range(10)) cmlenz@339: tmpl = MarkupTemplate(text) cmlenz@339: cmlenz@339: print 'Generated source:' cmlenz@339: for idx, line in enumerate(inline(tmpl)): cmlenz@339: print '%3d %s' % (idx + 1, line) cmlenz@339: cmlenz@339: print cmlenz@339: print 'Interpreted template:' cmlenz@826: print tmpl.generate(ctxt).render('html') cmlenz@339: cmlenz@339: print cmlenz@339: print 'Executed module:' cmlenz@339: module = tmpl.compile() cmlenz@826: print Stream(module.generate(ctxt)).render('html') cmlenz@339: cmlenz@339: print cmlenz@339: print cmlenz@348: t = timeit.Timer('list(tmpl.generate(**data))', ''' cmlenz@339: from genshi.template import Context, MarkupTemplate cmlenz@348: data = dict(hello='world', items=range(10)) cmlenz@339: tmpl = MarkupTemplate("""%s""")''' % text) cmlenz@359: print 'Interpreted: %.2f msec/pass' % (1000 * t.timeit(number=1000) / 1000) cmlenz@339: print cmlenz@339: cmlenz@348: t = timeit.Timer('list(module.generate(Context(**data)))', ''' cmlenz@339: from genshi.core import Stream cmlenz@339: from genshi.template import Context, MarkupTemplate cmlenz@348: data = dict(hello='world', items=range(10)) cmlenz@339: tmpl = MarkupTemplate("""%s""") cmlenz@339: module = tmpl.compile()''' % text) cmlenz@359: print 'Compiled: %.2f msec/pass' % (1000 * t.timeit(number=1000) / 1000) cmlenz@339: print