Templite May 26, 2006
Templite is a fast, light-weight, general purpose, and fully featured templating engine in ~40
lines of code. Unlike many templating engines, this one is not specific to any format (HTML, XML,
etc.). Instead, all the text surrounded by ${
and }$
is evaluated as python code.
Any python code can be used (import modules, define functions, etc.). In order to emit the rendered
text into the template, use the emit
or emitf
functions.
Security note: templating is mainly used in webservers. Having the template stored on the server
is fine, but since the template can contain arbitrary python code (like os.system("rm -rf /")
),
never accept templites from the client side.
Also note that Templite had given birth to Templite+, which is more sophisticated, and you’ll probably want to use the latter.
Features:
- All the parsing and compiling is performed at preprocessing, so rendering the text is fast
- You can escape the
${
delimiter by$\{
, and the}$
delimiter by}\$
- Use inline, as
t = Templite("the template")
or load from a file, ast = Templite.from_file("c:\\thefile.txt")
ort = Templite.from_file(open("c:\\thefile.txt"))
- Easy rendering via
__call__
(less typing)
Code
#
# module: templite.py
#
import re
class Templite(object):
delimiter = re.compile(r"\$\{(.*?)\}\$", re.DOTALL)
additional_namespace = {}
def __init__(self, template):
self.tokens = self.compile(template)
@classmethod
def from_file(cls, file):
"""
loads a template from a file. `file` can be either a string, specifying
a filename, or a file-like object, supporting read() directly
"""
if isinstance(file, basestring):
file = open(file)
return cls(file.read())
@classmethod
def compile(cls, template):
tokens = []
for i, part in enumerate(cls.delimiter.split(template)):
if i % 2 == 0:
if not part: continue
tokens.append((False, part.replace("$\\{", "${")))
else:
if not part.strip(): continue
lines = part.replace("}\\$", "}$").splitlines()
margin = min(len(l) - len(l.lstrip()) for l in lines if l.strip())
realigned = "\n".join(l[margin:] for l in lines)
code = compile(realigned, "<templite %r>" % (realigned[:20],), "exec")
tokens.append((True, code))
return tokens
def render(__self, __namespace = {}, **kw):
"""
renders the template according to the given namespace.
__namespace - a dictionary serving as a namespace for evaluation
**kw - keyword arguments which are added to the namespace
"""
namespace = {}
namespace.update(__self.additional_namespace)
namespace.update(__namespace)
namespace.update(kw)
def emitter(*args):
for a in args: output.append(str(a))
def fmt_emitter(fmt, *args):
output.append(fmt % args)
namespace["emit"] = emitter
namespace["emitf"] = fmt_emitter
output = []
for is_code, value in __self.tokens:
if is_code:
eval(value, namespace)
else:
output.append(value)
return "".join(output)
# shorthand
__call__ = render
Example
>>> demo = r"""
... <html>
... <body>
... ${
... def say_hello(arg):
... emit("hello ", arg, "<br>")
... }$
...
... <table>
... ${
... for i in range(10):
... emit("<tr><td> ")
... say_hello(i)
... emit(" </tr></td>\n")
... }$
... </table>
...
... ${emit("hi")}$
...
... tralala ${if x > 7:
... say_hello("big x")}$ lala
...
... $\{this is escaped starting delimiter
...
... ${emit("this }\$ is an escaped ending delimiter")}$
...
... ${# this is a python comment }$
...
... </body>
... </html>
... """
>>> t = Templite(demo)
>>> print t(x = 8)
<html>
<body>
<table>
<tr><td> hello 0<br> </tr></td>
<tr><td> hello 1<br> </tr></td>
<tr><td> hello 2<br> </tr></td>
<tr><td> hello 3<br> </tr></td>
<tr><td> hello 4<br> </tr></td>
<tr><td> hello 5<br> </tr></td>
<tr><td> hello 6<br> </tr></td>
<tr><td> hello 7<br> </tr></td>
<tr><td> hello 8<br> </tr></td>
<tr><td> hello 9<br> </tr></td>
</table>
hi
tralala hello big x<br> lala
${this is escaped starting delimiter
this }$ is an escaped ending delimiter
</body>
</html>