Skip to content
Snippets Groups Projects

Templite+ Python Templating Engine

  • Clone with SSH
  • Clone with HTTPS
  • Embed
  • Share
    The snippet can be accessed without any authentication.
    Authored by Thimo Kraemer

    Revised version of Templite, a light-weight, fully functional, general purpose templating engine.

    Enhancements:

    • Block statements do not break the template.
    • Single variables and expressions starting with quotes are substituted automatically.
    • Starting and ending delimiters are variable.
    • Possibility to include other templates from the file system.
    • Basic caching functionality.

    The functionality is comparable to Python Server Pages (PSP).

    Example:

    from templite import Templite
    
    template = r"""
    
    This template demonstrates the usage of Templite.
    
    Within the defined delimiters we can write pure Python code:
    
    ${
        def say_hello(name):
            write('Hello %s!' % name)
    }$
    
    And now we call the function: ${ say_hello('World') }$
    
    Escaped starting delimiter: $\{
    ${ write('Escaped ending delimiter: }\$') }$
    
    Also block statements are possible:
    
    ${ if x > 10: }$
    x is greater than 10
    ${ :elif x > 5: }$
    x is greater than 5
    ${ :else: }$
    x is not greater than 5
    ${ :end-if / only the starting colon is essential to close a block }$
    
    ${ for i in range(x): }$
    loop index is ${ i }$
    ${ :end-for }$
    
    ${ # this is a python comment }$
    
    Single variables and expressions starting with quotes are substituted
    automatically:
    Instead ${write(x)}$ you can write ${x}$ or ${'%s' % x}$ or ${"", x}$
    Therefore standalone statements like break, continue or pass
    must be enlosed by a semicolon: $\{continue;}\$
    
    To include another template, just call "include":
    ${ include('template.txt') }$
    """
    
    t = Templite(template)
    print t.render(x=8)

    Output is:

    
    This template demonstrates the usage of Templite.
    
    Within the defined delimiters we can write pure Python code:
    
    
    
    And now we call the function: Hello World!
    
    Escaped starting delimiter: ${
    Escaped ending delimiter: }$
    
    Also block statements are possible:
    
    
    x is greater than 5
    
    
    
    loop index is 0
    
    loop index is 1
    
    loop index is 2
    
    loop index is 3
    
    loop index is 4
    
    loop index is 5
    
    loop index is 6
    
    loop index is 7
    
    
    
    
    Single variables and expressions starting with quotes are substituted
    automatically:
    Instead 8 you can write 8 or 8 or 8
    Therefore standalone statements like break, continue or pass
    must be enlosed by a semicolon: ${continue;}$
    
    To include another template, just call "include":
    This is the content of template.txt
    Edited
    templite.py 4.97 KiB
    #!/usr/bin/env python
    #
    #  Templite+
    #  A light-weight, fully functional, general purpose templating engine
    #
    #  Copyright (c) 2009 joonis new media
    #  Author: Thimo Kraemer <thimo.kraemer@joonis.de>
    #
    #  Based on Templite by Tomer Filiba
    #  http://code.activestate.com/recipes/496702/
    #
    #  This program is free software; you can redistribute it and/or modify
    #  it under the terms of the GNU General Public License as published by
    #  the Free Software Foundation; either version 2 of the License, or
    #  (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software
    #  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
    #  MA 02110-1301, USA.
    
    
    import sys, os
    import re
    
    class Templite(object):
        
        autowrite = re.compile('(^[\'\"])|(^[a-zA-Z0-9_\[\]\'\"]+$)')
        delimiters = ('${', '}$')
        cache = {}
    
        def __init__(self, text=None, filename=None,
                        encoding='utf-8', delimiters=None, caching=False):
            """Loads a template from string or file."""
            if filename:
                filename = os.path.abspath(filename)
                mtime = os.path.getmtime(filename)
                self.file = key = filename
            elif text is not None:
                self.file = mtime = None
                key = hash(text)
            else:
                raise ValueError('either text or filename required')
            # set attributes
            self.encoding = encoding
            self.caching = caching
            if delimiters:
                start, end = delimiters
                if len(start) != 2 or len(end) != 2:
                    raise ValueError('each delimiter must be two characters long')
                self.delimiters = delimiters
            # check cache
            cache = self.cache
            if caching and key in cache and cache[key][0] == mtime:
                self._code = cache[key][1]
                return
            # read file
            if filename:
                with open(filename) as fh:
                    text = fh.read()
            self._code = self._compile(text)
            if caching:
                cache[key] = (mtime, self._code)
        
        def _compile(self, source):
            offset = 0
            tokens = ['# -*- coding: %s -*-' % self.encoding]
            start, end = self.delimiters
            escaped = (re.escape(start), re.escape(end))
            regex = re.compile('%s(.*?)%s' % escaped, re.DOTALL)
            for i, part in enumerate(regex.split(source)):
                part = part.replace('\\'.join(start), start)
                part = part.replace('\\'.join(end), end)
                if i % 2 == 0:
                    if not part: continue
                    part = part.replace('\\', '\\\\').replace('"', '\\"')
                    part = '\t' * offset + 'write("""%s""")' % part
                else:
                    part = part.rstrip()
                    if not part: continue
                    part_stripped = part.lstrip()
                    if part_stripped.startswith(':'):
                        if not offset:
                            raise SyntaxError('no block statement to terminate: ${%s}$' % part)
                        offset -= 1
                        part = part_stripped[1:]
                        if not part.endswith(':'): continue
                    elif self.autowrite.match(part_stripped):
                        part = 'write(%s)' % part_stripped
                    lines = part.splitlines()
                    margin = min(len(l) - len(l.lstrip()) for l in lines if l.strip())
                    part = '\n'.join('\t' * offset + l[margin:] for l in lines)
                    if part.endswith(':'):
                        offset += 1
                tokens.append(part)
            if offset:
                raise SyntaxError('%i block statement(s) not terminated' % offset)
            return compile('\n'.join(tokens), self.file or '<string>', 'exec')
    
        def render(self, **namespace):
            """Renders the template according to the given namespace."""
            stack = []
            namespace['__file__'] = self.file
            # add write method
            def write(*args):
                for value in args:
                    if isinstance(value, unicode):
                        value = value.encode(self.encoding)
                    stack.append(str(value))
            namespace['write'] = write
            # add include method
            def include(file):
                if not os.path.isabs(file):
                    if self.file:
                        base = os.path.dirname(self.file)
                    else:
                        base = os.path.dirname(sys.argv[0])
                    file = os.path.join(base, file)
                t = Templite(None, file, self.encoding,
                                self.delimiters, self.caching)
                stack.append(t.render(**namespace))
            namespace['include'] = include
            # execute template code
            exec self._code in namespace
            return ''.join(stack)
    0% or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment