You've already forked gitified_old_clb_svn
248 lines
6.7 KiB
Python
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()) |