Source code for kajiki.ir

import re
from itertools import chain

from .util import flattener, gen_name, window


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().__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().__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().__iter__(): yield x yield self.TemplateTail()
class ImportNode(Node): def __init__(self, tpl_name, alias=None): super().__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().__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().__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().__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().__init__() self.call = call def py(self): yield self.line("yield " + self.call) def __init__(self, caller, callee, *body): super().__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().__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().__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().__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 = [lhs for lhs, _ 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().__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().__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().__init__(body) self.decl = decl def py(self): yield self.line("if %s:" % self.decl) class ElseNode(HierNode): def __init__(self, *body): super().__init__(body) def py(self): yield self.line("else:") class TextNode(Node): """Node that outputs Python literals.""" def __init__(self, text, guard=None): super().__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().__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().__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().__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().__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 line in _body(): yield line.indent() else: for line in _body(): yield line class PythonNode(Node): def __init__(self, *body): super().__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 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)