#!/usr/bin/python
"""
Process Template Files

(based on Yet Another Python Templating Utility by Alex Martelli)
"""
#  Copyright (C) 2004  Henning Jacobs <henning@srcco.de>
#
#  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.
#
#  $Id: TemplateProcessor.py 82 2004-07-11 13:01:44Z henning $

import re, sys
import types

class _nevermatch:
    def match(self, line): return None
    def sub(self, repl, line): return line
_never = _nevermatch()

def identity(string, why=None):
    return string

def nohandle(string, kind):
    import traceback
    traceback.print_exc()
    return "*** Exception raised in %s '%s'\n" % (kind, string)

class TemplateProcessor:
    def __init__(self, postproc=identity, preproc=identity, handle=nohandle):
        self.reexpr = re.compile('<\?(=|!|#)([^?]+)\?>')
        self.restart = re.compile('\s*<\?([^}][^?]+){\?>')
        self.reend = re.compile('\s*<\?}\?>')
        self.recont = re.compile('\s*<\?}([^{]+){\?>')
        self.locals = {'_pb':self.process_block}
        self.postproc = postproc
        self.preproc = preproc
        self.handle = handle
    def process(self, block, outfunc, globals={}):
        self.outfunc = outfunc
        self.globals = globals
        self.locals['_bl'] = block
        self.process_block()
    def process_block(self, i=0, last=None):
        "Process lines [i, last) of block"
        def repl(match, self=self):
            cmd = match.group(1)
            if cmd == '#': # Comment
                return ''
            else:    
                expr = self.preproc(match.group(2), 'eval')
                try: ret = eval(expr, self.globals, self.locals)
                except: return self.postproc(self.handle(expr, 'eval'))
                if cmd == '=':
                    # Ensure that our Expression is a String:
                    if not isinstance(ret, types.StringTypes):
                        ret = str(ret)
                    return self.postproc(ret)
                else: # ! 
                    return ''
        block = self.locals['_bl']
        if last is None: last=len(block)
        while i < last:
            line = block[i]
            match = self.restart.search(line)
            if match: # a statement starts 'here'
                # i is the last line NOT to process
                stat = match.group(1)+':'
                j = i+1
                nest = 1
                while j < last:
                    line = block[j]
                    if self.reend.match(line):
                        nest += -1
                        if nest == 0: break
                    elif self.restart.match(line):
                        nest += 1
                    elif nest == 1:
                        match = self.recont.match(line)
                        if match: # found a continued statement
                            nestat = match.group(1)+':'
                            stat = '%s _pb(%s,%s)\n%s' % (stat, i+1, j, nestat)
                            i = j # i is the last line NOT to process
                    j += 1       
                stat = self.preproc(stat, 'exec')
                stat = '%s _pb(%s,%s)' % (stat, i+1, j)
                try: exec stat in self.globals, self.locals
                except: self.outfunc(self.postproc(self.handle(stat, 'exec')))
                i = j+1
            else:
                self.outfunc(self.reexpr.sub(repl, line))
                i += 1

if __name__ == "__main__":
    dummytemplate = """<?=Name?>
    a: <?=a?>, b: <?=b?>, foo: <?=foo()?>
    A Comment (invisible): <?# Ein Kommentar ?>
    A will be changed now! <?!changeA()?>
    a: <?=a?>, b: <?=b?>, foo: <?=foo()?>
    Hallo? <?!notExistingFunc()?>
    <?for i in range(5){?>
        i: <?=i?>
        <?if i==2{?>
            Zwei
        <?}elif i==3{?>
            Drei
        <?}else{?>
            Sonst
        <?}?>
    <?}?>
    Continues, but here is the END.
    """.splitlines(True)
    globals = {}
    def changeA(globals=globals):
        globals['a'] = 2004
    def foo():
        return 'foo returned by foo() function'
    globals.update({'Name':'A Dummy Template', 'a':42, 'b':'\xdcmlaute',
         'changeA':changeA, 
         'foo':foo})
    proc = TemplateProcessor()
    proc.process(dummytemplate, sys.stdout.write, globals)
    
