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.



# 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)
    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())
    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("$\\{", "${")))
                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 = {}
        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)
        return "".join(output)
    # shorthand
    __call__ = render


>>> 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)


            <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>



        tralala hello big x<br> lala

        ${this is escaped starting delimiter

        this }$ is an escaped ending delimiter