2 # $Id: Compiler.py,v 1.148 2006/06/22 00:18:22 tavis_rudd Exp $
3 """Compiler classes for Cheetah:
4 ModuleCompiler aka 'Compiler'
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.148 $
15 Start Date: 2001/09/19
16 Last Revision Date: $Date: 2006/06/22 00:18:22 $
18 __author__
= "Tavis Rudd <tavis@damnsimple.com>"
19 __revision__
= "$Revision: 1.148 $"[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
.Parser
import Parser
, ParseError
, specialVarRE
, \
36 STATIC_CACHE
, REFRESH_CACHE
, SET_LOCAL
, SET_GLOBAL
,SET_MODULE
37 from Cheetah
.Utils
.Indenter
import indentize
# an undocumented preprocessor
38 from Cheetah
import ErrorCatchers
39 from Cheetah
import NameMapper
41 from Cheetah
.NameMapper
import NotFound
, valueForName
, valueFromSearchList
, valueFromFrameOrSearchList
42 VFFSL
=valueFromFrameOrSearchList
43 VFSL
=valueFromSearchList
47 class Error(Exception): pass
49 DEFAULT_COMPILER_SETTINGS
= {
50 ## controlling the handling of Cheetah $placeholders
51 'useNameMapper': True, # Unified dotted notation and the searchList
52 'useSearchList': True, # if false, assume the first
53 # portion of the $variable (before the first dot) is a global,
54 # builtin, or local var that doesn't need
55 # looking up in the searchlist BUT use
56 # namemapper on the rest of the lookup
57 'allowSearchListAsMethArg': True,
58 'useAutocalling': True, # detect and call callable()'s, requires NameMapper
59 'useStackFrames': True, # use NameMapper.valueFromFrameOrSearchList
60 # rather than NameMapper.valueFromSearchList
61 'useErrorCatcher':False,
62 'alwaysFilterNone':True, # filter out None, before the filter is called
63 'useFilters':True, # use str instead if =False
64 'includeRawExprInFilterArgs':True,
67 #'lookForTransactionAttr':False,
68 'autoAssignDummyTransactionToSelf':False,
69 'useKWsDictArgForPassingTrans':True,
71 ## controlling the aesthetic appearance / behaviour of generated code
73 # should shorter str constant chunks be printed using repr rather than ''' quotes
74 'reprShortStrConstants': True,
75 'reprNewlineThreshold':3,
76 'outputRowColComments':True,
77 # should #block's be wrapped in a comment in the template's output
78 'includeBlockMarkers': False,
79 'blockMarkerStart':('\n<!-- START BLOCK: ',' -->\n'),
80 'blockMarkerEnd':('\n<!-- END BLOCK: ',' -->\n'),
81 'defDocStrMsg':'Autogenerated by CHEETAH: The Python-Powered Template Engine',
82 'setup__str__method': False,
83 'mainMethodName':'respond',
84 'mainMethodNameForSubclasses':'writeBody',
85 'indentationStep': ' '*4,
86 'initialMethIndentLevel': 2,
87 'monitorSrcFile':False,
88 'outputMethodsBeforeAttributes': True,
91 ## customizing the #extends directive
92 'autoImportForExtendsDirective':True,
93 'handlerForExtendsDirective':None, # baseClassName = handler(compiler, baseClassName)
94 # a callback hook for customizing the
95 # #extends directive. It can manipulate
96 # the compiler's state if needed.
97 # also see allowExpressionsInExtendsDirective
100 # input filtering/restriction
101 # use lower case keys here!!
102 'disabledDirectives':[], # list of directive keys, without the start token
103 'enabledDirectives':[], # list of directive keys, without the start token
105 'disabledDirectiveHooks':[], # callable(parser, directiveKey)
106 'preparseDirectiveHooks':[], # callable(parser, directiveKey)
107 'postparseDirectiveHooks':[], # callable(parser, directiveKey)
108 'preparsePlaceholderHooks':[], # callable(parser)
109 'postparsePlaceholderHooks':[], # callable(parser)
110 # the above hooks don't need to return anything
112 'expressionFilterHooks':[], # callable(parser, expr, exprType, rawExpr=None, startPos=None)
113 # exprType is the name of the directive, 'psp', or 'placeholder'. all
114 # lowercase. The filters *must* return the expr or raise an exception.
115 # They can modify the expr if needed.
117 'templateMetaclass':None, # strictly optional. Only works with new-style baseclasses
120 'i18NFunctionName':'self.i18n',
122 ## These are used in the parser, but I've put them here for the time being to
123 ## facilitate separating the parser and compiler:
124 'cheetahVarStartToken':'$',
125 'commentStartToken':'##',
126 'multiLineCommentStartToken':'#*',
127 'multiLineCommentEndToken':'*#',
128 'gobbleWhitespaceAroundMultiLineComments':True,
129 'directiveStartToken':'#',
130 'directiveEndToken':'#',
131 'allowWhitespaceAfterDirectiveStartToken':False,
132 'PSPStartToken':'<%',
135 'gettextTokens': ["_", "N_", "ngettext"],
136 'allowExpressionsInExtendsDirective': False, # the default restricts it to
137 # accepting dotted names
138 'allowEmptySingleLineMethods': False,
139 'allowNestedDefScopes': True,
140 'allowPlaceholderFilterArgs': True,
142 ## See Parser.initDirectives() for the use of the next 3
143 #'directiveNamesAndParsers':{}
144 #'endDirectiveNamesAndHandlers':{}
145 #'macroDirectives':{}
152 """An abstract baseclass for the Compiler classes that provides methods that
153 perform generic utility functions or generate pieces of output code from
154 information passed in by the Parser baseclass. These methods don't do any
158 def genTimeInterval(self
, timeString
):
159 ##@@ TR: need to add some error handling here
160 if timeString
[-1] == 's':
161 interval
= float(timeString
[:-1])
162 elif timeString
[-1] == 'm':
163 interval
= float(timeString
[:-1])*60
164 elif timeString
[-1] == 'h':
165 interval
= float(timeString
[:-1])*60*60
166 elif timeString
[-1] == 'd':
167 interval
= float(timeString
[:-1])*60*60*24
168 elif timeString
[-1] == 'w':
169 interval
= float(timeString
[:-1])*60*60*24*7
170 else: # default to minutes
171 interval
= float(timeString
)*60
174 def genCacheInfo(self
, cacheTokenParts
):
175 """Decipher a placeholder cachetoken
178 if cacheTokenParts
['REFRESH_CACHE']:
179 cacheInfo
['type'] = REFRESH_CACHE
180 cacheInfo
['interval'] = self
.genTimeInterval(cacheTokenParts
['interval'])
181 elif cacheTokenParts
['STATIC_CACHE']:
182 cacheInfo
['type'] = STATIC_CACHE
183 return cacheInfo
# is empty if no cache
185 def genCacheInfoFromArgList(self
, argList
):
186 cacheInfo
= {'type':REFRESH_CACHE
}
187 for key
, val
in argList
:
193 val
= self
.genTimeInterval(val
)
198 def genCheetahVar(self
, nameChunks
, plain
=False):
199 if nameChunks
[0][0] in self
.setting('gettextTokens'):
200 self
.addGetTextVar(nameChunks
)
201 if self
.setting('useNameMapper') and not plain
:
202 return self
.genNameMapperVar(nameChunks
)
204 return self
.genPlainVar(nameChunks
)
206 def addGetTextVar(self
, nameChunks
):
207 """Output something that gettext can recognize.
209 This is a harmless side effect necessary to make gettext work when it
210 is scanning compiled templates for strings marked for translation.
212 @@TR: another marginally more efficient approach would be to put the
213 output in a dummy method that is never called.
215 # @@TR: this should be in the compiler not here
216 self
.addChunk("if False:")
218 self
.addChunk(self
.genPlainVar(nameChunks
[:]))
221 def genPlainVar(self
, nameChunks
):
222 """Generate Python code for a Cheetah $var without using NameMapper
223 (Unified Dotted Notation with the SearchList).
226 chunk
= nameChunks
.pop()
227 pythonCode
= chunk
[0] + chunk
[2]
229 chunk
= nameChunks
.pop()
230 pythonCode
= (pythonCode
+ '.' + chunk
[0] + chunk
[2])
233 def genNameMapperVar(self
, nameChunks
):
234 """Generate valid Python code for a Cheetah $var, using NameMapper
235 (Unified Dotted Notation with the SearchList).
237 nameChunks = list of var subcomponents represented as tuples
238 [ (name,useAC,remainderOfExpr),
241 name = the dotted name base
242 useAC = where NameMapper should use autocalling on namemapperPart
243 remainderOfExpr = any arglist, index, or slice
245 If remainderOfExpr contains a call arglist (e.g. '(1234)') then useAC
246 is False, otherwise it defaults to True. It is overridden by the global
247 setting 'useAutocalling' if this setting is False.
250 ------------------------------------------------------------------------
251 if the raw Cheetah Var is
254 nameChunks is the list
255 [ ('a.b.c',True,'[1]'), # A
256 ('d',False,'()'), # B
257 ('x.y.z',True,''), # C
260 When this method is fed the list above it returns
261 VFN(VFN(VFFSL(SL, 'a.b.c',True)[1], 'd',False)(), 'x.y.z',True)
262 which can be represented as
263 VFN(B`, name=C[0], executeCallables=(useAC and C[1]))C[2]
265 VFN = NameMapper.valueForName
266 VFFSL = NameMapper.valueFromFrameOrSearchList
267 VFSL = NameMapper.valueFromSearchList # optionally used instead of VFFSL
268 SL = self.searchList()
269 useAC = self.setting('useAutocalling') # True in this example
271 A = ('a.b.c',True,'[1]')
273 C = ('x.y.z',True,'')
275 C` = VFN( VFN( VFFSL(SL, 'a.b.c',True)[1],
278 = VFN(B`, name='x.y.z', executeCallables=True)
280 B` = VFN(A`, name=B[0], executeCallables=(useAC and B[1]))B[2]
281 A` = VFFSL(SL, name=A[0], executeCallables=(useAC and A[1]))A[2]
284 Note, if the compiler setting useStackFrames=False (default is true)
286 A` = VFSL([locals()]+SL+[globals(), __builtin__], name=A[0], executeCallables=(useAC and A[1]))A[2]
287 This option allows Cheetah to be used with Psyco, which doesn't support
288 stack frame introspection.
290 defaultUseAC
= self
.setting('useAutocalling')
291 useSearchList
= self
.setting('useSearchList')
294 name
, useAC
, remainder
= nameChunks
.pop()
296 if not useSearchList
:
297 firstDotIdx
= name
.find('.')
298 if firstDotIdx
!= -1 and firstDotIdx
< len(name
):
299 beforeFirstDot
, afterDot
= name
[:firstDotIdx
], name
[firstDotIdx
+1:]
300 pythonCode
= ('VFN(' + beforeFirstDot
+
302 '",' + repr(defaultUseAC
and useAC
) + ')'
305 pythonCode
= name
+remainder
306 elif self
.setting('useStackFrames'):
307 pythonCode
= ('VFFSL(SL,'
309 + repr(defaultUseAC
and useAC
) + ')'
312 pythonCode
= ('VFSL([locals()]+SL+[globals(), __builtin__],'
314 + repr(defaultUseAC
and useAC
) + ')'
318 name
, useAC
, remainder
= nameChunks
.pop()
319 pythonCode
= ('VFN(' + pythonCode
+
321 '",' + repr(defaultUseAC
and useAC
) + ')'
325 ##################################################
328 class MethodCompiler(GenUtils
):
329 def __init__(self
, methodName
, classCompiler
,
330 initialMethodComment
=None,
332 self
._settingsManager
= classCompiler
333 self
._classCompiler
= classCompiler
334 self
._moduleCompiler
= classCompiler
._moduleCompiler
335 self
._methodName
= methodName
336 self
._initialMethodComment
= initialMethodComment
338 self
._decorator
= decorator
340 def setting(self
, key
):
341 return self
._settingsManager
.setting(key
)
343 def _setupState(self
):
344 self
._indent
= self
.setting('indentationStep')
345 self
._indentLev
= self
.setting('initialMethIndentLevel')
346 self
._pendingStrConstChunks
= []
347 self
._methodSignature
= None
348 self
._methodDef
= None
349 self
._docStringLines
= []
350 self
._methodBodyChunks
= []
352 self
._cacheRegionsStack
= []
353 self
._callRegionsStack
= []
354 self
._captureRegionsStack
= []
355 self
._filterRegionsStack
= []
357 self
._isErrorCatcherOn
= False
359 self
._hasReturnStatement
= False
360 self
._isGenerator
= False
363 def cleanupState(self
):
364 """Called by the containing class compiler instance
368 def methodName(self
):
369 return self
._methodName
371 def setMethodName(self
, name
):
372 self
._methodName
= name
374 ## methods for managing indentation
376 def indentation(self
):
377 return self
._indent
* self
._indentLev
386 raise Error('Attempt to dedent when the indentLev is 0')
388 ## methods for final code wrapping
392 return self
._methodDef
394 return self
.wrapCode()
399 self
.commitStrConst()
401 self
.methodSignature(),
405 methodDef
= ''.join(methodDefChunks
)
406 self
._methodDef
= methodDef
409 def methodSignature(self
):
410 return self
._indent
+ self
._methodSignature
+ ':'
412 def setMethodSignature(self
, signature
):
413 self
._methodSignature
= signature
415 def methodBody(self
):
416 return ''.join( self
._methodBodyChunks
)
419 if not self
._docStringLines
:
423 docStr
= (ind
+ '"""\n' + ind
+
424 ('\n' + ind
).join([ln
.replace('"""',"'''") for ln
in self
._docStringLines
]) +
425 '\n' + ind
+ '"""\n')
428 ## methods for adding code
429 def addMethDocString(self
, line
):
430 self
._docStringLines
.append(line
.replace('%','%%'))
432 def addChunk(self
, chunk
):
433 self
.commitStrConst()
434 chunk
= "\n" + self
.indentation() + chunk
435 self
._methodBodyChunks
.append(chunk
)
437 def appendToPrevChunk(self
, appendage
):
438 self
._methodBodyChunks
[-1] = self
._methodBodyChunks
[-1] + appendage
440 def addWriteChunk(self
, chunk
):
441 self
.addChunk('write(' + chunk
+ ')')
443 def addFilteredChunk(self
, chunk
, filterArgs
=None, rawExpr
=None, lineCol
=None):
444 if filterArgs
is None:
446 if self
.setting('includeRawExprInFilterArgs') and rawExpr
:
447 filterArgs
+= ', rawExpr=%s'%repr
(rawExpr
)
449 if self
.setting('alwaysFilterNone'):
450 if rawExpr
and rawExpr
.find('\n')==-1 and rawExpr
.find('\r')==-1:
451 self
.addChunk("_v = %s # %r"%(chunk
, rawExpr
))
453 self
.appendToPrevChunk(' on line %s, col %s'%lineCol
)
455 self
.addChunk("_v = %s"%chunk
)
457 if self
.setting('useFilters'):
458 self
.addChunk("if _v is not None: write(_filter(_v%s))"%filterArgs
)
460 self
.addChunk("if _v is not None: write(str(_v))")
462 if self
.setting('useFilters'):
463 self
.addChunk("write(_filter(%s%s))"%(chunk
,filterArgs
))
465 self
.addChunk("write(str(%s))"%chunk
)
467 def _appendToPrevStrConst(self
, strConst
):
468 if self
._pendingStrConstChunks
:
469 self
._pendingStrConstChunks
.append(strConst
)
471 self
._pendingStrConstChunks
= [strConst
]
473 def _unescapeCheetahVars(self
, theString
):
474 """Unescape any escaped Cheetah \$vars in the string.
477 token
= self
.setting('cheetahVarStartToken')
478 return theString
.replace('\\' + token
, token
)
480 def _unescapeDirectives(self
, theString
):
481 """Unescape any escaped Cheetah \$vars in the string.
484 token
= self
.setting('directiveStartToken')
485 return theString
.replace('\\' + token
, token
)
487 def commitStrConst(self
):
488 """Add the code for outputting the pending strConst without chopping off
489 any whitespace from it.
491 if self
._pendingStrConstChunks
:
492 strConst
= self
._unescapeCheetahVars
(''.join(self
._pendingStrConstChunks
))
493 strConst
= self
._unescapeDirectives
(strConst
)
494 self
._pendingStrConstChunks
= []
497 if self
.setting('reprShortStrConstants') and \
498 strConst
.count('\n') < self
.setting('reprNewlineThreshold'):
499 self
.addWriteChunk( repr(strConst
).replace('\\012','\\n'))
501 strConst
= strConst
.replace('\\','\\\\').replace("'''","'\'\'\'")
502 if strConst
[0] == "'":
503 strConst
= '\\' + strConst
504 if strConst
[-1] == "'":
505 strConst
= strConst
[:-1] + '\\' + strConst
[-1]
507 self
.addWriteChunk("'''" + strConst
+ "'''" )
509 def handleWSBeforeDirective(self
):
510 """Truncate the pending strCont to the beginning of the current line.
512 if self
._pendingStrConstChunks
:
513 src
= self
._pendingStrConstChunks
[-1]
514 BOL
= max(src
.rfind('\n')+1, src
.rfind('\r')+1, 0)
516 self
._pendingStrConstChunks
[-1] = src
[:BOL
]
520 def isErrorCatcherOn(self
):
521 return self
._isErrorCatcherOn
523 def turnErrorCatcherOn(self
):
524 self
._isErrorCatcherOn
= True
526 def turnErrorCatcherOff(self
):
527 self
._isErrorCatcherOn
= False
529 # @@TR: consider merging the next two methods into one
530 def addStrConst(self
, strConst
):
531 self
._appendToPrevStrConst
(strConst
)
533 def addRawText(self
, text
):
534 self
.addStrConst(text
)
536 def addMethComment(self
, comm
):
537 offSet
= self
.setting('commentOffset')
538 self
.addChunk('#' + ' '*offSet
+ comm
)
540 def addPlaceholder(self
, expr
, filterArgs
, rawPlaceholder
,
541 cacheTokenParts
, lineCol
,
543 cacheInfo
= self
.genCacheInfo(cacheTokenParts
)
545 cacheInfo
['ID'] = repr(rawPlaceholder
)[1:-1]
546 self
.startCacheRegion(cacheInfo
, lineCol
, rawPlaceholder
=rawPlaceholder
)
548 if self
.isErrorCatcherOn():
549 methodName
= self
._classCompiler
.addErrorCatcherCall(
550 expr
, rawCode
=rawPlaceholder
, lineCol
=lineCol
)
551 expr
= 'self.' + methodName
+ '(localsDict=locals())'
554 self
.addChunk('try:')
556 self
.addFilteredChunk(expr
, filterArgs
, rawPlaceholder
, lineCol
=lineCol
)
558 self
.addChunk('except NotFound: pass')
560 self
.addFilteredChunk(expr
, filterArgs
, rawPlaceholder
, lineCol
=lineCol
)
562 if self
.setting('outputRowColComments'):
563 self
.appendToPrevChunk(' # from line %s, col %s' % lineCol
+ '.')
565 self
.endCacheRegion()
567 def addSilent(self
, expr
):
568 self
.addChunk( expr
)
570 def addEcho(self
, expr
, rawExpr
=None):
571 self
.addFilteredChunk(expr
, rawExpr
=rawExpr
)
573 def addSet(self
, expr
, exprComponents
, setStyle
):
574 if setStyle
is SET_GLOBAL
:
575 (LVALUE
, OP
, RVALUE
) = (exprComponents
.LVALUE
,
577 exprComponents
.RVALUE
)
578 # we need to split the LVALUE to deal with globalSetVars
579 splitPos1
= LVALUE
.find('.')
580 splitPos2
= LVALUE
.find('[')
581 if splitPos1
> 0 and splitPos2
==-1:
583 elif splitPos1
> 0 and splitPos1
< max(splitPos2
,0):
589 primary
= LVALUE
[:splitPos
]
590 secondary
= LVALUE
[splitPos
:]
594 LVALUE
= 'self._CHEETAH__globalSetVars["' + primary
+ '"]' + secondary
595 expr
= LVALUE
+ ' ' + OP
+ ' ' + RVALUE
.strip()
597 if setStyle
is SET_MODULE
:
598 self
._moduleCompiler
.addModuleGlobal(expr
)
602 def addInclude(self
, sourceExpr
, includeFrom
, isRaw
):
603 self
.addChunk('self._handleCheetahInclude(' + sourceExpr
+
605 'includeFrom="' + includeFrom
+ '", raw=' +
608 def addWhile(self
, expr
, lineCol
=None):
609 self
.addIndentingDirective(expr
, lineCol
=lineCol
)
611 def addFor(self
, expr
, lineCol
=None):
612 self
.addIndentingDirective(expr
, lineCol
=lineCol
)
614 def addRepeat(self
, expr
, lineCol
=None):
615 #the _repeatCount stuff here allows nesting of #repeat directives
616 self
._repeatCount
= getattr(self
, "_repeatCount", -1) + 1
617 self
.addFor('for __i%s in range(%s)' % (self
._repeatCount
,expr
), lineCol
=lineCol
)
619 def addIndentingDirective(self
, expr
, lineCol
=None):
620 if expr
and not expr
[-1] == ':':
622 self
.addChunk( expr
)
624 self
.appendToPrevChunk(' # generated from line %s, col %s'%lineCol
)
627 def addReIndentingDirective(self
, expr
, dedent
=True, lineCol
=None):
628 self
.commitStrConst()
631 if not expr
[-1] == ':':
634 self
.addChunk( expr
)
636 self
.appendToPrevChunk(' # generated from line %s, col %s'%lineCol
)
639 def addIf(self
, expr
, lineCol
=None):
640 """For a full #if ... #end if directive
642 self
.addIndentingDirective(expr
, lineCol
=lineCol
)
644 def addOneLineIf(self
, expr
, lineCol
=None):
645 """For a full #if ... #end if directive
647 self
.addIndentingDirective(expr
, lineCol
=lineCol
)
649 def addTernaryExpr(self
, conditionExpr
, trueExpr
, falseExpr
, lineCol
=None):
650 """For a single-lie #if ... then .... else ... directive
651 <condition> then <trueExpr> else <falseExpr>
653 self
.addIndentingDirective(conditionExpr
, lineCol
=lineCol
)
654 self
.addFilteredChunk(trueExpr
)
656 self
.addIndentingDirective('else')
657 self
.addFilteredChunk(falseExpr
)
660 def addElse(self
, expr
, dedent
=True, lineCol
=None):
661 expr
= re
.sub(r
'else[ \f\t]+if','elif', expr
)
662 self
.addReIndentingDirective(expr
, dedent
=dedent
, lineCol
=lineCol
)
664 def addElif(self
, expr
, dedent
=True, lineCol
=None):
665 self
.addElse(expr
, dedent
=dedent
, lineCol
=lineCol
)
667 def addUnless(self
, expr
, lineCol
=None):
668 self
.addIf('if not (' + expr
+ ')')
670 def addClosure(self
, functionName
, argsList
, parserComment
):
674 if not arg
[1] == None:
675 chunk
+= '=' + arg
[1]
676 argStringChunks
.append(chunk
)
677 signature
= "def " + functionName
+ "(" + ','.join(argStringChunks
) + "):"
678 self
.addIndentingDirective(signature
)
679 self
.addChunk('#'+parserComment
)
681 def addTry(self
, expr
, lineCol
=None):
682 self
.addIndentingDirective(expr
, lineCol
=lineCol
)
684 def addExcept(self
, expr
, dedent
=True, lineCol
=None):
685 self
.addReIndentingDirective(expr
, dedent
=dedent
, lineCol
=lineCol
)
687 def addFinally(self
, expr
, dedent
=True, lineCol
=None):
688 self
.addReIndentingDirective(expr
, dedent
=dedent
, lineCol
=lineCol
)
690 def addReturn(self
, expr
):
691 assert not self
._isGenerator
693 self
._hasReturnStatement
= True
695 def addYield(self
, expr
):
696 assert not self
._hasReturnStatement
697 self
._isGenerator
= True
698 if expr
.replace('yield','').strip():
701 self
.addChunk('if _dummyTrans:')
703 self
.addChunk('yield trans.response().getvalue()')
704 self
.addChunk('trans = DummyTransaction()')
705 self
.addChunk('write = trans.response().write')
707 self
.addChunk('else:')
710 'raise TypeError("This method cannot be called with a trans arg")')
714 def addPass(self
, expr
):
717 def addDel(self
, expr
):
720 def addAssert(self
, expr
):
723 def addRaise(self
, expr
):
726 def addBreak(self
, expr
):
729 def addContinue(self
, expr
):
732 def addPSP(self
, PSP
):
733 self
.commitStrConst()
738 self
.addWriteChunk('_filter(' + PSP
+ ')')
741 elif PSP
.lower() == 'end':
750 for line
in PSP
.splitlines():
756 def nextCacheID(self
):
757 return ('_'+str(random
.randrange(100, 999))
758 + str(random
.randrange(10000, 99999)))
760 def startCacheRegion(self
, cacheInfo
, lineCol
, rawPlaceholder
=None):
762 # @@TR: we should add some runtime logging to this
764 ID
= self
.nextCacheID()
765 interval
= cacheInfo
.get('interval',None)
766 test
= cacheInfo
.get('test',None)
767 customID
= cacheInfo
.get('id',None)
770 varyBy
= cacheInfo
.get('varyBy', repr(ID
))
771 self
._cacheRegionsStack
.append(ID
) # attrib of current methodCompiler
773 # @@TR: add this to a special class var as well
776 self
.addChunk('## START CACHE REGION: ID='+ID
+
777 '. line %s, col %s'%lineCol
+ ' in the source.')
779 self
.addChunk('_RECACHE_%(ID)s = False'%locals())
780 self
.addChunk('_cacheRegion_%(ID)s = self.getCacheRegion(regionID='%locals()
782 + ', cacheInfo=%r'%cacheInfo
784 self
.addChunk('if _cacheRegion_%(ID)s.isNew():'%locals())
786 self
.addChunk('_RECACHE_%(ID)s = True'%locals())
789 self
.addChunk('_cacheItem_%(ID)s = _cacheRegion_%(ID)s.getCacheItem('%locals()
792 self
.addChunk('if _cacheItem_%(ID)s.hasExpired():'%locals())
794 self
.addChunk('_RECACHE_%(ID)s = True'%locals())
798 self
.addChunk('if ' + test
+ ':')
800 self
.addChunk('_RECACHE_%(ID)s = True'%locals())
803 self
.addChunk('if (not _RECACHE_%(ID)s) and _cacheItem_%(ID)s.getRefreshTime():'%locals())
805 #self.addChunk('print "DEBUG"+"-"*50')
806 self
.addChunk('try:')
808 self
.addChunk('_output = _cacheItem_%(ID)s.renderOutput()'%locals())
810 self
.addChunk('except KeyError:')
812 self
.addChunk('_RECACHE_%(ID)s = True'%locals())
813 #self.addChunk('print "DEBUG"+"*"*50')
815 self
.addChunk('else:')
817 self
.addWriteChunk('_output')
818 self
.addChunk('del _output')
823 self
.addChunk('if _RECACHE_%(ID)s or not _cacheItem_%(ID)s.getRefreshTime():'%locals())
825 self
.addChunk('_orig_trans%(ID)s = trans'%locals())
826 self
.addChunk('trans = _cacheCollector_%(ID)s = DummyTransaction()'%locals())
827 self
.addChunk('write = _cacheCollector_%(ID)s.response().write'%locals())
829 self
.addChunk(("_cacheItem_%(ID)s.setExpiryTime(currentTime() +"%locals())
830 + str(interval
) + ")")
832 def endCacheRegion(self
):
833 ID
= self
._cacheRegionsStack
.pop()
834 self
.addChunk('trans = _orig_trans%(ID)s'%locals())
835 self
.addChunk('write = trans.response().write')
836 self
.addChunk('_cacheData = _cacheCollector_%(ID)s.response().getvalue()'%locals())
837 self
.addChunk('_cacheItem_%(ID)s.setData(_cacheData)'%locals())
838 self
.addWriteChunk('_cacheData')
839 self
.addChunk('del _cacheData')
840 self
.addChunk('del _cacheCollector_%(ID)s'%locals())
841 self
.addChunk('del _orig_trans%(ID)s'%locals())
843 self
.addChunk('## END CACHE REGION: '+ID
)
846 def nextCallRegionID(self
):
847 return self
.nextCacheID()
849 def startCallRegion(self
, functionName
, args
, lineCol
, regionTitle
='CALL'):
850 class CallDetails
: pass
851 callDetails
= CallDetails()
852 callDetails
.ID
= ID
= self
.nextCallRegionID()
853 callDetails
.functionName
= functionName
854 callDetails
.args
= args
855 callDetails
.lineCol
= lineCol
856 callDetails
.usesKeywordArgs
= False
857 self
._callRegionsStack
.append((ID
, callDetails
)) # attrib of current methodCompiler
859 self
.addChunk('## START %(regionTitle)s REGION: '%locals()
862 +' at line %s, col %s'%lineCol
+ ' in the source.')
863 self
.addChunk('_orig_trans%(ID)s = trans'%locals())
864 self
.addChunk('_wasBuffering%(ID)s = self._CHEETAH__isBuffering'%locals())
865 self
.addChunk('self._CHEETAH__isBuffering = True')
866 self
.addChunk('trans = _callCollector%(ID)s = DummyTransaction()'%locals())
867 self
.addChunk('write = _callCollector%(ID)s.response().write'%locals())
869 def setCallArg(self
, argName
, lineCol
):
870 ID
, callDetails
= self
._callRegionsStack
[-1]
871 if callDetails
.usesKeywordArgs
:
874 callDetails
.usesKeywordArgs
= True
875 self
.addChunk('_callKws%(ID)s = {}'%locals())
876 self
.addChunk('_currentCallArgname%(ID)s = %(argName)r'%locals())
877 callDetails
.currentArgname
= argName
879 def _endCallArg(self
):
880 ID
, callDetails
= self
._callRegionsStack
[-1]
881 currCallArg
= callDetails
.currentArgname
882 self
.addChunk(('_callKws%(ID)s[%(currCallArg)r] ='
883 ' _callCollector%(ID)s.response().getvalue()')%locals())
884 self
.addChunk('del _callCollector%(ID)s'%locals())
885 self
.addChunk('trans = _callCollector%(ID)s = DummyTransaction()'%locals())
886 self
.addChunk('write = _callCollector%(ID)s.response().write'%locals())
888 def endCallRegion(self
, regionTitle
='CALL'):
889 ID
, callDetails
= self
._callRegionsStack
[-1]
890 functionName
, initialKwArgs
, lineCol
= (
891 callDetails
.functionName
, callDetails
.args
, callDetails
.lineCol
)
894 self
.addChunk('trans = _orig_trans%(ID)s'%locals())
895 self
.addChunk('write = trans.response().write')
896 self
.addChunk('self._CHEETAH__isBuffering = _wasBuffering%(ID)s '%locals())
897 self
.addChunk('del _wasBuffering%(ID)s'%locals())
898 self
.addChunk('del _orig_trans%(ID)s'%locals())
900 if not callDetails
.usesKeywordArgs
:
902 self
.addChunk('_callArgVal%(ID)s = _callCollector%(ID)s.response().getvalue()'%locals())
903 self
.addChunk('del _callCollector%(ID)s'%locals())
905 initialKwArgs
= ', '+initialKwArgs
906 self
.addFilteredChunk('%(functionName)s(_callArgVal%(ID)s%(initialKwArgs)s)'%locals())
907 self
.addChunk('del _callArgVal%(ID)s'%locals())
910 initialKwArgs
= initialKwArgs
+', '
913 self
.addFilteredChunk('%(functionName)s(%(initialKwArgs)s**_callKws%(ID)s)'%locals())
914 self
.addChunk('del _callKws%(ID)s'%locals())
915 self
.addChunk('## END %(regionTitle)s REGION: '%locals()
918 +' at line %s, col %s'%lineCol
+ ' in the source.')
920 self
._callRegionsStack
.pop() # attrib of current methodCompiler
922 def nextCaptureRegionID(self
):
923 return self
.nextCacheID()
925 def startCaptureRegion(self
, assignTo
, lineCol
):
926 class CaptureDetails
: pass
927 captureDetails
= CaptureDetails()
928 captureDetails
.ID
= ID
= self
.nextCaptureRegionID()
929 captureDetails
.assignTo
= assignTo
930 captureDetails
.lineCol
= lineCol
932 self
._captureRegionsStack
.append((ID
,captureDetails
)) # attrib of current methodCompiler
933 self
.addChunk('## START CAPTURE REGION: '+ID
935 +' at line %s, col %s'%lineCol
+ ' in the source.')
936 self
.addChunk('_orig_trans%(ID)s = trans'%locals())
937 self
.addChunk('_wasBuffering%(ID)s = self._CHEETAH__isBuffering'%locals())
938 self
.addChunk('self._CHEETAH__isBuffering = True')
939 self
.addChunk('trans = _captureCollector%(ID)s = DummyTransaction()'%locals())
940 self
.addChunk('write = _captureCollector%(ID)s.response().write'%locals())
942 def endCaptureRegion(self
):
943 ID
, captureDetails
= self
._captureRegionsStack
.pop()
944 assignTo
, lineCol
= (captureDetails
.assignTo
, captureDetails
.lineCol
)
945 self
.addChunk('trans = _orig_trans%(ID)s'%locals())
946 self
.addChunk('write = trans.response().write')
947 self
.addChunk('self._CHEETAH__isBuffering = _wasBuffering%(ID)s '%locals())
948 self
.addChunk('%(assignTo)s = _captureCollector%(ID)s.response().getvalue()'%locals())
949 self
.addChunk('del _orig_trans%(ID)s'%locals())
950 self
.addChunk('del _captureCollector%(ID)s'%locals())
951 self
.addChunk('del _wasBuffering%(ID)s'%locals())
953 def setErrorCatcher(self
, errorCatcherName
):
954 self
.turnErrorCatcherOn()
956 self
.addChunk('if self._CHEETAH__errorCatchers.has_key("' + errorCatcherName
+ '"):')
958 self
.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["' +
959 errorCatcherName
+ '"]')
961 self
.addChunk('else:')
963 self
.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["'
964 + errorCatcherName
+ '"] = ErrorCatchers.'
965 + errorCatcherName
+ '(self)'
969 def nextFilterRegionID(self
):
970 return self
.nextCacheID()
972 def setFilter(self
, theFilter
, isKlass
):
973 class FilterDetails
: pass
974 filterDetails
= FilterDetails()
975 filterDetails
.ID
= ID
= self
.nextFilterRegionID()
976 filterDetails
.theFilter
= theFilter
977 filterDetails
.isKlass
= isKlass
978 self
._filterRegionsStack
.append((ID
, filterDetails
)) # attrib of current methodCompiler
980 self
.addChunk('_orig_filter%(ID)s = _filter'%locals())
982 self
.addChunk('_filter = self._CHEETAH__currentFilter = ' + theFilter
.strip() +
985 if theFilter
.lower() == 'none':
986 self
.addChunk('_filter = self._CHEETAH__initialFilter')
988 # is string representing the name of a builtin filter
989 self
.addChunk('filterName = ' + repr(theFilter
))
990 self
.addChunk('if self._CHEETAH__filters.has_key("' + theFilter
+ '"):')
992 self
.addChunk('_filter = self._CHEETAH__currentFilter = self._CHEETAH__filters[filterName]')
994 self
.addChunk('else:')
996 self
.addChunk('_filter = self._CHEETAH__currentFilter'
997 +' = \\\n\t\t\tself._CHEETAH__filters[filterName] = '
998 + 'getattr(self._CHEETAH__filtersLib, filterName)(self).filter')
1001 def closeFilterBlock(self
):
1002 ID
, filterDetails
= self
._filterRegionsStack
.pop()
1003 #self.addChunk('_filter = self._CHEETAH__initialFilter')
1004 self
.addChunk('_filter = _orig_filter%(ID)s'%locals())
1006 class AutoMethodCompiler(MethodCompiler
):
1008 def _setupState(self
):
1009 MethodCompiler
._setupState
(self
)
1010 self
._argStringList
= [ ("self",None) ]
1011 self
._streamingEnabled
= True
1013 def _useKWsDictArgForPassingTrans(self
):
1014 alreadyHasTransArg
= [argname
for argname
,defval
in self
._argStringList
1015 if argname
=='trans']
1016 return (self
.methodName()!='respond'
1017 and not alreadyHasTransArg
1018 and self
.setting('useKWsDictArgForPassingTrans'))
1020 def cleanupState(self
):
1021 MethodCompiler
.cleanupState(self
)
1022 self
.commitStrConst()
1023 if self
._cacheRegionsStack
:
1024 self
.endCacheRegion()
1025 if self
._callRegionsStack
:
1026 self
.endCallRegion()
1028 if self
._streamingEnabled
:
1030 positionalArgsListName
= None
1031 for argname
,defval
in self
._argStringList
:
1032 if argname
.strip().startswith('**'):
1033 kwargsName
= argname
.strip().replace('**','')
1035 elif argname
.strip().startswith('*'):
1036 positionalArgsListName
= argname
.strip().replace('*','')
1038 if not kwargsName
and self
._useKWsDictArgForPassingTrans
():
1040 self
.addMethArg('**KWS', None)
1041 self
._kwargsName
= kwargsName
1043 if not self
._useKWsDictArgForPassingTrans
():
1044 if not kwargsName
and not positionalArgsListName
:
1045 self
.addMethArg('trans', 'None')
1047 self
._streamingEnabled
= False
1049 self
._indentLev
= self
.setting('initialMethIndentLevel')
1050 mainBodyChunks
= self
._methodBodyChunks
1051 self
._methodBodyChunks
= []
1052 self
._addAutoSetupCode
()
1053 self
._methodBodyChunks
.extend(mainBodyChunks
)
1054 self
._addAutoCleanupCode
()
1056 def _addAutoSetupCode(self
):
1057 if self
._initialMethodComment
:
1058 self
.addChunk(self
._initialMethodComment
)
1060 if self
._streamingEnabled
:
1061 if self
._useKWsDictArgForPassingTrans
() and self
._kwargsName
:
1062 self
.addChunk('trans = %s.get("trans")'%self
._kwargsName
)
1063 self
.addChunk('if (not trans and not self._CHEETAH__isBuffering'
1064 ' and not callable(self.transaction)):')
1066 self
.addChunk('trans = self.transaction'
1067 ' # is None unless self.awake() was called')
1069 self
.addChunk('if not trans:')
1071 self
.addChunk('trans = DummyTransaction()')
1072 if self
.setting('autoAssignDummyTransactionToSelf'):
1073 self
.addChunk('self.transaction = trans')
1074 self
.addChunk('_dummyTrans = True')
1076 self
.addChunk('else: _dummyTrans = False')
1078 self
.addChunk('trans = DummyTransaction()')
1079 self
.addChunk('_dummyTrans = True')
1080 self
.addChunk('write = trans.response().write')
1081 if self
.setting('useNameMapper'):
1082 argNames
= [arg
[0] for arg
in self
._argStringList
]
1083 allowSearchListAsMethArg
= self
.setting('allowSearchListAsMethArg')
1084 if allowSearchListAsMethArg
and 'SL' in argNames
:
1086 elif allowSearchListAsMethArg
and 'searchList' in argNames
:
1087 self
.addChunk('SL = searchList')
1089 self
.addChunk('SL = self._CHEETAH__searchList')
1090 if self
.setting('useFilters'):
1091 self
.addChunk('_filter = self._CHEETAH__currentFilter')
1093 self
.addChunk("#" *40)
1094 self
.addChunk('## START - generated method body')
1097 def _addAutoCleanupCode(self
):
1099 self
.addChunk("#" *40)
1100 self
.addChunk('## END - generated method body')
1103 if not self
._isGenerator
:
1107 def addStop(self
, expr
=None):
1108 self
.addChunk('return _dummyTrans and trans.response().getvalue() or ""')
1110 def addMethArg(self
, name
, defVal
=None):
1111 self
._argStringList
.append( (name
,defVal
) )
1113 def methodSignature(self
):
1114 argStringChunks
= []
1115 for arg
in self
._argStringList
:
1117 if not arg
[1] == None:
1118 chunk
+= '=' + arg
[1]
1119 argStringChunks
.append(chunk
)
1120 argString
= (', ').join(argStringChunks
)
1124 output
.append(self
._indent
+ self
._decorator
+'\n')
1125 output
.append(self
._indent
+ "def "
1126 + self
.methodName() + "(" +
1127 argString
+ "):\n\n")
1128 return ''.join(output
)
1131 ##################################################
1134 _initMethod_initCheetah
= """\
1135 if not self._CHEETAH__instanceInitialized:
1137 allowedKWs = 'searchList namespaces filter filtersLib errorCatcher'.split()
1138 for k,v in KWs.items():
1139 if k in allowedKWs: cheetahKWArgs[k] = v
1140 self._initCheetahInstance(**cheetahKWArgs)
1141 """.replace('\n','\n'+' '*8)
1143 class ClassCompiler(GenUtils
):
1144 methodCompilerClass
= AutoMethodCompiler
1145 methodCompilerClassForInit
= MethodCompiler
1147 def __init__(self
, className
, mainMethodName
='respond',
1148 moduleCompiler
=None,
1150 settingsManager
=None):
1152 self
._settingsManager
= settingsManager
1153 self
._fileName
= fileName
1154 self
._className
= className
1155 self
._moduleCompiler
= moduleCompiler
1156 self
._mainMethodName
= mainMethodName
1158 methodCompiler
= self
._spawnMethodCompiler
(
1160 initialMethodComment
='## CHEETAH: main method generated for this template')
1162 self
._setActiveMethodCompiler
(methodCompiler
)
1163 if fileName
and self
.setting('monitorSrcFile'):
1164 self
._addSourceFileMonitoring
(fileName
)
1166 def setting(self
, key
):
1167 return self
._settingsManager
.setting(key
)
1169 def __getattr__(self
, name
):
1170 """Provide access to the methods and attributes of the MethodCompiler
1171 at the top of the activeMethods stack: one-way namespace sharing
1174 WARNING: Use .setMethods to assign the attributes of the MethodCompiler
1175 from the methods of this class!!! or you will be assigning to attributes
1176 of this object instead."""
1178 if self
.__dict
__.has_key(name
):
1179 return self
.__dict
__[name
]
1180 elif hasattr(self
.__class
__, name
):
1181 return getattr(self
.__class
__, name
)
1182 elif self
._activeMethodsList
and hasattr(self
._activeMethodsList
[-1], name
):
1183 return getattr(self
._activeMethodsList
[-1], name
)
1185 raise AttributeError, name
1187 def _setupState(self
):
1188 self
._classDef
= None
1189 self
._decoratorForNextMethod
= None
1190 self
._activeMethodsList
= [] # stack while parsing/generating
1191 self
._finishedMethodsList
= [] # store by order
1192 self
._methodsIndex
= {} # store by name
1193 self
._baseClass
= 'Template'
1194 self
._classDocStringLines
= []
1195 # printed after methods in the gen class def:
1196 self
._generatedAttribs
= ['_CHEETAH__instanceInitialized = False']
1197 self
._generatedAttribs
.append('_CHEETAH_version = __CHEETAH_version__')
1198 self
._generatedAttribs
.append(
1199 '_CHEETAH_versionTuple = __CHEETAH_versionTuple__')
1200 self
._generatedAttribs
.append('_CHEETAH_genTime = __CHEETAH_genTime__')
1201 self
._generatedAttribs
.append('_CHEETAH_genTimestamp = __CHEETAH_genTimestamp__')
1202 self
._generatedAttribs
.append('_CHEETAH_src = __CHEETAH_src__')
1203 self
._generatedAttribs
.append(
1204 '_CHEETAH_srcLastModified = __CHEETAH_srcLastModified__')
1206 if self
.setting('templateMetaclass'):
1207 self
._generatedAttribs
.append('__metaclass__ = '+self
.setting('templateMetaclass'))
1208 self
._initMethChunks
= []
1209 self
._blockMetaData
= {}
1210 self
._errorCatcherCount
= 0
1211 self
._placeholderToErrorCatcherMap
= {}
1213 def cleanupState(self
):
1214 while self
._activeMethodsList
:
1215 methCompiler
= self
._popActiveMethodCompiler
()
1216 self
._swallowMethodCompiler
(methCompiler
)
1217 self
._setupInitMethod
()
1218 if self
._mainMethodName
== 'respond':
1219 if self
.setting('setup__str__method'):
1220 self
._generatedAttribs
.append('def __str__(self): return self.respond()')
1221 self
.addAttribute('_mainCheetahMethod_for_' + self
._className
+
1222 '= ' + repr(self
._mainMethodName
) )
1224 def _setupInitMethod(self
):
1225 __init__
= self
._spawnMethodCompiler
('__init__',
1226 klass
=self
.methodCompilerClassForInit
)
1227 __init__
.setMethodSignature("def __init__(self, *args, **KWs)")
1228 __init__
.addChunk("%s.__init__(self, *args, **KWs)" % self
._baseClass
)
1229 __init__
.addChunk(_initMethod_initCheetah
%{'className':self
._className
})
1230 for chunk
in self
._initMethChunks
:
1231 __init__
.addChunk(chunk
)
1232 __init__
.cleanupState()
1233 self
._swallowMethodCompiler
(__init__
, pos
=0)
1235 def _addSourceFileMonitoring(self
, fileName
):
1236 # @@TR: this stuff needs auditing for Cheetah 2.0
1237 # the first bit is added to init
1238 self
.addChunkToInit('self._filePath = ' + repr(fileName
))
1239 self
.addChunkToInit('self._fileMtime = ' + str(getmtime(fileName
)) )
1241 # the rest is added to the main output method of the class ('mainMethod')
1242 self
.addChunk('if exists(self._filePath) and ' +
1243 'getmtime(self._filePath) > self._fileMtime:')
1245 self
.addChunk('self._compile(file=self._filePath, moduleName='+className
+ ')')
1247 'write(getattr(self, self._mainCheetahMethod_for_' + self
._className
+
1252 def setClassName(self
, name
):
1253 self
._className
= name
1255 def className(self
):
1256 return self
._className
1258 def setBaseClass(self
, baseClassName
):
1259 self
._baseClass
= baseClassName
1261 def setMainMethodName(self
, methodName
):
1262 if methodName
== self
._mainMethodName
:
1264 ## change the name in the methodCompiler and add new reference
1265 mainMethod
= self
._methodsIndex
[self
._mainMethodName
]
1266 mainMethod
.setMethodName(methodName
)
1267 self
._methodsIndex
[methodName
] = mainMethod
1269 ## make sure that fileUpdate code still works properly:
1270 chunkToChange
= ('write(self.' + self
._mainMethodName
+ '(trans=trans))')
1271 chunks
= mainMethod
._methodBodyChunks
1272 if chunkToChange
in chunks
:
1273 for i
in range(len(chunks
)):
1274 if chunks
[i
] == chunkToChange
:
1275 chunks
[i
] = ('write(self.' + methodName
+ '(trans=trans))')
1276 ## get rid of the old reference and update self._mainMethodName
1277 del self
._methodsIndex
[self
._mainMethodName
]
1278 self
._mainMethodName
= methodName
1280 def setMainMethodArgs(self
, argsList
):
1281 mainMethodCompiler
= self
._methodsIndex
[self
._mainMethodName
]
1282 for argName
, defVal
in argsList
:
1283 mainMethodCompiler
.addMethArg(argName
, defVal
)
1286 def _spawnMethodCompiler(self
, methodName
, klass
=None,
1287 initialMethodComment
=None):
1289 klass
= self
.methodCompilerClass
1292 if self
._decoratorForNextMethod
:
1293 decorator
= self
._decoratorForNextMethod
1294 self
._decoratorForNextMethod
= None
1295 methodCompiler
= klass(methodName
, classCompiler
=self
,
1296 decorator
=decorator
,
1297 initialMethodComment
=initialMethodComment
)
1298 self
._methodsIndex
[methodName
] = methodCompiler
1299 return methodCompiler
1301 def _setActiveMethodCompiler(self
, methodCompiler
):
1302 self
._activeMethodsList
.append(methodCompiler
)
1304 def _getActiveMethodCompiler(self
):
1305 return self
._activeMethodsList
[-1]
1307 def _popActiveMethodCompiler(self
):
1308 return self
._activeMethodsList
.pop()
1310 def _swallowMethodCompiler(self
, methodCompiler
, pos
=None):
1311 methodCompiler
.cleanupState()
1313 self
._finishedMethodsList
.append( methodCompiler
)
1315 self
._finishedMethodsList
.insert(pos
, methodCompiler
)
1316 return methodCompiler
1318 def startMethodDef(self
, methodName
, argsList
, parserComment
):
1319 methodCompiler
= self
._spawnMethodCompiler
(
1320 methodName
, initialMethodComment
=parserComment
)
1321 self
._setActiveMethodCompiler
(methodCompiler
)
1322 for argName
, defVal
in argsList
:
1323 methodCompiler
.addMethArg(argName
, defVal
)
1325 def _finishedMethods(self
):
1326 return self
._finishedMethodsList
1328 def addDecorator(self
, decoratorExpr
):
1329 """Set the decorator to be used with the next method in the source.
1331 See _spawnMethodCompiler() and MethodCompiler for the details of how
1334 self
._decoratorForNextMethod
= decoratorExpr
1336 def addClassDocString(self
, line
):
1337 self
._classDocStringLines
.append( line
.replace('%','%%'))
1339 def addChunkToInit(self
,chunk
):
1340 self
._initMethChunks
.append(chunk
)
1342 def addAttribute(self
, attribExpr
):
1343 ## first test to make sure that the user hasn't used any fancy Cheetah syntax
1344 # (placeholders, directives, etc.) inside the expression
1345 if attribExpr
.find('VFN(') != -1 or attribExpr
.find('VFFSL(') != -1:
1346 raise ParseError(self
,
1347 'Invalid #attr directive.' +
1348 ' It should only contain simple Python literals.')
1349 ## now add the attribute
1350 self
._generatedAttribs
.append(attribExpr
)
1353 def addErrorCatcherCall(self
, codeChunk
, rawCode
='', lineCol
=''):
1354 if self
._placeholderToErrorCatcherMap
.has_key(rawCode
):
1355 methodName
= self
._placeholderToErrorCatcherMap
[rawCode
]
1356 if not self
.setting('outputRowColComments'):
1357 self
._methodsIndex
[methodName
].addMethDocString(
1358 'plus at line %s, col %s'%lineCol
)
1361 self
._errorCatcherCount
+= 1
1362 methodName
= '__errorCatcher' + str(self
._errorCatcherCount
)
1363 self
._placeholderToErrorCatcherMap
[rawCode
] = methodName
1365 catcherMeth
= self
._spawnMethodCompiler
(
1367 klass
=MethodCompiler
,
1368 initialMethodComment
=('## CHEETAH: Generated from ' + rawCode
+
1369 ' at line %s, col %s'%lineCol
+ '.')
1371 catcherMeth
.setMethodSignature('def ' + methodName
+
1372 '(self, localsDict={})')
1373 # is this use of localsDict right?
1374 catcherMeth
.addChunk('try:')
1375 catcherMeth
.indent()
1376 catcherMeth
.addChunk("return eval('''" + codeChunk
+
1377 "''', globals(), localsDict)")
1378 catcherMeth
.dedent()
1379 catcherMeth
.addChunk('except self._CHEETAH__errorCatcher.exceptions(), e:')
1380 catcherMeth
.indent()
1381 catcherMeth
.addChunk("return self._CHEETAH__errorCatcher.warn(exc_val=e, code= " +
1382 repr(codeChunk
) + " , rawCode= " +
1383 repr(rawCode
) + " , lineCol=" + str(lineCol
) +")")
1385 catcherMeth
.cleanupState()
1387 self
._swallowMethodCompiler
(catcherMeth
)
1391 self
.commitStrConst()
1392 methCompiler
= self
._popActiveMethodCompiler
()
1393 self
._swallowMethodCompiler
(methCompiler
)
1395 def closeBlock(self
):
1396 self
.commitStrConst()
1397 methCompiler
= self
._popActiveMethodCompiler
()
1398 methodName
= methCompiler
.methodName()
1399 if self
.setting('includeBlockMarkers'):
1400 endMarker
= self
.setting('blockMarkerEnd')
1401 methCompiler
.addStrConst(endMarker
[0] + methodName
+ endMarker
[1])
1402 self
._swallowMethodCompiler
(methCompiler
)
1404 #metaData = self._blockMetaData[methodName]
1405 #rawDirective = metaData['raw']
1406 #lineCol = metaData['lineCol']
1408 ## insert the code to call the block, caching if #cache directive is on
1409 codeChunk
= 'self.' + methodName
+ '(trans=trans)'
1410 self
.addChunk(codeChunk
)
1412 #self.appendToPrevChunk(' # generated from ' + repr(rawDirective) )
1413 #if self.setting('outputRowColComments'):
1414 # self.appendToPrevChunk(' at line %s, col %s' % lineCol + '.')
1417 ## code wrapping methods
1421 return self
._classDef
1423 return self
.wrapClassDef()
1427 def wrapClassDef(self
):
1428 ind
= self
.setting('indentationStep')
1429 classDefChunks
= [self
.classSignature(),
1430 self
.classDocstring(),
1433 classDefChunks
.extend([
1435 ind
+ '## CHEETAH GENERATED METHODS',
1439 def addAttributes():
1440 classDefChunks
.extend([
1442 ind
+ '## CHEETAH GENERATED ATTRIBUTES',
1446 if self
.setting('outputMethodsBeforeAttributes'):
1453 classDef
= '\n'.join(classDefChunks
)
1454 self
._classDef
= classDef
1458 def classSignature(self
):
1459 return "class %s(%s):" % (self
.className(), self
._baseClass
)
1461 def classDocstring(self
):
1462 if not self
._classDocStringLines
:
1464 ind
= self
.setting('indentationStep')
1465 docStr
= ('%(ind)s"""\n%(ind)s' +
1466 '\n%(ind)s'.join(self
._classDocStringLines
) +
1471 def methodDefs(self
):
1472 methodDefs
= [str(methGen
) for methGen
in self
._finishedMethods
() ]
1473 return '\n\n'.join(methodDefs
)
1475 def attributes(self
):
1476 attribs
= [self
.setting('indentationStep') + str(attrib
)
1477 for attrib
in self
._generatedAttribs
]
1478 return '\n\n'.join(attribs
)
1480 class AutoClassCompiler(ClassCompiler
):
1483 ##################################################
1486 class ModuleCompiler(SettingsManager
, GenUtils
):
1488 parserClass
= Parser
1489 classCompilerClass
= AutoClassCompiler
1491 def __init__(self
, source
=None, file=None,
1492 moduleName
='DynamicallyCompiledCheetahTemplate',
1493 mainClassName
=None, # string
1494 mainMethodName
=None, # string
1495 baseclassName
=None, # string
1496 extraImportStatements
=None, # list of strings
1497 settings
=None # dict
1499 SettingsManager
.__init
__(self
)
1501 self
.updateSettings(settings
)
1502 # disable useStackFrames if the C version of NameMapper isn't compiled
1503 # it's painfully slow in the Python version and bites Windows users all
1505 if not NameMapper
.C_VERSION
:
1506 if not sys
.platform
.startswith('java'):
1508 "\nYou don't have the C version of NameMapper installed! "
1509 "I'm disabling Cheetah's useStackFrames option as it is "
1510 "painfully slow with the Python version of NameMapper. "
1511 "You should get a copy of Cheetah with the compiled C version of NameMapper."
1513 self
.setSetting('useStackFrames', False)
1515 self
._compiled
= False
1516 self
._moduleName
= moduleName
1517 if not mainClassName
:
1518 self
._mainClassName
= moduleName
1520 self
._mainClassName
= mainClassName
1521 self
._mainMethodNameArg
= mainMethodName
1523 self
.setSetting('mainMethodName', mainMethodName
)
1524 self
._baseclassName
= baseclassName
1526 self
._filePath
= None
1527 self
._fileMtime
= None
1530 raise TypeError("Cannot compile from a source string AND file.")
1531 elif isinstance(file, types
.StringType
) or isinstance(file, types
.UnicodeType
): # it's a filename.
1533 f
= open(file) # Raises IOError.
1536 self
._filePath
= file
1537 self
._fileMtime
= os
.path
.getmtime(file)
1538 elif hasattr(file, 'read'):
1539 source
= file.read() # Can't set filename or mtime--they're not accessible.
1541 raise TypeError("'file' argument must be a filename string or file-like object")
1545 self
._fileDirName
, self
._fileBaseName
= os
.path
.split(self
._filePath
)
1546 self
._fileBaseNameRoot
, self
._fileBaseNameExt
= \
1547 os
.path
.splitext(self
._fileBaseName
)
1549 if not (isinstance(source
, str) or isinstance(source
, unicode)):
1550 source
= str( source
)
1551 # by converting to string here we allow objects such as other Templates
1554 # Handle the #indent directive by converting it to other directives.
1555 # (Over the long term we'll make it a real directive.)
1557 warnings
.warn("You supplied an empty string for the source!", )
1559 if source
.find('#indent') != -1: #@@TR: undocumented hack
1560 source
= indentize(source
)
1562 self
._parser
= self
.parserClass(source
, filename
=self
._filePath
, compiler
=self
)
1563 self
._setupCompilerState
()
1565 def __getattr__(self
, name
):
1566 """Provide one-way access to the methods and attributes of the
1567 ClassCompiler, and thereby the MethodCompilers as well.
1569 WARNING: Use .setMethods to assign the attributes of the ClassCompiler
1570 from the methods of this class!!! or you will be assigning to attributes
1571 of this object instead.
1573 if self
.__dict
__.has_key(name
):
1574 return self
.__dict
__[name
]
1575 elif hasattr(self
.__class
__, name
):
1576 return getattr(self
.__class
__, name
)
1577 elif self
._activeClassesList
and hasattr(self
._activeClassesList
[-1], name
):
1578 return getattr(self
._activeClassesList
[-1], name
)
1580 raise AttributeError, name
1582 def _initializeSettings(self
):
1583 self
.updateSettings(copy
.deepcopy(DEFAULT_COMPILER_SETTINGS
))
1585 def _setupCompilerState(self
):
1586 self
._activeClassesList
= []
1587 self
._finishedClassesList
= [] # listed by ordered
1588 self
._finishedClassIndex
= {} # listed by name
1589 self
._moduleDef
= None
1590 self
._moduleShBang
= '#!/usr/bin/env python'
1591 self
._moduleEncoding
= 'ascii'
1592 self
._moduleEncodingStr
= ''
1593 self
._moduleHeaderLines
= []
1594 self
._moduleDocStringLines
= []
1595 self
._specialVars
= {}
1596 self
._importStatements
= [
1600 "from os.path import getmtime, exists",
1603 "import __builtin__",
1604 "from Cheetah.Version import MinCompatibleVersion as RequiredCheetahVersion",
1605 "from Cheetah.Version import MinCompatibleVersionTuple as RequiredCheetahVersionTuple",
1606 "from Cheetah.Template import Template",
1607 "from Cheetah.DummyTransaction import DummyTransaction",
1608 "from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList",
1609 "from Cheetah.CacheRegion import CacheRegion",
1610 "import Cheetah.Filters as Filters",
1611 "import Cheetah.ErrorCatchers as ErrorCatchers",
1614 self
._importedVarNames
= ['sys',
1627 self
._moduleConstants
= [
1630 "except NameError:",
1631 " True, False = (1==1), (1==0)",
1632 "VFFSL=valueFromFrameOrSearchList",
1633 "VFSL=valueFromSearchList",
1635 "currentTime=time.time",
1639 classCompiler
= self
._spawnClassCompiler
(self
._mainClassName
)
1640 if self
._baseclassName
:
1641 classCompiler
.setBaseClass(self
._baseclassName
)
1642 self
._addActiveClassCompiler
(classCompiler
)
1643 self
._parser
.parse()
1644 self
._swallowClassCompiler
(self
._popActiveClassCompiler
())
1645 self
._compiled
= True
1646 self
._parser
.cleanup()
1648 def _spawnClassCompiler(self
, className
, klass
=None):
1650 klass
= self
.classCompilerClass
1651 classCompiler
= klass(className
,
1652 moduleCompiler
=self
,
1653 mainMethodName
=self
.setting('mainMethodName'),
1654 fileName
=self
._filePath
,
1655 settingsManager
=self
,
1657 return classCompiler
1659 def _addActiveClassCompiler(self
, classCompiler
):
1660 self
._activeClassesList
.append(classCompiler
)
1662 def _getActiveClassCompiler(self
):
1663 return self
._activeClassesList
[-1]
1665 def _popActiveClassCompiler(self
):
1666 return self
._activeClassesList
.pop()
1668 def _swallowClassCompiler(self
, classCompiler
):
1669 classCompiler
.cleanupState()
1670 self
._finishedClassesList
.append( classCompiler
)
1671 self
._finishedClassIndex
[classCompiler
.className()] = classCompiler
1672 return classCompiler
1674 def _finishedClasses(self
):
1675 return self
._finishedClassesList
1677 def importedVarNames(self
):
1678 return self
._importedVarNames
1680 def addImportedVarNames(self
, varNames
):
1681 self
._importedVarNames
.extend(varNames
)
1683 ## methods for adding stuff to the module and class definitions
1685 def setBaseClass(self
, baseClassName
):
1686 if self
._mainMethodNameArg
:
1687 self
.setMainMethodName(self
._mainMethodNameArg
)
1689 self
.setMainMethodName(self
.setting('mainMethodNameForSubclasses'))
1691 if self
.setting('handlerForExtendsDirective'):
1692 handler
= self
.setting('handlerForExtendsDirective')
1693 baseClassName
= handler(compiler
=self
, baseClassName
=baseClassName
)
1694 self
._getActiveClassCompiler
().setBaseClass(baseClassName
)
1695 elif (not self
.setting('autoImportForExtendsDirective')
1696 or baseClassName
=='object' or baseClassName
in self
.importedVarNames()):
1697 self
._getActiveClassCompiler
().setBaseClass(baseClassName
)
1700 ##################################################
1701 ## If the #extends directive contains a classname or modulename that isn't
1702 # in self.importedVarNames() already, we assume that we need to add
1703 # an implied 'from ModName import ClassName' where ModName == ClassName.
1704 # - This is the case in WebKit servlet modules.
1705 # - We also assume that the final . separates the classname from the
1706 # module name. This might break if people do something really fancy
1707 # with their dots and namespaces.
1708 chunks
= baseClassName
.split('.')
1710 self
._getActiveClassCompiler
().setBaseClass(baseClassName
)
1711 if baseClassName
not in self
.importedVarNames():
1712 modName
= baseClassName
1713 # we assume the class name to be the module name
1714 # and that it's not a builtin:
1715 importStatement
= "from %s import %s" % (modName
, baseClassName
)
1716 self
.addImportStatement(importStatement
)
1717 self
.addImportedVarNames( [baseClassName
,] )
1719 needToAddImport
= True
1721 #print chunks, ':', self.importedVarNames()
1722 for chunk
in chunks
[1:-1]:
1723 if modName
in self
.importedVarNames():
1724 needToAddImport
= False
1725 finalBaseClassName
= baseClassName
.replace(modName
+'.', '')
1726 self
._getActiveClassCompiler
().setBaseClass(finalBaseClassName
)
1729 modName
+= '.'+chunk
1731 modName
, finalClassName
= '.'.join(chunks
[:-1]), chunks
[-1]
1732 #if finalClassName != chunks[:-1][-1]:
1733 if finalClassName
!= chunks
[-2]:
1734 # we assume the class name to be the module name
1735 modName
= '.'.join(chunks
)
1736 self
._getActiveClassCompiler
().setBaseClass(finalClassName
)
1737 importStatement
= "from %s import %s" % (modName
, finalClassName
)
1738 self
.addImportStatement(importStatement
)
1739 self
.addImportedVarNames( [finalClassName
,] )
1741 def setCompilerSetting(self
, key
, valueExpr
):
1742 self
.setSetting(key
, eval(valueExpr
) )
1743 self
._parser
.configureParser()
1745 def setCompilerSettings(self
, keywords
, settingsStr
):
1748 if 'nomerge' in KWs
:
1752 # @@TR: this is actually caught by the parser at the moment.
1753 # subject to change in the future
1754 self
._initializeSettings
()
1755 self
._parser
.configureParser()
1757 elif 'python' in KWs
:
1758 settingsReader
= self
.updateSettingsFromPySrcStr
1759 # this comes from SettingsManager
1761 # this comes from SettingsManager
1762 settingsReader
= self
.updateSettingsFromConfigStr
1764 settingsReader(settingsStr
)
1765 self
._parser
.configureParser()
1767 def setShBang(self
, shBang
):
1768 self
._moduleShBang
= shBang
1770 def setModuleEncoding(self
, encoding
):
1771 self
._moduleEncoding
= encoding
1772 self
._moduleEncodingStr
= '# -*- coding: %s -*-' %encoding
1774 def getModuleEncoding(self
):
1775 return self
._moduleEncoding
1777 def addModuleHeader(self
, line
):
1778 """Adds a header comment to the top of the generated module.
1780 self
._moduleHeaderLines
.append(line
)
1782 def addModuleDocString(self
, line
):
1783 """Adds a line to the generated module docstring.
1785 self
._moduleDocStringLines
.append(line
)
1787 def addModuleGlobal(self
, line
):
1788 """Adds a line of global module code. It is inserted after the import
1789 statements and Cheetah default module constants.
1791 self
._moduleConstants
.append(line
)
1793 def addSpecialVar(self
, basename
, contents
, includeUnderscores
=True):
1794 """Adds module __specialConstant__ to the module globals.
1796 name
= includeUnderscores
and '__'+basename
+'__' or basename
1797 self
._specialVars
[name
] = contents
.strip()
1799 def addImportStatement(self
, impStatement
):
1800 self
._importStatements
.append(impStatement
)
1802 #@@TR 2005-01-01: there's almost certainly a cleaner way to do this!
1803 importVarNames
= impStatement
[impStatement
.find('import') + len('import'):].split(',')
1804 importVarNames
= [var
.split()[-1] for var
in importVarNames
] # handles aliases
1805 importVarNames
= [var
for var
in importVarNames
if var
!='*']
1806 self
.addImportedVarNames(importVarNames
) #used by #extend for auto-imports
1808 def addAttribute(self
, attribName
, expr
):
1809 self
._getActiveClassCompiler
().addAttribute(attribName
+ ' =' + expr
)
1811 def addComment(self
, comm
):
1812 if re
.match(r
'#+$',comm
): # skip bar comments
1815 specialVarMatch
= specialVarRE
.match(comm
)
1817 # @@TR: this is a bit hackish and is being replaced with
1818 # #set module varName = ...
1819 return self
.addSpecialVar(specialVarMatch
.group(1),
1820 comm
[specialVarMatch
.end():])
1821 elif comm
.startswith('doc:'):
1822 addLine
= self
.addMethDocString
1823 comm
= comm
[len('doc:'):].strip()
1824 elif comm
.startswith('doc-method:'):
1825 addLine
= self
.addMethDocString
1826 comm
= comm
[len('doc-method:'):].strip()
1827 elif comm
.startswith('doc-module:'):
1828 addLine
= self
.addModuleDocString
1829 comm
= comm
[len('doc-module:'):].strip()
1830 elif comm
.startswith('doc-class:'):
1831 addLine
= self
.addClassDocString
1832 comm
= comm
[len('doc-class:'):].strip()
1833 elif comm
.startswith('header:'):
1834 addLine
= self
.addModuleHeader
1835 comm
= comm
[len('header:'):].strip()
1837 addLine
= self
.addMethComment
1839 for line
in comm
.splitlines():
1842 ## methods for module code wrapping
1844 def getModuleCode(self
):
1845 if not self
._compiled
:
1848 return self
._moduleDef
1850 return self
.wrapModuleDef()
1852 __str__
= getModuleCode
1854 def wrapModuleDef(self
):
1855 self
.addSpecialVar('CHEETAH_docstring', self
.setting('defDocStrMsg'))
1856 self
.addModuleGlobal('__CHEETAH_version__ = %r'%Version
)
1857 self
.addModuleGlobal('__CHEETAH_versionTuple__ = %r'%(VersionTuple
,))
1858 self
.addModuleGlobal('__CHEETAH_genTime__ = %r'%time
.time())
1859 self
.addModuleGlobal('__CHEETAH_genTimestamp__ = %r'%self
.timestamp())
1861 timestamp
= self
.timestamp(self
._fileMtime
)
1862 self
.addModuleGlobal('__CHEETAH_src__ = %r'%self
._filePath
)
1863 self
.addModuleGlobal('__CHEETAH_srcLastModified__ = %r'%timestamp
)
1865 self
.addModuleGlobal('__CHEETAH_src__ = None')
1866 self
.addModuleGlobal('__CHEETAH_srcLastModified__ = None')
1868 moduleDef
= """%(header)s
1871 ##################################################
1875 ##################################################
1880 if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple:
1881 raise AssertionError(
1882 'This template was compiled with Cheetah version'
1883 ' %%s. Templates compiled before version %%s must be recompiled.'%%(
1884 __CHEETAH_version__, RequiredCheetahVersion))
1886 ##################################################
1891 ## END CLASS DEFINITION
1893 if not hasattr(%(mainClassName)s, '_initCheetahAttributes'):
1894 templateAPIClass = getattr(%(mainClassName)s, '_CHEETAH_templateClass', Template)
1895 templateAPIClass._addCheetahPlumbingCodeToClass(%(mainClassName)s)
1898 """ % {'header':self
.moduleHeader(),
1899 'docstring':self
.moduleDocstring(),
1900 'specialVars':self
.specialVars(),
1901 'imports':self
.importStatements(),
1902 'constants':self
.moduleConstants(),
1903 'classes':self
.classDefs(),
1904 'footer':self
.moduleFooter(),
1905 'mainClassName':self
._mainClassName
,
1908 self
._moduleDef
= moduleDef
1911 def timestamp(self
, theTime
=None):
1913 theTime
= time
.time()
1914 return time
.asctime(time
.localtime(theTime
))
1916 def moduleHeader(self
):
1917 header
= self
._moduleShBang
+ '\n'
1918 header
+= self
._moduleEncodingStr
+ '\n'
1919 if self
._moduleHeaderLines
:
1920 offSet
= self
.setting('commentOffset')
1924 ('\n#'+ ' '*offSet
).join(self
._moduleHeaderLines
) + '\n')
1928 def moduleDocstring(self
):
1929 if not self
._moduleDocStringLines
:
1933 '\n'.join(self
._moduleDocStringLines
) +
1936 def specialVars(self
):
1938 theVars
= self
._specialVars
1939 keys
= theVars
.keys()
1942 chunks
.append(key
+ ' = ' + repr(theVars
[key
]) )
1943 return '\n'.join(chunks
)
1945 def importStatements(self
):
1946 return '\n'.join(self
._importStatements
)
1948 def moduleConstants(self
):
1949 return '\n'.join(self
._moduleConstants
)
1951 def classDefs(self
):
1952 classDefs
= [str(klass
) for klass
in self
._finishedClasses
() ]
1953 return '\n\n'.join(classDefs
)
1955 def moduleFooter(self
):
1957 # CHEETAH was developed by Tavis Rudd and Mike Orr
1958 # with code, advice and input from many other volunteers.
1959 # For more information visit http://www.CheetahTemplate.org/
1961 ##################################################
1962 ## if run from command line:
1963 if __name__ == '__main__':
1964 from Cheetah.TemplateCmdLineIface import CmdLineIface
1965 CmdLineIface(templateObj=%(className)s()).run()
1967 """ % {'className':self
._mainClassName
}
1970 ##################################################
1971 ## Make Compiler an alias for ModuleCompiler
1973 Compiler
= ModuleCompiler