ASPN ActiveState Programmer Network  
ActiveState, a division of Sophos
/ Home / Perl / PHP / Python / Tcl / XSLT /
/ Safari / My ASPN /
Cookbooks | Documentation | Mailing Lists | Modules | News Feeds | Products | User Groups
Submit Recipe
My Recipes

All Recipes
All Cookbooks


View by Category

Title: Templite
Submitter: tomer filiba (other recipes)
Last Updated: 2006/05/16
Version no: 1.2
Category: Text

 

5 stars 2 vote(s)


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

>>>

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



Highest rated recipes:

1. A simple XML-RPC server

2. Web service accessible ...

3. IPy Notify

4. Changing return value ...

5. Quantum Superposition

6. Pickle objects under ...

7. Generalized delegates ...

8. Reorder a sequence (uses ...

9. Setting Win32 System ...

10. ObjectMerger




Privacy Policy | Email Opt-out | Feedback | Syndication
© 2006 ActiveState Software Inc. All rights reserved.