diff options
Diffstat (limited to 'cobbler/Cheetah/Compiler.py')
-rw-r--r-- | cobbler/Cheetah/Compiler.py | 1975 |
1 files changed, 1975 insertions, 0 deletions
diff --git a/cobbler/Cheetah/Compiler.py b/cobbler/Cheetah/Compiler.py new file mode 100644 index 0000000..a74267d --- /dev/null +++ b/cobbler/Cheetah/Compiler.py @@ -0,0 +1,1975 @@ +#!/usr/bin/env python +# $Id: Compiler.py,v 1.148 2006/06/22 00:18:22 tavis_rudd Exp $ +"""Compiler classes for Cheetah: +ModuleCompiler aka 'Compiler' +ClassCompiler +MethodCompiler + +If you are trying to grok this code start with ModuleCompiler.__init__, +ModuleCompiler.compile, and ModuleCompiler.__getattr__. + +Meta-Data +================================================================================ +Author: Tavis Rudd <tavis@damnsimple.com> +Version: $Revision: 1.148 $ +Start Date: 2001/09/19 +Last Revision Date: $Date: 2006/06/22 00:18:22 $ +""" +__author__ = "Tavis Rudd <tavis@damnsimple.com>" +__revision__ = "$Revision: 1.148 $"[11:-2] + +import sys +import os +import os.path +from os.path import getmtime, exists +import re +import types +import time +import random +import warnings +import __builtin__ +import copy + +from Cheetah.Version import Version, VersionTuple +from Cheetah.SettingsManager import SettingsManager +from Cheetah.Parser import Parser, ParseError, specialVarRE, \ + STATIC_CACHE, REFRESH_CACHE, SET_LOCAL, SET_GLOBAL,SET_MODULE +from Cheetah.Utils.Indenter import indentize # an undocumented preprocessor +from Cheetah import ErrorCatchers +from Cheetah import NameMapper + +from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList +VFFSL=valueFromFrameOrSearchList +VFSL=valueFromSearchList +VFN=valueForName +currentTime=time.time + +class Error(Exception): pass + +DEFAULT_COMPILER_SETTINGS = { + ## controlling the handling of Cheetah $placeholders + 'useNameMapper': True, # Unified dotted notation and the searchList + 'useSearchList': True, # if false, assume the first + # portion of the $variable (before the first dot) is a global, + # builtin, or local var that doesn't need + # looking up in the searchlist BUT use + # namemapper on the rest of the lookup + 'allowSearchListAsMethArg': True, + 'useAutocalling': True, # detect and call callable()'s, requires NameMapper + 'useStackFrames': True, # use NameMapper.valueFromFrameOrSearchList + # rather than NameMapper.valueFromSearchList + 'useErrorCatcher':False, + 'alwaysFilterNone':True, # filter out None, before the filter is called + 'useFilters':True, # use str instead if =False + 'includeRawExprInFilterArgs':True, + + + #'lookForTransactionAttr':False, + 'autoAssignDummyTransactionToSelf':False, + 'useKWsDictArgForPassingTrans':True, + + ## controlling the aesthetic appearance / behaviour of generated code + 'commentOffset': 1, + # should shorter str constant chunks be printed using repr rather than ''' quotes + 'reprShortStrConstants': True, + 'reprNewlineThreshold':3, + 'outputRowColComments':True, + # should #block's be wrapped in a comment in the template's output + 'includeBlockMarkers': False, + 'blockMarkerStart':('\n<!-- START BLOCK: ',' -->\n'), + 'blockMarkerEnd':('\n<!-- END BLOCK: ',' -->\n'), + 'defDocStrMsg':'Autogenerated by CHEETAH: The Python-Powered Template Engine', + 'setup__str__method': False, + 'mainMethodName':'respond', + 'mainMethodNameForSubclasses':'writeBody', + 'indentationStep': ' '*4, + 'initialMethIndentLevel': 2, + 'monitorSrcFile':False, + 'outputMethodsBeforeAttributes': True, + + + ## customizing the #extends directive + 'autoImportForExtendsDirective':True, + 'handlerForExtendsDirective':None, # baseClassName = handler(compiler, baseClassName) + # a callback hook for customizing the + # #extends directive. It can manipulate + # the compiler's state if needed. + # also see allowExpressionsInExtendsDirective + + + # input filtering/restriction + # use lower case keys here!! + 'disabledDirectives':[], # list of directive keys, without the start token + 'enabledDirectives':[], # list of directive keys, without the start token + + 'disabledDirectiveHooks':[], # callable(parser, directiveKey) + 'preparseDirectiveHooks':[], # callable(parser, directiveKey) + 'postparseDirectiveHooks':[], # callable(parser, directiveKey) + 'preparsePlaceholderHooks':[], # callable(parser) + 'postparsePlaceholderHooks':[], # callable(parser) + # the above hooks don't need to return anything + + 'expressionFilterHooks':[], # callable(parser, expr, exprType, rawExpr=None, startPos=None) + # exprType is the name of the directive, 'psp', or 'placeholder'. all + # lowercase. The filters *must* return the expr or raise an exception. + # They can modify the expr if needed. + + 'templateMetaclass':None, # strictly optional. Only works with new-style baseclasses + + + 'i18NFunctionName':'self.i18n', + + ## These are used in the parser, but I've put them here for the time being to + ## facilitate separating the parser and compiler: + 'cheetahVarStartToken':'$', + 'commentStartToken':'##', + 'multiLineCommentStartToken':'#*', + 'multiLineCommentEndToken':'*#', + 'gobbleWhitespaceAroundMultiLineComments':True, + 'directiveStartToken':'#', + 'directiveEndToken':'#', + 'allowWhitespaceAfterDirectiveStartToken':False, + 'PSPStartToken':'<%', + 'PSPEndToken':'%>', + 'EOLSlurpToken':'#', + 'gettextTokens': ["_", "N_", "ngettext"], + 'allowExpressionsInExtendsDirective': False, # the default restricts it to + # accepting dotted names + 'allowEmptySingleLineMethods': False, + 'allowNestedDefScopes': True, + 'allowPlaceholderFilterArgs': True, + + ## See Parser.initDirectives() for the use of the next 3 + #'directiveNamesAndParsers':{} + #'endDirectiveNamesAndHandlers':{} + #'macroDirectives':{} + + } + + + +class GenUtils: + """An abstract baseclass for the Compiler classes that provides methods that + perform generic utility functions or generate pieces of output code from + information passed in by the Parser baseclass. These methods don't do any + parsing themselves. + """ + + def genTimeInterval(self, timeString): + ##@@ TR: need to add some error handling here + if timeString[-1] == 's': + interval = float(timeString[:-1]) + elif timeString[-1] == 'm': + interval = float(timeString[:-1])*60 + elif timeString[-1] == 'h': + interval = float(timeString[:-1])*60*60 + elif timeString[-1] == 'd': + interval = float(timeString[:-1])*60*60*24 + elif timeString[-1] == 'w': + interval = float(timeString[:-1])*60*60*24*7 + else: # default to minutes + interval = float(timeString)*60 + return interval + + def genCacheInfo(self, cacheTokenParts): + """Decipher a placeholder cachetoken + """ + cacheInfo = {} + if cacheTokenParts['REFRESH_CACHE']: + cacheInfo['type'] = REFRESH_CACHE + cacheInfo['interval'] = self.genTimeInterval(cacheTokenParts['interval']) + elif cacheTokenParts['STATIC_CACHE']: + cacheInfo['type'] = STATIC_CACHE + return cacheInfo # is empty if no cache + + def genCacheInfoFromArgList(self, argList): + cacheInfo = {'type':REFRESH_CACHE} + for key, val in argList: + if val[0] in '"\'': + val = val[1:-1] + + if key == 'timer': + key = 'interval' + val = self.genTimeInterval(val) + + cacheInfo[key] = val + return cacheInfo + + def genCheetahVar(self, nameChunks, plain=False): + if nameChunks[0][0] in self.setting('gettextTokens'): + self.addGetTextVar(nameChunks) + if self.setting('useNameMapper') and not plain: + return self.genNameMapperVar(nameChunks) + else: + return self.genPlainVar(nameChunks) + + def addGetTextVar(self, nameChunks): + """Output something that gettext can recognize. + + This is a harmless side effect necessary to make gettext work when it + is scanning compiled templates for strings marked for translation. + + @@TR: another marginally more efficient approach would be to put the + output in a dummy method that is never called. + """ + # @@TR: this should be in the compiler not here + self.addChunk("if False:") + self.indent() + self.addChunk(self.genPlainVar(nameChunks[:])) + self.dedent() + + def genPlainVar(self, nameChunks): + """Generate Python code for a Cheetah $var without using NameMapper + (Unified Dotted Notation with the SearchList). + """ + nameChunks.reverse() + chunk = nameChunks.pop() + pythonCode = chunk[0] + chunk[2] + while nameChunks: + chunk = nameChunks.pop() + pythonCode = (pythonCode + '.' + chunk[0] + chunk[2]) + return pythonCode + + def genNameMapperVar(self, nameChunks): + """Generate valid Python code for a Cheetah $var, using NameMapper + (Unified Dotted Notation with the SearchList). + + nameChunks = list of var subcomponents represented as tuples + [ (name,useAC,remainderOfExpr), + ] + where: + name = the dotted name base + useAC = where NameMapper should use autocalling on namemapperPart + remainderOfExpr = any arglist, index, or slice + + If remainderOfExpr contains a call arglist (e.g. '(1234)') then useAC + is False, otherwise it defaults to True. It is overridden by the global + setting 'useAutocalling' if this setting is False. + + EXAMPLE + ------------------------------------------------------------------------ + if the raw Cheetah Var is + $a.b.c[1].d().x.y.z + + nameChunks is the list + [ ('a.b.c',True,'[1]'), # A + ('d',False,'()'), # B + ('x.y.z',True,''), # C + ] + + When this method is fed the list above it returns + VFN(VFN(VFFSL(SL, 'a.b.c',True)[1], 'd',False)(), 'x.y.z',True) + which can be represented as + VFN(B`, name=C[0], executeCallables=(useAC and C[1]))C[2] + where: + VFN = NameMapper.valueForName + VFFSL = NameMapper.valueFromFrameOrSearchList + VFSL = NameMapper.valueFromSearchList # optionally used instead of VFFSL + SL = self.searchList() + useAC = self.setting('useAutocalling') # True in this example + + A = ('a.b.c',True,'[1]') + B = ('d',False,'()') + C = ('x.y.z',True,'') + + C` = VFN( VFN( VFFSL(SL, 'a.b.c',True)[1], + 'd',False)(), + 'x.y.z',True) + = VFN(B`, name='x.y.z', executeCallables=True) + + B` = VFN(A`, name=B[0], executeCallables=(useAC and B[1]))B[2] + A` = VFFSL(SL, name=A[0], executeCallables=(useAC and A[1]))A[2] + + + Note, if the compiler setting useStackFrames=False (default is true) + then + A` = VFSL([locals()]+SL+[globals(), __builtin__], name=A[0], executeCallables=(useAC and A[1]))A[2] + This option allows Cheetah to be used with Psyco, which doesn't support + stack frame introspection. + """ + defaultUseAC = self.setting('useAutocalling') + useSearchList = self.setting('useSearchList') + + nameChunks.reverse() + name, useAC, remainder = nameChunks.pop() + + if not useSearchList: + firstDotIdx = name.find('.') + if firstDotIdx != -1 and firstDotIdx < len(name): + beforeFirstDot, afterDot = name[:firstDotIdx], name[firstDotIdx+1:] + pythonCode = ('VFN(' + beforeFirstDot + + ',"' + afterDot + + '",' + repr(defaultUseAC and useAC) + ')' + + remainder) + else: + pythonCode = name+remainder + elif self.setting('useStackFrames'): + pythonCode = ('VFFSL(SL,' + '"'+ name + '",' + + repr(defaultUseAC and useAC) + ')' + + remainder) + else: + pythonCode = ('VFSL([locals()]+SL+[globals(), __builtin__],' + '"'+ name + '",' + + repr(defaultUseAC and useAC) + ')' + + remainder) + ## + while nameChunks: + name, useAC, remainder = nameChunks.pop() + pythonCode = ('VFN(' + pythonCode + + ',"' + name + + '",' + repr(defaultUseAC and useAC) + ')' + + remainder) + return pythonCode + +################################################## +## METHOD COMPILERS + +class MethodCompiler(GenUtils): + def __init__(self, methodName, classCompiler, + initialMethodComment=None, + decorator=None): + self._settingsManager = classCompiler + self._classCompiler = classCompiler + self._moduleCompiler = classCompiler._moduleCompiler + self._methodName = methodName + self._initialMethodComment = initialMethodComment + self._setupState() + self._decorator = decorator + + def setting(self, key): + return self._settingsManager.setting(key) + + def _setupState(self): + self._indent = self.setting('indentationStep') + self._indentLev = self.setting('initialMethIndentLevel') + self._pendingStrConstChunks = [] + self._methodSignature = None + self._methodDef = None + self._docStringLines = [] + self._methodBodyChunks = [] + + self._cacheRegionsStack = [] + self._callRegionsStack = [] + self._captureRegionsStack = [] + self._filterRegionsStack = [] + + self._isErrorCatcherOn = False + + self._hasReturnStatement = False + self._isGenerator = False + + + def cleanupState(self): + """Called by the containing class compiler instance + """ + pass + + def methodName(self): + return self._methodName + + def setMethodName(self, name): + self._methodName = name + + ## methods for managing indentation + + def indentation(self): + return self._indent * self._indentLev + + def indent(self): + self._indentLev +=1 + + def dedent(self): + if self._indentLev: + self._indentLev -=1 + else: + raise Error('Attempt to dedent when the indentLev is 0') + + ## methods for final code wrapping + + def methodDef(self): + if self._methodDef: + return self._methodDef + else: + return self.wrapCode() + + __str__ = methodDef + + def wrapCode(self): + self.commitStrConst() + methodDefChunks = ( + self.methodSignature(), + '\n', + self.docString(), + self.methodBody() ) + methodDef = ''.join(methodDefChunks) + self._methodDef = methodDef + return methodDef + + def methodSignature(self): + return self._indent + self._methodSignature + ':' + + def setMethodSignature(self, signature): + self._methodSignature = signature + + def methodBody(self): + return ''.join( self._methodBodyChunks ) + + def docString(self): + if not self._docStringLines: + return '' + + ind = self._indent*2 + docStr = (ind + '"""\n' + ind + + ('\n' + ind).join([ln.replace('"""',"'''") for ln in self._docStringLines]) + + '\n' + ind + '"""\n') + return docStr + + ## methods for adding code + def addMethDocString(self, line): + self._docStringLines.append(line.replace('%','%%')) + + def addChunk(self, chunk): + self.commitStrConst() + chunk = "\n" + self.indentation() + chunk + self._methodBodyChunks.append(chunk) + + def appendToPrevChunk(self, appendage): + self._methodBodyChunks[-1] = self._methodBodyChunks[-1] + appendage + + def addWriteChunk(self, chunk): + self.addChunk('write(' + chunk + ')') + + def addFilteredChunk(self, chunk, filterArgs=None, rawExpr=None, lineCol=None): + if filterArgs is None: + filterArgs = '' + if self.setting('includeRawExprInFilterArgs') and rawExpr: + filterArgs += ', rawExpr=%s'%repr(rawExpr) + + if self.setting('alwaysFilterNone'): + if rawExpr and rawExpr.find('\n')==-1 and rawExpr.find('\r')==-1: + self.addChunk("_v = %s # %r"%(chunk, rawExpr)) + if lineCol: + self.appendToPrevChunk(' on line %s, col %s'%lineCol) + else: + self.addChunk("_v = %s"%chunk) + + if self.setting('useFilters'): + self.addChunk("if _v is not None: write(_filter(_v%s))"%filterArgs) + else: + self.addChunk("if _v is not None: write(str(_v))") + else: + if self.setting('useFilters'): + self.addChunk("write(_filter(%s%s))"%(chunk,filterArgs)) + else: + self.addChunk("write(str(%s))"%chunk) + + def _appendToPrevStrConst(self, strConst): + if self._pendingStrConstChunks: + self._pendingStrConstChunks.append(strConst) + else: + self._pendingStrConstChunks = [strConst] + + def _unescapeCheetahVars(self, theString): + """Unescape any escaped Cheetah \$vars in the string. + """ + + token = self.setting('cheetahVarStartToken') + return theString.replace('\\' + token, token) + + def _unescapeDirectives(self, theString): + """Unescape any escaped Cheetah \$vars in the string. + """ + + token = self.setting('directiveStartToken') + return theString.replace('\\' + token, token) + + def commitStrConst(self): + """Add the code for outputting the pending strConst without chopping off + any whitespace from it. + """ + if self._pendingStrConstChunks: + strConst = self._unescapeCheetahVars(''.join(self._pendingStrConstChunks)) + strConst = self._unescapeDirectives(strConst) + self._pendingStrConstChunks = [] + if not strConst: + return + if self.setting('reprShortStrConstants') and \ + strConst.count('\n') < self.setting('reprNewlineThreshold'): + self.addWriteChunk( repr(strConst).replace('\\012','\\n')) + else: + strConst = strConst.replace('\\','\\\\').replace("'''","'\'\'\'") + if strConst[0] == "'": + strConst = '\\' + strConst + if strConst[-1] == "'": + strConst = strConst[:-1] + '\\' + strConst[-1] + + self.addWriteChunk("'''" + strConst + "'''" ) + + def handleWSBeforeDirective(self): + """Truncate the pending strCont to the beginning of the current line. + """ + if self._pendingStrConstChunks: + src = self._pendingStrConstChunks[-1] + BOL = max(src.rfind('\n')+1, src.rfind('\r')+1, 0) + if BOL < len(src): + self._pendingStrConstChunks[-1] = src[:BOL] + + + + def isErrorCatcherOn(self): + return self._isErrorCatcherOn + + def turnErrorCatcherOn(self): + self._isErrorCatcherOn = True + + def turnErrorCatcherOff(self): + self._isErrorCatcherOn = False + + # @@TR: consider merging the next two methods into one + def addStrConst(self, strConst): + self._appendToPrevStrConst(strConst) + + def addRawText(self, text): + self.addStrConst(text) + + def addMethComment(self, comm): + offSet = self.setting('commentOffset') + self.addChunk('#' + ' '*offSet + comm) + + def addPlaceholder(self, expr, filterArgs, rawPlaceholder, + cacheTokenParts, lineCol, + silentMode=False): + cacheInfo = self.genCacheInfo(cacheTokenParts) + if cacheInfo: + cacheInfo['ID'] = repr(rawPlaceholder)[1:-1] + self.startCacheRegion(cacheInfo, lineCol, rawPlaceholder=rawPlaceholder) + + if self.isErrorCatcherOn(): + methodName = self._classCompiler.addErrorCatcherCall( + expr, rawCode=rawPlaceholder, lineCol=lineCol) + expr = 'self.' + methodName + '(localsDict=locals())' + + if silentMode: + self.addChunk('try:') + self.indent() + self.addFilteredChunk(expr, filterArgs, rawPlaceholder, lineCol=lineCol) + self.dedent() + self.addChunk('except NotFound: pass') + else: + self.addFilteredChunk(expr, filterArgs, rawPlaceholder, lineCol=lineCol) + + if self.setting('outputRowColComments'): + self.appendToPrevChunk(' # from line %s, col %s' % lineCol + '.') + if cacheInfo: + self.endCacheRegion() + + def addSilent(self, expr): + self.addChunk( expr ) + + def addEcho(self, expr, rawExpr=None): + self.addFilteredChunk(expr, rawExpr=rawExpr) + + def addSet(self, expr, exprComponents, setStyle): + if setStyle is SET_GLOBAL: + (LVALUE, OP, RVALUE) = (exprComponents.LVALUE, + exprComponents.OP, + exprComponents.RVALUE) + # we need to split the LVALUE to deal with globalSetVars + splitPos1 = LVALUE.find('.') + splitPos2 = LVALUE.find('[') + if splitPos1 > 0 and splitPos2==-1: + splitPos = splitPos1 + elif splitPos1 > 0 and splitPos1 < max(splitPos2,0): + splitPos = splitPos1 + else: + splitPos = splitPos2 + + if splitPos >0: + primary = LVALUE[:splitPos] + secondary = LVALUE[splitPos:] + else: + primary = LVALUE + secondary = '' + LVALUE = 'self._CHEETAH__globalSetVars["' + primary + '"]' + secondary + expr = LVALUE + ' ' + OP + ' ' + RVALUE.strip() + + if setStyle is SET_MODULE: + self._moduleCompiler.addModuleGlobal(expr) + else: + self.addChunk(expr) + + def addInclude(self, sourceExpr, includeFrom, isRaw): + self.addChunk('self._handleCheetahInclude(' + sourceExpr + + ', trans=trans, ' + + 'includeFrom="' + includeFrom + '", raw=' + + repr(isRaw) + ')') + + def addWhile(self, expr, lineCol=None): + self.addIndentingDirective(expr, lineCol=lineCol) + + def addFor(self, expr, lineCol=None): + self.addIndentingDirective(expr, lineCol=lineCol) + + def addRepeat(self, expr, lineCol=None): + #the _repeatCount stuff here allows nesting of #repeat directives + self._repeatCount = getattr(self, "_repeatCount", -1) + 1 + self.addFor('for __i%s in range(%s)' % (self._repeatCount,expr), lineCol=lineCol) + + def addIndentingDirective(self, expr, lineCol=None): + if expr and not expr[-1] == ':': + expr = expr + ':' + self.addChunk( expr ) + if lineCol: + self.appendToPrevChunk(' # generated from line %s, col %s'%lineCol ) + self.indent() + + def addReIndentingDirective(self, expr, dedent=True, lineCol=None): + self.commitStrConst() + if dedent: + self.dedent() + if not expr[-1] == ':': + expr = expr + ':' + + self.addChunk( expr ) + if lineCol: + self.appendToPrevChunk(' # generated from line %s, col %s'%lineCol ) + self.indent() + + def addIf(self, expr, lineCol=None): + """For a full #if ... #end if directive + """ + self.addIndentingDirective(expr, lineCol=lineCol) + + def addOneLineIf(self, expr, lineCol=None): + """For a full #if ... #end if directive + """ + self.addIndentingDirective(expr, lineCol=lineCol) + + def addTernaryExpr(self, conditionExpr, trueExpr, falseExpr, lineCol=None): + """For a single-lie #if ... then .... else ... directive + <condition> then <trueExpr> else <falseExpr> + """ + self.addIndentingDirective(conditionExpr, lineCol=lineCol) + self.addFilteredChunk(trueExpr) + self.dedent() + self.addIndentingDirective('else') + self.addFilteredChunk(falseExpr) + self.dedent() + + def addElse(self, expr, dedent=True, lineCol=None): + expr = re.sub(r'else[ \f\t]+if','elif', expr) + self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol) + + def addElif(self, expr, dedent=True, lineCol=None): + self.addElse(expr, dedent=dedent, lineCol=lineCol) + + def addUnless(self, expr, lineCol=None): + self.addIf('if not (' + expr + ')') + + def addClosure(self, functionName, argsList, parserComment): + argStringChunks = [] + for arg in argsList: + chunk = arg[0] + if not arg[1] == None: + chunk += '=' + arg[1] + argStringChunks.append(chunk) + signature = "def " + functionName + "(" + ','.join(argStringChunks) + "):" + self.addIndentingDirective(signature) + self.addChunk('#'+parserComment) + + def addTry(self, expr, lineCol=None): + self.addIndentingDirective(expr, lineCol=lineCol) + + def addExcept(self, expr, dedent=True, lineCol=None): + self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol) + + def addFinally(self, expr, dedent=True, lineCol=None): + self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol) + + def addReturn(self, expr): + assert not self._isGenerator + self.addChunk(expr) + self._hasReturnStatement = True + + def addYield(self, expr): + assert not self._hasReturnStatement + self._isGenerator = True + if expr.replace('yield','').strip(): + self.addChunk(expr) + else: + self.addChunk('if _dummyTrans:') + self.indent() + self.addChunk('yield trans.response().getvalue()') + self.addChunk('trans = DummyTransaction()') + self.addChunk('write = trans.response().write') + self.dedent() + self.addChunk('else:') + self.indent() + self.addChunk( + 'raise TypeError("This method cannot be called with a trans arg")') + self.dedent() + + + def addPass(self, expr): + self.addChunk(expr) + + def addDel(self, expr): + self.addChunk(expr) + + def addAssert(self, expr): + self.addChunk(expr) + + def addRaise(self, expr): + self.addChunk(expr) + + def addBreak(self, expr): + self.addChunk(expr) + + def addContinue(self, expr): + self.addChunk(expr) + + def addPSP(self, PSP): + self.commitStrConst() + autoIndent = False + if PSP[0] == '=': + PSP = PSP[1:] + if PSP: + self.addWriteChunk('_filter(' + PSP + ')') + return + + elif PSP.lower() == 'end': + self.dedent() + return + elif PSP[-1] == '$': + autoIndent = True + PSP = PSP[:-1] + elif PSP[-1] == ':': + autoIndent = True + + for line in PSP.splitlines(): + self.addChunk(line) + + if autoIndent: + self.indent() + + def nextCacheID(self): + return ('_'+str(random.randrange(100, 999)) + + str(random.randrange(10000, 99999))) + + def startCacheRegion(self, cacheInfo, lineCol, rawPlaceholder=None): + + # @@TR: we should add some runtime logging to this + + ID = self.nextCacheID() + interval = cacheInfo.get('interval',None) + test = cacheInfo.get('test',None) + customID = cacheInfo.get('id',None) + if customID: + ID = customID + varyBy = cacheInfo.get('varyBy', repr(ID)) + self._cacheRegionsStack.append(ID) # attrib of current methodCompiler + + # @@TR: add this to a special class var as well + self.addChunk('') + + self.addChunk('## START CACHE REGION: ID='+ID+ + '. line %s, col %s'%lineCol + ' in the source.') + + self.addChunk('_RECACHE_%(ID)s = False'%locals()) + self.addChunk('_cacheRegion_%(ID)s = self.getCacheRegion(regionID='%locals() + + repr(ID) + + ', cacheInfo=%r'%cacheInfo + + ')') + self.addChunk('if _cacheRegion_%(ID)s.isNew():'%locals()) + self.indent() + self.addChunk('_RECACHE_%(ID)s = True'%locals()) + self.dedent() + + self.addChunk('_cacheItem_%(ID)s = _cacheRegion_%(ID)s.getCacheItem('%locals() + +varyBy+')') + + self.addChunk('if _cacheItem_%(ID)s.hasExpired():'%locals()) + self.indent() + self.addChunk('_RECACHE_%(ID)s = True'%locals()) + self.dedent() + + if test: + self.addChunk('if ' + test + ':') + self.indent() + self.addChunk('_RECACHE_%(ID)s = True'%locals()) + self.dedent() + + self.addChunk('if (not _RECACHE_%(ID)s) and _cacheItem_%(ID)s.getRefreshTime():'%locals()) + self.indent() + #self.addChunk('print "DEBUG"+"-"*50') + self.addChunk('try:') + self.indent() + self.addChunk('_output = _cacheItem_%(ID)s.renderOutput()'%locals()) + self.dedent() + self.addChunk('except KeyError:') + self.indent() + self.addChunk('_RECACHE_%(ID)s = True'%locals()) + #self.addChunk('print "DEBUG"+"*"*50') + self.dedent() + self.addChunk('else:') + self.indent() + self.addWriteChunk('_output') + self.addChunk('del _output') + self.dedent() + + self.dedent() + + self.addChunk('if _RECACHE_%(ID)s or not _cacheItem_%(ID)s.getRefreshTime():'%locals()) + self.indent() + self.addChunk('_orig_trans%(ID)s = trans'%locals()) + self.addChunk('trans = _cacheCollector_%(ID)s = DummyTransaction()'%locals()) + self.addChunk('write = _cacheCollector_%(ID)s.response().write'%locals()) + if interval: + self.addChunk(("_cacheItem_%(ID)s.setExpiryTime(currentTime() +"%locals()) + + str(interval) + ")") + + def endCacheRegion(self): + ID = self._cacheRegionsStack.pop() + self.addChunk('trans = _orig_trans%(ID)s'%locals()) + self.addChunk('write = trans.response().write') + self.addChunk('_cacheData = _cacheCollector_%(ID)s.response().getvalue()'%locals()) + self.addChunk('_cacheItem_%(ID)s.setData(_cacheData)'%locals()) + self.addWriteChunk('_cacheData') + self.addChunk('del _cacheData') + self.addChunk('del _cacheCollector_%(ID)s'%locals()) + self.addChunk('del _orig_trans%(ID)s'%locals()) + self.dedent() + self.addChunk('## END CACHE REGION: '+ID) + self.addChunk('') + + def nextCallRegionID(self): + return self.nextCacheID() + + def startCallRegion(self, functionName, args, lineCol, regionTitle='CALL'): + class CallDetails: pass + callDetails = CallDetails() + callDetails.ID = ID = self.nextCallRegionID() + callDetails.functionName = functionName + callDetails.args = args + callDetails.lineCol = lineCol + callDetails.usesKeywordArgs = False + self._callRegionsStack.append((ID, callDetails)) # attrib of current methodCompiler + + self.addChunk('## START %(regionTitle)s REGION: '%locals() + +ID + +' of '+functionName + +' at line %s, col %s'%lineCol + ' in the source.') + self.addChunk('_orig_trans%(ID)s = trans'%locals()) + self.addChunk('_wasBuffering%(ID)s = self._CHEETAH__isBuffering'%locals()) + self.addChunk('self._CHEETAH__isBuffering = True') + self.addChunk('trans = _callCollector%(ID)s = DummyTransaction()'%locals()) + self.addChunk('write = _callCollector%(ID)s.response().write'%locals()) + + def setCallArg(self, argName, lineCol): + ID, callDetails = self._callRegionsStack[-1] + if callDetails.usesKeywordArgs: + self._endCallArg() + else: + callDetails.usesKeywordArgs = True + self.addChunk('_callKws%(ID)s = {}'%locals()) + self.addChunk('_currentCallArgname%(ID)s = %(argName)r'%locals()) + callDetails.currentArgname = argName + + def _endCallArg(self): + ID, callDetails = self._callRegionsStack[-1] + currCallArg = callDetails.currentArgname + self.addChunk(('_callKws%(ID)s[%(currCallArg)r] =' + ' _callCollector%(ID)s.response().getvalue()')%locals()) + self.addChunk('del _callCollector%(ID)s'%locals()) + self.addChunk('trans = _callCollector%(ID)s = DummyTransaction()'%locals()) + self.addChunk('write = _callCollector%(ID)s.response().write'%locals()) + + def endCallRegion(self, regionTitle='CALL'): + ID, callDetails = self._callRegionsStack[-1] + functionName, initialKwArgs, lineCol = ( + callDetails.functionName, callDetails.args, callDetails.lineCol) + + def reset(ID=ID): + self.addChunk('trans = _orig_trans%(ID)s'%locals()) + self.addChunk('write = trans.response().write') + self.addChunk('self._CHEETAH__isBuffering = _wasBuffering%(ID)s '%locals()) + self.addChunk('del _wasBuffering%(ID)s'%locals()) + self.addChunk('del _orig_trans%(ID)s'%locals()) + + if not callDetails.usesKeywordArgs: + reset() + self.addChunk('_callArgVal%(ID)s = _callCollector%(ID)s.response().getvalue()'%locals()) + self.addChunk('del _callCollector%(ID)s'%locals()) + if initialKwArgs: + initialKwArgs = ', '+initialKwArgs + self.addFilteredChunk('%(functionName)s(_callArgVal%(ID)s%(initialKwArgs)s)'%locals()) + self.addChunk('del _callArgVal%(ID)s'%locals()) + else: + if initialKwArgs: + initialKwArgs = initialKwArgs+', ' + self._endCallArg() + reset() + self.addFilteredChunk('%(functionName)s(%(initialKwArgs)s**_callKws%(ID)s)'%locals()) + self.addChunk('del _callKws%(ID)s'%locals()) + self.addChunk('## END %(regionTitle)s REGION: '%locals() + +ID + +' of '+functionName + +' at line %s, col %s'%lineCol + ' in the source.') + self.addChunk('') + self._callRegionsStack.pop() # attrib of current methodCompiler + + def nextCaptureRegionID(self): + return self.nextCacheID() + + def startCaptureRegion(self, assignTo, lineCol): + class CaptureDetails: pass + captureDetails = CaptureDetails() + captureDetails.ID = ID = self.nextCaptureRegionID() + captureDetails.assignTo = assignTo + captureDetails.lineCol = lineCol + + self._captureRegionsStack.append((ID,captureDetails)) # attrib of current methodCompiler + self.addChunk('## START CAPTURE REGION: '+ID + +' '+assignTo + +' at line %s, col %s'%lineCol + ' in the source.') + self.addChunk('_orig_trans%(ID)s = trans'%locals()) + self.addChunk('_wasBuffering%(ID)s = self._CHEETAH__isBuffering'%locals()) + self.addChunk('self._CHEETAH__isBuffering = True') + self.addChunk('trans = _captureCollector%(ID)s = DummyTransaction()'%locals()) + self.addChunk('write = _captureCollector%(ID)s.response().write'%locals()) + + def endCaptureRegion(self): + ID, captureDetails = self._captureRegionsStack.pop() + assignTo, lineCol = (captureDetails.assignTo, captureDetails.lineCol) + self.addChunk('trans = _orig_trans%(ID)s'%locals()) + self.addChunk('write = trans.response().write') + self.addChunk('self._CHEETAH__isBuffering = _wasBuffering%(ID)s '%locals()) + self.addChunk('%(assignTo)s = _captureCollector%(ID)s.response().getvalue()'%locals()) + self.addChunk('del _orig_trans%(ID)s'%locals()) + self.addChunk('del _captureCollector%(ID)s'%locals()) + self.addChunk('del _wasBuffering%(ID)s'%locals()) + + def setErrorCatcher(self, errorCatcherName): + self.turnErrorCatcherOn() + + self.addChunk('if self._CHEETAH__errorCatchers.has_key("' + errorCatcherName + '"):') + self.indent() + self.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["' + + errorCatcherName + '"]') + self.dedent() + self.addChunk('else:') + self.indent() + self.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["' + + errorCatcherName + '"] = ErrorCatchers.' + + errorCatcherName + '(self)' + ) + self.dedent() + + def nextFilterRegionID(self): + return self.nextCacheID() + + def setFilter(self, theFilter, isKlass): + class FilterDetails: pass + filterDetails = FilterDetails() + filterDetails.ID = ID = self.nextFilterRegionID() + filterDetails.theFilter = theFilter + filterDetails.isKlass = isKlass + self._filterRegionsStack.append((ID, filterDetails)) # attrib of current methodCompiler + + self.addChunk('_orig_filter%(ID)s = _filter'%locals()) + if isKlass: + self.addChunk('_filter = self._CHEETAH__currentFilter = ' + theFilter.strip() + + '(self).filter') + else: + if theFilter.lower() == 'none': + self.addChunk('_filter = self._CHEETAH__initialFilter') + else: + # is string representing the name of a builtin filter + self.addChunk('filterName = ' + repr(theFilter)) + self.addChunk('if self._CHEETAH__filters.has_key("' + theFilter + '"):') + self.indent() + self.addChunk('_filter = self._CHEETAH__currentFilter = self._CHEETAH__filters[filterName]') + self.dedent() + self.addChunk('else:') + self.indent() + self.addChunk('_filter = self._CHEETAH__currentFilter' + +' = \\\n\t\t\tself._CHEETAH__filters[filterName] = ' + + 'getattr(self._CHEETAH__filtersLib, filterName)(self).filter') + self.dedent() + + def closeFilterBlock(self): + ID, filterDetails = self._filterRegionsStack.pop() + #self.addChunk('_filter = self._CHEETAH__initialFilter') + self.addChunk('_filter = _orig_filter%(ID)s'%locals()) + +class AutoMethodCompiler(MethodCompiler): + + def _setupState(self): + MethodCompiler._setupState(self) + self._argStringList = [ ("self",None) ] + self._streamingEnabled = True + + def _useKWsDictArgForPassingTrans(self): + alreadyHasTransArg = [argname for argname,defval in self._argStringList + if argname=='trans'] + return (self.methodName()!='respond' + and not alreadyHasTransArg + and self.setting('useKWsDictArgForPassingTrans')) + + def cleanupState(self): + MethodCompiler.cleanupState(self) + self.commitStrConst() + if self._cacheRegionsStack: + self.endCacheRegion() + if self._callRegionsStack: + self.endCallRegion() + + if self._streamingEnabled: + kwargsName = None + positionalArgsListName = None + for argname,defval in self._argStringList: + if argname.strip().startswith('**'): + kwargsName = argname.strip().replace('**','') + break + elif argname.strip().startswith('*'): + positionalArgsListName = argname.strip().replace('*','') + + if not kwargsName and self._useKWsDictArgForPassingTrans(): + kwargsName = 'KWS' + self.addMethArg('**KWS', None) + self._kwargsName = kwargsName + + if not self._useKWsDictArgForPassingTrans(): + if not kwargsName and not positionalArgsListName: + self.addMethArg('trans', 'None') + else: + self._streamingEnabled = False + + self._indentLev = self.setting('initialMethIndentLevel') + mainBodyChunks = self._methodBodyChunks + self._methodBodyChunks = [] + self._addAutoSetupCode() + self._methodBodyChunks.extend(mainBodyChunks) + self._addAutoCleanupCode() + + def _addAutoSetupCode(self): + if self._initialMethodComment: + self.addChunk(self._initialMethodComment) + + if self._streamingEnabled: + if self._useKWsDictArgForPassingTrans() and self._kwargsName: + self.addChunk('trans = %s.get("trans")'%self._kwargsName) + self.addChunk('if (not trans and not self._CHEETAH__isBuffering' + ' and not callable(self.transaction)):') + self.indent() + self.addChunk('trans = self.transaction' + ' # is None unless self.awake() was called') + self.dedent() + self.addChunk('if not trans:') + self.indent() + self.addChunk('trans = DummyTransaction()') + if self.setting('autoAssignDummyTransactionToSelf'): + self.addChunk('self.transaction = trans') + self.addChunk('_dummyTrans = True') + self.dedent() + self.addChunk('else: _dummyTrans = False') + else: + self.addChunk('trans = DummyTransaction()') + self.addChunk('_dummyTrans = True') + self.addChunk('write = trans.response().write') + if self.setting('useNameMapper'): + argNames = [arg[0] for arg in self._argStringList] + allowSearchListAsMethArg = self.setting('allowSearchListAsMethArg') + if allowSearchListAsMethArg and 'SL' in argNames: + pass + elif allowSearchListAsMethArg and 'searchList' in argNames: + self.addChunk('SL = searchList') + else: + self.addChunk('SL = self._CHEETAH__searchList') + if self.setting('useFilters'): + self.addChunk('_filter = self._CHEETAH__currentFilter') + self.addChunk('') + self.addChunk("#" *40) + self.addChunk('## START - generated method body') + self.addChunk('') + + def _addAutoCleanupCode(self): + self.addChunk('') + self.addChunk("#" *40) + self.addChunk('## END - generated method body') + self.addChunk('') + + if not self._isGenerator: + self.addStop() + self.addChunk('') + + def addStop(self, expr=None): + self.addChunk('return _dummyTrans and trans.response().getvalue() or ""') + + def addMethArg(self, name, defVal=None): + self._argStringList.append( (name,defVal) ) + + def methodSignature(self): + argStringChunks = [] + for arg in self._argStringList: + chunk = arg[0] + if not arg[1] == None: + chunk += '=' + arg[1] + argStringChunks.append(chunk) + argString = (', ').join(argStringChunks) + + output = [] + if self._decorator: + output.append(self._indent + self._decorator+'\n') + output.append(self._indent + "def " + + self.methodName() + "(" + + argString + "):\n\n") + return ''.join(output) + + +################################################## +## CLASS COMPILERS + +_initMethod_initCheetah = """\ +if not self._CHEETAH__instanceInitialized: + cheetahKWArgs = {} + allowedKWs = 'searchList namespaces filter filtersLib errorCatcher'.split() + for k,v in KWs.items(): + if k in allowedKWs: cheetahKWArgs[k] = v + self._initCheetahInstance(**cheetahKWArgs) +""".replace('\n','\n'+' '*8) + +class ClassCompiler(GenUtils): + methodCompilerClass = AutoMethodCompiler + methodCompilerClassForInit = MethodCompiler + + def __init__(self, className, mainMethodName='respond', + moduleCompiler=None, + fileName=None, + settingsManager=None): + + self._settingsManager = settingsManager + self._fileName = fileName + self._className = className + self._moduleCompiler = moduleCompiler + self._mainMethodName = mainMethodName + self._setupState() + methodCompiler = self._spawnMethodCompiler( + mainMethodName, + initialMethodComment='## CHEETAH: main method generated for this template') + + self._setActiveMethodCompiler(methodCompiler) + if fileName and self.setting('monitorSrcFile'): + self._addSourceFileMonitoring(fileName) + + def setting(self, key): + return self._settingsManager.setting(key) + + def __getattr__(self, name): + """Provide access to the methods and attributes of the MethodCompiler + at the top of the activeMethods stack: one-way namespace sharing + + + WARNING: Use .setMethods to assign the attributes of the MethodCompiler + from the methods of this class!!! or you will be assigning to attributes + of this object instead.""" + + if self.__dict__.has_key(name): + return self.__dict__[name] + elif hasattr(self.__class__, name): + return getattr(self.__class__, name) + elif self._activeMethodsList and hasattr(self._activeMethodsList[-1], name): + return getattr(self._activeMethodsList[-1], name) + else: + raise AttributeError, name + + def _setupState(self): + self._classDef = None + self._decoratorForNextMethod = None + self._activeMethodsList = [] # stack while parsing/generating + self._finishedMethodsList = [] # store by order + self._methodsIndex = {} # store by name + self._baseClass = 'Template' + self._classDocStringLines = [] + # printed after methods in the gen class def: + self._generatedAttribs = ['_CHEETAH__instanceInitialized = False'] + self._generatedAttribs.append('_CHEETAH_version = __CHEETAH_version__') + self._generatedAttribs.append( + '_CHEETAH_versionTuple = __CHEETAH_versionTuple__') + self._generatedAttribs.append('_CHEETAH_genTime = __CHEETAH_genTime__') + self._generatedAttribs.append('_CHEETAH_genTimestamp = __CHEETAH_genTimestamp__') + self._generatedAttribs.append('_CHEETAH_src = __CHEETAH_src__') + self._generatedAttribs.append( + '_CHEETAH_srcLastModified = __CHEETAH_srcLastModified__') + + if self.setting('templateMetaclass'): + self._generatedAttribs.append('__metaclass__ = '+self.setting('templateMetaclass')) + self._initMethChunks = [] + self._blockMetaData = {} + self._errorCatcherCount = 0 + self._placeholderToErrorCatcherMap = {} + + def cleanupState(self): + while self._activeMethodsList: + methCompiler = self._popActiveMethodCompiler() + self._swallowMethodCompiler(methCompiler) + self._setupInitMethod() + if self._mainMethodName == 'respond': + if self.setting('setup__str__method'): + self._generatedAttribs.append('def __str__(self): return self.respond()') + self.addAttribute('_mainCheetahMethod_for_' + self._className + + '= ' + repr(self._mainMethodName) ) + + def _setupInitMethod(self): + __init__ = self._spawnMethodCompiler('__init__', + klass=self.methodCompilerClassForInit) + __init__.setMethodSignature("def __init__(self, *args, **KWs)") + __init__.addChunk("%s.__init__(self, *args, **KWs)" % self._baseClass) + __init__.addChunk(_initMethod_initCheetah%{'className':self._className}) + for chunk in self._initMethChunks: + __init__.addChunk(chunk) + __init__.cleanupState() + self._swallowMethodCompiler(__init__, pos=0) + + def _addSourceFileMonitoring(self, fileName): + # @@TR: this stuff needs auditing for Cheetah 2.0 + # the first bit is added to init + self.addChunkToInit('self._filePath = ' + repr(fileName)) + self.addChunkToInit('self._fileMtime = ' + str(getmtime(fileName)) ) + + # the rest is added to the main output method of the class ('mainMethod') + self.addChunk('if exists(self._filePath) and ' + + 'getmtime(self._filePath) > self._fileMtime:') + self.indent() + self.addChunk('self._compile(file=self._filePath, moduleName='+className + ')') + self.addChunk( + 'write(getattr(self, self._mainCheetahMethod_for_' + self._className + + ')(trans=trans))') + self.addStop() + self.dedent() + + def setClassName(self, name): + self._className = name + + def className(self): + return self._className + + def setBaseClass(self, baseClassName): + self._baseClass = baseClassName + + def setMainMethodName(self, methodName): + if methodName == self._mainMethodName: + return + ## change the name in the methodCompiler and add new reference + mainMethod = self._methodsIndex[self._mainMethodName] + mainMethod.setMethodName(methodName) + self._methodsIndex[methodName] = mainMethod + + ## make sure that fileUpdate code still works properly: + chunkToChange = ('write(self.' + self._mainMethodName + '(trans=trans))') + chunks = mainMethod._methodBodyChunks + if chunkToChange in chunks: + for i in range(len(chunks)): + if chunks[i] == chunkToChange: + chunks[i] = ('write(self.' + methodName + '(trans=trans))') + ## get rid of the old reference and update self._mainMethodName + del self._methodsIndex[self._mainMethodName] + self._mainMethodName = methodName + + def setMainMethodArgs(self, argsList): + mainMethodCompiler = self._methodsIndex[self._mainMethodName] + for argName, defVal in argsList: + mainMethodCompiler.addMethArg(argName, defVal) + + + def _spawnMethodCompiler(self, methodName, klass=None, + initialMethodComment=None): + if klass is None: + klass = self.methodCompilerClass + + decorator = None + if self._decoratorForNextMethod: + decorator = self._decoratorForNextMethod + self._decoratorForNextMethod = None + methodCompiler = klass(methodName, classCompiler=self, + decorator=decorator, + initialMethodComment=initialMethodComment) + self._methodsIndex[methodName] = methodCompiler + return methodCompiler + + def _setActiveMethodCompiler(self, methodCompiler): + self._activeMethodsList.append(methodCompiler) + + def _getActiveMethodCompiler(self): + return self._activeMethodsList[-1] + + def _popActiveMethodCompiler(self): + return self._activeMethodsList.pop() + + def _swallowMethodCompiler(self, methodCompiler, pos=None): + methodCompiler.cleanupState() + if pos==None: + self._finishedMethodsList.append( methodCompiler ) + else: + self._finishedMethodsList.insert(pos, methodCompiler) + return methodCompiler + + def startMethodDef(self, methodName, argsList, parserComment): + methodCompiler = self._spawnMethodCompiler( + methodName, initialMethodComment=parserComment) + self._setActiveMethodCompiler(methodCompiler) + for argName, defVal in argsList: + methodCompiler.addMethArg(argName, defVal) + + def _finishedMethods(self): + return self._finishedMethodsList + + def addDecorator(self, decoratorExpr): + """Set the decorator to be used with the next method in the source. + + See _spawnMethodCompiler() and MethodCompiler for the details of how + this is used. + """ + self._decoratorForNextMethod = decoratorExpr + + def addClassDocString(self, line): + self._classDocStringLines.append( line.replace('%','%%')) + + def addChunkToInit(self,chunk): + self._initMethChunks.append(chunk) + + def addAttribute(self, attribExpr): + ## first test to make sure that the user hasn't used any fancy Cheetah syntax + # (placeholders, directives, etc.) inside the expression + if attribExpr.find('VFN(') != -1 or attribExpr.find('VFFSL(') != -1: + raise ParseError(self, + 'Invalid #attr directive.' + + ' It should only contain simple Python literals.') + ## now add the attribute + self._generatedAttribs.append(attribExpr) + + + def addErrorCatcherCall(self, codeChunk, rawCode='', lineCol=''): + if self._placeholderToErrorCatcherMap.has_key(rawCode): + methodName = self._placeholderToErrorCatcherMap[rawCode] + if not self.setting('outputRowColComments'): + self._methodsIndex[methodName].addMethDocString( + 'plus at line %s, col %s'%lineCol) + return methodName + + self._errorCatcherCount += 1 + methodName = '__errorCatcher' + str(self._errorCatcherCount) + self._placeholderToErrorCatcherMap[rawCode] = methodName + + catcherMeth = self._spawnMethodCompiler( + methodName, + klass=MethodCompiler, + initialMethodComment=('## CHEETAH: Generated from ' + rawCode + + ' at line %s, col %s'%lineCol + '.') + ) + catcherMeth.setMethodSignature('def ' + methodName + + '(self, localsDict={})') + # is this use of localsDict right? + catcherMeth.addChunk('try:') + catcherMeth.indent() + catcherMeth.addChunk("return eval('''" + codeChunk + + "''', globals(), localsDict)") + catcherMeth.dedent() + catcherMeth.addChunk('except self._CHEETAH__errorCatcher.exceptions(), e:') + catcherMeth.indent() + catcherMeth.addChunk("return self._CHEETAH__errorCatcher.warn(exc_val=e, code= " + + repr(codeChunk) + " , rawCode= " + + repr(rawCode) + " , lineCol=" + str(lineCol) +")") + + catcherMeth.cleanupState() + + self._swallowMethodCompiler(catcherMeth) + return methodName + + def closeDef(self): + self.commitStrConst() + methCompiler = self._popActiveMethodCompiler() + self._swallowMethodCompiler(methCompiler) + + def closeBlock(self): + self.commitStrConst() + methCompiler = self._popActiveMethodCompiler() + methodName = methCompiler.methodName() + if self.setting('includeBlockMarkers'): + endMarker = self.setting('blockMarkerEnd') + methCompiler.addStrConst(endMarker[0] + methodName + endMarker[1]) + self._swallowMethodCompiler(methCompiler) + + #metaData = self._blockMetaData[methodName] + #rawDirective = metaData['raw'] + #lineCol = metaData['lineCol'] + + ## insert the code to call the block, caching if #cache directive is on + codeChunk = 'self.' + methodName + '(trans=trans)' + self.addChunk(codeChunk) + + #self.appendToPrevChunk(' # generated from ' + repr(rawDirective) ) + #if self.setting('outputRowColComments'): + # self.appendToPrevChunk(' at line %s, col %s' % lineCol + '.') + + + ## code wrapping methods + + def classDef(self): + if self._classDef: + return self._classDef + else: + return self.wrapClassDef() + + __str__ = classDef + + def wrapClassDef(self): + ind = self.setting('indentationStep') + classDefChunks = [self.classSignature(), + self.classDocstring(), + ] + def addMethods(): + classDefChunks.extend([ + ind + '#'*50, + ind + '## CHEETAH GENERATED METHODS', + '\n', + self.methodDefs(), + ]) + def addAttributes(): + classDefChunks.extend([ + ind + '#'*50, + ind + '## CHEETAH GENERATED ATTRIBUTES', + '\n', + self.attributes(), + ]) + if self.setting('outputMethodsBeforeAttributes'): + addMethods() + addAttributes() + else: + addAttributes() + addMethods() + + classDef = '\n'.join(classDefChunks) + self._classDef = classDef + return classDef + + + def classSignature(self): + return "class %s(%s):" % (self.className(), self._baseClass) + + def classDocstring(self): + if not self._classDocStringLines: + return '' + ind = self.setting('indentationStep') + docStr = ('%(ind)s"""\n%(ind)s' + + '\n%(ind)s'.join(self._classDocStringLines) + + '\n%(ind)s"""\n' + ) % {'ind':ind} + return docStr + + def methodDefs(self): + methodDefs = [str(methGen) for methGen in self._finishedMethods() ] + return '\n\n'.join(methodDefs) + + def attributes(self): + attribs = [self.setting('indentationStep') + str(attrib) + for attrib in self._generatedAttribs ] + return '\n\n'.join(attribs) + +class AutoClassCompiler(ClassCompiler): + pass + +################################################## +## MODULE COMPILERS + +class ModuleCompiler(SettingsManager, GenUtils): + + parserClass = Parser + classCompilerClass = AutoClassCompiler + + def __init__(self, source=None, file=None, + moduleName='DynamicallyCompiledCheetahTemplate', + mainClassName=None, # string + mainMethodName=None, # string + baseclassName=None, # string + extraImportStatements=None, # list of strings + settings=None # dict + ): + SettingsManager.__init__(self) + if settings: + self.updateSettings(settings) + # disable useStackFrames if the C version of NameMapper isn't compiled + # it's painfully slow in the Python version and bites Windows users all + # the time: + if not NameMapper.C_VERSION: + # disable warning noise as Cobbler is noarch, hence no C version -- mdehaan + # + # if not sys.platform.startswith('java'): + # warnings.warn( + # "\nYou don't have the C version of NameMapper installed! " + # "I'm disabling Cheetah's useStackFrames option as it is " + # "painfully slow with the Python version of NameMapper. " + # "You should get a copy of Cheetah with the compiled C version of NameMapper." + # ) + self.setSetting('useStackFrames', False) + + self._compiled = False + self._moduleName = moduleName + if not mainClassName: + self._mainClassName = moduleName + else: + self._mainClassName = mainClassName + self._mainMethodNameArg = mainMethodName + if mainMethodName: + self.setSetting('mainMethodName', mainMethodName) + self._baseclassName = baseclassName + + self._filePath = None + self._fileMtime = None + + if source and file: + raise TypeError("Cannot compile from a source string AND file.") + elif isinstance(file, types.StringType) or isinstance(file, types.UnicodeType): # it's a filename. + + f = open(file) # Raises IOError. + source = f.read() + f.close() + self._filePath = file + self._fileMtime = os.path.getmtime(file) + elif hasattr(file, 'read'): + source = file.read() # Can't set filename or mtime--they're not accessible. + elif file: + raise TypeError("'file' argument must be a filename string or file-like object") + + + if self._filePath: + self._fileDirName, self._fileBaseName = os.path.split(self._filePath) + self._fileBaseNameRoot, self._fileBaseNameExt = \ + os.path.splitext(self._fileBaseName) + + if not (isinstance(source, str) or isinstance(source, unicode)): + source = str( source ) + # by converting to string here we allow objects such as other Templates + # to be passed in + + # Handle the #indent directive by converting it to other directives. + # (Over the long term we'll make it a real directive.) + if source == "": + warnings.warn("You supplied an empty string for the source!", ) + + if source.find('#indent') != -1: #@@TR: undocumented hack + source = indentize(source) + + self._parser = self.parserClass(source, filename=self._filePath, compiler=self) + self._setupCompilerState() + + def __getattr__(self, name): + """Provide one-way access to the methods and attributes of the + ClassCompiler, and thereby the MethodCompilers as well. + + WARNING: Use .setMethods to assign the attributes of the ClassCompiler + from the methods of this class!!! or you will be assigning to attributes + of this object instead. + """ + if self.__dict__.has_key(name): + return self.__dict__[name] + elif hasattr(self.__class__, name): + return getattr(self.__class__, name) + elif self._activeClassesList and hasattr(self._activeClassesList[-1], name): + return getattr(self._activeClassesList[-1], name) + else: + raise AttributeError, name + + def _initializeSettings(self): + self.updateSettings(copy.deepcopy(DEFAULT_COMPILER_SETTINGS)) + + def _setupCompilerState(self): + self._activeClassesList = [] + self._finishedClassesList = [] # listed by ordered + self._finishedClassIndex = {} # listed by name + self._moduleDef = None + self._moduleShBang = '#!/usr/bin/env python' + self._moduleEncoding = 'ascii' + self._moduleEncodingStr = '' + self._moduleHeaderLines = [] + self._moduleDocStringLines = [] + self._specialVars = {} + self._importStatements = [ + "import sys", + "import os", + "import os.path", + "from os.path import getmtime, exists", + "import time", + "import types", + "import __builtin__", + "from Cheetah.Version import MinCompatibleVersion as RequiredCheetahVersion", + "from Cheetah.Version import MinCompatibleVersionTuple as RequiredCheetahVersionTuple", + "from Cheetah.Template import Template", + "from Cheetah.DummyTransaction import DummyTransaction", + "from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList", + "from Cheetah.CacheRegion import CacheRegion", + "import Cheetah.Filters as Filters", + "import Cheetah.ErrorCatchers as ErrorCatchers", + ] + + self._importedVarNames = ['sys', + 'os', + 'os.path', + 'time', + 'types', + 'Template', + 'DummyTransaction', + 'NotFound', + 'Filters', + 'ErrorCatchers', + 'CacheRegion', + ] + + self._moduleConstants = [ + "try:", + " True, False", + "except NameError:", + " True, False = (1==1), (1==0)", + "VFFSL=valueFromFrameOrSearchList", + "VFSL=valueFromSearchList", + "VFN=valueForName", + "currentTime=time.time", + ] + + def compile(self): + classCompiler = self._spawnClassCompiler(self._mainClassName) + if self._baseclassName: + classCompiler.setBaseClass(self._baseclassName) + self._addActiveClassCompiler(classCompiler) + self._parser.parse() + self._swallowClassCompiler(self._popActiveClassCompiler()) + self._compiled = True + self._parser.cleanup() + + def _spawnClassCompiler(self, className, klass=None): + if klass is None: + klass = self.classCompilerClass + classCompiler = klass(className, + moduleCompiler=self, + mainMethodName=self.setting('mainMethodName'), + fileName=self._filePath, + settingsManager=self, + ) + return classCompiler + + def _addActiveClassCompiler(self, classCompiler): + self._activeClassesList.append(classCompiler) + + def _getActiveClassCompiler(self): + return self._activeClassesList[-1] + + def _popActiveClassCompiler(self): + return self._activeClassesList.pop() + + def _swallowClassCompiler(self, classCompiler): + classCompiler.cleanupState() + self._finishedClassesList.append( classCompiler ) + self._finishedClassIndex[classCompiler.className()] = classCompiler + return classCompiler + + def _finishedClasses(self): + return self._finishedClassesList + + def importedVarNames(self): + return self._importedVarNames + + def addImportedVarNames(self, varNames): + self._importedVarNames.extend(varNames) + + ## methods for adding stuff to the module and class definitions + + def setBaseClass(self, baseClassName): + if self._mainMethodNameArg: + self.setMainMethodName(self._mainMethodNameArg) + else: + self.setMainMethodName(self.setting('mainMethodNameForSubclasses')) + + if self.setting('handlerForExtendsDirective'): + handler = self.setting('handlerForExtendsDirective') + baseClassName = handler(compiler=self, baseClassName=baseClassName) + self._getActiveClassCompiler().setBaseClass(baseClassName) + elif (not self.setting('autoImportForExtendsDirective') + or baseClassName=='object' or baseClassName in self.importedVarNames()): + self._getActiveClassCompiler().setBaseClass(baseClassName) + # no need to import + else: + ################################################## + ## If the #extends directive contains a classname or modulename that isn't + # in self.importedVarNames() already, we assume that we need to add + # an implied 'from ModName import ClassName' where ModName == ClassName. + # - This is the case in WebKit servlet modules. + # - We also assume that the final . separates the classname from the + # module name. This might break if people do something really fancy + # with their dots and namespaces. + chunks = baseClassName.split('.') + if len(chunks)==1: + self._getActiveClassCompiler().setBaseClass(baseClassName) + if baseClassName not in self.importedVarNames(): + modName = baseClassName + # we assume the class name to be the module name + # and that it's not a builtin: + importStatement = "from %s import %s" % (modName, baseClassName) + self.addImportStatement(importStatement) + self.addImportedVarNames( [baseClassName,] ) + else: + needToAddImport = True + modName = chunks[0] + #print chunks, ':', self.importedVarNames() + for chunk in chunks[1:-1]: + if modName in self.importedVarNames(): + needToAddImport = False + finalBaseClassName = baseClassName.replace(modName+'.', '') + self._getActiveClassCompiler().setBaseClass(finalBaseClassName) + break + else: + modName += '.'+chunk + if needToAddImport: + modName, finalClassName = '.'.join(chunks[:-1]), chunks[-1] + #if finalClassName != chunks[:-1][-1]: + if finalClassName != chunks[-2]: + # we assume the class name to be the module name + modName = '.'.join(chunks) + self._getActiveClassCompiler().setBaseClass(finalClassName) + importStatement = "from %s import %s" % (modName, finalClassName) + self.addImportStatement(importStatement) + self.addImportedVarNames( [finalClassName,] ) + + def setCompilerSetting(self, key, valueExpr): + self.setSetting(key, eval(valueExpr) ) + self._parser.configureParser() + + def setCompilerSettings(self, keywords, settingsStr): + KWs = keywords + merge = True + if 'nomerge' in KWs: + merge = False + + if 'reset' in KWs: + # @@TR: this is actually caught by the parser at the moment. + # subject to change in the future + self._initializeSettings() + self._parser.configureParser() + return + elif 'python' in KWs: + settingsReader = self.updateSettingsFromPySrcStr + # this comes from SettingsManager + else: + # this comes from SettingsManager + settingsReader = self.updateSettingsFromConfigStr + + settingsReader(settingsStr) + self._parser.configureParser() + + def setShBang(self, shBang): + self._moduleShBang = shBang + + def setModuleEncoding(self, encoding): + self._moduleEncoding = encoding + self._moduleEncodingStr = '# -*- coding: %s -*-' %encoding + + def getModuleEncoding(self): + return self._moduleEncoding + + def addModuleHeader(self, line): + """Adds a header comment to the top of the generated module. + """ + self._moduleHeaderLines.append(line) + + def addModuleDocString(self, line): + """Adds a line to the generated module docstring. + """ + self._moduleDocStringLines.append(line) + + def addModuleGlobal(self, line): + """Adds a line of global module code. It is inserted after the import + statements and Cheetah default module constants. + """ + self._moduleConstants.append(line) + + def addSpecialVar(self, basename, contents, includeUnderscores=True): + """Adds module __specialConstant__ to the module globals. + """ + name = includeUnderscores and '__'+basename+'__' or basename + self._specialVars[name] = contents.strip() + + def addImportStatement(self, impStatement): + self._importStatements.append(impStatement) + + #@@TR 2005-01-01: there's almost certainly a cleaner way to do this! + importVarNames = impStatement[impStatement.find('import') + len('import'):].split(',') + importVarNames = [var.split()[-1] for var in importVarNames] # handles aliases + importVarNames = [var for var in importVarNames if var!='*'] + self.addImportedVarNames(importVarNames) #used by #extend for auto-imports + + def addAttribute(self, attribName, expr): + self._getActiveClassCompiler().addAttribute(attribName + ' =' + expr) + + def addComment(self, comm): + if re.match(r'#+$',comm): # skip bar comments + return + + specialVarMatch = specialVarRE.match(comm) + if specialVarMatch: + # @@TR: this is a bit hackish and is being replaced with + # #set module varName = ... + return self.addSpecialVar(specialVarMatch.group(1), + comm[specialVarMatch.end():]) + elif comm.startswith('doc:'): + addLine = self.addMethDocString + comm = comm[len('doc:'):].strip() + elif comm.startswith('doc-method:'): + addLine = self.addMethDocString + comm = comm[len('doc-method:'):].strip() + elif comm.startswith('doc-module:'): + addLine = self.addModuleDocString + comm = comm[len('doc-module:'):].strip() + elif comm.startswith('doc-class:'): + addLine = self.addClassDocString + comm = comm[len('doc-class:'):].strip() + elif comm.startswith('header:'): + addLine = self.addModuleHeader + comm = comm[len('header:'):].strip() + else: + addLine = self.addMethComment + + for line in comm.splitlines(): + addLine(line) + + ## methods for module code wrapping + + def getModuleCode(self): + if not self._compiled: + self.compile() + if self._moduleDef: + return self._moduleDef + else: + return self.wrapModuleDef() + + __str__ = getModuleCode + + def wrapModuleDef(self): + self.addSpecialVar('CHEETAH_docstring', self.setting('defDocStrMsg')) + self.addModuleGlobal('__CHEETAH_version__ = %r'%Version) + self.addModuleGlobal('__CHEETAH_versionTuple__ = %r'%(VersionTuple,)) + self.addModuleGlobal('__CHEETAH_genTime__ = %r'%time.time()) + self.addModuleGlobal('__CHEETAH_genTimestamp__ = %r'%self.timestamp()) + if self._filePath: + timestamp = self.timestamp(self._fileMtime) + self.addModuleGlobal('__CHEETAH_src__ = %r'%self._filePath) + self.addModuleGlobal('__CHEETAH_srcLastModified__ = %r'%timestamp) + else: + self.addModuleGlobal('__CHEETAH_src__ = None') + self.addModuleGlobal('__CHEETAH_srcLastModified__ = None') + + moduleDef = """%(header)s +%(docstring)s + +################################################## +## DEPENDENCIES +%(imports)s + +################################################## +## MODULE CONSTANTS +%(constants)s +%(specialVars)s + +if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple: + raise AssertionError( + 'This template was compiled with Cheetah version' + ' %%s. Templates compiled before version %%s must be recompiled.'%%( + __CHEETAH_version__, RequiredCheetahVersion)) + +################################################## +## CLASSES + +%(classes)s + +## END CLASS DEFINITION + +if not hasattr(%(mainClassName)s, '_initCheetahAttributes'): + templateAPIClass = getattr(%(mainClassName)s, '_CHEETAH_templateClass', Template) + templateAPIClass._addCheetahPlumbingCodeToClass(%(mainClassName)s) + +%(footer)s +""" % {'header':self.moduleHeader(), + 'docstring':self.moduleDocstring(), + 'specialVars':self.specialVars(), + 'imports':self.importStatements(), + 'constants':self.moduleConstants(), + 'classes':self.classDefs(), + 'footer':self.moduleFooter(), + 'mainClassName':self._mainClassName, + } + + self._moduleDef = moduleDef + return moduleDef + + def timestamp(self, theTime=None): + if not theTime: + theTime = time.time() + return time.asctime(time.localtime(theTime)) + + def moduleHeader(self): + header = self._moduleShBang + '\n' + header += self._moduleEncodingStr + '\n' + if self._moduleHeaderLines: + offSet = self.setting('commentOffset') + + header += ( + '#' + ' '*offSet + + ('\n#'+ ' '*offSet).join(self._moduleHeaderLines) + '\n') + + return header + + def moduleDocstring(self): + if not self._moduleDocStringLines: + return '' + + return ('"""' + + '\n'.join(self._moduleDocStringLines) + + '\n"""\n') + + def specialVars(self): + chunks = [] + theVars = self._specialVars + keys = theVars.keys() + keys.sort() + for key in keys: + chunks.append(key + ' = ' + repr(theVars[key]) ) + return '\n'.join(chunks) + + def importStatements(self): + return '\n'.join(self._importStatements) + + def moduleConstants(self): + return '\n'.join(self._moduleConstants) + + def classDefs(self): + classDefs = [str(klass) for klass in self._finishedClasses() ] + return '\n\n'.join(classDefs) + + def moduleFooter(self): + return """ +# CHEETAH was developed by Tavis Rudd and Mike Orr +# with code, advice and input from many other volunteers. +# For more information visit http://www.CheetahTemplate.org/ + +################################################## +## if run from command line: +if __name__ == '__main__': + from Cheetah.TemplateCmdLineIface import CmdLineIface + CmdLineIface(templateObj=%(className)s()).run() + +""" % {'className':self._mainClassName} + + +################################################## +## Make Compiler an alias for ModuleCompiler + +Compiler = ModuleCompiler |