minor fixes for clang++
[senf.git] / site_scons / yaptu.py
1 "Yet Another Python Templating Utility, Version 1.2"
2
3 import sys, re
4 from cStringIO import StringIO
5
6 # utility stuff to avoid tests in the mainline code
7 class _nevermatch:
8     "Polymorphic with a regex that never matches"
9     def match(self, line):
10         return None
11 _never = _nevermatch()     # one reusable instance of it suffices
12 def identity(string, why):
13     "A do-nothing-special-to-the-input, just-return-it function"
14     return string
15 def nohandle(string):
16     "A do-nothing handler that just re-raises the exception"
17     raise
18
19 # and now the real thing
20 class copier:
21     "Smart-copier (YAPTU) class"
22     def copyblock(self, i=0, last=None):
23         "Main copy method: process lines [i,last) of block"
24         def repl(match, self=self):
25             "return the eval of a found expression, for replacement"
26             # uncomment for debug: print '!!! replacing',match.group(1)
27             expr = self.preproc(match.group(1), 'eval')
28             try: return str(eval(expr, self.globals, self.locals))
29             except: return str(self.handle(expr))
30         block = self.locals['_bl']
31         if last is None: last = len(block)
32         while i<last:
33             line = block[i]
34             match = self.restat.match(line)
35             if match:   # a statement starts "here" (at line block[i])
36                 # i is the last line to _not_ process
37                 stat = match.string[match.end(0):].strip()
38                 j=i+1   # look for 'finish' from here onwards
39                 nest=1  # count nesting levels of statements
40                 while j<last:
41                     line = block[j]
42                     # first look for nested statements or 'finish' lines
43                     if self.restend.match(line):    # found a statement-end
44                         nest = nest - 1     # update (decrease) nesting
45                         if nest==0: break   # j is first line to _not_ process
46                     elif self.restat.match(line):   # found a nested statement
47                         nest = nest + 1     # update (increase) nesting
48                     elif nest==1:   # look for continuation only at this nesting
49                         match = self.recont.match(line)
50                         if match:                   # found a contin.-statement
51                             nestat = match.string[match.end(0):].strip()
52                             stat = '%s _cb(%s,%s)\n%s' % (stat,i+1,j,nestat)
53                             i=j     # again, i is the last line to _not_ process
54                     j=j+1
55                 stat = self.preproc(stat, 'exec')
56                 stat = '%s _cb(%s,%s)' % (stat,i+1,j)
57                 # for debugging, uncomment...: print "-> Executing: {"+stat+"}"
58                 exec stat in self.globals,self.locals
59                 i=j+1
60             else:       # normal line, just copy with substitution
61                 self.ouf.write(self.regex.sub(repl,line))
62                 i=i+1
63     def __init__(self, regex=_never, dict={},
64             restat=_never, restend=_never, recont=_never, 
65             preproc=identity, handle=nohandle, ouf=sys.stdout):
66         "Initialize self's attributes"
67         self.regex   = regex
68         self.globals = dict
69         self.locals  = { '_cb':self.copyblock }
70         self.restat  = restat
71         self.restend = restend
72         self.recont  = recont
73         self.preproc = preproc
74         self.handle  = handle
75         self.ouf     = ouf
76     def copy(self, block=None, inf=sys.stdin):
77         "Entry point: copy-with-processing a file, or a block of lines"
78         if block is None: block = inf.readlines()
79         self.locals['_bl'] = block
80         self.copyblock()
81
82 _RE_EXPR = re.compile('\${([^{}]+)}')
83 _RE_BEGIN = re.compile('{{')
84 _RE_END = re.compile('}}')
85 _RE_CONT = re.compile(r'\|\|')
86
87 def process(text,*args):
88     vardict = {}
89     for arg in args : vardict.update(arg)
90     output = StringIO()
91     c = copier(_RE_EXPR, vardict, _RE_BEGIN, _RE_END, _RE_CONT,
92                ouf = output)
93     lines = [ line+'\n' for line in text.split("\n") ]
94     c.copy(lines)
95     return output.getvalue()