2 # $Id: Compiler.py,v 1.156 2007/10/30 20:17:09 tavis_rudd Exp $
3 """Compiler classes for Cheetah:
4 ModuleCompiler aka 'Compiler'
8 If you are trying to grok this code start with ModuleCompiler.__init__,
9 ModuleCompiler.compile, and ModuleCompiler.__getattr__.
12 ================================================================================
13 Author: Tavis Rudd <tavis@damnsimple.com>
14 Version: $Revision: 1.156 $
15 Start Date: 2001/09/19
16 Last Revision Date: $Date: 2007/10/30 20:17:09 $
18 __author__
= "Tavis Rudd <tavis@damnsimple.com>"
19 __revision__
= "$Revision: 1.156 $"[11:-2]
24 from os
.path
import getmtime
, exists
33 from Cheetah
.Version
import Version
, VersionTuple
34 from Cheetah
.SettingsManager
import SettingsManager
35 from Cheetah
.Utils
.Indenter
import indentize
# an undocumented preprocessor
36 from Cheetah
import ErrorCatchers
37 from Cheetah
import NameMapper
38 from Cheetah
.Parser
import Parser
, ParseError
, specialVarRE
, \
39 STATIC_CACHE
, REFRESH_CACHE
, SET_LOCAL
, SET_GLOBAL
,SET_MODULE
, \
40 unicodeDirectiveRE
, encodingDirectiveRE
,escapedNewlineRE
42 from Cheetah
.NameMapper
import NotFound
, valueForName
, valueFromSearchList
, valueFromFrameOrSearchList
43 VFFSL
=valueFromFrameOrSearchList
44 VFSL
=valueFromSearchList
48 class Error(Exception): pass
50 DEFAULT_COMPILER_SETTINGS
= {
51 ## controlling the handling of Cheetah $placeholders
52 'useNameMapper': True, # Unified dotted notation and the searchList
53 'useSearchList': True, # if false, assume the first
54 # portion of the $variable (before the first dot) is a global,
55 # builtin, or local var that doesn't need
56 # looking up in the searchlist BUT use
57 # namemapper on the rest of the lookup
58 'allowSearchListAsMethArg': True,
59 'useAutocalling': True, # detect and call callable()'s, requires NameMapper
60 'useStackFrames': True, # use NameMapper.valueFromFrameOrSearchList
61 # rather than NameMapper.valueFromSearchList
62 'useErrorCatcher':False,
63 'alwaysFilterNone':True, # filter out None, before the filter is called
64 'useFilters':True, # use str instead if =False
65 'includeRawExprInFilterArgs':True,
67 #'lookForTransactionAttr':False,
68 'autoAssignDummyTransactionToSelf':False,
69 'useKWsDictArgForPassingTrans':True,
71 ## controlling the aesthetic appearance / behaviour of generated code
73 'outputRowColComments':True,
74 # should #block's be wrapped in a comment in the template's output
75 'includeBlockMarkers': False,
76 'blockMarkerStart':('\n<!-- START BLOCK: ',' -->\n'),
77 'blockMarkerEnd':('\n<!-- END BLOCK: ',' -->\n'),
78 'defDocStrMsg':'Autogenerated by CHEETAH: The Python-Powered Template Engine',
79 'setup__str__method': False,
80 'mainMethodName':'respond',
81 'mainMethodNameForSubclasses':'writeBody',
82 'indentationStep': ' '*4,
83 'initialMethIndentLevel': 2,
84 'monitorSrcFile':False,
85 'outputMethodsBeforeAttributes': True,
86 'addTimestampsToCompilerOutput': True,
88 ## customizing the #extends directive
89 'autoImportForExtendsDirective':True,
90 'handlerForExtendsDirective':None, # baseClassName = handler(compiler, baseClassName)
91 # a callback hook for customizing the
92 # #extends directive. It can manipulate
93 # the compiler's state if needed.
94 # also see allowExpressionsInExtendsDirective
97 # input filtering/restriction
98 # use lower case keys here!!
99 'disabledDirectives':[], # list of directive keys, without the start token
100 'enabledDirectives':[], # list of directive keys, without the start token
102 'disabledDirectiveHooks':[], # callable(parser, directiveKey)
103 'preparseDirectiveHooks':[], # callable(parser, directiveKey)
104 'postparseDirectiveHooks':[], # callable(parser, directiveKey)
105 'preparsePlaceholderHooks':[], # callable(parser)
106 'postparsePlaceholderHooks':[], # callable(parser)
107 # the above hooks don't need to return anything
109 'expressionFilterHooks':[], # callable(parser, expr, exprType, rawExpr=None, startPos=None)
110 # exprType is the name of the directive, 'psp', or 'placeholder'. all
111 # lowercase. The filters *must* return the expr or raise an exception.
112 # They can modify the expr if needed.
114 'templateMetaclass':None, # strictly optional. Only works with new-style baseclasses
117 'i18NFunctionName':'self.i18n',
119 ## These are used in the parser, but I've put them here for the time being to
120 ## facilitate separating the parser and compiler:
121 'cheetahVarStartToken':'$',
122 'commentStartToken':'##',
123 'multiLineCommentStartToken':'#*',
124 'multiLineCommentEndToken':'*#',
125 'gobbleWhitespaceAroundMultiLineComments':True,
126 'directiveStartToken':'#',
127 'directiveEndToken':'#',
128 'allowWhitespaceAfterDirectiveStartToken':False,
129 'PSPStartToken':'<%',
132 'gettextTokens': ["_", "N_", "ngettext"],
133 'allowExpressionsInExtendsDirective': False, # the default restricts it to
134 # accepting dotted names
135 'allowEmptySingleLineMethods': False,
136 'allowNestedDefScopes': True,
137 'allowPlaceholderFilterArgs': True,
139 ## See Parser.initDirectives() for the use of the next 3
140 #'directiveNamesAndParsers':{}
141 #'endDirectiveNamesAndHandlers':{}
142 #'macroDirectives':{}
149 """An abstract baseclass for the Compiler classes that provides methods that
150 perform generic utility functions or generate pieces of output code from
151 information passed in by the Parser baseclass. These methods don't do any
155 def genTimeInterval(self
, timeString
):
156 ##@@ TR: need to add some error handling here
157 if timeString
[-1] == 's':
158 interval
= float(timeString
[:-1])
159 elif timeString
[-1] == 'm':
160 interval
= float(timeString
[:-1])*60
161 elif timeString
[-1] == 'h':
162 interval
= float(timeString
[:-1])*60*60
163 elif timeString
[-1] == 'd':
164 interval
= float(timeString
[:-1])*60*60*24
165 elif timeString
[-1] == 'w':
166 interval
= float(timeString
[:-1])*60*60*24*7
167 else: # default to minutes
168 interval
= float(timeString
)*60
171 def genCacheInfo(self
, cacheTokenParts
):
172 """Decipher a placeholder cachetoken
175 if cacheTokenParts
['REFRESH_CACHE']:
176 cacheInfo
['type'] = REFRESH_CACHE
177 cacheInfo
['interval'] = self
.genTimeInterval(cacheTokenParts
['interval'])
178 elif cacheTokenParts
['STATIC_CACHE']:
179 cacheInfo
['type'] = STATIC_CACHE
180 return cacheInfo
# is empty if no cache
182 def genCacheInfoFromArgList(self
, argList
):
183 cacheInfo
= {'type':REFRESH_CACHE
}
184 for key
, val
in argList
:
190 val
= self
.genTimeInterval(val
)
195 def genCheetahVar(self
, nameChunks
, plain
=False):
196 if nameChunks
[0][0] in self
.setting('gettextTokens'):
197 self
.addGetTextVar(nameChunks
)
198 if self
.setting('useNameMapper') and not plain
:
199 return self
.genNameMapperVar(nameChunks
)
201 return self
.genPlainVar(nameChunks
)
203 def addGetTextVar(self
, nameChunks
):
204 """Output something that gettext can recognize.
206 This is a harmless side effect necessary to make gettext work when it
207 is scanning compiled templates for strings marked for translation.
209 @@TR: another marginally more efficient approach would be to put the
210 output in a dummy method that is never called.
212 # @@TR: this should be in the compiler not here
213 self
.addChunk("if False:")
215 self
.addChunk(self
.genPlainVar(nameChunks
[:]))
218 def genPlainVar(self
, nameChunks
):
219 """Generate Python code for a Cheetah $var without using NameMapper
220 (Unified Dotted Notation with the SearchList).
223 chunk
= nameChunks
.pop()
224 pythonCode
= chunk
[0] + chunk
[2]
226 chunk
= nameChunks
.pop()
227 pythonCode
= (pythonCode
+ '.' + chunk
[0] + chunk
[2])
230 def genNameMapperVar(self
, nameChunks
):
231 """Generate valid Python code for a Cheetah $var, using NameMapper
232 (Unified Dotted Notation with the SearchList).
234 nameChunks = list of var subcomponents represented as tuples
235 [ (name,useAC,remainderOfExpr),
238 name = the dotted name base
239 useAC = where NameMapper should use autocalling on namemapperPart
240 remainderOfExpr = any arglist, index, or slice
242 If remainderOfExpr contains a call arglist (e.g. '(1234)') then useAC
243 is False, otherwise it defaults to True. It is overridden by the global
244 setting 'useAutocalling' if this setting is False.
247 ------------------------------------------------------------------------
248 if the raw Cheetah Var is
251 nameChunks is the list
252 [ ('a.b.c',True,'[1]'), # A
253 ('d',False,'()'), # B
254 ('x.y.z',True,''), # C
257 When this method is fed the list above it returns
258 VFN(VFN(VFFSL(SL, 'a.b.c',True)[1], 'd',False)(), 'x.y.z',True)
259 which can be represented as
260 VFN(B`, name=C[0], executeCallables=(useAC and C[1]))C[2]
262 VFN = NameMapper.valueForName
263 VFFSL = NameMapper.valueFromFrameOrSearchList
264 VFSL = NameMapper.valueFromSearchList # optionally used instead of VFFSL
265 SL = self.searchList()
266 useAC = self.setting('useAutocalling') # True in this example
268 A = ('a.b.c',True,'[1]')
270 C = ('x.y.z',True,'')
272 C` = VFN( VFN( VFFSL(SL, 'a.b.c',True)[1],
275 = VFN(B`, name='x.y.z', executeCallables=True)
277 B` = VFN(A`, name=B[0], executeCallables=(useAC and B[1]))B[2]
278 A` = VFFSL(SL, name=A[0], executeCallables=(useAC and A[1]))A[2]
281 Note, if the compiler setting useStackFrames=False (default is true)
283 A` = VFSL([locals()]+SL+[globals(), __builtin__], name=A[0], executeCallables=(useAC and A[1]))A[2]
284 This option allows Cheetah to be used with Psyco, which doesn't support
285 stack frame introspection.
287 defaultUseAC
= self
.setting('useAutocalling')
288 useSearchList
= self
.setting('useSearchList')
291 name
, useAC
, remainder
= nameChunks
.pop()
293 if not useSearchList
:
294 firstDotIdx
= name
.find('.')
295 if firstDotIdx
!= -1 and firstDotIdx
< len(name
):
296 beforeFirstDot
, afterDot
= name
[:firstDotIdx
], name
[firstDotIdx
+1:]
297 pythonCode
= ('VFN(' + beforeFirstDot
+
299 '",' + repr(defaultUseAC
and useAC
) + ')'
302 pythonCode
= name
+remainder
303 elif self
.setting('useStackFrames'):
304 pythonCode
= ('VFFSL(SL,'
306 + repr(defaultUseAC
and useAC
) + ')'
309 pythonCode
= ('VFSL([locals()]+SL+[globals(), __builtin__],'
311 + repr(defaultUseAC
and useAC
) + ')'
315 name
, useAC
, remainder
= nameChunks
.pop()
316 pythonCode
= ('VFN(' + pythonCode
+
318 '",' + repr(defaultUseAC
and useAC
) + ')'
322 ##################################################
325 class MethodCompiler(GenUtils
):
326 def __init__(self
, methodName
, classCompiler
,
327 initialMethodComment
=None,
329 self
._settingsManager
= classCompiler
330 self
._classCompiler
= classCompiler
331 self
._moduleCompiler
= classCompiler
._moduleCompiler
332 self
._methodName
= methodName
333 self
._initialMethodComment
= initialMethodComment
335 self
._decorator
= decorator
337 def setting(self
, key
):
338 return self
._settingsManager
.setting(key
)
340 def _setupState(self
):
341 self
._indent
= self
.setting('indentationStep')
342 self
._indentLev
= self
.setting('initialMethIndentLevel')
343 self
._pendingStrConstChunks
= []
344 self
._methodSignature
= None
345 self
._methodDef
= None
346 self
._docStringLines
= []
347 self
._methodBodyChunks
= []
349 self
._cacheRegionsStack
= []
350 self
._callRegionsStack
= []
351 self
._captureRegionsStack
= []
352 self
._filterRegionsStack
= []
354 self
._isErrorCatcherOn
= False
356 self
._hasReturnStatement
= False
357 self
._isGenerator
= False
360 def cleanupState(self
):
361 """Called by the containing class compiler instance
365 def methodName(self
):
366 return self
._methodName
368 def setMethodName(self
, name
):
369 self
._methodName
= name
371 ## methods for managing indentation
373 def indentation(self
):
374 return self
._indent
* self
._indentLev
383 raise Error('Attempt to dedent when the indentLev is 0')
385 ## methods for final code wrapping
389 return self
._methodDef
391 return self
.wrapCode()
394 __unicode__
= methodDef
397 self
.commitStrConst()
399 self
.methodSignature(),
403 methodDef
= ''.join(methodDefChunks
)
404 self
._methodDef
= methodDef
407 def methodSignature(self
):
408 return self
._indent
+ self
._methodSignature
+ ':'
410 def setMethodSignature(self
, signature
):
411 self
._methodSignature
= signature
413 def methodBody(self
):
414 return ''.join( self
._methodBodyChunks
)
417 if not self
._docStringLines
:
421 docStr
= (ind
+ '"""\n' + ind
+
422 ('\n' + ind
).join([ln
.replace('"""',"'''") for ln
in self
._docStringLines
]) +
423 '\n' + ind
+ '"""\n')
426 ## methods for adding code
427 def addMethDocString(self
, line
):
428 self
._docStringLines
.append(line
.replace('%','%%'))
430 def addChunk(self
, chunk
):
431 self
.commitStrConst()
432 chunk
= "\n" + self
.indentation() + chunk
433 self
._methodBodyChunks
.append(chunk
)
435 def appendToPrevChunk(self
, appendage
):
436 self
._methodBodyChunks
[-1] = self
._methodBodyChunks
[-1] + appendage
438 def addWriteChunk(self
, chunk
):
439 self
.addChunk('write(' + chunk
+ ')')
441 def addFilteredChunk(self
, chunk
, filterArgs
=None, rawExpr
=None, lineCol
=None):
442 if filterArgs
is None:
444 if self
.setting('includeRawExprInFilterArgs') and rawExpr
:
445 filterArgs
+= ', rawExpr=%s'%repr
(rawExpr
)
447 if self
.setting('alwaysFilterNone'):
448 if rawExpr
and rawExpr
.find('\n')==-1 and rawExpr
.find('\r')==-1:
449 self
.addChunk("_v = %s # %r"%(chunk
, rawExpr
))
451 self
.appendToPrevChunk(' on line %s, col %s'%lineCol
)
453 self
.addChunk("_v = %s"%chunk
)
455 if self
.setting('useFilters'):
456 self
.addChunk("if _v is not None: write(_filter(_v%s))"%filterArgs
)
458 self
.addChunk("if _v is not None: write(str(_v))")
460 if self
.setting('useFilters'):
461 self
.addChunk("write(_filter(%s%s))"%(chunk
,filterArgs
))
463 self
.addChunk("write(str(%s))"%chunk
)
465 def _appendToPrevStrConst(self
, strConst
):
466 if self
._pendingStrConstChunks
:
467 self
._pendingStrConstChunks
.append(strConst
)
469 self
._pendingStrConstChunks
= [strConst
]
471 def _unescapeCheetahVars(self
, theString
):
472 """Unescape any escaped Cheetah \$vars in the string.
475 token
= self
.setting('cheetahVarStartToken')
476 return theString
.replace('\\' + token
, token
)
478 def _unescapeDirectives(self
, theString
):
479 """Unescape any escaped Cheetah \$vars in the string.
482 token
= self
.setting('directiveStartToken')
483 return theString
.replace('\\' + token
, token
)
485 def commitStrConst(self
):
486 """Add the code for outputting the pending strConst without chopping off
487 any whitespace from it.
489 if self
._pendingStrConstChunks
:
490 strConst
= self
._unescapeCheetahVars
(''.join(self
._pendingStrConstChunks
))
491 strConst
= self
._unescapeDirectives
(strConst
)
492 self
._pendingStrConstChunks
= []
496 reprstr
= repr(strConst
).replace('\\012','\n')
499 if reprstr
.startswith('u'):
502 body
= escapedNewlineRE
.sub('\n', reprstr
[i
+1:-1])
512 self
.addWriteChunk(''.join(out
))
514 def handleWSBeforeDirective(self
):
515 """Truncate the pending strCont to the beginning of the current line.
517 if self
._pendingStrConstChunks
:
518 src
= self
._pendingStrConstChunks
[-1]
519 BOL
= max(src
.rfind('\n')+1, src
.rfind('\r')+1, 0)
521 self
._pendingStrConstChunks
[-1] = src
[:BOL
]
525 def isErrorCatcherOn(self
):
526 return self
._isErrorCatcherOn
528 def turnErrorCatcherOn(self
):
529 self
._isErrorCatcherOn
= True
531 def turnErrorCatcherOff(self
):
532 self
._isErrorCatcherOn
= False
534 # @@TR: consider merging the next two methods into one
535 def addStrConst(self
, strConst
):
536 self
._appendToPrevStrConst
(strConst
)
538 def addRawText(self
, text
):
539 self
.addStrConst(text
)
541 def addMethComment(self
, comm
):
542 offSet
= self
.setting('commentOffset')
543 self
.addChunk('#' + ' '*offSet
+ comm
)
545 def addPlaceholder(self
, expr
, filterArgs
, rawPlaceholder
,
546 cacheTokenParts
, lineCol
,
548 cacheInfo
= self
.genCacheInfo(cacheTokenParts
)
550 cacheInfo
['ID'] = repr(rawPlaceholder
)[1:-1]
551 self
.startCacheRegion(cacheInfo
, lineCol
, rawPlaceholder
=rawPlaceholder
)
553 if self
.isErrorCatcherOn():
554 methodName
= self
._classCompiler
.addErrorCatcherCall(
555 expr
, rawCode
=rawPlaceholder
, lineCol
=lineCol
)
556 expr
= 'self.' + methodName
+ '(localsDict=locals())'
559 self
.addChunk('try:')
561 self
.addFilteredChunk(expr
, filterArgs
, rawPlaceholder
, lineCol
=lineCol
)
563 self
.addChunk('except NotFound: pass')
565 self
.addFilteredChunk(expr
, filterArgs
, rawPlaceholder
, lineCol
=lineCol
)
567 if self
.setting('outputRowColComments'):
568 self
.appendToPrevChunk(' # from line %s, col %s' % lineCol
+ '.')
570 self
.endCacheRegion()
572 def addSilent(self
, expr
):
573 self
.addChunk( expr
)
575 def addEcho(self
, expr
, rawExpr
=None):
576 self
.addFilteredChunk(expr
, rawExpr
=rawExpr
)
578 def addSet(self
, expr
, exprComponents
, setStyle
):
579 if setStyle
is SET_GLOBAL
:
580 (LVALUE
, OP
, RVALUE
) = (exprComponents
.LVALUE
,
582 exprComponents
.RVALUE
)
583 # we need to split the LVALUE to deal with globalSetVars
584 splitPos1
= LVALUE
.find('.')
585 splitPos2
= LVALUE
.find('[')
586 if splitPos1
> 0 and splitPos2
==-1:
588 elif splitPos1
> 0 and splitPos1
< max(splitPos2
,0):
594 primary
= LVALUE
[:splitPos
]
595 secondary
= LVALUE
[splitPos
:]
599 LVALUE
= 'self._CHEETAH__globalSetVars["' + primary
+ '"]' + secondary
600 expr
= LVALUE
+ ' ' + OP
+ ' ' + RVALUE
.strip()
602 if setStyle
is SET_MODULE
:
603 self
._moduleCompiler
.addModuleGlobal(expr
)
607 def addInclude(self
, sourceExpr
, includeFrom
, isRaw
):
608 self
.addChunk('self._handleCheetahInclude(' + sourceExpr
+
610 'includeFrom="' + includeFrom
+ '", raw=' +
613 def addWhile(self
, expr
, lineCol
=None):
614 self
.addIndentingDirective(expr
, lineCol
=lineCol
)
616 def addFor(self
, expr
, lineCol
=None):
617 self
.addIndentingDirective(expr
, lineCol
=lineCol
)
619 def addRepeat(self
, expr
, lineCol
=None):
620 #the _repeatCount stuff here allows nesting of #repeat directives
621 self
._repeatCount
= getattr(self
, "_repeatCount", -1) + 1
622 self
.addFor('for __i%s in range(%s)' % (self
._repeatCount
,expr
), lineCol
=lineCol
)
624 def addIndentingDirective(self
, expr
, lineCol
=None):
625 if expr
and not expr
[-1] == ':':
627 self
.addChunk( expr
)
629 self
.appendToPrevChunk(' # generated from line %s, col %s'%lineCol
)
632 def addReIndentingDirective(self
, expr
, dedent
=True, lineCol
=None):
633 self
.commitStrConst()
636 if not expr
[-1] == ':':
639 self
.addChunk( expr
)
641 self
.appendToPrevChunk(' # generated from line %s, col %s'%lineCol
)
644 def addIf(self
, expr
, lineCol
=None):
645 """For a full #if ... #end if directive
647 self
.addIndentingDirective(expr
, lineCol
=lineCol
)
649 def addOneLineIf(self
, expr
, lineCol
=None):
650 """For a full #if ... #end if directive
652 self
.addIndentingDirective(expr
, lineCol
=lineCol
)
654 def addTernaryExpr(self
, conditionExpr
, trueExpr
, falseExpr
, lineCol
=None):
655 """For a single-lie #if ... then .... else ... directive
656 <condition> then <trueExpr> else <falseExpr>
658 self
.addIndentingDirective(conditionExpr
, lineCol
=lineCol
)
659 self
.addFilteredChunk(trueExpr
)
661 self
.addIndentingDirective('else')
662 self
.addFilteredChunk(falseExpr
)
665 def addElse(self
, expr
, dedent
=True, lineCol
=None):
666 expr
= re
.sub(r
'else[ \f\t]+if','elif', expr
)
667 self
.addReIndentingDirective(expr
, dedent
=dedent
, lineCol
=lineCol
)
669 def addElif(self
, expr
, dedent
=True, lineCol
=None):
670 self
.addElse(expr
, dedent
=dedent
, lineCol
=lineCol
)
672 def addUnless(self
, expr
, lineCol
=None):
673 self
.addIf('if not (' + expr
+ ')')
675 def addClosure(self
, functionName
, argsList
, parserComment
):
679 if not arg
[1] == None:
680 chunk
+= '=' + arg
[1]
681 argStringChunks
.append(chunk
)
682 signature
= "def " + functionName
+ "(" + ','.join(argStringChunks
) + "):"
683 self
.addIndentingDirective(signature
)
684 self
.addChunk('#'+parserComment
)
686 def addTry(self
, expr
, lineCol
=None):
687 self
.addIndentingDirective(expr
, lineCol
=lineCol
)
689 def addExcept(self
, expr
, dedent
=True, lineCol
=None):
690 self
.addReIndentingDirective(expr
, dedent
=dedent
, lineCol
=lineCol
)
692 def addFinally(self
, expr
, dedent
=True, lineCol
=None):
693 self
.addReIndentingDirective(expr
, dedent
=dedent
, lineCol
=lineCol
)
695 def addReturn(self
, expr
):
696 assert not self
._isGenerator
698 self
._hasReturnStatement
= True
700 def addYield(self
, expr
):
701 assert not self
._hasReturnStatement
702 self
._isGenerator
= True
703 if expr
.replace('yield','').strip():
706 self
.addChunk('if _dummyTrans:')
708 self
.addChunk('yield trans.response().getvalue()')
709 self
.addChunk('trans = DummyTransaction()')
710 self
.addChunk('write = trans.response().write')
712 self
.addChunk('else:')
715 'raise TypeError("This method cannot be called with a trans arg")')
719 def addPass(self
, expr
):
722 def addDel(self
, expr
):
725 def addAssert(self
, expr
):
728 def addRaise(self
, expr
):
731 def addBreak(self
, expr
):
734 def addContinue(self
, expr
):
737 def addPSP(self
, PSP
):
738 self
.commitStrConst()
743 self
.addWriteChunk('_filter(' + PSP
+ ')')
746 elif PSP
.lower() == 'end':
755 for line
in PSP
.splitlines():
761 def nextCacheID(self
):
762 return ('_'+str(random
.randrange(100, 999))
763 + str(random
.randrange(10000, 99999)))
765 def startCacheRegion(self
, cacheInfo
, lineCol
, rawPlaceholder
=None):
767 # @@TR: we should add some runtime logging to this
769 ID
= self
.nextCacheID()
770 interval
= cacheInfo
.get('interval',None)
771 test
= cacheInfo
.get('test',None)
772 customID
= cacheInfo
.get('id',None)
775 varyBy
= cacheInfo
.get('varyBy', repr(ID
))
776 self
._cacheRegionsStack
.append(ID
) # attrib of current methodCompiler
778 # @@TR: add this to a special class var as well
781 self
.addChunk('## START CACHE REGION: ID='+ID
+
782 '. line %s, col %s'%lineCol
+ ' in the source.')
784 self
.addChunk('_RECACHE_%(ID)s = False'%locals())
785 self
.addChunk('_cacheRegion_%(ID)s = self.getCacheRegion(regionID='%locals()
787 + ', cacheInfo=%r'%cacheInfo
789 self
.addChunk('if _cacheRegion_%(ID)s.isNew():'%locals())
791 self
.addChunk('_RECACHE_%(ID)s = True'%locals())
794 self
.addChunk('_cacheItem_%(ID)s = _cacheRegion_%(ID)s.getCacheItem('%locals()
797 self
.addChunk('if _cacheItem_%(ID)s.hasExpired():'%locals())
799 self
.addChunk('_RECACHE_%(ID)s = True'%locals())
803 self
.addChunk('if ' + test
+ ':')
805 self
.addChunk('_RECACHE_%(ID)s = True'%locals())
808 self
.addChunk('if (not _RECACHE_%(ID)s) and _cacheItem_%(ID)s.getRefreshTime():'%locals())
810 #self.addChunk('print "DEBUG"+"-"*50')
811 self
.addChunk('try:')
813 self
.addChunk('_output = _cacheItem_%(ID)s.renderOutput()'%locals())
815 self
.addChunk('except KeyError:')
817 self
.addChunk('_RECACHE_%(ID)s = True'%locals())
818 #self.addChunk('print "DEBUG"+"*"*50')
820 self
.addChunk('else:')
822 self
.addWriteChunk('_output')
823 self
.addChunk('del _output')
828 self
.addChunk('if _RECACHE_%(ID)s or not _cacheItem_%(ID)s.getRefreshTime():'%locals())
830 self
.addChunk('_orig_trans%(ID)s = trans'%locals())
831 self
.addChunk('trans = _cacheCollector_%(ID)s = DummyTransaction()'%locals())
832 self
.addChunk('write = _cacheCollector_%(ID)s.response().write'%locals())
834 self
.addChunk(("_cacheItem_%(ID)s.setExpiryTime(currentTime() +"%locals())
835 + str(interval
) + ")")
837 def endCacheRegion(self
):
838 ID
= self
._cacheRegionsStack
.pop()
839 self
.addChunk('trans = _orig_trans%(ID)s'%locals())
840 self
.addChunk('write = trans.response().write')
841 self
.addChunk('_cacheData = _cacheCollector_%(ID)s.response().getvalue()'%locals())
842 self
.addChunk('_cacheItem_%(ID)s.setData(_cacheData)'%locals())
843 self
.addWriteChunk('_cacheData')
844 self
.addChunk('del _cacheData')
845 self
.addChunk('del _cacheCollector_%(ID)s'%locals())
846 self
.addChunk('del _orig_trans%(ID)s'%locals())
848 self
.addChunk('## END CACHE REGION: '+ID
)
851 def nextCallRegionID(self
):
852 return self
.nextCacheID()
854 def startCallRegion(self
, functionName
, args
, lineCol
, regionTitle
='CALL'):
855 class CallDetails
: pass
856 callDetails
= CallDetails()
857 callDetails
.ID
= ID
= self
.nextCallRegionID()
858 callDetails
.functionName
= functionName
859 callDetails
.args
= args
860 callDetails
.lineCol
= lineCol
861 callDetails
.usesKeywordArgs
= False
862 self
._callRegionsStack
.append((ID
, callDetails
)) # attrib of current methodCompiler
864 self
.addChunk('## START %(regionTitle)s REGION: '%locals()
867 +' at line %s, col %s'%lineCol
+ ' in the source.')
868 self
.addChunk('_orig_trans%(ID)s = trans'%locals())
869 self
.addChunk('_wasBuffering%(ID)s = self._CHEETAH__isBuffering'%locals())
870 self
.addChunk('self._CHEETAH__isBuffering = True')
871 self
.addChunk('trans = _callCollector%(ID)s = DummyTransaction()'%locals())
872 self
.addChunk('write = _callCollector%(ID)s.response().write'%locals())
874 def setCallArg(self
, argName
, lineCol
):
875 ID
, callDetails
= self
._callRegionsStack
[-1]
876 if callDetails
.usesKeywordArgs
:
879 callDetails
.usesKeywordArgs
= True
880 self
.addChunk('_callKws%(ID)s = {}'%locals())
881 self
.addChunk('_currentCallArgname%(ID)s = %(argName)r'%locals())
882 callDetails
.currentArgname
= argName
884 def _endCallArg(self
):
885 ID
, callDetails
= self
._callRegionsStack
[-1]
886 currCallArg
= callDetails
.currentArgname
887 self
.addChunk(('_callKws%(ID)s[%(currCallArg)r] ='
888 ' _callCollector%(ID)s.response().getvalue()')%locals())
889 self
.addChunk('del _callCollector%(ID)s'%locals())
890 self
.addChunk('trans = _callCollector%(ID)s = DummyTransaction()'%locals())
891 self
.addChunk('write = _callCollector%(ID)s.response().write'%locals())
893 def endCallRegion(self
, regionTitle
='CALL'):
894 ID
, callDetails
= self
._callRegionsStack
[-1]
895 functionName
, initialKwArgs
, lineCol
= (
896 callDetails
.functionName
, callDetails
.args
, callDetails
.lineCol
)
899 self
.addChunk('trans = _orig_trans%(ID)s'%locals())
900 self
.addChunk('write = trans.response().write')
901 self
.addChunk('self._CHEETAH__isBuffering = _wasBuffering%(ID)s '%locals())
902 self
.addChunk('del _wasBuffering%(ID)s'%locals())
903 self
.addChunk('del _orig_trans%(ID)s'%locals())
905 if not callDetails
.usesKeywordArgs
:
907 self
.addChunk('_callArgVal%(ID)s = _callCollector%(ID)s.response().getvalue()'%locals())
908 self
.addChunk('del _callCollector%(ID)s'%locals())
910 initialKwArgs
= ', '+initialKwArgs
911 self
.addFilteredChunk('%(functionName)s(_callArgVal%(ID)s%(initialKwArgs)s)'%locals())
912 self
.addChunk('del _callArgVal%(ID)s'%locals())
915 initialKwArgs
= initialKwArgs
+', '
918 self
.addFilteredChunk('%(functionName)s(%(initialKwArgs)s**_callKws%(ID)s)'%locals())
919 self
.addChunk('del _callKws%(ID)s'%locals())
920 self
.addChunk('## END %(regionTitle)s REGION: '%locals()
923 +' at line %s, col %s'%lineCol
+ ' in the source.')
925 self
._callRegionsStack
.pop() # attrib of current methodCompiler
927 def nextCaptureRegionID(self
):
928 return self
.nextCacheID()
930 def startCaptureRegion(self
, assignTo
, lineCol
):
931 class CaptureDetails
: pass
932 captureDetails
= CaptureDetails()
933 captureDetails
.ID
= ID
= self
.nextCaptureRegionID()
934 captureDetails
.assignTo
= assignTo
935 captureDetails
.lineCol
= lineCol
937 self
._captureRegionsStack
.append((ID
,captureDetails
)) # attrib of current methodCompiler
938 self
.addChunk('## START CAPTURE REGION: '+ID
940 +' at line %s, col %s'%lineCol
+ ' in the source.')
941 self
.addChunk('_orig_trans%(ID)s = trans'%locals())
942 self
.addChunk('_wasBuffering%(ID)s = self._CHEETAH__isBuffering'%locals())
943 self
.addChunk('self._CHEETAH__isBuffering = True')
944 self
.addChunk('trans = _captureCollector%(ID)s = DummyTransaction()'%locals())
945 self
.addChunk('write = _captureCollector%(ID)s.response().write'%locals())
947 def endCaptureRegion(self
):
948 ID
, captureDetails
= self
._captureRegionsStack
.pop()
949 assignTo
, lineCol
= (captureDetails
.assignTo
, captureDetails
.lineCol
)
950 self
.addChunk('trans = _orig_trans%(ID)s'%locals())
951 self
.addChunk('write = trans.response().write')
952 self
.addChunk('self._CHEETAH__isBuffering = _wasBuffering%(ID)s '%locals())
953 self
.addChunk('%(assignTo)s = _captureCollector%(ID)s.response().getvalue()'%locals())
954 self
.addChunk('del _orig_trans%(ID)s'%locals())
955 self
.addChunk('del _captureCollector%(ID)s'%locals())
956 self
.addChunk('del _wasBuffering%(ID)s'%locals())
958 def setErrorCatcher(self
, errorCatcherName
):
959 self
.turnErrorCatcherOn()
961 self
.addChunk('if self._CHEETAH__errorCatchers.has_key("' + errorCatcherName
+ '"):')
963 self
.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["' +
964 errorCatcherName
+ '"]')
966 self
.addChunk('else:')
968 self
.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["'
969 + errorCatcherName
+ '"] = ErrorCatchers.'
970 + errorCatcherName
+ '(self)'
974 def nextFilterRegionID(self
):
975 return self
.nextCacheID()
977 def setFilter(self
, theFilter
, isKlass
):
978 class FilterDetails
: pass
979 filterDetails
= FilterDetails()
980 filterDetails
.ID
= ID
= self
.nextFilterRegionID()
981 filterDetails
.theFilter
= theFilter
982 filterDetails
.isKlass
= isKlass
983 self
._filterRegionsStack
.append((ID
, filterDetails
)) # attrib of current methodCompiler
985 self
.addChunk('_orig_filter%(ID)s = _filter'%locals())
987 self
.addChunk('_filter = self._CHEETAH__currentFilter = ' + theFilter
.strip() +
990 if theFilter
.lower() == 'none':
991 self
.addChunk('_filter = self._CHEETAH__initialFilter')
993 # is string representing the name of a builtin filter
994 self
.addChunk('filterName = ' + repr(theFilter
))
995 self
.addChunk('if self._CHEETAH__filters.has_key("' + theFilter
+ '"):')
997 self
.addChunk('_filter = self._CHEETAH__currentFilter = self._CHEETAH__filters[filterName]')
999 self
.addChunk('else:')
1001 self
.addChunk('_filter = self._CHEETAH__currentFilter'
1002 +' = \\\n\t\t\tself._CHEETAH__filters[filterName] = '
1003 + 'getattr(self._CHEETAH__filtersLib, filterName)(self).filter')
1006 def closeFilterBlock(self
):
1007 ID
, filterDetails
= self
._filterRegionsStack
.pop()
1008 #self.addChunk('_filter = self._CHEETAH__initialFilter')
1009 #self.addChunk('_filter = _orig_filter%(ID)s'%locals())
1010 self
.addChunk('_filter = self._CHEETAH__currentFilter = _orig_filter%(ID)s'%locals())
1012 class AutoMethodCompiler(MethodCompiler
):
1014 def _setupState(self
):
1015 MethodCompiler
._setupState
(self
)
1016 self
._argStringList
= [ ("self",None) ]
1017 self
._streamingEnabled
= True
1019 def _useKWsDictArgForPassingTrans(self
):
1020 alreadyHasTransArg
= [argname
for argname
,defval
in self
._argStringList
1021 if argname
=='trans']
1022 return (self
.methodName()!='respond'
1023 and not alreadyHasTransArg
1024 and self
.setting('useKWsDictArgForPassingTrans'))
1026 def cleanupState(self
):
1027 MethodCompiler
.cleanupState(self
)
1028 self
.commitStrConst()
1029 if self
._cacheRegionsStack
:
1030 self
.endCacheRegion()
1031 if self
._callRegionsStack
:
1032 self
.endCallRegion()
1034 if self
._streamingEnabled
:
1036 positionalArgsListName
= None
1037 for argname
,defval
in self
._argStringList
:
1038 if argname
.strip().startswith('**'):
1039 kwargsName
= argname
.strip().replace('**','')
1041 elif argname
.strip().startswith('*'):
1042 positionalArgsListName
= argname
.strip().replace('*','')
1044 if not kwargsName
and self
._useKWsDictArgForPassingTrans
():
1046 self
.addMethArg('**KWS', None)
1047 self
._kwargsName
= kwargsName
1049 if not self
._useKWsDictArgForPassingTrans
():
1050 if not kwargsName
and not positionalArgsListName
:
1051 self
.addMethArg('trans', 'None')
1053 self
._streamingEnabled
= False
1055 self
._indentLev
= self
.setting('initialMethIndentLevel')
1056 mainBodyChunks
= self
._methodBodyChunks
1057 self
._methodBodyChunks
= []
1058 self
._addAutoSetupCode
()
1059 self
._methodBodyChunks
.extend(mainBodyChunks
)
1060 self
._addAutoCleanupCode
()
1062 def _addAutoSetupCode(self
):
1063 if self
._initialMethodComment
:
1064 self
.addChunk(self
._initialMethodComment
)
1066 if self
._streamingEnabled
:
1067 if self
._useKWsDictArgForPassingTrans
() and self
._kwargsName
:
1068 self
.addChunk('trans = %s.get("trans")'%self
._kwargsName
)
1069 self
.addChunk('if (not trans and not self._CHEETAH__isBuffering'
1070 ' and not callable(self.transaction)):')
1072 self
.addChunk('trans = self.transaction'
1073 ' # is None unless self.awake() was called')
1075 self
.addChunk('if not trans:')
1077 self
.addChunk('trans = DummyTransaction()')
1078 if self
.setting('autoAssignDummyTransactionToSelf'):
1079 self
.addChunk('self.transaction = trans')
1080 self
.addChunk('_dummyTrans = True')
1082 self
.addChunk('else: _dummyTrans = False')
1084 self
.addChunk('trans = DummyTransaction()')
1085 self
.addChunk('_dummyTrans = True')
1086 self
.addChunk('write = trans.response().write')
1087 if self
.setting('useNameMapper'):
1088 argNames
= [arg
[0] for arg
in self
._argStringList
]
1089 allowSearchListAsMethArg
= self
.setting('allowSearchListAsMethArg')
1090 if allowSearchListAsMethArg
and 'SL' in argNames
:
1092 elif allowSearchListAsMethArg
and 'searchList' in argNames
:
1093 self
.addChunk('SL = searchList')
1095 self
.addChunk('SL = self._CHEETAH__searchList')
1096 if self
.setting('useFilters'):
1097 self
.addChunk('_filter = self._CHEETAH__currentFilter')
1099 self
.addChunk("#" *40)
1100 self
.addChunk('## START - generated method body')
1103 def _addAutoCleanupCode(self
):
1105 self
.addChunk("#" *40)
1106 self
.addChunk('## END - generated method body')
1109 if not self
._isGenerator
:
1113 def addStop(self
, expr
=None):
1114 self
.addChunk('return _dummyTrans and trans.response().getvalue() or ""')
1116 def addMethArg(self
, name
, defVal
=None):
1117 self
._argStringList
.append( (name
,defVal
) )
1119 def methodSignature(self
):
1120 argStringChunks
= []
1121 for arg
in self
._argStringList
:
1123 if not arg
[1] == None:
1124 chunk
+= '=' + arg
[1]
1125 argStringChunks
.append(chunk
)
1126 argString
= (', ').join(argStringChunks
)
1130 output
.append(self
._indent
+ self
._decorator
+'\n')
1131 output
.append(self
._indent
+ "def "
1132 + self
.methodName() + "(" +
1133 argString
+ "):\n\n")
1134 return ''.join(output
)
1137 ##################################################
1140 _initMethod_initCheetah
= """\
1141 if not self._CHEETAH__instanceInitialized:
1143 allowedKWs = 'searchList namespaces filter filtersLib errorCatcher'.split()
1144 for k,v in KWs.items():
1145 if k in allowedKWs: cheetahKWArgs[k] = v
1146 self._initCheetahInstance(**cheetahKWArgs)
1147 """.replace('\n','\n'+' '*8)
1149 class ClassCompiler(GenUtils
):
1150 methodCompilerClass
= AutoMethodCompiler
1151 methodCompilerClassForInit
= MethodCompiler
1153 def __init__(self
, className
, mainMethodName
='respond',
1154 moduleCompiler
=None,
1156 settingsManager
=None):
1158 self
._settingsManager
= settingsManager
1159 self
._fileName
= fileName
1160 self
._className
= className
1161 self
._moduleCompiler
= moduleCompiler
1162 self
._mainMethodName
= mainMethodName
1164 methodCompiler
= self
._spawnMethodCompiler
(
1166 initialMethodComment
='## CHEETAH: main method generated for this template')
1168 self
._setActiveMethodCompiler
(methodCompiler
)
1169 if fileName
and self
.setting('monitorSrcFile'):
1170 self
._addSourceFileMonitoring
(fileName
)
1172 def setting(self
, key
):
1173 return self
._settingsManager
.setting(key
)
1175 def __getattr__(self
, name
):
1176 """Provide access to the methods and attributes of the MethodCompiler
1177 at the top of the activeMethods stack: one-way namespace sharing
1180 WARNING: Use .setMethods to assign the attributes of the MethodCompiler
1181 from the methods of this class!!! or you will be assigning to attributes
1182 of this object instead."""
1184 if self
.__dict
__.has_key(name
):
1185 return self
.__dict
__[name
]
1186 elif hasattr(self
.__class
__, name
):
1187 return getattr(self
.__class
__, name
)
1188 elif self
._activeMethodsList
and hasattr(self
._activeMethodsList
[-1], name
):
1189 return getattr(self
._activeMethodsList
[-1], name
)
1191 raise AttributeError, name
1193 def _setupState(self
):
1194 self
._classDef
= None
1195 self
._decoratorForNextMethod
= None
1196 self
._activeMethodsList
= [] # stack while parsing/generating
1197 self
._finishedMethodsList
= [] # store by order
1198 self
._methodsIndex
= {} # store by name
1199 self
._baseClass
= 'Template'
1200 self
._classDocStringLines
= []
1201 # printed after methods in the gen class def:
1202 self
._generatedAttribs
= ['_CHEETAH__instanceInitialized = False']
1203 self
._generatedAttribs
.append('_CHEETAH_version = __CHEETAH_version__')
1204 self
._generatedAttribs
.append(
1205 '_CHEETAH_versionTuple = __CHEETAH_versionTuple__')
1207 if self
.setting('addTimestampsToCompilerOutput'):
1208 self
._generatedAttribs
.append('_CHEETAH_genTime = __CHEETAH_genTime__')
1209 self
._generatedAttribs
.append('_CHEETAH_genTimestamp = __CHEETAH_genTimestamp__')
1211 self
._generatedAttribs
.append('_CHEETAH_src = __CHEETAH_src__')
1212 self
._generatedAttribs
.append(
1213 '_CHEETAH_srcLastModified = __CHEETAH_srcLastModified__')
1215 if self
.setting('templateMetaclass'):
1216 self
._generatedAttribs
.append('__metaclass__ = '+self
.setting('templateMetaclass'))
1217 self
._initMethChunks
= []
1218 self
._blockMetaData
= {}
1219 self
._errorCatcherCount
= 0
1220 self
._placeholderToErrorCatcherMap
= {}
1222 def cleanupState(self
):
1223 while self
._activeMethodsList
:
1224 methCompiler
= self
._popActiveMethodCompiler
()
1225 self
._swallowMethodCompiler
(methCompiler
)
1226 self
._setupInitMethod
()
1227 if self
._mainMethodName
== 'respond':
1228 if self
.setting('setup__str__method'):
1229 self
._generatedAttribs
.append('def __str__(self): return self.respond()')
1230 self
.addAttribute('_mainCheetahMethod_for_' + self
._className
+
1231 '= ' + repr(self
._mainMethodName
) )
1233 def _setupInitMethod(self
):
1234 __init__
= self
._spawnMethodCompiler
('__init__',
1235 klass
=self
.methodCompilerClassForInit
)
1236 __init__
.setMethodSignature("def __init__(self, *args, **KWs)")
1237 __init__
.addChunk("%s.__init__(self, *args, **KWs)" % self
._baseClass
)
1238 __init__
.addChunk(_initMethod_initCheetah
%{'className':self
._className
})
1239 for chunk
in self
._initMethChunks
:
1240 __init__
.addChunk(chunk
)
1241 __init__
.cleanupState()
1242 self
._swallowMethodCompiler
(__init__
, pos
=0)
1244 def _addSourceFileMonitoring(self
, fileName
):
1245 # @@TR: this stuff needs auditing for Cheetah 2.0
1246 # the first bit is added to init
1247 self
.addChunkToInit('self._filePath = ' + repr(fileName
))
1248 self
.addChunkToInit('self._fileMtime = ' + str(getmtime(fileName
)) )
1250 # the rest is added to the main output method of the class ('mainMethod')
1251 self
.addChunk('if exists(self._filePath) and ' +
1252 'getmtime(self._filePath) > self._fileMtime:')
1254 self
.addChunk('self._compile(file=self._filePath, moduleName='+self
._className
+ ')')
1256 'write(getattr(self, self._mainCheetahMethod_for_' + self
._className
+
1261 def setClassName(self
, name
):
1262 self
._className
= name
1264 def className(self
):
1265 return self
._className
1267 def setBaseClass(self
, baseClassName
):
1268 self
._baseClass
= baseClassName
1270 def setMainMethodName(self
, methodName
):
1271 if methodName
== self
._mainMethodName
:
1273 ## change the name in the methodCompiler and add new reference
1274 mainMethod
= self
._methodsIndex
[self
._mainMethodName
]
1275 mainMethod
.setMethodName(methodName
)
1276 self
._methodsIndex
[methodName
] = mainMethod
1278 ## make sure that fileUpdate code still works properly:
1279 chunkToChange
= ('write(self.' + self
._mainMethodName
+ '(trans=trans))')
1280 chunks
= mainMethod
._methodBodyChunks
1281 if chunkToChange
in chunks
:
1282 for i
in range(len(chunks
)):
1283 if chunks
[i
] == chunkToChange
:
1284 chunks
[i
] = ('write(self.' + methodName
+ '(trans=trans))')
1285 ## get rid of the old reference and update self._mainMethodName
1286 del self
._methodsIndex
[self
._mainMethodName
]
1287 self
._mainMethodName
= methodName
1289 def setMainMethodArgs(self
, argsList
):
1290 mainMethodCompiler
= self
._methodsIndex
[self
._mainMethodName
]
1291 for argName
, defVal
in argsList
:
1292 mainMethodCompiler
.addMethArg(argName
, defVal
)
1295 def _spawnMethodCompiler(self
, methodName
, klass
=None,
1296 initialMethodComment
=None):
1298 klass
= self
.methodCompilerClass
1301 if self
._decoratorForNextMethod
:
1302 decorator
= self
._decoratorForNextMethod
1303 self
._decoratorForNextMethod
= None
1304 methodCompiler
= klass(methodName
, classCompiler
=self
,
1305 decorator
=decorator
,
1306 initialMethodComment
=initialMethodComment
)
1307 self
._methodsIndex
[methodName
] = methodCompiler
1308 return methodCompiler
1310 def _setActiveMethodCompiler(self
, methodCompiler
):
1311 self
._activeMethodsList
.append(methodCompiler
)
1313 def _getActiveMethodCompiler(self
):
1314 return self
._activeMethodsList
[-1]
1316 def _popActiveMethodCompiler(self
):
1317 return self
._activeMethodsList
.pop()
1319 def _swallowMethodCompiler(self
, methodCompiler
, pos
=None):
1320 methodCompiler
.cleanupState()
1322 self
._finishedMethodsList
.append( methodCompiler
)
1324 self
._finishedMethodsList
.insert(pos
, methodCompiler
)
1325 return methodCompiler
1327 def startMethodDef(self
, methodName
, argsList
, parserComment
):
1328 methodCompiler
= self
._spawnMethodCompiler
(
1329 methodName
, initialMethodComment
=parserComment
)
1330 self
._setActiveMethodCompiler
(methodCompiler
)
1331 for argName
, defVal
in argsList
:
1332 methodCompiler
.addMethArg(argName
, defVal
)
1334 def _finishedMethods(self
):
1335 return self
._finishedMethodsList
1337 def addDecorator(self
, decoratorExpr
):
1338 """Set the decorator to be used with the next method in the source.
1340 See _spawnMethodCompiler() and MethodCompiler for the details of how
1343 self
._decoratorForNextMethod
= decoratorExpr
1345 def addClassDocString(self
, line
):
1346 self
._classDocStringLines
.append( line
.replace('%','%%'))
1348 def addChunkToInit(self
,chunk
):
1349 self
._initMethChunks
.append(chunk
)
1351 def addAttribute(self
, attribExpr
):
1352 ## first test to make sure that the user hasn't used any fancy Cheetah syntax
1353 # (placeholders, directives, etc.) inside the expression
1354 if attribExpr
.find('VFN(') != -1 or attribExpr
.find('VFFSL(') != -1:
1355 raise ParseError(self
,
1356 'Invalid #attr directive.' +
1357 ' It should only contain simple Python literals.')
1358 ## now add the attribute
1359 self
._generatedAttribs
.append(attribExpr
)
1361 def addSuper(self
, argsList
, parserComment
=None):
1362 className
= self
._className
#self._baseClass
1363 methodName
= self
._getActiveMethodCompiler
().methodName()
1365 argStringChunks
= []
1366 for arg
in argsList
:
1368 if not arg
[1] == None:
1369 chunk
+= '=' + arg
[1]
1370 argStringChunks
.append(chunk
)
1371 argString
= ','.join(argStringChunks
)
1373 self
.addFilteredChunk(
1374 'super(%(className)s, self).%(methodName)s(%(argString)s)'%locals())
1376 def addErrorCatcherCall(self
, codeChunk
, rawCode
='', lineCol
=''):
1377 if self
._placeholderToErrorCatcherMap
.has_key(rawCode
):
1378 methodName
= self
._placeholderToErrorCatcherMap
[rawCode
]
1379 if not self
.setting('outputRowColComments'):
1380 self
._methodsIndex
[methodName
].addMethDocString(
1381 'plus at line %s, col %s'%lineCol
)
1384 self
._errorCatcherCount
+= 1
1385 methodName
= '__errorCatcher' + str(self
._errorCatcherCount
)
1386 self
._placeholderToErrorCatcherMap
[rawCode
] = methodName
1388 catcherMeth
= self
._spawnMethodCompiler
(
1390 klass
=MethodCompiler
,
1391 initialMethodComment
=('## CHEETAH: Generated from ' + rawCode
+
1392 ' at line %s, col %s'%lineCol
+ '.')
1394 catcherMeth
.setMethodSignature('def ' + methodName
+
1395 '(self, localsDict={})')
1396 # is this use of localsDict right?
1397 catcherMeth
.addChunk('try:')
1398 catcherMeth
.indent()
1399 catcherMeth
.addChunk("return eval('''" + codeChunk
+
1400 "''', globals(), localsDict)")
1401 catcherMeth
.dedent()
1402 catcherMeth
.addChunk('except self._CHEETAH__errorCatcher.exceptions(), e:')
1403 catcherMeth
.indent()
1404 catcherMeth
.addChunk("return self._CHEETAH__errorCatcher.warn(exc_val=e, code= " +
1405 repr(codeChunk
) + " , rawCode= " +
1406 repr(rawCode
) + " , lineCol=" + str(lineCol
) +")")
1408 catcherMeth
.cleanupState()
1410 self
._swallowMethodCompiler
(catcherMeth
)
1414 self
.commitStrConst()
1415 methCompiler
= self
._popActiveMethodCompiler
()
1416 self
._swallowMethodCompiler
(methCompiler
)
1418 def closeBlock(self
):
1419 self
.commitStrConst()
1420 methCompiler
= self
._popActiveMethodCompiler
()
1421 methodName
= methCompiler
.methodName()
1422 if self
.setting('includeBlockMarkers'):
1423 endMarker
= self
.setting('blockMarkerEnd')
1424 methCompiler
.addStrConst(endMarker
[0] + methodName
+ endMarker
[1])
1425 self
._swallowMethodCompiler
(methCompiler
)
1427 #metaData = self._blockMetaData[methodName]
1428 #rawDirective = metaData['raw']
1429 #lineCol = metaData['lineCol']
1431 ## insert the code to call the block, caching if #cache directive is on
1432 codeChunk
= 'self.' + methodName
+ '(trans=trans)'
1433 self
.addChunk(codeChunk
)
1435 #self.appendToPrevChunk(' # generated from ' + repr(rawDirective) )
1436 #if self.setting('outputRowColComments'):
1437 # self.appendToPrevChunk(' at line %s, col %s' % lineCol + '.')
1440 ## code wrapping methods
1444 return self
._classDef
1446 return self
.wrapClassDef()
1449 __unicode__
= classDef
1451 def wrapClassDef(self
):
1452 ind
= self
.setting('indentationStep')
1453 classDefChunks
= [self
.classSignature(),
1454 self
.classDocstring(),
1457 classDefChunks
.extend([
1459 ind
+ '## CHEETAH GENERATED METHODS',
1463 def addAttributes():
1464 classDefChunks
.extend([
1466 ind
+ '## CHEETAH GENERATED ATTRIBUTES',
1470 if self
.setting('outputMethodsBeforeAttributes'):
1477 classDef
= '\n'.join(classDefChunks
)
1478 self
._classDef
= classDef
1482 def classSignature(self
):
1483 return "class %s(%s):" % (self
.className(), self
._baseClass
)
1485 def classDocstring(self
):
1486 if not self
._classDocStringLines
:
1488 ind
= self
.setting('indentationStep')
1489 docStr
= ('%(ind)s"""\n%(ind)s' +
1490 '\n%(ind)s'.join(self
._classDocStringLines
) +
1495 def methodDefs(self
):
1496 methodDefs
= [methGen
.methodDef() for methGen
in self
._finishedMethods
()]
1497 return '\n\n'.join(methodDefs
)
1499 def attributes(self
):
1500 attribs
= [self
.setting('indentationStep') + str(attrib
)
1501 for attrib
in self
._generatedAttribs
]
1502 return '\n\n'.join(attribs
)
1504 class AutoClassCompiler(ClassCompiler
):
1507 ##################################################
1510 class ModuleCompiler(SettingsManager
, GenUtils
):
1512 parserClass
= Parser
1513 classCompilerClass
= AutoClassCompiler
1515 def __init__(self
, source
=None, file=None,
1516 moduleName
='DynamicallyCompiledCheetahTemplate',
1517 mainClassName
=None, # string
1518 mainMethodName
=None, # string
1519 baseclassName
=None, # string
1520 extraImportStatements
=None, # list of strings
1521 settings
=None # dict
1523 SettingsManager
.__init
__(self
)
1525 self
.updateSettings(settings
)
1526 # disable useStackFrames if the C version of NameMapper isn't compiled
1527 # it's painfully slow in the Python version and bites Windows users all
1529 if not NameMapper
.C_VERSION
:
1530 #if not sys.platform.startswith('java'):
1532 # "\nYou don't have the C version of NameMapper installed! "
1533 # "I'm disabling Cheetah's useStackFrames option as it is "
1534 # "painfully slow with the Python version of NameMapper. "
1535 # "You should get a copy of Cheetah with the compiled C version of NameMapper."
1537 self
.setSetting('useStackFrames', False)
1539 self
._compiled
= False
1540 self
._moduleName
= moduleName
1541 if not mainClassName
:
1542 self
._mainClassName
= moduleName
1544 self
._mainClassName
= mainClassName
1545 self
._mainMethodNameArg
= mainMethodName
1547 self
.setSetting('mainMethodName', mainMethodName
)
1548 self
._baseclassName
= baseclassName
1550 self
._filePath
= None
1551 self
._fileMtime
= None
1554 raise TypeError("Cannot compile from a source string AND file.")
1555 elif isinstance(file, (str, unicode)): # it's a filename.
1556 f
= open(file) # Raises IOError.
1559 self
._filePath
= file
1560 self
._fileMtime
= os
.path
.getmtime(file)
1561 elif hasattr(file, 'read'):
1562 source
= file.read() # Can't set filename or mtime--they're not accessible.
1564 raise TypeError("'file' argument must be a filename string or file-like object")
1567 self
._fileDirName
, self
._fileBaseName
= os
.path
.split(self
._filePath
)
1568 self
._fileBaseNameRoot
, self
._fileBaseNameExt
= os
.path
.splitext(self
._fileBaseName
)
1570 if not isinstance(source
, (str,unicode)):
1571 source
= str(source
)
1572 # by converting to string here we allow objects such as other Templates
1575 # Handle the #indent directive by converting it to other directives.
1576 # (Over the long term we'll make it a real directive.)
1578 warnings
.warn("You supplied an empty string for the source!", )
1581 unicodeMatch
= unicodeDirectiveRE
.search(source
)
1583 if encodingDirectiveRE
.match(source
):
1585 self
, "#encoding and #unicode are mutually exclusive! "
1586 "Use one or the other.")
1587 source
= unicodeDirectiveRE
.sub('', source
)
1588 if isinstance(source
, str):
1589 encoding
= unicodeMatch
.group(1) or 'ascii'
1590 source
= unicode(source
, encoding
)
1594 if source
.find('#indent') != -1: #@@TR: undocumented hack
1595 source
= indentize(source
)
1597 self
._parser
= self
.parserClass(source
, filename
=self
._filePath
, compiler
=self
)
1598 self
._setupCompilerState
()
1600 def __getattr__(self
, name
):
1601 """Provide one-way access to the methods and attributes of the
1602 ClassCompiler, and thereby the MethodCompilers as well.
1604 WARNING: Use .setMethods to assign the attributes of the ClassCompiler
1605 from the methods of this class!!! or you will be assigning to attributes
1606 of this object instead.
1608 if self
.__dict
__.has_key(name
):
1609 return self
.__dict
__[name
]
1610 elif hasattr(self
.__class
__, name
):
1611 return getattr(self
.__class
__, name
)
1612 elif self
._activeClassesList
and hasattr(self
._activeClassesList
[-1], name
):
1613 return getattr(self
._activeClassesList
[-1], name
)
1615 raise AttributeError, name
1617 def _initializeSettings(self
):
1618 self
.updateSettings(copy
.deepcopy(DEFAULT_COMPILER_SETTINGS
))
1620 def _setupCompilerState(self
):
1621 self
._activeClassesList
= []
1622 self
._finishedClassesList
= [] # listed by ordered
1623 self
._finishedClassIndex
= {} # listed by name
1624 self
._moduleDef
= None
1625 self
._moduleShBang
= '#!/usr/bin/env python'
1626 self
._moduleEncoding
= 'ascii'
1627 self
._moduleEncodingStr
= ''
1628 self
._moduleHeaderLines
= []
1629 self
._moduleDocStringLines
= []
1630 self
._specialVars
= {}
1631 self
._importStatements
= [
1635 "from os.path import getmtime, exists",
1638 "import __builtin__",
1639 "from Cheetah.Version import MinCompatibleVersion as RequiredCheetahVersion",
1640 "from Cheetah.Version import MinCompatibleVersionTuple as RequiredCheetahVersionTuple",
1641 "from Cheetah.Template import Template",
1642 "from Cheetah.DummyTransaction import DummyTransaction",
1643 "from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList",
1644 "from Cheetah.CacheRegion import CacheRegion",
1645 "import Cheetah.Filters as Filters",
1646 "import Cheetah.ErrorCatchers as ErrorCatchers",
1649 self
._importedVarNames
= ['sys',
1662 self
._moduleConstants
= [
1665 "except NameError:",
1666 " True, False = (1==1), (1==0)",
1667 "VFFSL=valueFromFrameOrSearchList",
1668 "VFSL=valueFromSearchList",
1670 "currentTime=time.time",
1674 classCompiler
= self
._spawnClassCompiler
(self
._mainClassName
)
1675 if self
._baseclassName
:
1676 classCompiler
.setBaseClass(self
._baseclassName
)
1677 self
._addActiveClassCompiler
(classCompiler
)
1678 self
._parser
.parse()
1679 self
._swallowClassCompiler
(self
._popActiveClassCompiler
())
1680 self
._compiled
= True
1681 self
._parser
.cleanup()
1683 def _spawnClassCompiler(self
, className
, klass
=None):
1685 klass
= self
.classCompilerClass
1686 classCompiler
= klass(className
,
1687 moduleCompiler
=self
,
1688 mainMethodName
=self
.setting('mainMethodName'),
1689 fileName
=self
._filePath
,
1690 settingsManager
=self
,
1692 return classCompiler
1694 def _addActiveClassCompiler(self
, classCompiler
):
1695 self
._activeClassesList
.append(classCompiler
)
1697 def _getActiveClassCompiler(self
):
1698 return self
._activeClassesList
[-1]
1700 def _popActiveClassCompiler(self
):
1701 return self
._activeClassesList
.pop()
1703 def _swallowClassCompiler(self
, classCompiler
):
1704 classCompiler
.cleanupState()
1705 self
._finishedClassesList
.append( classCompiler
)
1706 self
._finishedClassIndex
[classCompiler
.className()] = classCompiler
1707 return classCompiler
1709 def _finishedClasses(self
):
1710 return self
._finishedClassesList
1712 def importedVarNames(self
):
1713 return self
._importedVarNames
1715 def addImportedVarNames(self
, varNames
):
1716 self
._importedVarNames
.extend(varNames
)
1718 ## methods for adding stuff to the module and class definitions
1720 def setBaseClass(self
, baseClassName
):
1721 if self
._mainMethodNameArg
:
1722 self
.setMainMethodName(self
._mainMethodNameArg
)
1724 self
.setMainMethodName(self
.setting('mainMethodNameForSubclasses'))
1726 if self
.setting('handlerForExtendsDirective'):
1727 handler
= self
.setting('handlerForExtendsDirective')
1728 baseClassName
= handler(compiler
=self
, baseClassName
=baseClassName
)
1729 self
._getActiveClassCompiler
().setBaseClass(baseClassName
)
1730 elif (not self
.setting('autoImportForExtendsDirective')
1731 or baseClassName
=='object' or baseClassName
in self
.importedVarNames()):
1732 self
._getActiveClassCompiler
().setBaseClass(baseClassName
)
1735 ##################################################
1736 ## If the #extends directive contains a classname or modulename that isn't
1737 # in self.importedVarNames() already, we assume that we need to add
1738 # an implied 'from ModName import ClassName' where ModName == ClassName.
1739 # - This is the case in WebKit servlet modules.
1740 # - We also assume that the final . separates the classname from the
1741 # module name. This might break if people do something really fancy
1742 # with their dots and namespaces.
1743 chunks
= baseClassName
.split('.')
1745 self
._getActiveClassCompiler
().setBaseClass(baseClassName
)
1746 if baseClassName
not in self
.importedVarNames():
1747 modName
= baseClassName
1748 # we assume the class name to be the module name
1749 # and that it's not a builtin:
1750 importStatement
= "from %s import %s" % (modName
, baseClassName
)
1751 self
.addImportStatement(importStatement
)
1752 self
.addImportedVarNames( [baseClassName
,] )
1754 needToAddImport
= True
1756 #print chunks, ':', self.importedVarNames()
1757 for chunk
in chunks
[1:-1]:
1758 if modName
in self
.importedVarNames():
1759 needToAddImport
= False
1760 finalBaseClassName
= baseClassName
.replace(modName
+'.', '')
1761 self
._getActiveClassCompiler
().setBaseClass(finalBaseClassName
)
1764 modName
+= '.'+chunk
1766 modName
, finalClassName
= '.'.join(chunks
[:-1]), chunks
[-1]
1767 #if finalClassName != chunks[:-1][-1]:
1768 if finalClassName
!= chunks
[-2]:
1769 # we assume the class name to be the module name
1770 modName
= '.'.join(chunks
)
1771 self
._getActiveClassCompiler
().setBaseClass(finalClassName
)
1772 importStatement
= "from %s import %s" % (modName
, finalClassName
)
1773 self
.addImportStatement(importStatement
)
1774 self
.addImportedVarNames( [finalClassName
,] )
1776 def setCompilerSetting(self
, key
, valueExpr
):
1777 self
.setSetting(key
, eval(valueExpr
) )
1778 self
._parser
.configureParser()
1780 def setCompilerSettings(self
, keywords
, settingsStr
):
1783 if 'nomerge' in KWs
:
1787 # @@TR: this is actually caught by the parser at the moment.
1788 # subject to change in the future
1789 self
._initializeSettings
()
1790 self
._parser
.configureParser()
1792 elif 'python' in KWs
:
1793 settingsReader
= self
.updateSettingsFromPySrcStr
1794 # this comes from SettingsManager
1796 # this comes from SettingsManager
1797 settingsReader
= self
.updateSettingsFromConfigStr
1799 settingsReader(settingsStr
)
1800 self
._parser
.configureParser()
1802 def setShBang(self
, shBang
):
1803 self
._moduleShBang
= shBang
1805 def setModuleEncoding(self
, encoding
):
1806 self
._moduleEncoding
= encoding
1807 self
._moduleEncodingStr
= '# -*- coding: %s -*-' %encoding
1809 def getModuleEncoding(self
):
1810 return self
._moduleEncoding
1812 def addModuleHeader(self
, line
):
1813 """Adds a header comment to the top of the generated module.
1815 self
._moduleHeaderLines
.append(line
)
1817 def addModuleDocString(self
, line
):
1818 """Adds a line to the generated module docstring.
1820 self
._moduleDocStringLines
.append(line
)
1822 def addModuleGlobal(self
, line
):
1823 """Adds a line of global module code. It is inserted after the import
1824 statements and Cheetah default module constants.
1826 self
._moduleConstants
.append(line
)
1828 def addSpecialVar(self
, basename
, contents
, includeUnderscores
=True):
1829 """Adds module __specialConstant__ to the module globals.
1831 name
= includeUnderscores
and '__'+basename
+'__' or basename
1832 self
._specialVars
[name
] = contents
.strip()
1834 def addImportStatement(self
, impStatement
):
1835 self
._importStatements
.append(impStatement
)
1837 #@@TR 2005-01-01: there's almost certainly a cleaner way to do this!
1838 importVarNames
= impStatement
[impStatement
.find('import') + len('import'):].split(',')
1839 importVarNames
= [var
.split()[-1] for var
in importVarNames
] # handles aliases
1840 importVarNames
= [var
for var
in importVarNames
if var
!='*']
1841 self
.addImportedVarNames(importVarNames
) #used by #extend for auto-imports
1843 def addAttribute(self
, attribName
, expr
):
1844 self
._getActiveClassCompiler
().addAttribute(attribName
+ ' =' + expr
)
1846 def addComment(self
, comm
):
1847 if re
.match(r
'#+$',comm
): # skip bar comments
1850 specialVarMatch
= specialVarRE
.match(comm
)
1852 # @@TR: this is a bit hackish and is being replaced with
1853 # #set module varName = ...
1854 return self
.addSpecialVar(specialVarMatch
.group(1),
1855 comm
[specialVarMatch
.end():])
1856 elif comm
.startswith('doc:'):
1857 addLine
= self
.addMethDocString
1858 comm
= comm
[len('doc:'):].strip()
1859 elif comm
.startswith('doc-method:'):
1860 addLine
= self
.addMethDocString
1861 comm
= comm
[len('doc-method:'):].strip()
1862 elif comm
.startswith('doc-module:'):
1863 addLine
= self
.addModuleDocString
1864 comm
= comm
[len('doc-module:'):].strip()
1865 elif comm
.startswith('doc-class:'):
1866 addLine
= self
.addClassDocString
1867 comm
= comm
[len('doc-class:'):].strip()
1868 elif comm
.startswith('header:'):
1869 addLine
= self
.addModuleHeader
1870 comm
= comm
[len('header:'):].strip()
1872 addLine
= self
.addMethComment
1874 for line
in comm
.splitlines():
1877 ## methods for module code wrapping
1879 def getModuleCode(self
):
1880 if not self
._compiled
:
1883 return self
._moduleDef
1885 return self
.wrapModuleDef()
1887 __str__
= getModuleCode
1889 def wrapModuleDef(self
):
1890 self
.addSpecialVar('CHEETAH_docstring', self
.setting('defDocStrMsg'))
1891 self
.addModuleGlobal('__CHEETAH_version__ = %r'%Version
)
1892 self
.addModuleGlobal('__CHEETAH_versionTuple__ = %r'%(VersionTuple
,))
1893 if self
.setting('addTimestampsToCompilerOutput'):
1894 self
.addModuleGlobal('__CHEETAH_genTime__ = %r'%time
.time())
1895 self
.addModuleGlobal('__CHEETAH_genTimestamp__ = %r'%self
.timestamp())
1897 timestamp
= self
.timestamp(self
._fileMtime
)
1898 self
.addModuleGlobal('__CHEETAH_src__ = %r'%self
._filePath
)
1899 self
.addModuleGlobal('__CHEETAH_srcLastModified__ = %r'%timestamp
)
1901 self
.addModuleGlobal('__CHEETAH_src__ = None')
1902 self
.addModuleGlobal('__CHEETAH_srcLastModified__ = None')
1904 moduleDef
= """%(header)s
1907 ##################################################
1911 ##################################################
1916 if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple:
1917 raise AssertionError(
1918 'This template was compiled with Cheetah version'
1919 ' %%s. Templates compiled before version %%s must be recompiled.'%%(
1920 __CHEETAH_version__, RequiredCheetahVersion))
1922 ##################################################
1927 ## END CLASS DEFINITION
1929 if not hasattr(%(mainClassName)s, '_initCheetahAttributes'):
1930 templateAPIClass = getattr(%(mainClassName)s, '_CHEETAH_templateClass', Template)
1931 templateAPIClass._addCheetahPlumbingCodeToClass(%(mainClassName)s)
1934 """ % {'header':self
.moduleHeader(),
1935 'docstring':self
.moduleDocstring(),
1936 'specialVars':self
.specialVars(),
1937 'imports':self
.importStatements(),
1938 'constants':self
.moduleConstants(),
1939 'classes':self
.classDefs(),
1940 'footer':self
.moduleFooter(),
1941 'mainClassName':self
._mainClassName
,
1944 self
._moduleDef
= moduleDef
1947 def timestamp(self
, theTime
=None):
1949 theTime
= time
.time()
1950 return time
.asctime(time
.localtime(theTime
))
1952 def moduleHeader(self
):
1953 header
= self
._moduleShBang
+ '\n'
1954 header
+= self
._moduleEncodingStr
+ '\n'
1955 if self
._moduleHeaderLines
:
1956 offSet
= self
.setting('commentOffset')
1960 ('\n#'+ ' '*offSet
).join(self
._moduleHeaderLines
) + '\n')
1964 def moduleDocstring(self
):
1965 if not self
._moduleDocStringLines
:
1969 '\n'.join(self
._moduleDocStringLines
) +
1972 def specialVars(self
):
1974 theVars
= self
._specialVars
1975 keys
= theVars
.keys()
1978 chunks
.append(key
+ ' = ' + repr(theVars
[key
]) )
1979 return '\n'.join(chunks
)
1981 def importStatements(self
):
1982 return '\n'.join(self
._importStatements
)
1984 def moduleConstants(self
):
1985 return '\n'.join(self
._moduleConstants
)
1987 def classDefs(self
):
1988 classDefs
= [klass
.classDef() for klass
in self
._finishedClasses
()]
1989 return '\n\n'.join(classDefs
)
1991 def moduleFooter(self
):
1993 # CHEETAH was developed by Tavis Rudd and Mike Orr
1994 # with code, advice and input from many other volunteers.
1995 # For more information visit http://www.CheetahTemplate.org/
1997 ##################################################
1998 ## if run from command line:
1999 if __name__ == '__main__':
2000 from Cheetah.TemplateCmdLineIface import CmdLineIface
2001 CmdLineIface(templateObj=%(className)s()).run()
2003 """ % {'className':self
._mainClassName
}
2006 ##################################################
2007 ## Make Compiler an alias for ModuleCompiler
2009 Compiler
= ModuleCompiler