Files
2016-12-07 11:48:54 +00:00

248 lines
6.7 KiB
Python

'''
utpl, or microtemplate is a very tiny template library. It is more an extension to the
standard python formatting.
Merging is done with the merge method. This expects a dictionary of templates, a context an
the template name to start rendering.
Templates exist out of simple formatting statements, like %(test)s, but also requesting members
with the dot '.' notation is supported, e.g. person.name.
Other supported constructs:
%(foreach:<iterable>,<item-var-name>,<call template>[,<seperator template>])s
%(call:<call template>)
%(if:<condition>,<if true call template>,[<condition>,<if true call template>,]*
[<else call template>])
For example:
@code
class Person():
def __init__(self, name, age):
self.name = name
self.age = age
ctx = {
'persons' : (
Person('John', 21),
Person('Peter', 25),
Person('Mary', 30)
)
}
tpl = {
'main' : 'Lets say hi!\n%(foreach:persons,person,show)s',
'show' : 'Hello %(person.name)s, you are %(person.age)!'
}
sio = StringIO()
utpl.merge(tpl, 'main', ctx, sio)
print sio.get_value()
@endcode
@author: Vincen van Beveren
'''
from __future__ import print_function
try:
from io import StringIO
except:
from cStringIO import StringIO
try:
unicode("")
except:
unicode = lambda x : x
class __Renderer(object):
def __init__(self, templates, context):
self.__context = [ context ]
self.__templates = templates
self.__templates.update({
'_c' : ',', # comma
'_ce' : ',\n', # comma enter
'_cs' : ', ', # comma space
'_s' : ' ', # space
'_e' : '\n' # enter
})
def __push(self, context):
self.__context.append(context)
def __pop(self):
self.__context.pop()
def _merge(self, tplname, outstream):
try:
outstream.write(unicode(self.__templates[tplname] % self))
except Exception as e:
raise #RuntimeError("Rendering of template %s failed" % tplname, e)
def __execcmd(self, cmd, params):
sio = StringIO()
if cmd == 'foreach':
items = self.__get(params[0])
varname = params[1]
tplname = params[2]
jointpl = params[3] if len(params) == 4 else None
ctx = {}
self.__push(ctx)
l = len(items)
for i in range(l):
ctx[varname] = items[i]
self._merge(tplname, sio)
if jointpl and i < l - 1:
self._merge(jointpl, sio)
self.__pop()
elif cmd == 'call':
tplname = params[0]
self._merge(tplname, sio)
elif cmd == "if":
while len(params) >= 2:
expr = self.__get(params[0])
if expr:
self._merge(params[1], sio)
break
params = params[2:]
if len(params) == 1:
self._merge(params[0], sio)
elif cmd == "sel":
while len(params) >= 2:
expr = self.__get(params[0])
if expr:
sio.write(params[1])
break
params = params[2:]
if len(params) == 1:
sio.write(params[0])
else:
raise KeyError("Command: %s", cmd)
return sio.getvalue()
def __getitem__(self, directive):
column = directive.find(':')
if column != -1:
cmd = directive[0:column]
if column != len(cmd) - 1:
params = directive[column + 1:].split(",")
else:
params = []
return self.__execcmd(cmd, params)
return self.__get(directive)
def __resolve(self, obj, name):
v = None
f = False
if hasattr(obj, "__getitem__"):
if name in obj:
f = True
v = obj[name]
try:
# for sequence types, just see if this does not crash
if int(name) < len(obj):
f = True
v = obj[int(name)]
except:
pass
else:
if hasattr(obj, name):
f = True
v = getattr(obj,name)
if f and callable(v):
v = v()
return f, v
def __get(self, expression):
dot = expression.find('.')
if dot >= 0:
tail = expression[dot + 1:].split(".")
expression = expression[0:dot]
else:
tail = []
found = False
val = None
for ctx in reversed(self.__context):
found, val = self.__resolve(ctx, expression)
if found:
break
if not found:
raise KeyError(expression)
for t in tail:
found, cur = self.__resolve(val, t)
if not found:
raise KeyError("Member %s of %s (type=%s)" % (t, val,type(val)))
val = cur
return val
def merge(templates, starttpl, context, outstream):
"""
Merge a template with a context to the outstream. StartTpl is the template to begin
the rendering process.
@param templates A dictionary with templates
@param starttpl The template to start rendering
@param context The context to use for rendering
@param outstream The output stream to write to.
"""
ctx = __Renderer(templates, context)
ctx._merge(starttpl, outstream)
if __name__ == "__main__":
sio = StringIO()
templates = { "root" : """\
hello %(name)s
What would you like to eat? We have
%(foreach:foods,food,item)s
""",
"item": "- %(food)s (its good %(name)s)\n"
}
context = {
'name' : 'Vincent',
'foods' : (
'lemon pie',
'cheese cake',
'carrot cake'
)
}
merge(templates, "root", context, sio)
print(sio.getvalue())