|
Description:
A light-weight, fully functional, general purpose templating engine, in just 40 lines of code
Source: Text Source
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)
__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>
>>>
Discussion:
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.).
|
|
Add comment
|
|
Number of comments: 6
There are a couple of errors in published code, Oscar Micheli, 2006/05/15
I've found in the following lines a missing '[' and ']' in string comprehension
1. padding = min([len(l) - len(l.lstrip()) for l in lines if l.strip()])
2. unpadded = "\n".join([l[padding:] for l in lines])
Thanks for the code. Nice indeed
Oscar
Add comment
these are generator expressions, tomer filiba, 2006/05/16
python 2.4 has generator expressions, i.e.
>>> (x for x in range(10))
<generator object at 0x009ED0F8>
-tomer
Add comment
exec instead of eval, Ehud Ben-Reuven, 2006/09/17
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.
Add comment
Shrewd!, Ori Peleg, 2006/05/15
And totally cool.
Add comment
Escaping delimiters, Tal Einat, 2006/05/17
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.
Add comment
Josiah Carlson, 2006/08/06
Very nice. That's just the right amount of markup for embedding Python into html documents.
Add comment
|
|
|