Trivial Templates

A template is used to generate a text string based on some data parameters. A simple template for a letter:

Dear ${name},
please pay us ${amount} dollars.

You use a template like this:

letterTemplate = Template(templateString)
letter1 = letterTemplate(name='John', amount='100')
letter2 = letterTemplate(name='Tom', amount='200')

The template can be more powerful than simple text substitution, by using loops, conditionals, etc.

Hello ${name},
you ordered these ${len(items)} items:

{{for (i, item) in enumerate(items):}}
item ${i+1}: ${item}
{{end}}

Calling the template with some parameters

print template(name='Joshua', items=['bottled water', 'milk', 'icecream'])

generates this output:

Hello Joshua,
you ordered these 3 items:

item 1: bottled water
item 2: milk
item 3: icecream

How difficult is it to implement such a template engine? 60 lines of python code:

import cStringIO as StringIO
import re

_reExpr  = re.compile(r'\${(.*?)}')
_reCode  = re.compile(r'(?:\n *)?{{(.*?)}}')

class Adder():
    def __init__(self):
        self.indent = ''
        self.o = StringIO.StringIO()

    def incIndent(self):
        self.indent = self.indent + '  '

    def decIndent(self):
        self.indent = self.indent[:-2]

    def write(self, txt):
        self.o.write(self.indent + txt + '\n')

    def getvalue(self):
        value = self.o.getvalue()
        self.o.close()
        return value

def pairify(seq):
    it = iter(seq)
    while True:
        yield (it.next(), it.next())

class SimpleTemplate():
    def __init__(self, string, origin = None):
        string = _reExpr.sub(r'{{_o(str(\1))}}', string)
        parts = _reCode.split('{{}}' + string)[1:]
        adder = Adder()
        for (code, txt) in pairify(parts):
            code = code.strip()
            if code:
                if code == 'end':
                    adder.decIndent()
                else:
                    firstWord = code.split(None, 1)[0]
                    if firstWord[-1] == ':': firstWord = firstWord[:-1]
                    if firstWord in ['else', 'elif', 'except', 'finally']:
                        adder.decIndent()
                    adder.write(code)
                    if code[-1] == ':': adder.incIndent()
            if txt:
                txt = txt.replace('\\\\', '\\\\\\\\').replace('"', '\\\\"').replace('\\n', '\\\\n')
                adder.write('_o("' + txt + '")')
        source = adder.getvalue()
        self.code = compile(source, origin if origin else source, 'exec')

    def __call__(self, **varMap):
        output = StringIO.StringIO()
        varMap.update(_o = output.write)
        exec self.code in varMap
        content = output.getvalue()
        output.close()
        return content

Leave a Reply

two to the power eight 2^8