root/Cheetah/Compiler.py

Revision f17b49bd2a9cb5c693518283252cdbca4d04136b, 76.4 kB (checked in by Jason Michalski <armooo@armooo.net>, 2 years ago)

Lets try the import again

  • Property mode set to 100644
Line 
1 #!/usr/bin/env python
2 # $Id: Compiler.py,v 1.148 2006/06/22 00:18:22 tavis_rudd Exp $
3 """Compiler classes for Cheetah:
4 ModuleCompiler aka 'Compiler'
5 ClassCompiler
6 MethodCompiler
7
8 If you are trying to grok this code start with ModuleCompiler.__init__,
9 ModuleCompiler.compile, and ModuleCompiler.__getattr__.
10
11 Meta-Data
12 ================================================================================
13 Author: Tavis Rudd <tavis@damnsimple.com>
14 Version: $Revision: 1.148 $
15 Start Date: 2001/09/19
16 Last Revision Date: $Date: 2006/06/22 00:18:22 $
17 """
18 __author__ = "Tavis Rudd <tavis@damnsimple.com>"
19 __revision__ = "$Revision: 1.148 $"[11:-2]
20
21 import sys
22 import os
23 import os.path
24 from os.path import getmtime, exists
25 import re
26 import types
27 import time
28 import random
29 import warnings
30 import __builtin__
31 import copy
32
33 from Cheetah.Version import Version, VersionTuple
34 from Cheetah.SettingsManager import SettingsManager
35 from Cheetah.Parser import Parser, ParseError, specialVarRE, \
36      STATIC_CACHE, REFRESH_CACHE, SET_LOCAL, SET_GLOBAL,SET_MODULE
37 from Cheetah.Utils.Indenter import indentize # an undocumented preprocessor
38 from Cheetah import ErrorCatchers
39 from Cheetah import NameMapper
40
41 from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList
42 VFFSL=valueFromFrameOrSearchList
43 VFSL=valueFromSearchList
44 VFN=valueForName
45 currentTime=time.time
46
47 class Error(Exception): pass
48
49 DEFAULT_COMPILER_SETTINGS = {
50     ## controlling the handling of Cheetah $placeholders
51     'useNameMapper': True,      # Unified dotted notation and the searchList
52     'useSearchList': True,      # if false, assume the first
53                                 # portion of the $variable (before the first dot) is a global,
54                                 # builtin, or local var that doesn't need
55                                 # looking up in the searchlist BUT use
56                                 # namemapper on the rest of the lookup
57     'allowSearchListAsMethArg': True,
58     'useAutocalling': True, # detect and call callable()'s, requires NameMapper
59     'useStackFrames': True, # use NameMapper.valueFromFrameOrSearchList
60     # rather than NameMapper.valueFromSearchList
61     'useErrorCatcher':False,
62     'alwaysFilterNone':True, # filter out None, before the filter is called
63     'useFilters':True, # use str instead if =False
64     'includeRawExprInFilterArgs':True,
65
66    
67     #'lookForTransactionAttr':False,
68     'autoAssignDummyTransactionToSelf':False,
69     'useKWsDictArgForPassingTrans':True,
70    
71     ## controlling the aesthetic appearance / behaviour of generated code
72     'commentOffset': 1,
73     # should shorter str constant chunks be printed using repr rather than ''' quotes
74     'reprShortStrConstants': True,
75     'reprNewlineThreshold':3,
76     'outputRowColComments':True,
77     # should #block's be wrapped in a comment in the template's output
78     'includeBlockMarkers': False,   
79     'blockMarkerStart':('\n<!-- START BLOCK: ',' -->\n'),
80     'blockMarkerEnd':('\n<!-- END BLOCK: ',' -->\n'),           
81     'defDocStrMsg':'Autogenerated by CHEETAH: The Python-Powered Template Engine',
82     'setup__str__method': False,
83     'mainMethodName':'respond',
84     'mainMethodNameForSubclasses':'writeBody',
85     'indentationStep': ' '*4,
86     'initialMethIndentLevel': 2,
87     'monitorSrcFile':False,
88     'outputMethodsBeforeAttributes': True,
89
90
91     ## customizing the #extends directive
92     'autoImportForExtendsDirective':True,
93     'handlerForExtendsDirective':None, # baseClassName = handler(compiler, baseClassName)
94                                       # a callback hook for customizing the
95                                       # #extends directive.  It can manipulate
96                                       # the compiler's state if needed.
97     # also see allowExpressionsInExtendsDirective
98     
99
100     # input filtering/restriction
101     # use lower case keys here!!
102     'disabledDirectives':[], # list of directive keys, without the start token
103     'enabledDirectives':[], # list of directive keys, without the start token
104
105     'disabledDirectiveHooks':[], # callable(parser, directiveKey)
106     'preparseDirectiveHooks':[], # callable(parser, directiveKey)
107     'postparseDirectiveHooks':[], # callable(parser, directiveKey)
108     'preparsePlaceholderHooks':[], # callable(parser)
109     'postparsePlaceholderHooks':[], # callable(parser)
110     # the above hooks don't need to return anything
111
112     'expressionFilterHooks':[], # callable(parser, expr, exprType, rawExpr=None, startPos=None)
113     # exprType is the name of the directive, 'psp', or 'placeholder'. all
114     # lowercase.  The filters *must* return the expr or raise an exception.
115     # They can modify the expr if needed.
116
117     'templateMetaclass':None, # strictly optional. Only works with new-style baseclasses
118
119
120     'i18NFunctionName':'self.i18n',
121    
122     ## These are used in the parser, but I've put them here for the time being to
123     ## facilitate separating the parser and compiler:   
124     'cheetahVarStartToken':'$',
125     'commentStartToken':'##',
126     'multiLineCommentStartToken':'#*',
127     'multiLineCommentEndToken':'*#',
128     'gobbleWhitespaceAroundMultiLineComments':True,
129     'directiveStartToken':'#',
130     'directiveEndToken':'#',
131     'allowWhitespaceAfterDirectiveStartToken':False,   
132     'PSPStartToken':'<%',
133     'PSPEndToken':'%>',
134     'EOLSlurpToken':'#',
135     'gettextTokens': ["_", "N_", "ngettext"],
136     'allowExpressionsInExtendsDirective': False, # the default restricts it to
137                                         # accepting dotted names   
138     'allowEmptySingleLineMethods': False,
139     'allowNestedDefScopes': True,
140     'allowPlaceholderFilterArgs': True,
141
142     ## See Parser.initDirectives() for the use of the next 3
143     #'directiveNamesAndParsers':{}
144     #'endDirectiveNamesAndHandlers':{}
145     #'macroDirectives':{}
146
147     }
148
149
150
151 class GenUtils:
152     """An abstract baseclass for the Compiler classes that provides methods that
153     perform generic utility functions or generate pieces of output code from
154     information passed in by the Parser baseclass.  These methods don't do any
155     parsing themselves.
156     """
157
158     def genTimeInterval(self, timeString):
159         ##@@ TR: need to add some error handling here
160         if timeString[-1] == 's':
161             interval = float(timeString[:-1])
162         elif timeString[-1] == 'm':
163             interval = float(timeString[:-1])*60
164         elif timeString[-1] == 'h':
165             interval = float(timeString[:-1])*60*60
166         elif timeString[-1] == 'd':
167             interval = float(timeString[:-1])*60*60*24
168         elif timeString[-1] == 'w':
169             interval = float(timeString[:-1])*60*60*24*7
170         else:                       # default to minutes
171             interval = float(timeString)*60
172         return interval
173
174     def genCacheInfo(self, cacheTokenParts):
175         """Decipher a placeholder cachetoken
176         """
177         cacheInfo = {}
178         if cacheTokenParts['REFRESH_CACHE']:
179             cacheInfo['type'] = REFRESH_CACHE
180             cacheInfo['interval'] = self.genTimeInterval(cacheTokenParts['interval'])
181         elif cacheTokenParts['STATIC_CACHE']:
182             cacheInfo['type'] = STATIC_CACHE
183         return cacheInfo                # is empty if no cache
184
185     def genCacheInfoFromArgList(self, argList):
186         cacheInfo = {'type':REFRESH_CACHE}
187         for key, val in argList:
188             if val[0] in '"\'':
189                 val = val[1:-1]
190
191             if key == 'timer':
192                 key = 'interval'
193                 val = self.genTimeInterval(val)
194                
195             cacheInfo[key] = val
196         return cacheInfo
197        
198     def genCheetahVar(self, nameChunks, plain=False):
199         if nameChunks[0][0] in self.setting('gettextTokens'):
200             self.addGetTextVar(nameChunks)
201         if self.setting('useNameMapper') and not plain:
202             return self.genNameMapperVar(nameChunks)
203         else:
204             return self.genPlainVar(nameChunks)
205
206     def addGetTextVar(self, nameChunks):
207         """Output something that gettext can recognize.
208         
209         This is a harmless side effect necessary to make gettext work when it
210         is scanning compiled templates for strings marked for translation.
211
212         @@TR: another marginally more efficient approach would be to put the
213         output in a dummy method that is never called.
214         """
215         # @@TR: this should be in the compiler not here
216         self.addChunk("if False:")
217         self.indent()
218         self.addChunk(self.genPlainVar(nameChunks[:]))
219         self.dedent()
220
221     def genPlainVar(self, nameChunks):       
222         """Generate Python code for a Cheetah $var without using NameMapper
223         (Unified Dotted Notation with the SearchList).
224         """
225         nameChunks.reverse()
226         chunk = nameChunks.pop()
227         pythonCode = chunk[0] + chunk[2]
228         while nameChunks:
229             chunk = nameChunks.pop()
230             pythonCode = (pythonCode + '.' + chunk[0] + chunk[2])
231         return pythonCode
232
233     def genNameMapperVar(self, nameChunks):
234         """Generate valid Python code for a Cheetah $var, using NameMapper
235         (Unified Dotted Notation with the SearchList).
236
237         nameChunks = list of var subcomponents represented as tuples
238           [ (name,useAC,remainderOfExpr),
239           ]
240         where:
241           name = the dotted name base
242           useAC = where NameMapper should use autocalling on namemapperPart
243           remainderOfExpr = any arglist, index, or slice
244
245         If remainderOfExpr contains a call arglist (e.g. '(1234)') then useAC
246         is False, otherwise it defaults to True. It is overridden by the global
247         setting 'useAutocalling' if this setting is False.
248
249         EXAMPLE
250         ------------------------------------------------------------------------
251         if the raw Cheetah Var is
252           $a.b.c[1].d().x.y.z
253           
254         nameChunks is the list
255           [ ('a.b.c',True,'[1]'), # A
256             ('d',False,'()'),     # B
257             ('x.y.z',True,''),    # C
258           ]
259         
260         When this method is fed the list above it returns
261           VFN(VFN(VFFSL(SL, 'a.b.c',True)[1], 'd',False)(), 'x.y.z',True)
262         which can be represented as
263           VFN(B`, name=C[0], executeCallables=(useAC and C[1]))C[2]
264         where:
265           VFN = NameMapper.valueForName
266           VFFSL = NameMapper.valueFromFrameOrSearchList
267           VFSL = NameMapper.valueFromSearchList # optionally used instead of VFFSL
268           SL = self.searchList()
269           useAC = self.setting('useAutocalling') # True in this example
270           
271           A = ('a.b.c',True,'[1]')
272           B = ('d',False,'()')
273           C = ('x.y.z',True,'')
274
275           C` = VFN( VFN( VFFSL(SL, 'a.b.c',True)[1],
276                          'd',False)(),
277                     'x.y.z',True)
278              = VFN(B`, name='x.y.z', executeCallables=True)
279             
280           B` = VFN(A`, name=B[0], executeCallables=(useAC and B[1]))B[2]
281           A` = VFFSL(SL, name=A[0], executeCallables=(useAC and A[1]))A[2]
282
283
284         Note, if the compiler setting useStackFrames=False (default is true)
285         then
286           A` = VFSL([locals()]+SL+[globals(), __builtin__], name=A[0], executeCallables=(useAC and A[1]))A[2]
287         This option allows Cheetah to be used with Psyco, which doesn't support
288         stack frame introspection.
289         """
290         defaultUseAC = self.setting('useAutocalling')
291         useSearchList = self.setting('useSearchList')
292
293         nameChunks.reverse()
294         name, useAC, remainder = nameChunks.pop()
295
296         if not useSearchList:
297             firstDotIdx = name.find('.')
298             if firstDotIdx != -1 and firstDotIdx < len(name):
299                 beforeFirstDot, afterDot = name[:firstDotIdx], name[firstDotIdx+1:]
300                 pythonCode = ('VFN(' + beforeFirstDot +
301                               ',"' + afterDot +
302                               '",' + repr(defaultUseAC and useAC) + ')'
303                               + remainder)
304             else:
305                 pythonCode = name+remainder
306         elif self.setting('useStackFrames'):
307             pythonCode = ('VFFSL(SL,'
308                           '"'+ name + '",'
309                           + repr(defaultUseAC and useAC) + ')'
310                           + remainder)
311         else:
312             pythonCode = ('VFSL([locals()]+SL+[globals(), __builtin__],'
313                           '"'+ name + '",'
314                           + repr(defaultUseAC and useAC) + ')'
315                           + remainder)
316         ##   
317         while nameChunks:
318             name, useAC, remainder = nameChunks.pop()
319             pythonCode = ('VFN(' + pythonCode +
320                           ',"' + name +
321                           '",' + repr(defaultUseAC and useAC) + ')'
322                           + remainder)
323         return pythonCode
324    
325 ##################################################
326 ## METHOD COMPILERS
327
328 class MethodCompiler(GenUtils):
329     def __init__(self, methodName, classCompiler,
330                  initialMethodComment=None,
331                  decorator=None):
332         self._settingsManager = classCompiler
333         self._classCompiler = classCompiler
334         self._moduleCompiler = classCompiler._moduleCompiler
335         self._methodName = methodName
336         self._initialMethodComment = initialMethodComment
337         self._setupState()
338         self._decorator = decorator
339
340     def setting(self, key):
341         return self._settingsManager.setting(key)
342
343     def _setupState(self):
344         self._indent = self.setting('indentationStep')
345         self._indentLev = self.setting('initialMethIndentLevel')
346         self._pendingStrConstChunks = []
347         self._methodSignature = None
348         self._methodDef = None
349         self._docStringLines = []
350         self._methodBodyChunks = []
351
352         self._cacheRegionsStack = []
353         self._callRegionsStack = []
354         self._captureRegionsStack = []
355         self._filterRegionsStack = []
356
357         self._isErrorCatcherOn = False
358
359         self._hasReturnStatement = False
360         self._isGenerator = False
361        
362        
363     def cleanupState(self):
364         """Called by the containing class compiler instance
365         """
366         pass
367
368     def methodName(self):
369         return self._methodName
370
371     def setMethodName(self, name):
372         self._methodName = name
373        
374     ## methods for managing indentation
375     
376     def indentation(self):
377         return self._indent * self._indentLev
378    
379     def indent(self):
380         self._indentLev +=1
381        
382     def dedent(self):
383         if self._indentLev:
384             self._indentLev -=1
385         else:
386             raise Error('Attempt to dedent when the indentLev is 0')
387
388     ## methods for final code wrapping
389
390     def methodDef(self):
391         if self._methodDef:
392             return self._methodDef
393         else:
394             return self.wrapCode()
395
396     __str__ = methodDef
397    
398     def wrapCode(self):
399         self.commitStrConst()
400         methodDefChunks = (
401             self.methodSignature(),
402             '\n',
403             self.docString(),
404             self.methodBody() )
405         methodDef = ''.join(methodDefChunks)
406         self._methodDef = methodDef
407         return methodDef
408
409     def methodSignature(self):
410         return self._indent + self._methodSignature + ':'
411
412     def setMethodSignature(self, signature):
413         self._methodSignature = signature
414
415     def methodBody(self):
416         return ''.join( self._methodBodyChunks )
417
418     def docString(self):
419         if not self._docStringLines:
420             return ''
421        
422         ind = self._indent*2       
423         docStr = (ind + '"""\n' + ind +
424                   ('\n' + ind).join([ln.replace('"""',"'''") for ln in self._docStringLines]) +
425                   '\n' + ind + '"""\n')
426         return  docStr
427
428     ## methods for adding code
429     def addMethDocString(self, line):
430         self._docStringLines.append(line.replace('%','%%'))
431        
432     def addChunk(self, chunk):
433         self.commitStrConst()
434         chunk = "\n" + self.indentation() + chunk
435         self._methodBodyChunks.append(chunk)
436
437     def appendToPrevChunk(self, appendage):
438         self._methodBodyChunks[-1] = self._methodBodyChunks[-1] + appendage
439
440     def addWriteChunk(self, chunk):
441         self.addChunk('write(' + chunk + ')')
442
443     def addFilteredChunk(self, chunk, filterArgs=None, rawExpr=None, lineCol=None):
444         if filterArgs is None:
445             filterArgs = ''
446         if self.setting('includeRawExprInFilterArgs') and rawExpr:
447             filterArgs += ', rawExpr=%s'%repr(rawExpr)
448
449         if self.setting('alwaysFilterNone'):
450             if rawExpr and rawExpr.find('\n')==-1 and rawExpr.find('\r')==-1:
451                 self.addChunk("_v = %s # %r"%(chunk, rawExpr))
452                 if lineCol:
453                     self.appendToPrevChunk(' on line %s, col %s'%lineCol)
454             else:
455                 self.addChunk("_v = %s"%chunk)
456                
457             if self.setting('useFilters'):
458                 self.addChunk("if _v is not None: write(_filter(_v%s))"%filterArgs)
459             else:
460                 self.addChunk("if _v is not None: write(str(_v))")
461         else:
462             if self.setting('useFilters'):
463                 self.addChunk("write(_filter(%s%s))"%(chunk,filterArgs))
464             else:
465                 self.addChunk("write(str(%s))"%chunk)
466
467     def _appendToPrevStrConst(self, strConst):
468         if self._pendingStrConstChunks:
469             self._pendingStrConstChunks.append(strConst)
470         else:
471             self._pendingStrConstChunks = [strConst]
472
473     def _unescapeCheetahVars(self, theString):
474         """Unescape any escaped Cheetah \$vars in the string.
475         """
476        
477         token = self.setting('cheetahVarStartToken')
478         return theString.replace('\\' + token, token)
479
480     def _unescapeDirectives(self, theString):
481         """Unescape any escaped Cheetah \$vars in the string.
482         """
483        
484         token = self.setting('directiveStartToken')
485         return theString.replace('\\' + token, token)
486        
487     def commitStrConst(self):
488         """Add the code for outputting the pending strConst without chopping off
489         any whitespace from it.
490         """
491         if self._pendingStrConstChunks:
492             strConst = self._unescapeCheetahVars(''.join(self._pendingStrConstChunks))
493             strConst = self._unescapeDirectives(strConst)
494             self._pendingStrConstChunks = []
495             if not strConst:
496                 return
497             if self.setting('reprShortStrConstants') and \
498                strConst.count('\n') < self.setting('reprNewlineThreshold'):
499                 self.addWriteChunk( repr(strConst).replace('\\012','\\n'))
500             else:
501                 strConst = strConst.replace('\\','\\\\').replace("'''","'\'\'\'")
502                 if strConst[0] == "'":
503                     strConst = '\\' + strConst
504                 if strConst[-1] == "'":
505                     strConst = strConst[:-1] + '\\' + strConst[-1]
506                    
507                 self.addWriteChunk("'''" + strConst + "'''" )
508
509     def handleWSBeforeDirective(self):
510         """Truncate the pending strCont to the beginning of the current line.
511         """
512         if self._pendingStrConstChunks:
513             src = self._pendingStrConstChunks[-1]
514             BOL = max(src.rfind('\n')+1, src.rfind('\r')+1, 0)
515             if BOL < len(src):
516                 self._pendingStrConstChunks[-1] = src[:BOL]
517
518
519
520     def isErrorCatcherOn(self):
521         return self._isErrorCatcherOn
522    
523     def turnErrorCatcherOn(self):
524         self._isErrorCatcherOn = True
525
526     def turnErrorCatcherOff(self):
527         self._isErrorCatcherOn = False
528            
529     # @@TR: consider merging the next two methods into one
530     def addStrConst(self, strConst):
531         self._appendToPrevStrConst(strConst)
532
533     def addRawText(self, text):
534         self.addStrConst(text)
535        
536     def addMethComment(self, comm):
537         offSet = self.setting('commentOffset')
538         self.addChunk('#' + ' '*offSet + comm)
539
540     def addPlaceholder(self, expr, filterArgs, rawPlaceholder,
541                        cacheTokenParts, lineCol,
542                        silentMode=False):
543         cacheInfo = self.genCacheInfo(cacheTokenParts)
544         if cacheInfo:
545             cacheInfo['ID'] = repr(rawPlaceholder)[1:-1]
546             self.startCacheRegion(cacheInfo, lineCol, rawPlaceholder=rawPlaceholder)
547
548         if self.isErrorCatcherOn():
549             methodName = self._classCompiler.addErrorCatcherCall(
550                 expr, rawCode=rawPlaceholder, lineCol=lineCol)
551             expr = 'self.' + methodName + '(localsDict=locals())'
552
553         if silentMode:
554             self.addChunk('try:')
555             self.indent()           
556             self.addFilteredChunk(expr, filterArgs, rawPlaceholder, lineCol=lineCol)
557             self.dedent()
558             self.addChunk('except NotFound: pass')           
559         else:
560             self.addFilteredChunk(expr, filterArgs, rawPlaceholder, lineCol=lineCol)
561
562         if self.setting('outputRowColComments'):
563             self.appendToPrevChunk(' # from line %s, col %s' % lineCol + '.')
564         if cacheInfo:
565             self.endCacheRegion()
566
567     def addSilent(self, expr):
568         self.addChunk( expr )
569
570     def addEcho(self, expr, rawExpr=None):
571         self.addFilteredChunk(expr, rawExpr=rawExpr)
572        
573     def addSet(self, expr, exprComponents, setStyle):
574         if setStyle is SET_GLOBAL:
575             (LVALUE, OP, RVALUE) = (exprComponents.LVALUE,
576                                     exprComponents.OP,
577                                     exprComponents.RVALUE)
578             # we need to split the LVALUE to deal with globalSetVars
579             splitPos1 = LVALUE.find('.')
580             splitPos2 = LVALUE.find('[')
581             if splitPos1 > 0 and splitPos2==-1:
582                 splitPos = splitPos1
583             elif splitPos1 > 0 and splitPos1 < max(splitPos2,0):
584                 splitPos = splitPos1
585             else:
586                 splitPos = splitPos2
587
588             if splitPos >0:
589                 primary = LVALUE[:splitPos]
590                 secondary = LVALUE[splitPos:]
591             else:
592                 primary = LVALUE
593                 secondary = ''           
594             LVALUE = 'self._CHEETAH__globalSetVars["' + primary + '"]' + secondary
595             expr = LVALUE + ' ' + OP + ' ' + RVALUE.strip()
596
597         if setStyle is SET_MODULE:
598             self._moduleCompiler.addModuleGlobal(expr)
599         else:
600             self.addChunk(expr)
601
602     def addInclude(self, sourceExpr, includeFrom, isRaw):
603         self.addChunk('self._handleCheetahInclude(' + sourceExpr +
604                            ', trans=trans, ' +
605                            'includeFrom="' + includeFrom + '", raw=' +
606                            repr(isRaw) + ')')
607
608     def addWhile(self, expr, lineCol=None):
609         self.addIndentingDirective(expr, lineCol=lineCol)
610        
611     def addFor(self, expr, lineCol=None):
612         self.addIndentingDirective(expr, lineCol=lineCol)
613
614     def addRepeat(self, expr, lineCol=None):
615         #the _repeatCount stuff here allows nesting of #repeat directives       
616         self._repeatCount = getattr(self, "_repeatCount", -1) + 1
617         self.addFor('for __i%s in range(%s)' % (self._repeatCount,expr), lineCol=lineCol)
618
619     def addIndentingDirective(self, expr, lineCol=None):
620         if expr and not expr[-1] == ':':
621             expr = expr  + ':'
622         self.addChunk( expr )
623         if lineCol:
624             self.appendToPrevChunk(' # generated from line %s, col %s'%lineCol )
625         self.indent()
626
627     def addReIndentingDirective(self, expr, dedent=True, lineCol=None):
628         self.commitStrConst()
629         if dedent:
630             self.dedent()
631         if not expr[-1] == ':':
632             expr = expr  + ':'
633            
634         self.addChunk( expr )
635         if lineCol:
636             self.appendToPrevChunk(' # generated from line %s, col %s'%lineCol )
637         self.indent()
638
639     def addIf(self, expr, lineCol=None):
640         """For a full #if ... #end if directive
641         """
642         self.addIndentingDirective(expr, lineCol=lineCol)
643
644     def addOneLineIf(self, expr, lineCol=None):
645         """For a full #if ... #end if directive
646         """
647         self.addIndentingDirective(expr, lineCol=lineCol)
648
649     def addTernaryExpr(self, conditionExpr, trueExpr, falseExpr, lineCol=None):
650         """For a single-lie #if ... then .... else ... directive
651         <condition> then <trueExpr> else <falseExpr>
652         """
653         self.addIndentingDirective(conditionExpr, lineCol=lineCol)           
654         self.addFilteredChunk(trueExpr)
655         self.dedent()
656         self.addIndentingDirective('else')           
657         self.addFilteredChunk(falseExpr)
658         self.dedent()
659
660     def addElse(self, expr, dedent=True, lineCol=None):
661         expr = re.sub(r'else[ \f\t]+if','elif', expr)
662         self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol)
663
664     def addElif(self, expr, dedent=True, lineCol=None):
665         self.addElse(expr, dedent=dedent, lineCol=lineCol)
666        
667     def addUnless(self, expr, lineCol=None):
668         self.addIf('if not (' + expr + ')')
669
670     def addClosure(self, functionName, argsList, parserComment):
671         argStringChunks = []
672         for arg in argsList:
673             chunk = arg[0]
674             if not arg[1] == None:
675                 chunk += '=' + arg[1]
676             argStringChunks.append(chunk)
677         signature = "def " + functionName + "(" + ','.join(argStringChunks) + "):"
678         self.addIndentingDirective(signature)
679         self.addChunk('#'+parserComment)
680
681     def addTry(self, expr, lineCol=None):
682         self.addIndentingDirective(expr, lineCol=lineCol)
683        
684     def addExcept(self, expr, dedent=True, lineCol=None):
685         self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol)
686        
687     def addFinally(self, expr, dedent=True, lineCol=None):
688         self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol)
689            
690     def addReturn(self, expr):
691         assert not self._isGenerator
692         self.addChunk(expr)
693         self._hasReturnStatement = True
694
695     def addYield(self, expr):
696         assert not self._hasReturnStatement
697         self._isGenerator = True
698         if expr.replace('yield','').strip():
699             self.addChunk(expr)
700         else:
701             self.addChunk('if _dummyTrans:')
702             self.indent()
703             self.addChunk('yield trans.response().getvalue()')
704             self.addChunk('trans = DummyTransaction()')
705             self.addChunk('write = trans.response().write')
706             self.dedent()
707             self.addChunk('else:')
708             self.indent()
709             self.addChunk(
710                 'raise TypeError("This method cannot be called with a trans arg")')
711             self.dedent()
712            
713
714     def addPass(self, expr):
715         self.addChunk(expr)
716
717     def addDel(self, expr):
718         self.addChunk(expr)
719
720     def addAssert(self, expr):
721         self.addChunk(expr)
722
723     def addRaise(self, expr):
724         self.addChunk(expr)
725
726     def addBreak(self, expr):
727         self.addChunk(expr)
728
729     def addContinue(self, expr):
730         self.addChunk(expr)
731
732     def addPSP(self, PSP):
733         self.commitStrConst()
734         autoIndent = False
735         if PSP[0] == '=':
736             PSP = PSP[1:]
737             if PSP:
738                 self.addWriteChunk('_filter(' + PSP + ')')
739             return
740                    
741         elif PSP.lower() == 'end':
742             self.dedent()
743             return
744         elif PSP[-1] == '$':
745             autoIndent = True
746             PSP = PSP[:-1]
747         elif PSP[-1] == ':':
748             autoIndent = True
749            
750         for line in PSP.splitlines():
751             self.addChunk(line)
752            
753         if autoIndent:
754             self.indent()
755    
756     def nextCacheID(self):
757         return ('_'+str(random.randrange(100, 999))
758                 + str(random.randrange(10000, 99999)))
759        
760     def startCacheRegion(self, cacheInfo, lineCol, rawPlaceholder=None):
761
762         # @@TR: we should add some runtime logging to this
763         
764         ID = self.nextCacheID()
765         interval = cacheInfo.get('interval',None)
766         test = cacheInfo.get('test',None)
767         customID = cacheInfo.get('id',None)
768         if customID:
769             ID = customID
770         varyBy = cacheInfo.get('varyBy', repr(ID))
771         self._cacheRegionsStack.append(ID) # attrib of current methodCompiler
772
773         # @@TR: add this to a special class var as well
774         self.addChunk('')
775
776         self.addChunk('## START CACHE REGION: ID='+ID+
777                       '. line %s, col %s'%lineCol + ' in the source.')
778        
779         self.addChunk('_RECACHE_%(ID)s = False'%locals())
780         self.addChunk('_cacheRegion_%(ID)s = self.getCacheRegion(regionID='%locals()
781                       + repr(ID)
782                       + ', cacheInfo=%r'%cacheInfo
783                       + ')')
784         self.addChunk('if _cacheRegion_%(ID)s.isNew():'%locals())
785         self.indent()
786         self.addChunk('_RECACHE_%(ID)s = True'%locals())
787         self.dedent()
788        
789         self.addChunk('_cacheItem_%(ID)s = _cacheRegion_%(ID)s.getCacheItem('%locals()
790                       +varyBy+')')
791
792         self.addChunk('if _cacheItem_%(ID)s.hasExpired():'%locals())
793         self.indent()
794         self.addChunk('_RECACHE_%(ID)s = True'%locals())
795         self.dedent()
796            
797         if test:
798             self.addChunk('if ' + test + ':')
799             self.indent()
800             self.addChunk('_RECACHE_%(ID)s = True'%locals())
801