Blob Blame Raw

import os
import html
import markdown
os.path.dirname(os.path.abspath(__file__))


def dictGet(d, path):
    for k in str(path).split('.'):
        if type(d) is list or type(d) is tuple:
            d = d[int(k)]
            continue
        elif type(d) is dict:
            if (not k in d) and ('ex' in d):
                print("query ex for field: %s (%s)" % (path, k))
                d = d['ex'](d)
            if k in d:
                d = d[k]
                continue
        print("variable not found: %s (%s)" % (path, k))
        return None
    return d

def tostr(s):
    return '' if s is None else str(s) 
def text2html(t):
    return html.escape(tostr(t)).replace("\n", "<br />")
def md2html(t):
    return markdown.markdown(tostr(t))


tabsym = ' -- '


class TemplateText:
    def __init__(self, text = ''):
        self.text = text
    def write(self, writer, context):
        writer.write(self.text)
    def optimize(self):
        return self if self.text else None
    def dbgPrint(self, tab):
        print("%stext(%s)" % (tab, self.text.replace("\n", "\\n")))

class TemplateVarHtml:
    def __init__(self, varpath = None):
        self.varpath = varpath
    def write(self, writer, context):
        writer.write(tostr(dictGet( context, self.varpath )))
    def optimize(self):
        return self
    def dbgPrint(self, tab):
        print("%svarHtml(%s)" % (tab, self.varpath))

class TemplateVarMd:
    def __init__(self, varpath = None):
        self.varpath = varpath
    def write(self, writer, context):
        writer.write(md2html(dictGet( context, self.varpath )))
    def optimize(self):
        return self
    def dbgPrint(self, tab):
        print("%svarMd(%s)" % (tab, self.varpath))

class TemplateVarText:
    def __init__(self, varpath = None):
        self.varpath = varpath
    def write(self, writer, context):
        writer.write(text2html(dictGet( context, self.varpath )))
    def optimize(self):
        return self
    def dbgPrint(self, tab):
        print("%svarText(%s)" % (tab, self.varpath))

class TemplateVarTextMultiline:
    def __init__(self, varpath = None):
        self.varpath = varpath
    def write(self, writer, context):
        writer.write(text2html(dictGet( context, self.varpath )).replace("\n", "<br />"))
    def optimize(self):
        return self
    def dbgPrint(self, tab):
        print("%svarTextMultiline(%s)" % (tab, self.varpath))

class TemplateVarCount:
    def __init__(self, varpath = None):
        self.varpath = varpath
    def write(self, writer, context):
        v = dictGet( context, self.varpath )
        writer.write(str(len(v) if hasattr(v, '__len__') else 0))
    def optimize(self):
        return self
    def dbgPrint(self, tab):
        print("%svarCount(%s)" % (tab, self.varpath))

class TemplateVarField:
    def __init__(self, varpath = None):
        self.varpath = varpath
    def write(self, writer, context):
        f = self.varpath.split('.')[-1]
        writer.write("<p><b>%s:</b> %s</p>" % (
            text2html( f ),
            text2html( dictGet(context, self.varpath) ) ))
    def optimize(self):
        return self
    def dbgPrint(self, tab):
        print("%svarField(%s)" % (tab, self.varpath))

class TemplateIf:
    def __init__(self, varpath = None, sub = None, alt = None):
        self.varpath = varpath
        self.sub = sub
        self.alt = alt
    def write(self, writer, context):
        if dictGet(context, self.varpath):
            if self.sub: self.sub.write(writer, context)
        else:
            if self.alt: self.alt.write(writer, context)
    def optimize(self):
        self.sub = self.sub.optimize() if self.sub else None
        self.alt = self.alt.optimize() if self.alt else None
        return self if self.sub or self.alt else None
    def dbgPrint(self, tab):
        print("%sif(%s):" % (tab, self.varpath))
        if self.sub:
            self.sub.dbgPrint(tab + tabsym)
        if self.alt:
            print("%selse:" % tab)
            self.alt.dbgPrint(tab + tabsym)


class TemplateWith:
    def __init__(self, varpath = None, varname = None, sub = None):
        self.varpath = varpath
        self.varname = varname
        self.sub = sub
    def write(self, writer, context):
        if self.sub:
            context = dict(context)
            context[self.varname] = dictGet(context, self.varpath)
            self.sub.write(writer, context)
    def optimize(self):
        self.sub = self.sub.optimize() if self.sub else None
        return self if self.sub else None
    def dbgPrint(self, tab):
        print("%swith(%s:%s):" % (tab, self.varpath, self.varname))
        if self.sub: self.sub.dbgPrint(tab + tabsym)


class TemplateComment:
    def __init__(self, text = None):
        self.text = text
    def write(self, writer, context):
        pass
    def optimize(self):
        return None
    def dbgPrint(self, tab):
        print("%scomment(%s)" % (texttab, self.text))


class TemplateLoop:
    def __init__(self, varpath = None, keyvarname = None, varname = None, sub = None, sep = None):
        self.varpath = varpath
        self.keyvarname = keyvarname
        self.varname = varname
        self.sub = sub
        self.sep = sep
    def writeItem(self, writer, context, first, k, v):
        if self.sep and not first:
            self.sep.write(writer, context)
        if self.sub:
            ctx = dict(context)
            ctx[self.keyvarname] = k
            ctx[self.varname] = v
            self.sub.write(writer, ctx)
    def write(self, writer, context):
        if self.sep or self.sub:
            vv = dictGet(context, self.varpath)
            if type(vv) is dict:
                f = True
                keys = list(vv.keys())
                keys.sort()
                for k in keys:
                    self.writeItem(writer, context, f, k, vv[k])
                    f = False
            elif hasattr(vv, '__iter__'):
                idx = 0
                for v in vv:
                    self.writeItem(writer, context, not idx, idx, v)
                    idx = idx + 1
    def optimize(self):
        self.sub = self.sub.optimize() if self.sub else None
        self.sep = self.sep.optimize() if self.sep else None
        return self if self.sub or self.sep else None
    def dbgPrint(self, tab):
        print("%sloop(%s:%s:%s):" % (tab, self.varpath, self.keyvarname, self.varname))
        if self.sub:
            self.sub.dbgPrint(tab + tabsym)
        if self.sep:
            print("%ssep:" % tab)
            self.sep.dbgPrint(tab + tabsym)

class Template:
    def __init__(self):
        self.items = []
    def write(self, writer, context):
        for i in self.items:
            i.write(writer, context)
    def optimize(self):
        it = []
        for i in self.items:
            ii = i.optimize()
            if ii:
                if it and type(ii) is TemplateText and type(it[-1]) is TemplateText:
                    it[-1].text = it[-1].text + ii.text
                else:
                    it.append(ii)
        self.items = it
        if len(self.items) == 1: return self.items[0]
        return self if self.items else None
    def dbgPrint(self, tab):
        print("%stemplate:" % tab)
        for i in self.items:
            i.dbgPrint(tab + tabsym)


class TplLoader:
    @staticmethod
    def loadtext(filename):
        filename = os.path.abspath(filename)
        path = os.path.dirname(filename)
        res = ''
        text = ''
        with open(filename, 'r') as f: text = f.read()
        ii = 0
        i = 0
        while i < len(text):
            if text[i:i+9] == '{include:':
                res = res + text[ii:i]
                a = i+9
                b = text.find('}', a)
                if b<0: raise Exception("include parse error: %s:%d" % (filename, a))
                fn = text[a:b]
                res = res + TplLoader.loadtext(path +'/' + fn)
                i = b+1
                ii = i
            else:
                i = i + 1
        return res + text[ii:i]

    def __init__(self, filename):
        self.filename = os.path.abspath(filename)
        self.text = TplLoader.loadtext(self.filename)
        self.ptr = 0

    def error(self):
        with open('tplerr.txt', 'w') as f: f.write(self.text)
        raise Exception("parse error: %s:%d" % (self.filename, self.ptr))

    def readKey(self, k):
        if self.text[self.ptr:self.ptr + len(k)] == k:
            self.ptr = self.ptr + len(k)
            return True
        return False

    def readVarname(self):
        i = self.ptr
        while i < len(self.text) and (self.text[i].isalnum() or self.text[i] in '_'):
            i = i + 1
        if i == self.ptr: return None
        i, self.ptr = self.ptr, i
        return self.text[i:self.ptr]

    def readVarpath(self):
        i = self.ptr
        varpath = None
        while True:
            varname = self.readVarname()
            if not varname:
                if varpath: self.error()
                break
            varpath = varpath + '.' + varname if varpath else varname
            if not self.readKey('.'): break
        if not varpath:
            self.ptr = i
        return varpath

    def loadVarText(self):
        if not self.readKey("{:"): return None
        varpath = self.readVarpath()
        if not varpath or not self.readKey("}"): self.error()
        return TemplateVarText(varpath)

    def loadVarTextMultiline(self):
        if not self.readKey("{t:"): return None
        varpath = self.readVarpath()
        if not varpath or not self.readKey("}"): self.error()
        return TemplateVarTextMultiline(varpath)

    def loadVarHtml(self):
        if not self.readKey("{html:"): return None
        varpath = self.readVarpath()
        if not varpath or not self.readKey("}"): self.error()
        return TemplateVarHtml(varpath)

    def loadVarMd(self):
        if not self.readKey("{md:"): return None
        varpath = self.readVarpath()
        if not varpath or not self.readKey("}"): self.error()
        return TemplateVarMd(varpath)

    def loadVarCount(self):
        if not self.readKey("{count:"): return None
        varpath = self.readVarpath()
        if not varpath or not self.readKey("}"): self.error()
        return TemplateVarCount(varpath)

    def loadVarField(self):
        if not self.readKey("{f:"): return None
        varpath = self.readVarpath()
        if not varpath or not self.readKey("}"): self.error()
        return TemplateVarField(varpath)

    def loadIf(self):
        if not self.readKey("{if:"): return None
        varpath = self.readVarpath()
        if not varpath or not self.readKey("}"): self.error()
        res = TemplateIf(varpath, Template(), Template())
        e = False
        while True:
            if self.readKey("{endif}"): break
            if self.readKey("{else}"):
                if e: self.error()
                e = True
                continue
            item = self.loadTemplate()
            if not item: self.error()
            if e:
                res.alt.items.append(item)
            else:
                res.sub.items.append(item)
        return res

    def loadWith(self):
        if not self.readKey("{with:"): return None
        varpath = self.readVarpath()
        if not varpath or not self.readKey(":"): self.error()
        varname = self.readVarname()
        if not varname or not self.readKey("}"): self.error()
        res = TemplateWith(varpath, varname, Template())
        while True:
            if self.readKey("{endwith}"): break
            item = self.loadTemplate()
            if not item: self.error()
            res.sub.items.append(item)
        return res

    def loadComment(self):
        if not self.readKey("{comment}"): return None
        res = TemplateComment()
        i = self.text.find('{endcomment}', self.ptr)
        if i < 0:
            i = len(self.text)
        res = TemplateComment(self.text[self.ptr:i])
        self.ptr = i
        if not self.readKey("{endcomment}"): self.error()
        return res

    def loadLoop(self):
        if not self.readKey("{loop:"): return None
        varpath = self.readVarpath()
        if not varpath or not self.readKey(":"): self.error()
        keyvarname = self.readVarname()
        if not self.readKey(":"): self.error()
        varname = self.readVarname()
        if not varname or not self.readKey("}"): self.error()
        res = TemplateLoop(varpath, keyvarname, varname, Template(), Template())
        s = False
        while True:
            if self.readKey("{endloop}"): break
            if self.readKey("{sep}"):
                if s: self.error()
                s = True
                continue
            item = self.loadTemplate()
            if not item: self.error()
            if s:
                res.sep.items.append(item)
            else:
                res.sub.items.append(item)
        return res

    def loadTemplate(self):
        t = self.loadVarText()
        if t: return t
        t = self.loadVarTextMultiline()
        if t: return t
        t = self.loadVarHtml()
        if t: return t
        t = self.loadVarMd()
        if t: return t
        t = self.loadVarCount()
        if t: return t
        t = self.loadVarField()
        if t: return t
        t = self.loadIf()
        if t: return t
        t = self.loadWith()
        if t: return t
        t = self.loadComment()
        if t: return t
        t = self.loadLoop()
        if t: return t

        i = self.text.find('{', self.ptr)
        if i == self.ptr:
            i = self.text.find('{', self.ptr+1)
        if i < 0:
            i = len(self.text)
        if i <= self.ptr:
            return None
        i, self.ptr = self.ptr, i
        return TemplateText(self.text[i:self.ptr])

    @staticmethod
    def load(filename):
        l = TplLoader(filename)
        t = Template()
        while True:
            tt = l.loadTemplate()
            if not tt: break
            t.items.append(tt)
        if l.ptr < len(l.text): l.error()
        t = t.optimize()
        return t if t else TemplateText()