# Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2.0, # as published by the Free Software Foundation. # # This program is also distributed with certain software (including # but not limited to OpenSSL) that is licensed under separate terms, as # designated in a particular file or component or in included license # documentation. The authors of MySQL hereby grant you an additional # permission to link the program and your derivative works with the # separately licensed software that they have included with MySQL. # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See # the GNU General Public License, version 2.0, for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import re debug = 0 class MiniTemplate: def __init__(self, templ): self._tokens = re.split("({{[^}]+}}|\[\[[^]]+\]\])", templ) def t2l(self, tok): return "line %s " % sum([tok.count("\n") for tok in self._tokens[:tok]]) def render_(self, data, i, result, context): assert data is not None if debug: import traceback print(("ENTER render_() from %s" % traceback.format_stack()[-2].split(",", 1)[-1].strip().split("\n")[0].strip())) def get(d, key, default=""): for k in key.split("."): if d is None: #raise ValueError("%s not in %s" % (key, od.keys())) return default if hasattr(d, "__getitem__"): d = d[k] else: d = getattr(d, k) return d while i < len(self._tokens): orig_token = token = self._tokens[i] if debug: print(("--"*len(context)+"> process %s: %s, token: %s (%i)" % (self.t2l(i), ".".join(context), token.strip(), i))) if token.startswith("{{") and token.endswith("}}"): token = token[2:-2] try: if token[0] == '#': l = get(data, token[1:]) if l is not None: out = str(len(l)) else: out = "0" else: out = get(data, token) except KeyError: raise KeyError("%s: No value for key %s. context: %s possible: %s" % (self.t2l(i), orig_token, ".".join(context), list(data.keys()))) except ValueError as e: raise ValueError("%s: %s. context: %s possible: %s" % (self.t2l(i), e, ".".join(context), list(data.keys()))) if out is not None: result.append(out) elif token.startswith("[[") and token.endswith("]]"): if debug: print(("Found block %s" % token)) token = token[2:-2] if token[0] in ("/", "!") and token[1:] == (context[-1] if not context[-1].startswith("[") else context[-2]): if debug: print(("%s: leaving context %s through %s at %s" % (self.t2l(i), ".".join(context), token, i))) if token[0] == "!": # skip until the real end of the block count = 1 i += 1 while i < len(self._tokens): if self._tokens[i] == "[[/"+token[1:]+"]]": count -= 1 if count == 0: break elif self._tokens[i] in ("[["+token[1:]+"]]", "[[?"+token[1:]+"]]"): count += 1 i += 1 return i elif token[0] == "?": token = token[1:] enter = False if token.startswith("if|"): try: enter = eval(token[3:], data, data) if debug: print(("%s: evaluated %s in context %s to %s" % (self.t2l(i), token, list(data.keys()), enter))) token = "if" except Exception as exc: print(("%s: Error evaluating %s in context %s: %s" % (self.t2l(i), token, list(data.keys()), exc))) raise if enter or token in data and get(data, token): if debug: print(("%s: entering context %s at %s" % (self.t2l(i), ".".join(context + [token]), i+1))) i = self.render_(data, i+1, result, context+[token]) else: if debug: print(("%s: context %s at %s has no value for block %s, trynig to find !%s or /%s" % (self.t2l(i), ".".join(context + [token]), i+1, token, token, token))) count = 1 i += 1 while i < len(self._tokens): if self._tokens[i] == "[[!"+token+"]]" and count == 1: i = self.render_(data, i+1, result, context + [token]) i += 1 count = 0 break elif self._tokens[i] == "[[/"+token+"]]": count -= 1 if count == 0: break elif self._tokens[i] in ("[["+token+"]]", "[[?"+token+"]]"): count += 1 i += 1 if count != 0: print(("/%s not found!" % token)) else: try: sub = get(data, token) except KeyError: raise KeyError("%s: No value for key %s. context: %s possible: %s" % (self.t2l(i), orig_token, ".".join(context), list(data.keys()))) if type(sub) is list: if debug: print(("%s: token %s in context %s is a list of size %i" % (self.t2l(i), ".".join(context), token, len(sub)))) k = i for j, item in enumerate(sub): itemd = dict(item) itemd[":#"] = str(j+1) if j < len(sub)-1: itemd["needsep"] = 1 else: itemd["needsep"] = 0 if debug: print(("%s: entering lcontext %s at %s" % (self.t2l(i), ".".join(context + [token, "[%s]"%j]), i+1))) k = self.render_(itemd, i+1, result, context + [token, "[%s]"%j]) if not sub: # empty list, go through tokens until we find the closing one count = 1 k += 1 while k < len(self._tokens): if debug: print(("empty list %s, skip %s (%i)" % (token, self._tokens[k], count))) if self._tokens[k] == "[[/"+token+"]]": count -= 1 if count == 0: break elif self._tokens[k] in ("[["+token+"]]", "[[?"+token+"]]"): count += 1 k += 1 i = k else: if debug: print(("%s: entering context %s at %s" % (self.t2l(i), ".".join(context + [token]), i+1))) i = self.render_(sub, i+1, result, context + [token]) else: result.append(token) i += 1 if debug: print(("leaving at token %i" % i)) return i def render(self, data): l = [] self.render_(data, 0, l, []) return "".join([str(s) for s in l]) if __name__ == "__main__": template = """ {{title}} [[objects]] {{name}} There are {{#subobjects}} objects in this object: {{:#}}.[[subobjects]]{{:#}} - {{name}} [[?error]]ERROR![[/error]][[/subobjects]] Type: {{thing.type}} [[thing]] Value: {{value}} [[/thing]] [[/objects]] """ data = { "title" : "Title", "name" : "Some Name", "objects" : [ { "name" : "object1", "subobjects" : [ { "name" : "subobject1", "error" : 1}, ], "thing" : { "type" : "int", "value" : 12345 } }, { "name" : "object2", "subobjects" : [ { "name" : "subobject1of2", "error" : 0 }, ], "thing" : { "type" : "str", "value" : "qqqq" } }, ] } tem = MiniTemplate(template) print((tem.render(data).replace("\\\n", "")))