1 "Yet Another Python Templating Utility, Version 1.2"
4 from cStringIO import StringIO
6 # utility stuff to avoid tests in the mainline code
8 "Polymorphic with a regex that never matches"
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"
16 "A do-nothing handler that just re-raises the exception"
19 # and now the real thing
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)
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
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
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
60 else: # normal line, just copy with substitution
61 self.ouf.write(self.regex.sub(repl,line))
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"
69 self.locals = { '_cb':self.copyblock }
71 self.restend = restend
73 self.preproc = preproc
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
82 _RE_EXPR = re.compile('\${([^{}]+)}')
83 _RE_BEGIN = re.compile('{{')
84 _RE_END = re.compile('}}')
85 _RE_CONT = re.compile(r'\|\|')
87 def process(text,*args):
89 for arg in args : vardict.update(arg)
91 c = copier(_RE_EXPR, vardict, _RE_BEGIN, _RE_END, _RE_CONT,
93 lines = [ line+'\n' for line in text.split("\n") ]
95 return output.getvalue()