Source code for kajiki.ir

# -*- coding: utf-8 -*-

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
from itertools import chain
import re

from .util import gen_name, flattener, window
from nine import nine


def generate_python(ir):
    cur_indent = 0
    for node in flattener(ir):
        if isinstance(node, IndentNode):
            cur_indent += 4
        elif isinstance(node, DedentNode):
            cur_indent -= 4
        for line in node.py():
            yield line.indent(cur_indent)


class Node(object):
    def __init__(self):
        self.filename = '<string>'
        self.lineno = 0

    def py(self):  # pragma no cover
        return []

    def __iter__(self):
        yield self

    def line(self, text):
        return PyLine(self.filename, self.lineno, text)


class PassNode(Node):
    def py(self):
        # 'pass' would result in: TypeError: 'NoneType' object is not iterable
        yield self.line('yield ""')


class HierNode(Node):
    '''Base for nodes that contain an indented Python block (def, for, if etc.)
    '''
    def __init__(self, body):
        super(HierNode, self).__init__()
        self.body = tuple(x for x in body if x is not None)

    def body_iter(self):
        for x in optimize(flattener(map(flattener, self.body))):
            yield x

    def __iter__(self):
        yield self
        yield IndentNode()
        for x in self.body_iter():
            yield x
        yield DedentNode()


class IndentNode(Node):
    pass


class DedentNode(Node):
    pass


[docs]class TemplateNode(HierNode): """Represents the root Intermediate Representation node of a template. Iterating over this will generate the Python code for the class that provides all the functions that are part of the template including the ``__main__`` function that represents the template code itself. The generated class will then be passed to :meth:`kajiki.template.Template` to create a :class:`kajiki.template._Template` subclass that has the ``render`` method to render the template. """ class TemplateTail(Node): def py(self): yield self.line('template = kajiki.Template(template)') def __init__(self, mod_py=None, defs=None): super(TemplateNode, self).__init__(defs) if mod_py is None: mod_py = [] if defs is None: defs = [] self.mod_py = [x for x in mod_py if x is not None] def py(self): yield self.line('class template:') def __iter__(self): for x in flattener(self.mod_py): yield x for x in super(TemplateNode, self).__iter__(): yield x yield self.TemplateTail()
class ImportNode(Node): def __init__(self, tpl_name, alias=None): super(ImportNode, self).__init__() self.tpl_name = tpl_name self.alias = alias def py(self): yield self.line( 'local.__kj__.import_(%r, %r, self.__globals__)' % ( self.tpl_name, self.alias)) class IncludeNode(Node): def __init__(self, tpl_name): super(IncludeNode, self).__init__() self.tpl_name = tpl_name def py(self): yield self.line( 'yield local.__kj__.import_(%r, None, self.__globals__).__main__()' % ( self.tpl_name)) class ExtendNode(Node): def __init__(self, tpl_name): super(ExtendNode, self).__init__() self.tpl_name = tpl_name def py(self): yield self.line( 'yield local.__kj__.extend(%r).__main__()' % ( self.tpl_name)) class DefNode(HierNode): prefix = '@kajiki.expose' def __init__(self, decl, *body): super(DefNode, self).__init__(body) self.decl = decl def py(self): yield self.line(self.prefix) yield self.line('def %s:' % (self.decl)) def __iter__(self): yield self yield IndentNode() is_empty = True for x in self.body_iter(): yield x is_empty = False if is_empty: # In Python, a function without a body is a SyntaxError. yield PassNode() # Prevent creation of a function without a body yield DedentNode() class InnerDefNode(DefNode): prefix = '@__kj__.flattener.decorate' class CallNode(HierNode): class CallTail(Node): def __init__(self, call): super(CallNode.CallTail, self).__init__() self.call = call def py(self): yield self.line('yield ' + self.call) def __init__(self, caller, callee, *body): super(CallNode, self).__init__(body) fname = gen_name() self.decl = caller.replace('$caller', fname) self.call = callee.replace('$caller', fname) def py(self): yield self.line('@__kj__.flattener.decorate') yield self.line('def %s:' % (self.decl)) def __iter__(self): yield self yield IndentNode() for x in self.body_iter(): yield x yield DedentNode() yield self.CallTail(self.call) class ForNode(HierNode): def __init__(self, decl, *body): super(ForNode, self).__init__(body) self.decl = decl def py(self): yield self.line('for %s:' % (self.decl)) class WithNode(HierNode): assignment_pattern = re.compile(r'(?:^|;)\s*([^;=]+)=(?!=)', re.M) class WithTail(Node): def __init__(self, var_names): super(WithNode.WithTail, self).__init__() self.var_names = var_names def py(self): yield self.line('(%s,) = local.__kj__.pop_with()' % (','.join(self.var_names),)) # yield self.line('if %s == (): del %s' % (v, v)) def __init__(self, vars, *body): super(WithNode, self).__init__(body) assignments = [] matches = self.assignment_pattern.finditer(vars) for m1, m2 in window(chain(matches, [None]), 2): lhs = m1.group(1).strip() rhs = vars[m1.end():(m2.start() if m2 else len(vars))] assignments.append((lhs, rhs)) self.vars = assignments self.var_names = [l for l, _ in assignments] def py(self): yield self.line( 'local.__kj__.push_with(locals(), [%s])' % ( ','.join('"%s"' % k for k in self.var_names),)) for k, v in self.vars: yield self.line('%s = %s' % (k, v)) def __iter__(self): yield self for x in self.body_iter(): yield x yield self.WithTail(self.var_names) class SwitchNode(HierNode): class SwitchTail(Node): def py(self): yield self.line('local.__kj__.pop_switch()') def __init__(self, decl, *body): super(SwitchNode, self).__init__(body) self.decl = decl def py(self): yield self.line('local.__kj__.push_switch(%s)' % self.decl) yield self.line('if False: pass') def __iter__(self): yield self for x in self.body_iter(): yield x yield self.SwitchTail() class CaseNode(HierNode): def __init__(self, decl, *body): super(CaseNode, self).__init__(body) self.decl = decl def py(self): yield self.line('elif local.__kj__.case(%s):' % self.decl) class IfNode(HierNode): def __init__(self, decl, *body): super(IfNode, self).__init__(body) self.decl = decl def py(self): yield self.line('if %s:' % self.decl) class ElseNode(HierNode): def __init__(self, *body): super(ElseNode, self).__init__(body) def py(self): yield self.line('else:') class TextNode(Node): '''Node that outputs Python literals.''' def __init__(self, text, guard=None): super(TextNode, self).__init__() self.text = text self.guard = guard def py(self): s = 'yield %r' % self.text if self.guard: yield self.line('if %s: %s' % (self.guard, s)) else: yield self.line(s) class TranslatableTextNode(TextNode): def py(self): text = self.text.strip() if text: s = 'yield local.__kj__.gettext(%r)' % self.text else: s = 'yield %r' % self.text if self.guard: yield self.line('if %s: %s' % (self.guard, s)) else: yield self.line(s) class ExprNode(Node): '''Node that contains a Python expression to be evaluated when the template is executed. ''' def __init__(self, text, safe=False): super(ExprNode, self).__init__() self.text = text self.safe = safe def py(self): if self.safe: yield self.line('yield %s' % self.text) else: yield self.line('yield self.__kj__.escape(%s)' % self.text) class AttrNode(HierNode): '''Node that renders HTML/XML attributes.''' class AttrTail(Node): def __init__(self, parent): super(AttrNode.AttrTail, self).__init__() self.p = parent def py(self): gen = self.p.genname x = gen_name() yield self.line("%s = self.__kj__.collect(%s())" % (gen, gen)) yield self.line( 'for %s in self.__kj__.render_attrs({%r:%s}, %r):' % (x, self.p.attr, gen, self.p.mode)) yield self.line(' yield %s' % x) def __init__(self, attr, value, guard=None, mode='xml'): super(AttrNode, self).__init__(value) self.attr = attr self.guard = guard self.mode = mode self.genname = gen_name() def py(self): yield self.line('def %s():' % self.genname) def __iter__(self): if self.guard: new_body = IfNode( self.guard, AttrNode(self.attr, value=self.body, mode=self.mode)) for x in new_body: yield x else: yield self yield IndentNode() if self.body: for part in self.body_iter(): yield part else: yield TextNode('') yield DedentNode() yield self.AttrTail(self) class AttrsNode(Node): def __init__(self, attrs, guard=None, mode='xml'): super(AttrsNode, self).__init__() self.attrs = attrs self.guard = guard self.mode = mode def py(self): x = gen_name() def _body(): yield self.line( 'for %s in self.__kj__.render_attrs(%s, %r):' % ( x, self.attrs, self.mode)) yield self.line(' yield %s' % x) if self.guard: yield self.line('if %s:' % self.guard) for l in _body(): yield l.indent() else: for l in _body(): yield l class PythonNode(Node): def __init__(self, *body): super(PythonNode, self).__init__() self.module_level = False blocks = [] for b in body: assert isinstance(b, TextNode) blocks.append(b.text) text = ''.join(blocks) if text[0] == '%': self.module_level = True text = text[1:] self.lines = list(self._normalize(text)) def py(self): for line in self.lines: yield self.line(line) def _normalize(self, text): if text.startswith('#\n'): text = text[2:] prefix = None for line in text.splitlines(): if prefix is None: rest = line.lstrip() prefix = line[:len(line) - len(rest)] assert line.startswith(prefix) yield line[len(prefix):] def optimize(iter_node): last_node = None for node in iter_node: if (type(node) == TextNode and type(last_node) == TextNode and last_node.guard == node.guard): last_node.text += node.text # Erase this node by not yielding it. continue if last_node is not None: yield last_node last_node = node if last_node is not None: yield last_node @nine class PyLine(object): def __init__(self, filename, lineno, text, indent=0): self._filename = filename self._lineno = lineno self._text = text self._indent = indent def indent(self, sz=4): return PyLine(self._filename, self._lineno, self._text, self._indent + sz) def __str__(self): return (' ' * self._indent) + self._text if self._lineno: return (' ' * self._indent) + self._text + '\t# %s:%d' % ( self._filename, self._lineno) else: return (' ' * self._indent) + self._text def __repr__(self): return '%s:%s %s' % (self._filename, self._lineno, self)