Rename senfscons to site_scons and move python files arround
[senf.git] / site_scons / yaptu.py
diff --git a/site_scons/yaptu.py b/site_scons/yaptu.py
new file mode 100644 (file)
index 0000000..5c8d14f
--- /dev/null
@@ -0,0 +1,95 @@
+"Yet Another Python Templating Utility, Version 1.2"
+
+import sys, re
+from cStringIO import StringIO
+
+# utility stuff to avoid tests in the mainline code
+class _nevermatch:
+    "Polymorphic with a regex that never matches"
+    def match(self, line):
+        return None
+_never = _nevermatch()     # one reusable instance of it suffices
+def identity(string, why):
+    "A do-nothing-special-to-the-input, just-return-it function"
+    return string
+def nohandle(string):
+    "A do-nothing handler that just re-raises the exception"
+    raise
+
+# and now the real thing
+class copier:
+    "Smart-copier (YAPTU) class"
+    def copyblock(self, i=0, last=None):
+        "Main copy method: process lines [i,last) of block"
+        def repl(match, self=self):
+            "return the eval of a found expression, for replacement"
+            # uncomment for debug: print '!!! replacing',match.group(1)
+            expr = self.preproc(match.group(1), 'eval')
+            try: return str(eval(expr, self.globals, self.locals))
+            except: return str(self.handle(expr))
+        block = self.locals['_bl']
+        if last is None: last = len(block)
+        while i<last:
+            line = block[i]
+            match = self.restat.match(line)
+            if match:   # a statement starts "here" (at line block[i])
+                # i is the last line to _not_ process
+                stat = match.string[match.end(0):].strip()
+                j=i+1   # look for 'finish' from here onwards
+                nest=1  # count nesting levels of statements
+                while j<last:
+                    line = block[j]
+                    # first look for nested statements or 'finish' lines
+                    if self.restend.match(line):    # found a statement-end
+                        nest = nest - 1     # update (decrease) nesting
+                        if nest==0: break   # j is first line to _not_ process
+                    elif self.restat.match(line):   # found a nested statement
+                        nest = nest + 1     # update (increase) nesting
+                    elif nest==1:   # look for continuation only at this nesting
+                        match = self.recont.match(line)
+                        if match:                   # found a contin.-statement
+                            nestat = match.string[match.end(0):].strip()
+                            stat = '%s _cb(%s,%s)\n%s' % (stat,i+1,j,nestat)
+                            i=j     # again, i is the last line to _not_ process
+                    j=j+1
+                stat = self.preproc(stat, 'exec')
+                stat = '%s _cb(%s,%s)' % (stat,i+1,j)
+                # for debugging, uncomment...: print "-> Executing: {"+stat+"}"
+                exec stat in self.globals,self.locals
+                i=j+1
+            else:       # normal line, just copy with substitution
+                self.ouf.write(self.regex.sub(repl,line))
+                i=i+1
+    def __init__(self, regex=_never, dict={},
+            restat=_never, restend=_never, recont=_never, 
+            preproc=identity, handle=nohandle, ouf=sys.stdout):
+        "Initialize self's attributes"
+        self.regex   = regex
+        self.globals = dict
+        self.locals  = { '_cb':self.copyblock }
+        self.restat  = restat
+        self.restend = restend
+        self.recont  = recont
+        self.preproc = preproc
+        self.handle  = handle
+        self.ouf     = ouf
+    def copy(self, block=None, inf=sys.stdin):
+        "Entry point: copy-with-processing a file, or a block of lines"
+        if block is None: block = inf.readlines()
+        self.locals['_bl'] = block
+        self.copyblock()
+
+_RE_EXPR = re.compile('\${([^{}]+)}')
+_RE_BEGIN = re.compile('{{')
+_RE_END = re.compile('}}')
+_RE_CONT = re.compile(r'\|\|')
+
+def process(text,*args):
+    vardict = {}
+    for arg in args : vardict.update(arg)
+    output = StringIO()
+    c = copier(_RE_EXPR, vardict, _RE_BEGIN, _RE_END, _RE_CONT,
+               ouf = output)
+    lines = [ line+'\n' for line in text.split("\n") ]
+    c.copy(lines)
+    return output.getvalue()