A light-weight, fully functional, general purpose templating engine, in just 40 lines of code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | import re
class Templite(object):
delimiter = re.compile(r"\$\{(.*?)\}\$", re.DOTALL)
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 part:
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 = None, **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 = {}
if __namespace: namespace.update(__namespace)
if kw: 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:
---------
>>> from templite import Templite
>>>
>>> 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>
>>>
|
Templite -- A light-weight, fully functional, general purpose templating engine, allowing you to embed python code directly into your text. This engine is suitable for any templating (not only HTML/XML), and is minimal (40 lines of code!) and fast (all preprocessing is done in "compile time")
All text between ${
and }$
is considered python code, and is evaluated when the Templite is rendered. You can escape the ${
delimiter by $\{
and the }$
delimiter by }\$
.
Emitting output is done with the emit() function, which accepts any number of arguments, converts them to a string, and appends to the output where the template was located.
Security notice: IT'S NOT SECURE, as the template-generating code is arbitrary python code. So be sure you don't just evaluate user-provided Templites, at least not before taking a look at them. It's meant to be a light and fast templating engine, for trusted server side code (generating reports, etc.).
There are a couple of errors in published code. I've found in the following lines a missing '[' and ']' in string comprehension
padding = min([len(l) - len(l.lstrip()) for l in lines if l.strip()])
unpadded = "\n".join([l[padding:] for l in lines])
Thanks for the code. Nice indeed
Oscar
Shrewd! And totally cool.
these are generator expressions. python 2.4 has generator expressions, i.e.
<generator object at 0x009ED0F8>
-tomer
Escaping delimiters. Having to escape only '${' outside code and only '}$' inside code can be a bit confusing, and IMO should be more noticeably and clearly noted in the documentation (or changed). I admit that the choice of delimiters is such that not much escaping will probably be needed in day-to-day use; still, someone getting the escaping wrong could have a time understanding why.
Personally I would prefer to have to escape both '${' and '}$' everywhere, for better uniformity. Of course, this is a personal preference.
Very nice. That's just the right amount of markup for embedding Python into html documents.
exec instead of eval. According to Python doc "eval(value, namespace)" should only be used for value compiled as "eval" and for "exec" you should use instead "exec value in namespace". Which worked.
Revised version where you do not need to break your template within block statements.