Merge commit 'wmcbrine/master'
[pyTivo/TheBayer.git] / Cheetah / Compiler.py
blobe3ffb167b8cad455155f6b533479795fcb675f7a
1 #!/usr/bin/env python
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'
5 ClassCompiler
6 MethodCompiler
8 If you are trying to grok this code start with ModuleCompiler.__init__,
9 ModuleCompiler.compile, and ModuleCompiler.__getattr__.
11 Meta-Data
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 $
17 """
18 __author__ = "Tavis Rudd <tavis@damnsimple.com>"
19 __revision__ = "$Revision: 1.156 $"[11:-2]
21 import sys
22 import os
23 import os.path
24 from os.path import getmtime, exists
25 import re
26 import types
27 import time
28 import random
29 import warnings
30 import __builtin__
31 import copy
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
45 VFN=valueForName
46 currentTime=time.time
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
72 'commentOffset': 1,
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':'<%',
130 'PSPEndToken':'%>',
131 'EOLSlurpToken':'#',
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':{}
148 class GenUtils:
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
152 parsing themselves.
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
169 return interval
171 def genCacheInfo(self, cacheTokenParts):
172 """Decipher a placeholder cachetoken
174 cacheInfo = {}
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:
185 if val[0] in '"\'':
186 val = val[1:-1]
188 if key == 'timer':
189 key = 'interval'
190 val = self.genTimeInterval(val)
192 cacheInfo[key] = val
193 return cacheInfo
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)
200 else:
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:")
214 self.indent()
215 self.addChunk(self.genPlainVar(nameChunks[:]))
216 self.dedent()
218 def genPlainVar(self, nameChunks):
219 """Generate Python code for a Cheetah $var without using NameMapper
220 (Unified Dotted Notation with the SearchList).
222 nameChunks.reverse()
223 chunk = nameChunks.pop()
224 pythonCode = chunk[0] + chunk[2]
225 while nameChunks:
226 chunk = nameChunks.pop()
227 pythonCode = (pythonCode + '.' + chunk[0] + chunk[2])
228 return pythonCode
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),
237 where:
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.
246 EXAMPLE
247 ------------------------------------------------------------------------
248 if the raw Cheetah Var is
249 $a.b.c[1].d().x.y.z
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]
261 where:
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]')
269 B = ('d',False,'()')
270 C = ('x.y.z',True,'')
272 C` = VFN( VFN( VFFSL(SL, 'a.b.c',True)[1],
273 'd',False)(),
274 'x.y.z',True)
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)
282 then
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')
290 nameChunks.reverse()
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 +
298 ',"' + afterDot +
299 '",' + repr(defaultUseAC and useAC) + ')'
300 + remainder)
301 else:
302 pythonCode = name+remainder
303 elif self.setting('useStackFrames'):
304 pythonCode = ('VFFSL(SL,'
305 '"'+ name + '",'
306 + repr(defaultUseAC and useAC) + ')'
307 + remainder)
308 else:
309 pythonCode = ('VFSL([locals()]+SL+[globals(), __builtin__],'
310 '"'+ name + '",'
311 + repr(defaultUseAC and useAC) + ')'
312 + remainder)
314 while nameChunks:
315 name, useAC, remainder = nameChunks.pop()
316 pythonCode = ('VFN(' + pythonCode +
317 ',"' + name +
318 '",' + repr(defaultUseAC and useAC) + ')'
319 + remainder)
320 return pythonCode
322 ##################################################
323 ## METHOD COMPILERS
325 class MethodCompiler(GenUtils):
326 def __init__(self, methodName, classCompiler,
327 initialMethodComment=None,
328 decorator=None):
329 self._settingsManager = classCompiler
330 self._classCompiler = classCompiler
331 self._moduleCompiler = classCompiler._moduleCompiler
332 self._methodName = methodName
333 self._initialMethodComment = initialMethodComment
334 self._setupState()
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
363 pass
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
376 def indent(self):
377 self._indentLev +=1
379 def dedent(self):
380 if self._indentLev:
381 self._indentLev -=1
382 else:
383 raise Error('Attempt to dedent when the indentLev is 0')
385 ## methods for final code wrapping
387 def methodDef(self):
388 if self._methodDef:
389 return self._methodDef
390 else:
391 return self.wrapCode()
393 __str__ = methodDef
394 __unicode__ = methodDef
396 def wrapCode(self):
397 self.commitStrConst()
398 methodDefChunks = (
399 self.methodSignature(),
400 '\n',
401 self.docString(),
402 self.methodBody() )
403 methodDef = ''.join(methodDefChunks)
404 self._methodDef = methodDef
405 return 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 )
416 def docString(self):
417 if not self._docStringLines:
418 return ''
420 ind = self._indent*2
421 docStr = (ind + '"""\n' + ind +
422 ('\n' + ind).join([ln.replace('"""',"'''") for ln in self._docStringLines]) +
423 '\n' + ind + '"""\n')
424 return docStr
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:
443 filterArgs = ''
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))
450 if lineCol:
451 self.appendToPrevChunk(' on line %s, col %s'%lineCol)
452 else:
453 self.addChunk("_v = %s"%chunk)
455 if self.setting('useFilters'):
456 self.addChunk("if _v is not None: write(_filter(_v%s))"%filterArgs)
457 else:
458 self.addChunk("if _v is not None: write(str(_v))")
459 else:
460 if self.setting('useFilters'):
461 self.addChunk("write(_filter(%s%s))"%(chunk,filterArgs))
462 else:
463 self.addChunk("write(str(%s))"%chunk)
465 def _appendToPrevStrConst(self, strConst):
466 if self._pendingStrConstChunks:
467 self._pendingStrConstChunks.append(strConst)
468 else:
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 = []
493 if not strConst:
494 return
495 else:
496 reprstr = repr(strConst).replace('\\012','\n')
497 i = 0
498 out = []
499 if reprstr.startswith('u'):
500 i = 1
501 out = ['u']
502 body = escapedNewlineRE.sub('\n', reprstr[i+1:-1])
504 if reprstr[i]=="'":
505 out.append("'''")
506 out.append(body)
507 out.append("'''")
508 else:
509 out.append('"""')
510 out.append(body)
511 out.append('"""')
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)
520 if BOL < len(src):
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,
547 silentMode=False):
548 cacheInfo = self.genCacheInfo(cacheTokenParts)
549 if cacheInfo:
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())'
558 if silentMode:
559 self.addChunk('try:')
560 self.indent()
561 self.addFilteredChunk(expr, filterArgs, rawPlaceholder, lineCol=lineCol)
562 self.dedent()
563 self.addChunk('except NotFound: pass')
564 else:
565 self.addFilteredChunk(expr, filterArgs, rawPlaceholder, lineCol=lineCol)
567 if self.setting('outputRowColComments'):
568 self.appendToPrevChunk(' # from line %s, col %s' % lineCol + '.')
569 if cacheInfo:
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,
581 exprComponents.OP,
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:
587 splitPos = splitPos1
588 elif splitPos1 > 0 and splitPos1 < max(splitPos2,0):
589 splitPos = splitPos1
590 else:
591 splitPos = splitPos2
593 if splitPos >0:
594 primary = LVALUE[:splitPos]
595 secondary = LVALUE[splitPos:]
596 else:
597 primary = LVALUE
598 secondary = ''
599 LVALUE = 'self._CHEETAH__globalSetVars["' + primary + '"]' + secondary
600 expr = LVALUE + ' ' + OP + ' ' + RVALUE.strip()
602 if setStyle is SET_MODULE:
603 self._moduleCompiler.addModuleGlobal(expr)
604 else:
605 self.addChunk(expr)
607 def addInclude(self, sourceExpr, includeFrom, isRaw):
608 self.addChunk('self._handleCheetahInclude(' + sourceExpr +
609 ', trans=trans, ' +
610 'includeFrom="' + includeFrom + '", raw=' +
611 repr(isRaw) + ')')
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] == ':':
626 expr = expr + ':'
627 self.addChunk( expr )
628 if lineCol:
629 self.appendToPrevChunk(' # generated from line %s, col %s'%lineCol )
630 self.indent()
632 def addReIndentingDirective(self, expr, dedent=True, lineCol=None):
633 self.commitStrConst()
634 if dedent:
635 self.dedent()
636 if not expr[-1] == ':':
637 expr = expr + ':'
639 self.addChunk( expr )
640 if lineCol:
641 self.appendToPrevChunk(' # generated from line %s, col %s'%lineCol )
642 self.indent()
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)
660 self.dedent()
661 self.addIndentingDirective('else')
662 self.addFilteredChunk(falseExpr)
663 self.dedent()
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):
676 argStringChunks = []
677 for arg in argsList:
678 chunk = arg[0]
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
697 self.addChunk(expr)
698 self._hasReturnStatement = True
700 def addYield(self, expr):
701 assert not self._hasReturnStatement
702 self._isGenerator = True
703 if expr.replace('yield','').strip():
704 self.addChunk(expr)
705 else:
706 self.addChunk('if _dummyTrans:')
707 self.indent()
708 self.addChunk('yield trans.response().getvalue()')
709 self.addChunk('trans = DummyTransaction()')
710 self.addChunk('write = trans.response().write')
711 self.dedent()
712 self.addChunk('else:')
713 self.indent()
714 self.addChunk(
715 'raise TypeError("This method cannot be called with a trans arg")')
716 self.dedent()
719 def addPass(self, expr):
720 self.addChunk(expr)
722 def addDel(self, expr):
723 self.addChunk(expr)
725 def addAssert(self, expr):
726 self.addChunk(expr)
728 def addRaise(self, expr):
729 self.addChunk(expr)
731 def addBreak(self, expr):
732 self.addChunk(expr)
734 def addContinue(self, expr):
735 self.addChunk(expr)
737 def addPSP(self, PSP):
738 self.commitStrConst()
739 autoIndent = False
740 if PSP[0] == '=':
741 PSP = PSP[1:]
742 if PSP:
743 self.addWriteChunk('_filter(' + PSP + ')')
744 return
746 elif PSP.lower() == 'end':
747 self.dedent()
748 return
749 elif PSP[-1] == '$':
750 autoIndent = True
751 PSP = PSP[:-1]
752 elif PSP[-1] == ':':
753 autoIndent = True
755 for line in PSP.splitlines():
756 self.addChunk(line)
758 if autoIndent:
759 self.indent()
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)
773 if customID:
774 ID = customID
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
779 self.addChunk('')
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()
786 + repr(ID)
787 + ', cacheInfo=%r'%cacheInfo
788 + ')')
789 self.addChunk('if _cacheRegion_%(ID)s.isNew():'%locals())
790 self.indent()
791 self.addChunk('_RECACHE_%(ID)s = True'%locals())
792 self.dedent()
794 self.addChunk('_cacheItem_%(ID)s = _cacheRegion_%(ID)s.getCacheItem('%locals()
795 +varyBy+')')
797 self.addChunk('if _cacheItem_%(ID)s.hasExpired():'%locals())
798 self.indent()
799 self.addChunk('_RECACHE_%(ID)s = True'%locals())
800 self.dedent()
802 if test:
803 self.addChunk('if ' + test + ':')
804 self.indent()
805 self.addChunk('_RECACHE_%(ID)s = True'%locals())
806 self.dedent()
808 self.addChunk('if (not _RECACHE_%(ID)s) and _cacheItem_%(ID)s.getRefreshTime():'%locals())
809 self.indent()
810 #self.addChunk('print "DEBUG"+"-"*50')
811 self.addChunk('try:')
812 self.indent()
813 self.addChunk('_output = _cacheItem_%(ID)s.renderOutput()'%locals())
814 self.dedent()
815 self.addChunk('except KeyError:')
816 self.indent()
817 self.addChunk('_RECACHE_%(ID)s = True'%locals())
818 #self.addChunk('print "DEBUG"+"*"*50')
819 self.dedent()
820 self.addChunk('else:')
821 self.indent()
822 self.addWriteChunk('_output')
823 self.addChunk('del _output')
824 self.dedent()
826 self.dedent()
828 self.addChunk('if _RECACHE_%(ID)s or not _cacheItem_%(ID)s.getRefreshTime():'%locals())
829 self.indent()
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())
833 if interval:
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())
847 self.dedent()
848 self.addChunk('## END CACHE REGION: '+ID)
849 self.addChunk('')
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()
866 +' of '+functionName
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:
877 self._endCallArg()
878 else:
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)
898 def reset(ID=ID):
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:
906 reset()
907 self.addChunk('_callArgVal%(ID)s = _callCollector%(ID)s.response().getvalue()'%locals())
908 self.addChunk('del _callCollector%(ID)s'%locals())
909 if initialKwArgs:
910 initialKwArgs = ', '+initialKwArgs
911 self.addFilteredChunk('%(functionName)s(_callArgVal%(ID)s%(initialKwArgs)s)'%locals())
912 self.addChunk('del _callArgVal%(ID)s'%locals())
913 else:
914 if initialKwArgs:
915 initialKwArgs = initialKwArgs+', '
916 self._endCallArg()
917 reset()
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()
922 +' of '+functionName
923 +' at line %s, col %s'%lineCol + ' in the source.')
924 self.addChunk('')
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
939 +' '+assignTo
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 + '"):')
962 self.indent()
963 self.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["' +
964 errorCatcherName + '"]')
965 self.dedent()
966 self.addChunk('else:')
967 self.indent()
968 self.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["'
969 + errorCatcherName + '"] = ErrorCatchers.'
970 + errorCatcherName + '(self)'
972 self.dedent()
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())
986 if isKlass:
987 self.addChunk('_filter = self._CHEETAH__currentFilter = ' + theFilter.strip() +
988 '(self).filter')
989 else:
990 if theFilter.lower() == 'none':
991 self.addChunk('_filter = self._CHEETAH__initialFilter')
992 else:
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 + '"):')
996 self.indent()
997 self.addChunk('_filter = self._CHEETAH__currentFilter = self._CHEETAH__filters[filterName]')
998 self.dedent()
999 self.addChunk('else:')
1000 self.indent()
1001 self.addChunk('_filter = self._CHEETAH__currentFilter'
1002 +' = \\\n\t\t\tself._CHEETAH__filters[filterName] = '
1003 + 'getattr(self._CHEETAH__filtersLib, filterName)(self).filter')
1004 self.dedent()
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:
1035 kwargsName = None
1036 positionalArgsListName = None
1037 for argname,defval in self._argStringList:
1038 if argname.strip().startswith('**'):
1039 kwargsName = argname.strip().replace('**','')
1040 break
1041 elif argname.strip().startswith('*'):
1042 positionalArgsListName = argname.strip().replace('*','')
1044 if not kwargsName and self._useKWsDictArgForPassingTrans():
1045 kwargsName = 'KWS'
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')
1052 else:
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)):')
1071 self.indent()
1072 self.addChunk('trans = self.transaction'
1073 ' # is None unless self.awake() was called')
1074 self.dedent()
1075 self.addChunk('if not trans:')
1076 self.indent()
1077 self.addChunk('trans = DummyTransaction()')
1078 if self.setting('autoAssignDummyTransactionToSelf'):
1079 self.addChunk('self.transaction = trans')
1080 self.addChunk('_dummyTrans = True')
1081 self.dedent()
1082 self.addChunk('else: _dummyTrans = False')
1083 else:
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:
1091 pass
1092 elif allowSearchListAsMethArg and 'searchList' in argNames:
1093 self.addChunk('SL = searchList')
1094 else:
1095 self.addChunk('SL = self._CHEETAH__searchList')
1096 if self.setting('useFilters'):
1097 self.addChunk('_filter = self._CHEETAH__currentFilter')
1098 self.addChunk('')
1099 self.addChunk("#" *40)
1100 self.addChunk('## START - generated method body')
1101 self.addChunk('')
1103 def _addAutoCleanupCode(self):
1104 self.addChunk('')
1105 self.addChunk("#" *40)
1106 self.addChunk('## END - generated method body')
1107 self.addChunk('')
1109 if not self._isGenerator:
1110 self.addStop()
1111 self.addChunk('')
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:
1122 chunk = arg[0]
1123 if not arg[1] == None:
1124 chunk += '=' + arg[1]
1125 argStringChunks.append(chunk)
1126 argString = (', ').join(argStringChunks)
1128 output = []
1129 if self._decorator:
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 ##################################################
1138 ## CLASS COMPILERS
1140 _initMethod_initCheetah = """\
1141 if not self._CHEETAH__instanceInitialized:
1142 cheetahKWArgs = {}
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,
1155 fileName=None,
1156 settingsManager=None):
1158 self._settingsManager = settingsManager
1159 self._fileName = fileName
1160 self._className = className
1161 self._moduleCompiler = moduleCompiler
1162 self._mainMethodName = mainMethodName
1163 self._setupState()
1164 methodCompiler = self._spawnMethodCompiler(
1165 mainMethodName,
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)
1190 else:
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:')
1253 self.indent()
1254 self.addChunk('self._compile(file=self._filePath, moduleName='+self._className + ')')
1255 self.addChunk(
1256 'write(getattr(self, self._mainCheetahMethod_for_' + self._className +
1257 ')(trans=trans))')
1258 self.addStop()
1259 self.dedent()
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:
1272 return
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):
1297 if klass is None:
1298 klass = self.methodCompilerClass
1300 decorator = None
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()
1321 if pos==None:
1322 self._finishedMethodsList.append( methodCompiler )
1323 else:
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
1341 this is used.
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:
1367 chunk = arg[0]
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)
1382 return methodName
1384 self._errorCatcherCount += 1
1385 methodName = '__errorCatcher' + str(self._errorCatcherCount)
1386 self._placeholderToErrorCatcherMap[rawCode] = methodName
1388 catcherMeth = self._spawnMethodCompiler(
1389 methodName,
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)
1411 return methodName
1413 def closeDef(self):
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
1442 def classDef(self):
1443 if self._classDef:
1444 return self._classDef
1445 else:
1446 return self.wrapClassDef()
1448 __str__ = classDef
1449 __unicode__ = classDef
1451 def wrapClassDef(self):
1452 ind = self.setting('indentationStep')
1453 classDefChunks = [self.classSignature(),
1454 self.classDocstring(),
1456 def addMethods():
1457 classDefChunks.extend([
1458 ind + '#'*50,
1459 ind + '## CHEETAH GENERATED METHODS',
1460 '\n',
1461 self.methodDefs(),
1463 def addAttributes():
1464 classDefChunks.extend([
1465 ind + '#'*50,
1466 ind + '## CHEETAH GENERATED ATTRIBUTES',
1467 '\n',
1468 self.attributes(),
1470 if self.setting('outputMethodsBeforeAttributes'):
1471 addMethods()
1472 addAttributes()
1473 else:
1474 addAttributes()
1475 addMethods()
1477 classDef = '\n'.join(classDefChunks)
1478 self._classDef = classDef
1479 return classDef
1482 def classSignature(self):
1483 return "class %s(%s):" % (self.className(), self._baseClass)
1485 def classDocstring(self):
1486 if not self._classDocStringLines:
1487 return ''
1488 ind = self.setting('indentationStep')
1489 docStr = ('%(ind)s"""\n%(ind)s' +
1490 '\n%(ind)s'.join(self._classDocStringLines) +
1491 '\n%(ind)s"""\n'
1492 ) % {'ind':ind}
1493 return docStr
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):
1505 pass
1507 ##################################################
1508 ## MODULE COMPILERS
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)
1524 if settings:
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
1528 # the time:
1529 if not NameMapper.C_VERSION:
1530 #if not sys.platform.startswith('java'):
1531 # warnings.warn(
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
1543 else:
1544 self._mainClassName = mainClassName
1545 self._mainMethodNameArg = mainMethodName
1546 if mainMethodName:
1547 self.setSetting('mainMethodName', mainMethodName)
1548 self._baseclassName = baseclassName
1550 self._filePath = None
1551 self._fileMtime = None
1553 if source and file:
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.
1557 source = f.read()
1558 f.close()
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.
1563 elif file:
1564 raise TypeError("'file' argument must be a filename string or file-like object")
1566 if self._filePath:
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
1573 # to be passed in
1575 # Handle the #indent directive by converting it to other directives.
1576 # (Over the long term we'll make it a real directive.)
1577 if source == "":
1578 warnings.warn("You supplied an empty string for the source!", )
1580 else:
1581 unicodeMatch = unicodeDirectiveRE.search(source)
1582 if unicodeMatch:
1583 if encodingDirectiveRE.match(source):
1584 raise ParseError(
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)
1592 #print 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)
1614 else:
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 = [
1632 "import sys",
1633 "import os",
1634 "import os.path",
1635 "from os.path import getmtime, exists",
1636 "import time",
1637 "import types",
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',
1650 'os',
1651 'os.path',
1652 'time',
1653 'types',
1654 'Template',
1655 'DummyTransaction',
1656 'NotFound',
1657 'Filters',
1658 'ErrorCatchers',
1659 'CacheRegion',
1662 self._moduleConstants = [
1663 "try:",
1664 " True, False",
1665 "except NameError:",
1666 " True, False = (1==1), (1==0)",
1667 "VFFSL=valueFromFrameOrSearchList",
1668 "VFSL=valueFromSearchList",
1669 "VFN=valueForName",
1670 "currentTime=time.time",
1673 def compile(self):
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):
1684 if klass is 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)
1723 else:
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)
1733 # no need to import
1734 else:
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('.')
1744 if len(chunks)==1:
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,] )
1753 else:
1754 needToAddImport = True
1755 modName = chunks[0]
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)
1762 break
1763 else:
1764 modName += '.'+chunk
1765 if needToAddImport:
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):
1781 KWs = keywords
1782 merge = True
1783 if 'nomerge' in KWs:
1784 merge = False
1786 if 'reset' 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()
1791 return
1792 elif 'python' in KWs:
1793 settingsReader = self.updateSettingsFromPySrcStr
1794 # this comes from SettingsManager
1795 else:
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
1848 return
1850 specialVarMatch = specialVarRE.match(comm)
1851 if specialVarMatch:
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()
1871 else:
1872 addLine = self.addMethComment
1874 for line in comm.splitlines():
1875 addLine(line)
1877 ## methods for module code wrapping
1879 def getModuleCode(self):
1880 if not self._compiled:
1881 self.compile()
1882 if self._moduleDef:
1883 return self._moduleDef
1884 else:
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())
1896 if self._filePath:
1897 timestamp = self.timestamp(self._fileMtime)
1898 self.addModuleGlobal('__CHEETAH_src__ = %r'%self._filePath)
1899 self.addModuleGlobal('__CHEETAH_srcLastModified__ = %r'%timestamp)
1900 else:
1901 self.addModuleGlobal('__CHEETAH_src__ = None')
1902 self.addModuleGlobal('__CHEETAH_srcLastModified__ = None')
1904 moduleDef = """%(header)s
1905 %(docstring)s
1907 ##################################################
1908 ## DEPENDENCIES
1909 %(imports)s
1911 ##################################################
1912 ## MODULE CONSTANTS
1913 %(constants)s
1914 %(specialVars)s
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 ##################################################
1923 ## CLASSES
1925 %(classes)s
1927 ## END CLASS DEFINITION
1929 if not hasattr(%(mainClassName)s, '_initCheetahAttributes'):
1930 templateAPIClass = getattr(%(mainClassName)s, '_CHEETAH_templateClass', Template)
1931 templateAPIClass._addCheetahPlumbingCodeToClass(%(mainClassName)s)
1933 %(footer)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
1945 return moduleDef
1947 def timestamp(self, theTime=None):
1948 if not theTime:
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')
1958 header += (
1959 '#' + ' '*offSet +
1960 ('\n#'+ ' '*offSet).join(self._moduleHeaderLines) + '\n')
1962 return header
1964 def moduleDocstring(self):
1965 if not self._moduleDocStringLines:
1966 return ''
1968 return ('"""' +
1969 '\n'.join(self._moduleDocStringLines) +
1970 '\n"""\n')
1972 def specialVars(self):
1973 chunks = []
1974 theVars = self._specialVars
1975 keys = theVars.keys()
1976 keys.sort()
1977 for key in 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):
1992 return """
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