''' 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__. ''' import sys import os import os.path from os.path import getmtime, exists import re import types import time import random import warnings import copy import codecs from Cheetah.Version import Version, VersionTuple from Cheetah.SettingsManager import SettingsManager from Cheetah.Utils.Indenter import indentize # an undocumented preprocessor from Cheetah import ErrorCatchers from Cheetah import NameMapper from Cheetah.Parser import Parser, ParseError, specialVarRE, \ STATIC_CACHE, REFRESH_CACHE, SET_LOCAL, SET_GLOBAL, SET_MODULE, \ unicodeDirectiveRE, encodingDirectiveRE, escapedNewlineRE from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList VFFSL=valueFromFrameOrSearchList VFSL=valueFromSearchList VFN=valueForName currentTime=time.time class Error(Exception): pass # Settings format: (key, default, docstring) _DEFAULT_COMPILER_SETTINGS = [ ('useNameMapper', True, 'Enable NameMapper for dotted notation and searchList support'), ('useSearchList', True, 'Enable the searchList, requires useNameMapper=True, if disabled, first portion of the $variable is a global, builtin, or local variable that doesn\'t need looking up in the searchList'), ('allowSearchListAsMethArg', True, ''), ('useAutocalling', True, 'Detect and call callable objects in searchList, requires useNameMapper=True'), ('useStackFrames', True, 'Used for NameMapper.valueFromFrameOrSearchList rather than NameMapper.valueFromSearchList'), ('useErrorCatcher', False, 'Turn on the #errorCatcher directive for catching NameMapper errors, etc'), ('alwaysFilterNone', True, 'Filter out None prior to calling the #filter'), ('useFilters', True, 'If False, pass output through str()'), ('includeRawExprInFilterArgs', True, ''), ('useLegacyImportMode', True, 'All #import statements are relocated to the top of the generated Python module'), ('prioritizeSearchListOverSelf', False, 'When iterating the searchList, look into the searchList passed into the initializer instead of Template members first'), ('autoAssignDummyTransactionToSelf', False, ''), ('useKWsDictArgForPassingTrans', True, ''), ('commentOffset', 1, ''), ('outputRowColComments', True, ''), ('includeBlockMarkers', False, 'Wrap #block\'s in a comment in the template\'s output'), ('blockMarkerStart', ('\n\n'), ''), ('blockMarkerEnd', ('\n\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, ''), ('addTimestampsToCompilerOutput', True, ''), ## Customizing the #extends directive ('autoImportForExtendsDirective', True, ''), ('handlerForExtendsDirective', None, ''), ('disabledDirectives', [], 'List of directive keys to disable (without starting "#")'), ('enabledDirectives', [], 'List of directive keys to enable (without starting "#")'), ('disabledDirectiveHooks', [], 'callable(parser, directiveKey)'), ('preparseDirectiveHooks', [], 'callable(parser, directiveKey)'), ('postparseDirectiveHooks', [], 'callable(parser, directiveKey)'), ('preparsePlaceholderHooks', [], 'callable(parser)'), ('postparsePlaceholderHooks', [], 'callable(parser)'), ('expressionFilterHooks', [], '''callable(parser, expr, exprType, rawExpr=None, startPos=None), exprType is the name of the directive, "psp" or "placeholder" The filters *must* return the expr or raise an expression, they can modify the expr if needed'''), ('templateMetaclass', None, 'Strictly optional, only will work with new-style basecalsses as well'), ('i18NFunctionName', 'self.i18n', ''), ('cheetahVarStartToken', '$', ''), ('commentStartToken', '##', ''), ('multiLineCommentStartToken', '#*', ''), ('multiLineCommentEndToken', '*#', ''), ('gobbleWhitespaceAroundMultiLineComments', True, ''), ('directiveStartToken', '#', ''), ('directiveEndToken', '#', ''), ('allowWhitespaceAfterDirectiveStartToken', False, ''), ('PSPStartToken', '<%', ''), ('PSPEndToken', '%>', ''), ('EOLSlurpToken', '#', ''), ('gettextTokens', ["_", "N_", "ngettext"], ''), ('allowExpressionsInExtendsDirective', False, ''), ('allowEmptySingleLineMethods', False, ''), ('allowNestedDefScopes', True, ''), ('allowPlaceholderFilterArgs', True, ''), ('encoding', None, 'The encoding to read input files as (or None for ASCII)'), ] DEFAULT_COMPILER_SETTINGS = dict([(v[0], v[1]) for v in _DEFAULT_COMPILER_SETTINGS]) class GenUtils(object): """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, decorators=None): self._settingsManager = classCompiler self._classCompiler = classCompiler self._moduleCompiler = classCompiler._moduleCompiler self._methodName = methodName self._initialMethodComment = initialMethodComment self._setupState() self._decorators = decorators or [] 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 __unicode__ = 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 commitStrConst(self): """Add the code for outputting the pending strConst without chopping off any whitespace from it. """ if not self._pendingStrConstChunks: return strConst = ''.join(self._pendingStrConstChunks) self._pendingStrConstChunks = [] if not strConst: return reprstr = repr(strConst) i = 0 out = [] if reprstr.startswith('u'): i = 1 out = ['u'] body = escapedNewlineRE.sub('\\1\n', reprstr[i+1:-1]) if reprstr[i]=="'": out.append("'''") out.append(body) out.append("'''") else: out.append('"""') out.append(body) out.append('"""') self.addWriteChunk(''.join(out)) 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 then else """ 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(object): 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] argName = str(argName) 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 setTransform(self, transformer, isKlass): self.addChunk('trans = TransformerTransaction()') self.addChunk('trans._response = trans.response()') self.addChunk('trans._response._filter = %s' % transformer) self.addChunk('write = trans._response.write') 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()) self.addChunk('_filter = self._CHEETAH__currentFilter = _orig_filter%(ID)s'%locals()) class AutoMethodCompiler(MethodCompiler): def _setupState(self): MethodCompiler._setupState(self) self._argStringList = [ ("self", None) ] self._streamingEnabled = True self._isClassMethod = None self._isStaticMethod = None 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 isClassMethod(self): if self._isClassMethod is None: self._isClassMethod = '@classmethod' in self._decorators return self._isClassMethod def isStaticMethod(self): if self._isStaticMethod is None: self._isStaticMethod = '@staticmethod' in self._decorators return self._isStaticMethod 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 and not self.isClassMethod() and not self.isStaticMethod(): 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') elif not self.isClassMethod() and not self.isStaticMethod(): self.addChunk('SL = self._CHEETAH__searchList') else: self.addChunk('SL = [KWS]') if self.setting('useFilters'): if self.isClassMethod() or self.isStaticMethod(): self.addChunk('_filter = lambda x, **kwargs: unicode(x)') else: 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 chunk == 'self' and self.isClassMethod(): chunk = 'cls' if chunk == 'self' and self.isStaticMethod(): # Skip the "self" method for @staticmethod decorators continue if not arg[1] == None: chunk += '=' + arg[1] argStringChunks.append(chunk) argString = (', ').join(argStringChunks) output = [] if self._decorators: output.append(''.join([self._indent + decorator + '\n' for decorator in self._decorators])) 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 name in self.__dict__: 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._decoratorsForNextMethod = [] 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__') if self.setting('addTimestampsToCompilerOutput'): 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('super(%s, self).__init__(*args, **KWs)' % self._className) __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='+self._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 decorators = self._decoratorsForNextMethod or [] self._decoratorsForNextMethod = [] methodCompiler = klass(methodName, classCompiler=self, decorators=decorators, 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._decoratorsForNextMethod.append(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 addSuper(self, argsList, parserComment=None): className = self._className #self._baseClass methodName = self._getActiveMethodCompiler().methodName() argStringChunks = [] for arg in argsList: chunk = arg[0] if not arg[1] == None: chunk += '=' + arg[1] argStringChunks.append(chunk) argString = ','.join(argStringChunks) self.addFilteredChunk( 'super(%(className)s, self).%(methodName)s(%(argString)s)'%locals()) def addErrorCatcherCall(self, codeChunk, rawCode='', lineCol=''): if rawCode in self._placeholderToErrorCatcherMap: 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 __unicode__ = 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 = [methGen.methodDef() for methGen in self._finishedMethods()] return '\n\n'.join(methodDefs) def attributes(self): try: attribs = [self.setting('indentationStep') + str(attrib) for attrib in self._generatedAttribs ] except UnicodeEncodeError: attribs = [self.setting('indentationStep') + unicode(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 ): super(ModuleCompiler, self).__init__() 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: 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, basestring): # it's a filename. encoding = settings.get('encoding') if encoding: f = codecs.open(file, 'r', encoding=encoding) else: f = open(file, 'r') # if no encoding is specified, use the # builtin open function, which will # effectively read data as a bytestream 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, basestring): source = unicode(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!", ) else: unicodeMatch = unicodeDirectiveRE.search(source) encodingMatch = encodingDirectiveRE.search(source) if unicodeMatch: if encodingMatch: raise ParseError( self, "#encoding and #unicode are mutually exclusive! " "Use one or the other.") source = unicodeDirectiveRE.sub('', source) if isinstance(source, str): encoding = unicodeMatch.group(1) or 'ascii' source = unicode(source, encoding) elif encodingMatch: encodings = encodingMatch.groups() if len(encodings): encoding = encodings[0] source = source.decode(encoding) else: source = unicode(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 name in self.__dict__: 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", 'try:', ' import builtins as builtin', 'except ImportError:', ' import __builtin__ as builtin', "from os.path import getmtime, exists", "import time", "import types", "from Cheetah.Version import MinCompatibleVersion as RequiredCheetahVersion", "from Cheetah.Version import MinCompatibleVersionTuple as RequiredCheetahVersionTuple", "from Cheetah.Template import Template", "from Cheetah.DummyTransaction import *", "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 = [ "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, raw_statement=None): settings = self.settings() if not varNames: return if not settings.get('useLegacyImportMode'): if raw_statement and getattr(self, '_methodBodyChunks'): self.addChunk(raw_statement) else: 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. baseclasses = baseClassName.split(',') for klass in baseclasses: chunks = klass.split('.') if len(chunks)==1: self._getActiveClassCompiler().setBaseClass(klass) if klass not in self.importedVarNames(): modName = klass # we assume the class name to be the module name # and that it's not a builtin: importStatement = "from %s import %s" % (modName, klass) self.addImportStatement(importStatement) self.addImportedVarNames((klass,)) else: needToAddImport = True modName = chunks[0] #print chunks, ':', self.importedVarNames() for chunk in chunks[1:-1]: if modName in self.importedVarNames(): needToAddImport = False finalBaseClassName = klass.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 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): settings = self.settings() if not self._methodBodyChunks or settings.get('useLegacyImportMode'): # In the case where we are importing inline in the middle of a source block # we don't want to inadvertantly import the module at the top of the file either 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 not var == '*'] self.addImportedVarNames(importVarNames, raw_statement=impStatement) #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,)) if self.setting('addTimestampsToCompilerOutput'): 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 = sorted(theVars.keys()) 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 = [klass.classDef() 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