Module xyaptu
[hide private]
[frames] | no frames]

Source Code for Module xyaptu

  1  #!/usr/bin/env python 
  2   
  3  """ 
  4  This code is taken from the ASPN Python cookbook.  I have 
  5  combined YAPTU and XYAPTU into a single file.  The copyright, 
  6  warranty and license remain with the original authors.  Please 
  7  consult these two URLs 
  8   
  9    - U{http://aspn.activestate.com/ASPN/Python/Cookbook/Recipe/52305} 
 10    - U{http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/162292} 
 11   
 12  The following changes were made: 
 13   
 14    - Removed very lengthy examples from the end (see the above URLs for 
 15      them) 
 16    - Added: setupxcopy() takes the template text and remembers it 
 17    - Added: xcopywithdns() does the copy with supplied DNS (Document Name Space which is 
 18      a dict of variables) and remembered template 
 19      and returns the resulting string 
 20    - Exception handling for statements was added (xyaptu only did it for 
 21      expressions) 
 22    - The default behaviour for exceptions now puts a dump of the exception 
 23      into the generated output as an HTML <!-- --> style comment so you 
 24      can view source to find out what has happened.  My common library 
 25      exception formatter (which also dumps local variables) is used. 
 26     
 27   
 28  """ 
 29   
 30  import sys 
 31  import re 
 32  import string 
 33  import common 
 34   
 35  ## "Yet Another Python Templating Utility, Version 1.2" 
 36   
 37  # utility stuff to avoid tests in the mainline code 
38 -class _nevermatch:
39 "Polymorphic with a regex that never matches"
40 - def match(self, line):
41 return None
42 _never = _nevermatch() # one reusable instance of it suffices
43 -def identity(string, why):
44 "A do-nothing-special-to-the-input, just-return-it function" 45 return string
46 -def nohandle(*args):
47 "A do-nothing handler that just re-raises the exception" 48 raise
49 50 # and now the real thing
51 -class copier:
52 "Smart-copier (YAPTU) class"
53 - def copyblock(self, i=0, last=None):
54 "Main copy method: process lines [i,last) of block" 55 def repl(match, self=self): 56 "return the eval of a found expression, for replacement" 57 # uncomment for debug: print '!!! replacing',match.group(1) 58 expr = self.preproc(match.group(1), 'eval') 59 try: return common.strorunicode(eval(expr, self.globals, self.locals)) 60 except: return common.strorunicode(self.handle(expr))
61 block = self.locals['_bl'] 62 if last is None: last = len(block) 63 while i<last: 64 line = block[i] 65 match = self.restat.match(line) 66 if match: # a statement starts "here" (at line block[i]) 67 # i is the last line to _not_ process 68 stat = match.string[match.end(0):].strip() 69 j=i+1 # look for 'finish' from here onwards 70 nest=1 # count nesting levels of statements 71 while j<last: 72 line = block[j] 73 # first look for nested statements or 'finish' lines 74 if self.restend.match(line): # found a statement-end 75 nest = nest - 1 # update (decrease) nesting 76 if nest==0: break # j is first line to _not_ process 77 elif self.restat.match(line): # found a nested statement 78 nest = nest + 1 # update (increase) nesting 79 elif nest==1: # look for continuation only at this nesting 80 match = self.recont.match(line) 81 if match: # found a contin.-statement 82 nestat = match.string[match.end(0):].strip() 83 stat = '%s _cb(%s,%s)\n%s' % (stat,i+1,j,nestat) 84 i=j # again, i is the last line to _not_ process 85 j=j+1 86 stat = self.preproc(stat, 'exec') 87 stat = '%s _cb(%s,%s)' % (stat,i+1,j) 88 # for debugging, uncomment...: print "-> Executing: {"+stat+"}" 89 try: 90 exec stat in self.globals,self.locals 91 except: 92 # not as good as it could be 93 self.ouf.write(str(self.handle(stat,self.locals,self.globals))) 94 i=j+1 95 else: # normal line, just copy with substitution 96 self.ouf.write(self.regex.sub(repl,line)) 97 i=i+1
98 - def __init__(self, regex=_never, dict={}, 99 restat=_never, restend=_never, recont=_never, 100 preproc=identity, handle=nohandle, ouf=sys.stdout):
101 "Initialize self's attributes" 102 self.regex = regex 103 self.globals = dict 104 self.locals = { '_cb':self.copyblock } 105 self.restat = restat 106 self.restend = restend 107 self.recont = recont 108 self.preproc = preproc 109 self.handle = handle 110 self.ouf = ouf
111 - def copy(self, block=None, inf=sys.stdin):
112 "Entry point: copy-with-processing a file, or a block of lines" 113 if block is None: block = inf.readlines() 114 self.locals['_bl'] = block 115 self.copyblock()
116 117 ##"XYAPTU: Lightweight XML/HTML Document Template Engine for Python" 118 119 ##__version__ = '1.0.0' 120 ##__author__= [ 121 ## 'Alex Martelli (aleax@aleax.it)', 122 ## 'Mario Ruggier (mario@ruggier.org)' 123 ##] 124 ##__copyright__ = '(c) Python Style Copyright. All Rights Reserved. No Warranty.' 125 ##__dependencies__ = ['YAPTU 1.2, http://aspn.activestate.com/ASPN/Python/Cookbook/Recipe/52305'] 126 ##__history__= { 127 ## '1.0.0' : '2002/11/13: First Released Version', 128 ##} 129
130 -class xcopier(copier):
131 ' xcopier class, inherits from yaptu.copier ' 132
133 - def __init__(self, dns, rExpr=None, rOpen=None, rClose=None, rClause=None, 134 ouf=sys.stdout, dbg=0, dbgOuf=sys.stdout):
135 ' set default regular expressions required by yaptu.copier ' 136 137 # Default regexps for yaptu delimeters (what xyaptu tags are first converted to) 138 # These must be in sync with what is output in self._x2y_translate 139 _reExpression = re.compile('_:@([^:@]+)@:_') 140 _reOpen = re.compile('\++yaptu ') 141 _reClose = re.compile('--yaptu') 142 _reClause = re.compile('==yaptu ') 143 144 rExpr = rExpr or _reExpression 145 rOpen = rOpen or _reOpen 146 rClose = rClose or _reClose 147 rClause = rClause or _reClause 148 149 # Debugging 150 self.dbg = dbg 151 self.dbgOuf = dbgOuf 152 _preproc = self._preProcess 153 if dbg: _preproc = self._preProcessDbg 154 155 # Call super init 156 copier.__init__(self, rExpr, dns, rOpen, rClose, rClause, 157 preproc=_preproc, handle=self._handleBadExps, ouf=ouf)
158 159
160 - def xcopy(self, inputText):
161 ''' 162 Converts the value of the input stream (or contents of input filename) 163 from xyaptu format to yaptu format, and invokes yaptu.copy 164 ''' 165 166 # Translate (xyaptu) input to (yaptu) input, and call yaptu.copy() 167 from StringIO import StringIO 168 yinf = StringIO(self._x2y_translate(inputText)) 169 self.copy(inf=yinf) 170 yinf.close()
171
172 - def setupxcopy(self, inputText):
173 from StringIO import StringIO 174 yinf = StringIO(self._x2y_translate(inputText)) 175 # we have to build the list since you can only run 176 # readline/s once on a file 177 self.remembered=[line for line in yinf.readlines()]
178
179 - def xcopywithdns(self, dns):
180 from StringIO import StringIO 181 self.globals=dns 182 out=StringIO() 183 self.ouf=out 184 self.copy(self.remembered) 185 return out.getvalue()
186
187 - def _x2y_translate(self, xStr):
188 ' Converts xyaptu markup in input string to yaptu delimeters ' 189 190 # Define regexps to match xml elements on. 191 # The variations (all except for py-expr, py-close) we look for are: 192 # <py-elem code="{python code}" /> | 193 # <py-elem code="{python code}">ignored text</py-elem> | 194 # <py-elem>{python code}</py-elem> 195 196 # ${py-expr} | $py-expr | <py-expr code="pvkey" /> 197 reExpr = re.compile(r''' 198 \$\{([^}]+)\} | # ${py-expr} 199 \$([_\w]+) | # $py-expr 200 <py-expr\s+code\s*=\s*"([^"]*)"\s*/> | 201 <py-expr\s+code\s*=\s*"([^"]*)"\s*>[^<]*</py-expr> | 202 <py-expr\s*>([^<]*)</py-expr\s*> 203 ''', re.VERBOSE) 204 205 # <py-line code="pvkeys=pageVars.keys()"/> 206 reLine = re.compile(r''' 207 <py-line\s+code\s*=\s*"([^"]*)"\s*/> | 208 <py-line\s+code\s*=\s*"([^"]*)"\s*>[^<]*</py-line> | 209 <py-line\s*>([^<]*)</py-line\s*> 210 ''', re.VERBOSE) 211 212 # <py-open code="for k in pageVars.keys():" /> 213 reOpen = re.compile(r''' 214 <py-open\s+code\s*=\s*"([^"]*)"\s*/> | 215 <py-open\s+code\s*=\s*"([^"]*)"\s*>[^<]*</py-open\s*> | 216 <py-open\s*>([^<]*)</py-open\s*> 217 ''', re.VERBOSE) 218 219 # <py-clause code="else:" /> 220 reClause = re.compile(r''' 221 <py-clause\s+code\s*=\s*"([^"]*)"\s*/> | 222 <py-clause\s+code\s*=\s*"([^"]*)"\s*>[^<]*</py-clause\s*> | 223 <py-clause\s*>([^<]*)</py-clause\s*> 224 ''', re.VERBOSE) 225 226 # <py-close /> 227 reClose = re.compile(r''' 228 <py-close\s*/> | 229 <py-close\s*>.*</py-close\s*> 230 ''', re.VERBOSE) 231 232 # Call-back functions for re substitutions 233 # These must be in sync with what is expected in self.__init__ 234 def rexpr(match,self=self): 235 return '_:@%s@:_' % match.group(match.lastindex)
236 def rline(match,self=self): 237 return '\n++yaptu %s #\n--yaptu \n' % match.group(match.lastindex)
238 def ropen(match,self=self): 239 return '\n++yaptu %s \n' % match.group(match.lastindex) 240 def rclause(match,self=self): 241 return '\n==yaptu %s \n' % match.group(match.lastindex) 242 def rclose(match,self=self): 243 return '\n--yaptu \n' 244 245 # Substitutions 246 xStr = reExpr.sub(rexpr, xStr) 247 xStr = reLine.sub(rline, xStr) 248 xStr = reOpen.sub(ropen, xStr) 249 xStr = reClause.sub(rclause, xStr) 250 xStr = reClose.sub(rclose, xStr) 251 252 # When in debug mode, keep a copy of intermediate template format 253 if self.dbg: 254 _sep = '====================\n' 255 self.dbgOuf.write('%sIntermediate YAPTU format:\n%s\n%s' % (_sep, xStr, _sep)) 256 257 return xStr 258 259 # Handle expressions that do not evaluate
260 - def _handleBadExps(self, s, locals=None, globals=None):
261 ' Handle expressions that do not evaluate ' 262 if self.dbg: 263 self.dbgOuf.write('!!! ERROR: failed to evaluate expression: %s \n' % s) 264 res="<!-- EXCEPTION: \nExpression: "+s+"\n" 265 res+=common.formatexception()+"\n-->" 266 print common.formatexception() 267 return res+('***! %s !***' % s)
268 269 # Preprocess code
270 - def _preProcess(self, s, why):
271 ' Preprocess embedded python statements and expressions ' 272 return self._xmlDecode(s)
273 - def _preProcessDbg(self, s, why):
274 ' Preprocess embedded python statements and expressions ' 275 self.dbgOuf.write('!!! DBG: %s %s \n' % (s, why)) 276 return self._xmlDecode(s)
277 278 # Decode utility for XML/HTML special characters 279 _xmlCodes = [ 280 ['"', '&quot;'], 281 ['>', '&gt;'], 282 ['<', '&lt;'], 283 ['&', '&amp;'], 284 ]
285 - def _xmlDecode(self, s):
286 ' Returns the ASCII decoded version of the given HTML string. ' 287 codes = self._xmlCodes 288 for code in codes: 289 s = string.replace(s, code[1], code[0]) 290 return s
291