"""A lightweight python templating engine in 70 lines of code. Each template is a subclass of Template. Templates should define a class attribute 'template' that contains the code. Instantiate a template using a dictionary or keyword arguments. The template will be expanded when you consult the string conversion of the instance. For example: class MyTemplate(templex.Template): template = "the $animal jumped over the $object." print MyTemplate(animal='cow', object='moon') The template language understands the following forms: $myvar - inserts the value of the variable 'myvar' ${...} - evaluates the expression and inserts the result ${{...}} - executes enclosed code; use 'self.emit(text)' to insert text $$ - an escape for a single $ $ (at the end of the line) - a line continuation $ - shorthand for '${{self.sub_template(vars())}}' Of course in addition to the main template a Template class may have methods, members, subclasses, etc. Any class attribute ending with '_template' will be compiled into a subtemplate that can be called as a method or by using $<...>. This is helpful for decomposing a template and when subclassing. A longer example: import cgi class RecipeTemplate(templex.Template): template = r''' $dish $ $ ''' header_template = r'''

${cgi.escape(dish)}

''' body_template = r'''
    ${{ for item in ingredients: self.emit('
  1. ', item, '\n') }}
''' This template can be expanded as follows: print RecipeTemplate(dish='burger', ingredients=['bun', 'beef', 'lettuce']) And it can be subclassed like this: class RecipeWithPriceTemplate(RecipeTemplate): header_template = "

${cgi.escape(dish)} - $$$price

\n" Templex is by David Bau and was inspired by Tomer Filba's Templite class. """ import sys, re class _TemplateMetaClass(type): __pattern = re.compile(r"""\$( # Directives begin with a $ \$ | # $$ is an escape for $ [^\S\n]*\n | # $\n is a line continuation [_a-z][_a-z0-9]* | # $simple Python identifier \{(?!\{)[^\}]*\} | # ${...} expression to eval \{\{.*?\}\} | # ${{...}} multiline code to exec <[_a-z][_a-z0-9]*> | # $ method call )(?:(?:(?<=\}\})|(?<=>))[^\S\n]*\n)? # eat some trailing newlines """, re.IGNORECASE | re.VERBOSE | re.DOTALL) def __realign(self, str): lines = str.splitlines(); if lines and not lines[0].strip(): del lines[0] lspace = [len(l) - len(l.lstrip()) for l in lines if l.lstrip()] margin = lspace and min(lspace) or 0 return '\n'.join(l[margin:] for l in lines) def __filename(self, n, globals): if '__file__' in globals: return '%s: <%s %s>' % (globals['__file__'], self.__name__, n) return '<%s %s>' % (self.__name__, n) def __compile(self, template, name): text, code = [], [] for i, part in enumerate(self.__pattern.split(self.__realign(template))): if i % 2 == 0: if part: code.append('self.output.append(_text[%d])' % len(text)) text.append(part) else: if part == '$': code.append('self.output.append("$")') elif part.endswith('\n'): continue elif part.startswith('{{'): code.append(self.__realign(part[2:-2])) elif part.startswith('{'): code.append('self.emit(%s)' % part[1:-1]) elif part.startswith('<'): code.append('self.%s(vars())' % part[1:-1]) else: code.append('self.emit(%s)' % part) globals = sys.modules[self.__module__].__dict__ code = compile('\n'.join(code), self.__filename(name, globals), 'exec') del template, name, i, part def run(self, _dict = {}, **locals): locals.update([i for i in _dict.iteritems() if i[0] not in locals]) locals.update({'self':self, '_text': text}) exec code in globals, locals return run def __init__(self, *args): for attr, val in self.__dict__.items(): if attr == 'template' or attr.endswith('_template'): setattr(self, attr, self.__compile(val, attr)) type.__init__(self, *args) class Template(object): """A base class for exec-style templates.""" __metaclass__ = _TemplateMetaClass def __init__(self, *args, **kw): self.output = [] self.template(*args, **kw) def emit(self, *args): for a in args: if isinstance(a, Template): self.output.extend(a.output) else: self.output.append(str(a)) def __str__(self): return ''.join(self.output) # When executed as a script, run some testing code. if __name__ == '__main__': ok = True def expect(actual, expected): global ok if expected != actual: print "error - got:\n%s" % actual ok = False class TestAll(Template): """A test of all the $ forms""" template = r""" Bought: $count ${name}s$ at $$$price. ${{ for i in xrange(count): self.emit(TestCalls(vars()), "\n") # inherit all the local $variables }} Total: $$${"%.2f" % (count * price)} """ class TestCalls(Template): """A recursive test""" template = "$name$i ${*[TestCalls(name=name[0], i=n) for n in xrange(i)]}" expect( str(TestAll(count=5, name="template call", price=1.23)), "Bought: 5 template calls at $1.23.\n" "template call0 \n" "template call1 t0 \n" "template call2 t0 t1 t0 \n" "template call3 t0 t1 t0 t2 t0 t1 t0 \n" "template call4 t0 t1 t0 t2 t0 t1 t0 t3 t0 t1 t0 t2 t0 t1 t0 \n" "Total: $6.15\n") class TestBase(Template): template = r""" $ $ """ class TestDerived(TestBase): head_template = "$name" body_template = "${TestAll(vars())}" expect( str(TestDerived(count=4, name="template call", price=2.88)), "template call\n" "" "Bought: 4 template calls at $2.88.\n" "template call0 \n" "template call1 t0 \n" "template call2 t0 t1 t0 \n" "template call3 t0 t1 t0 t2 t0 t1 t0 \n" "Total: $11.52\n" "\n") if ok: print "OK"