Merge branch 'master' of git://repo.or.cz/pyTivo/wmcbrine
[pyTivo.git] / Cheetah / Compiler.py
blob4669e171fec58193272f58bd7b305ba7d5f7aed0
1 #!/usr/bin/env python
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'
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.148 $
15 Start Date: 2001/09/19
16 Last Revision Date: $Date: 2006/06/22 00:18:22 $
17 """
18 __author__ = "Tavis Rudd <tavis@damnsimple.com>"
19 __revision__ = "$Revision: 1.148 $"[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.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
44 VFN=valueForName
45 currentTime=time.time
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
72 'commentOffset': 1,
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':'<%',
133 'PSPEndToken':'%>',
134 'EOLSlurpToken':'#',
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':{}
151 class GenUtils:
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
155 parsing themselves.
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
172 return interval
174 def genCacheInfo(self, cacheTokenParts):
175 """Decipher a placeholder cachetoken
177 cacheInfo = {}
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:
188 if val[0] in '"\'':
189 val = val[1:-1]
191 if key == 'timer':
192 key = 'interval'
193 val = self.genTimeInterval(val)
195 cacheInfo[key] = val
196 return cacheInfo
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)
203 else:
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:")
217 self.indent()
218 self.addChunk(self.genPlainVar(nameChunks[:]))
219 self.dedent()
221 def genPlainVar(self, nameChunks):
222 """Generate Python code for a Cheetah $var without using NameMapper
223 (Unified Dotted Notation with the SearchList).
225 nameChunks.reverse()
226 chunk = nameChunks.pop()
227 pythonCode = chunk[0] + chunk[2]
228 while nameChunks:
229 chunk = nameChunks.pop()
230 pythonCode = (pythonCode + '.' + chunk[0] + chunk[2])
231 return pythonCode
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),
240 where:
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.
249 EXAMPLE
250 ------------------------------------------------------------------------
251 if the raw Cheetah Var is
252 $a.b.c[1].d().x.y.z
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]
264 where:
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]')
272 B = ('d',False,'()')
273 C = ('x.y.z',True,'')
275 C` = VFN( VFN( VFFSL(SL, 'a.b.c',True)[1],
276 'd',False)(),
277 'x.y.z',True)
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)
285 then
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')
293 nameChunks.reverse()
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 +
301 ',"' + afterDot +
302 '",' + repr(defaultUseAC and useAC) + ')'
303 + remainder)
304 else:
305 pythonCode = name+remainder
306 elif self.setting('useStackFrames'):
307 pythonCode = ('VFFSL(SL,'
308 '"'+ name + '",'
309 + repr(defaultUseAC and useAC) + ')'
310 + remainder)
311 else:
312 pythonCode = ('VFSL([locals()]+SL+[globals(), __builtin__],'
313 '"'+ name + '",'
314 + repr(defaultUseAC and useAC) + ')'
315 + remainder)
317 while nameChunks:
318 name, useAC, remainder = nameChunks.pop()
319 pythonCode = ('VFN(' + pythonCode +
320 ',"' + name +
321 '",' + repr(defaultUseAC and useAC) + ')'
322 + remainder)
323 return pythonCode
325 ##################################################
326 ## METHOD COMPILERS
328 class MethodCompiler(GenUtils):
329 def __init__(self, methodName, classCompiler,
330 initialMethodComment=None,
331 decorator=None):
332 self._settingsManager = classCompiler
333 self._classCompiler = classCompiler
334 self._moduleCompiler = classCompiler._moduleCompiler
335 self._methodName = methodName
336 self._initialMethodComment = initialMethodComment
337 self._setupState()
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
366 pass
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
379 def indent(self):
380 self._indentLev +=1
382 def dedent(self):
383 if self._indentLev:
384 self._indentLev -=1
385 else:
386 raise Error('Attempt to dedent when the indentLev is 0')
388 ## methods for final code wrapping
390 def methodDef(self):
391 if self._methodDef:
392 return self._methodDef
393 else:
394 return self.wrapCode()
396 __str__ = methodDef
398 def wrapCode(self):
399 self.commitStrConst()
400 methodDefChunks = (
401 self.methodSignature(),
402 '\n',
403 self.docString(),
404 self.methodBody() )
405 methodDef = ''.join(methodDefChunks)
406 self._methodDef = methodDef
407 return 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 )
418 def docString(self):
419 if not self._docStringLines:
420 return ''
422 ind = self._indent*2
423 docStr = (ind + '"""\n' + ind +
424 ('\n' + ind).join([ln.replace('"""',"'''") for ln in self._docStringLines]) +
425 '\n' + ind + '"""\n')
426 return docStr
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:
445 filterArgs = ''
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))
452 if lineCol:
453 self.appendToPrevChunk(' on line %s, col %s'%lineCol)
454 else:
455 self.addChunk("_v = %s"%chunk)
457 if self.setting('useFilters'):
458 self.addChunk("if _v is not None: write(_filter(_v%s))"%filterArgs)
459 else:
460 self.addChunk("if _v is not None: write(str(_v))")
461 else:
462 if self.setting('useFilters'):
463 self.addChunk("write(_filter(%s%s))"%(chunk,filterArgs))
464 else:
465 self.addChunk("write(str(%s))"%chunk)
467 def _appendToPrevStrConst(self, strConst):
468 if self._pendingStrConstChunks:
469 self._pendingStrConstChunks.append(strConst)
470 else:
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 = []
495 if not strConst:
496 return
497 if self.setting('reprShortStrConstants') and \
498 strConst.count('\n') < self.setting('reprNewlineThreshold'):
499 self.addWriteChunk( repr(strConst).replace('\\012','\\n'))
500 else:
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)
515 if BOL < len(src):
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,
542 silentMode=False):
543 cacheInfo = self.genCacheInfo(cacheTokenParts)
544 if cacheInfo:
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())'
553 if silentMode:
554 self.addChunk('try:')
555 self.indent()
556 self.addFilteredChunk(expr, filterArgs, rawPlaceholder, lineCol=lineCol)
557 self.dedent()
558 self.addChunk('except NotFound: pass')
559 else:
560 self.addFilteredChunk(expr, filterArgs, rawPlaceholder, lineCol=lineCol)
562 if self.setting('outputRowColComments'):
563 self.appendToPrevChunk(' # from line %s, col %s' % lineCol + '.')
564 if cacheInfo:
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,
576 exprComponents.OP,
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:
582 splitPos = splitPos1
583 elif splitPos1 > 0 and splitPos1 < max(splitPos2,0):
584 splitPos = splitPos1
585 else:
586 splitPos = splitPos2
588 if splitPos >0:
589 primary = LVALUE[:splitPos]
590 secondary = LVALUE[splitPos:]
591 else:
592 primary = LVALUE
593 secondary = ''
594 LVALUE = 'self._CHEETAH__globalSetVars["' + primary + '"]' + secondary
595 expr = LVALUE + ' ' + OP + ' ' + RVALUE.strip()
597 if setStyle is SET_MODULE:
598 self._moduleCompiler.addModuleGlobal(expr)
599 else:
600 self.addChunk(expr)
602 def addInclude(self, sourceExpr, includeFrom, isRaw):
603 self.addChunk('self._handleCheetahInclude(' + sourceExpr +
604 ', trans=trans, ' +
605 'includeFrom="' + includeFrom + '", raw=' +
606 repr(isRaw) + ')')
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] == ':':
621 expr = expr + ':'
622 self.addChunk( expr )
623 if lineCol:
624 self.appendToPrevChunk(' # generated from line %s, col %s'%lineCol )
625 self.indent()
627 def addReIndentingDirective(self, expr, dedent=True, lineCol=None):
628 self.commitStrConst()
629 if dedent:
630 self.dedent()
631 if not expr[-1] == ':':
632 expr = expr + ':'
634 self.addChunk( expr )
635 if lineCol:
636 self.appendToPrevChunk(' # generated from line %s, col %s'%lineCol )
637 self.indent()
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)
655 self.dedent()
656 self.addIndentingDirective('else')
657 self.addFilteredChunk(falseExpr)
658 self.dedent()
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):
671 argStringChunks = []
672 for arg in argsList:
673 chunk = arg[0]
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
692 self.addChunk(expr)
693 self._hasReturnStatement = True
695 def addYield(self, expr):
696 assert not self._hasReturnStatement
697 self._isGenerator = True
698 if expr.replace('yield','').strip():
699 self.addChunk(expr)
700 else:
701 self.addChunk('if _dummyTrans:')
702 self.indent()
703 self.addChunk('yield trans.response().getvalue()')
704 self.addChunk('trans = DummyTransaction()')
705 self.addChunk('write = trans.response().write')
706 self.dedent()
707 self.addChunk('else:')
708 self.indent()
709 self.addChunk(
710 'raise TypeError("This method cannot be called with a trans arg")')
711 self.dedent()
714 def addPass(self, expr):
715 self.addChunk(expr)
717 def addDel(self, expr):
718 self.addChunk(expr)
720 def addAssert(self, expr):
721 self.addChunk(expr)
723 def addRaise(self, expr):
724 self.addChunk(expr)
726 def addBreak(self, expr):
727 self.addChunk(expr)
729 def addContinue(self, expr):
730 self.addChunk(expr)
732 def addPSP(self, PSP):
733 self.commitStrConst()
734 autoIndent = False
735 if PSP[0] == '=':
736 PSP = PSP[1:]
737 if PSP:
738 self.addWriteChunk('_filter(' + PSP + ')')
739 return
741 elif PSP.lower() == 'end':
742 self.dedent()
743 return
744 elif PSP[-1] == '$':
745 autoIndent = True
746 PSP = PSP[:-1]
747 elif PSP[-1] == ':':
748 autoIndent = True
750 for line in PSP.splitlines():
751 self.addChunk(line)
753 if autoIndent:
754 self.indent()
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)
768 if customID:
769 ID = customID
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
774 self.addChunk('')
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()
781 + repr(ID)
782 + ', cacheInfo=%r'%cacheInfo
783 + ')')
784 self.addChunk('if _cacheRegion_%(ID)s.isNew():'%locals())
785 self.indent()
786 self.addChunk('_RECACHE_%(ID)s = True'%locals())
787 self.dedent()
789 self.addChunk('_cacheItem_%(ID)s = _cacheRegion_%(ID)s.getCacheItem('%locals()
790 +varyBy+')')
792 self.addChunk('if _cacheItem_%(ID)s.hasExpired():'%locals())
793 self.indent()
794 self.addChunk('_RECACHE_%(ID)s = True'%locals())
795 self.dedent()
797 if test:
798 self.addChunk('if ' + test + ':')
799 self.indent()
800 self.addChunk('_RECACHE_%(ID)s = True'%locals())
801 self.dedent()
803 self.addChunk('if (not _RECACHE_%(ID)s) and _cacheItem_%(ID)s.getRefreshTime():'%locals())
804 self.indent()
805 #self.addChunk('print "DEBUG"+"-"*50')
806 self.addChunk('try:')
807 self.indent()
808 self.addChunk('_output = _cacheItem_%(ID)s.renderOutput()'%locals())
809 self.dedent()
810 self.addChunk('except KeyError:')
811 self.indent()
812 self.addChunk('_RECACHE_%(ID)s = True'%locals())
813 #self.addChunk('print "DEBUG"+"*"*50')
814 self.dedent()
815 self.addChunk('else:')
816 self.indent()
817 self.addWriteChunk('_output')
818 self.addChunk('del _output')
819 self.dedent()
821 self.dedent()
823 self.addChunk('if _RECACHE_%(ID)s or not _cacheItem_%(ID)s.getRefreshTime():'%locals())
824 self.indent()
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())
828 if interval:
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())
842 self.dedent()
843 self.addChunk('## END CACHE REGION: '+ID)
844 self.addChunk('')
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()
861 +' of '+functionName
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:
872 self._endCallArg()
873 else:
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)
893 def reset(ID=ID):
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:
901 reset()
902 self.addChunk('_callArgVal%(ID)s = _callCollector%(ID)s.response().getvalue()'%locals())
903 self.addChunk('del _callCollector%(ID)s'%locals())
904 if initialKwArgs:
905 initialKwArgs = ', '+initialKwArgs
906 self.addFilteredChunk('%(functionName)s(_callArgVal%(ID)s%(initialKwArgs)s)'%locals())
907 self.addChunk('del _callArgVal%(ID)s'%locals())
908 else:
909 if initialKwArgs:
910 initialKwArgs = initialKwArgs+', '
911 self._endCallArg()
912 reset()
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()
917 +' of '+functionName
918 +' at line %s, col %s'%lineCol + ' in the source.')
919 self.addChunk('')
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
934 +' '+assignTo
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 + '"):')
957 self.indent()
958 self.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["' +
959 errorCatcherName + '"]')
960 self.dedent()
961 self.addChunk('else:')
962 self.indent()
963 self.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["'
964 + errorCatcherName + '"] = ErrorCatchers.'
965 + errorCatcherName + '(self)'
967 self.dedent()
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())
981 if isKlass:
982 self.addChunk('_filter = self._CHEETAH__currentFilter = ' + theFilter.strip() +
983 '(self).filter')
984 else:
985 if theFilter.lower() == 'none':
986 self.addChunk('_filter = self._CHEETAH__initialFilter')
987 else:
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 + '"):')
991 self.indent()
992 self.addChunk('_filter = self._CHEETAH__currentFilter = self._CHEETAH__filters[filterName]')
993 self.dedent()
994 self.addChunk('else:')
995 self.indent()
996 self.addChunk('_filter = self._CHEETAH__currentFilter'
997 +' = \\\n\t\t\tself._CHEETAH__filters[filterName] = '
998 + 'getattr(self._CHEETAH__filtersLib, filterName)(self).filter')
999 self.dedent()
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:
1029 kwargsName = None
1030 positionalArgsListName = None
1031 for argname,defval in self._argStringList:
1032 if argname.strip().startswith('**'):
1033 kwargsName = argname.strip().replace('**','')
1034 break
1035 elif argname.strip().startswith('*'):
1036 positionalArgsListName = argname.strip().replace('*','')
1038 if not kwargsName and self._useKWsDictArgForPassingTrans():
1039 kwargsName = 'KWS'
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')
1046 else:
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)):')
1065 self.indent()
1066 self.addChunk('trans = self.transaction'
1067 ' # is None unless self.awake() was called')
1068 self.dedent()
1069 self.addChunk('if not trans:')
1070 self.indent()
1071 self.addChunk('trans = DummyTransaction()')
1072 if self.setting('autoAssignDummyTransactionToSelf'):
1073 self.addChunk('self.transaction = trans')
1074 self.addChunk('_dummyTrans = True')
1075 self.dedent()
1076 self.addChunk('else: _dummyTrans = False')
1077 else:
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:
1085 pass
1086 elif allowSearchListAsMethArg and 'searchList' in argNames:
1087 self.addChunk('SL = searchList')
1088 else:
1089 self.addChunk('SL = self._CHEETAH__searchList')
1090 if self.setting('useFilters'):
1091 self.addChunk('_filter = self._CHEETAH__currentFilter')
1092 self.addChunk('')
1093 self.addChunk("#" *40)
1094 self.addChunk('## START - generated method body')
1095 self.addChunk('')
1097 def _addAutoCleanupCode(self):
1098 self.addChunk('')
1099 self.addChunk("#" *40)
1100 self.addChunk('## END - generated method body')
1101 self.addChunk('')
1103 if not self._isGenerator:
1104 self.addStop()
1105 self.addChunk('')
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:
1116 chunk = arg[0]
1117 if not arg[1] == None:
1118 chunk += '=' + arg[1]
1119 argStringChunks.append(chunk)
1120 argString = (', ').join(argStringChunks)
1122 output = []
1123 if self._decorator:
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 ##################################################
1132 ## CLASS COMPILERS
1134 _initMethod_initCheetah = """\
1135 if not self._CHEETAH__instanceInitialized:
1136 cheetahKWArgs = {}
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,
1149 fileName=None,
1150 settingsManager=None):
1152 self._settingsManager = settingsManager
1153 self._fileName = fileName
1154 self._className = className
1155 self._moduleCompiler = moduleCompiler
1156 self._mainMethodName = mainMethodName
1157 self._setupState()
1158 methodCompiler = self._spawnMethodCompiler(
1159 mainMethodName,
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)
1184 else:
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:')
1244 self.indent()
1245 self.addChunk('self._compile(file=self._filePath, moduleName='+className + ')')
1246 self.addChunk(
1247 'write(getattr(self, self._mainCheetahMethod_for_' + self._className +
1248 ')(trans=trans))')
1249 self.addStop()
1250 self.dedent()
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:
1263 return
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):
1288 if klass is None:
1289 klass = self.methodCompilerClass
1291 decorator = None
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()
1312 if pos==None:
1313 self._finishedMethodsList.append( methodCompiler )
1314 else:
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
1332 this is used.
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)
1359 return methodName
1361 self._errorCatcherCount += 1
1362 methodName = '__errorCatcher' + str(self._errorCatcherCount)
1363 self._placeholderToErrorCatcherMap[rawCode] = methodName
1365 catcherMeth = self._spawnMethodCompiler(
1366 methodName,
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)
1388 return methodName
1390 def closeDef(self):
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
1419 def classDef(self):
1420 if self._classDef:
1421 return self._classDef
1422 else:
1423 return self.wrapClassDef()
1425 __str__ = classDef
1427 def wrapClassDef(self):
1428 ind = self.setting('indentationStep')
1429 classDefChunks = [self.classSignature(),
1430 self.classDocstring(),
1432 def addMethods():
1433 classDefChunks.extend([
1434 ind + '#'*50,
1435 ind + '## CHEETAH GENERATED METHODS',
1436 '\n',
1437 self.methodDefs(),
1439 def addAttributes():
1440 classDefChunks.extend([
1441 ind + '#'*50,
1442 ind + '## CHEETAH GENERATED ATTRIBUTES',
1443 '\n',
1444 self.attributes(),
1446 if self.setting('outputMethodsBeforeAttributes'):
1447 addMethods()
1448 addAttributes()
1449 else:
1450 addAttributes()
1451 addMethods()
1453 classDef = '\n'.join(classDefChunks)
1454 self._classDef = classDef
1455 return classDef
1458 def classSignature(self):
1459 return "class %s(%s):" % (self.className(), self._baseClass)
1461 def classDocstring(self):
1462 if not self._classDocStringLines:
1463 return ''
1464 ind = self.setting('indentationStep')
1465 docStr = ('%(ind)s"""\n%(ind)s' +
1466 '\n%(ind)s'.join(self._classDocStringLines) +
1467 '\n%(ind)s"""\n'
1468 ) % {'ind':ind}
1469 return docStr
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):
1481 pass
1483 ##################################################
1484 ## MODULE COMPILERS
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)
1500 if settings:
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
1504 # the time:
1505 if not NameMapper.C_VERSION:
1506 if not sys.platform.startswith('java'):
1507 warnings.warn(
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
1519 else:
1520 self._mainClassName = mainClassName
1521 self._mainMethodNameArg = mainMethodName
1522 if mainMethodName:
1523 self.setSetting('mainMethodName', mainMethodName)
1524 self._baseclassName = baseclassName
1526 self._filePath = None
1527 self._fileMtime = None
1529 if source and file:
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.
1534 source = f.read()
1535 f.close()
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.
1540 elif file:
1541 raise TypeError("'file' argument must be a filename string or file-like object")
1544 if self._filePath:
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
1552 # to be passed in
1554 # Handle the #indent directive by converting it to other directives.
1555 # (Over the long term we'll make it a real directive.)
1556 if source == "":
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)
1579 else:
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 = [
1597 "import sys",
1598 "import os",
1599 "import os.path",
1600 "from os.path import getmtime, exists",
1601 "import time",
1602 "import types",
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',
1615 'os',
1616 'os.path',
1617 'time',
1618 'types',
1619 'Template',
1620 'DummyTransaction',
1621 'NotFound',
1622 'Filters',
1623 'ErrorCatchers',
1624 'CacheRegion',
1627 self._moduleConstants = [
1628 "try:",
1629 " True, False",
1630 "except NameError:",
1631 " True, False = (1==1), (1==0)",
1632 "VFFSL=valueFromFrameOrSearchList",
1633 "VFSL=valueFromSearchList",
1634 "VFN=valueForName",
1635 "currentTime=time.time",
1638 def compile(self):
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):
1649 if klass is 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)
1688 else:
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)
1698 # no need to import
1699 else:
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('.')
1709 if len(chunks)==1:
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,] )
1718 else:
1719 needToAddImport = True
1720 modName = chunks[0]
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)
1727 break
1728 else:
1729 modName += '.'+chunk
1730 if needToAddImport:
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):
1746 KWs = keywords
1747 merge = True
1748 if 'nomerge' in KWs:
1749 merge = False
1751 if 'reset' 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()
1756 return
1757 elif 'python' in KWs:
1758 settingsReader = self.updateSettingsFromPySrcStr
1759 # this comes from SettingsManager
1760 else:
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
1813 return
1815 specialVarMatch = specialVarRE.match(comm)
1816 if specialVarMatch:
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()
1836 else:
1837 addLine = self.addMethComment
1839 for line in comm.splitlines():
1840 addLine(line)
1842 ## methods for module code wrapping
1844 def getModuleCode(self):
1845 if not self._compiled:
1846 self.compile()
1847 if self._moduleDef:
1848 return self._moduleDef
1849 else:
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())
1860 if self._filePath:
1861 timestamp = self.timestamp(self._fileMtime)
1862 self.addModuleGlobal('__CHEETAH_src__ = %r'%self._filePath)
1863 self.addModuleGlobal('__CHEETAH_srcLastModified__ = %r'%timestamp)
1864 else:
1865 self.addModuleGlobal('__CHEETAH_src__ = None')
1866 self.addModuleGlobal('__CHEETAH_srcLastModified__ = None')
1868 moduleDef = """%(header)s
1869 %(docstring)s
1871 ##################################################
1872 ## DEPENDENCIES
1873 %(imports)s
1875 ##################################################
1876 ## MODULE CONSTANTS
1877 %(constants)s
1878 %(specialVars)s
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 ##################################################
1887 ## CLASSES
1889 %(classes)s
1891 ## END CLASS DEFINITION
1893 if not hasattr(%(mainClassName)s, '_initCheetahAttributes'):
1894 templateAPIClass = getattr(%(mainClassName)s, '_CHEETAH_templateClass', Template)
1895 templateAPIClass._addCheetahPlumbingCodeToClass(%(mainClassName)s)
1897 %(footer)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
1909 return moduleDef
1911 def timestamp(self, theTime=None):
1912 if not theTime:
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')
1922 header += (
1923 '#' + ' '*offSet +
1924 ('\n#'+ ' '*offSet).join(self._moduleHeaderLines) + '\n')
1926 return header
1928 def moduleDocstring(self):
1929 if not self._moduleDocStringLines:
1930 return ''
1932 return ('"""' +
1933 '\n'.join(self._moduleDocStringLines) +
1934 '\n"""\n')
1936 def specialVars(self):
1937 chunks = []
1938 theVars = self._specialVars
1939 keys = theVars.keys()
1940 keys.sort()
1941 for key in 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):
1956 return """
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