root/galaxy-central/eggs/Cheetah-2.2.2-py2.6-macosx-10.6-universal-ucs2.egg/Cheetah/Compiler.py @ 3

リビジョン 3, 79.1 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1'''
2    Compiler classes for Cheetah:
3    ModuleCompiler aka 'Compiler'
4    ClassCompiler
5    MethodCompiler
6
7    If you are trying to grok this code start with ModuleCompiler.__init__,
8    ModuleCompiler.compile, and ModuleCompiler.__getattr__.
9'''
10
11import sys
12import os
13import os.path
14from os.path import getmtime, exists
15import re
16import types
17import time
18import random
19import warnings
20import copy
21
22from Cheetah.Version import Version, VersionTuple
23from Cheetah.SettingsManager import SettingsManager
24from Cheetah.Utils.Indenter import indentize # an undocumented preprocessor
25from Cheetah import ErrorCatchers
26from Cheetah import NameMapper
27from Cheetah.Parser import Parser, ParseError, specialVarRE, \
28     STATIC_CACHE, REFRESH_CACHE, SET_LOCAL, SET_GLOBAL,SET_MODULE, \
29     unicodeDirectiveRE, encodingDirectiveRE,escapedNewlineRE
30
31from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList
32VFFSL=valueFromFrameOrSearchList
33VFSL=valueFromSearchList
34VFN=valueForName
35currentTime=time.time
36
37class Error(Exception): pass
38
39# Settings format: (key, default, docstring)
40_DEFAULT_COMPILER_SETTINGS = [
41    ('useNameMapper', True, 'Enable NameMapper for dotted notation and searchList support'),
42    ('useSearchList', True, 'Enable the searchList, requires useNameMapper=True, if disabled, first portion of the $variable is a global, builtin, or local variable that doesn\'t need looking up in the searchList'),
43    ('allowSearchListAsMethArg', True, ''),
44    ('useAutocalling', True, 'Detect and call callable objects in searchList, requires useNameMapper=True'),
45    ('useStackFrames', True, 'Used for NameMapper.valueFromFrameOrSearchList rather than NameMapper.valueFromSearchList'),
46    ('useErrorCatcher', False, 'Turn on the #errorCatcher directive for catching NameMapper errors, etc'),
47    ('alwaysFilterNone', True, 'Filter out None prior to calling the #filter'),
48    ('useFilters', True, 'If False, pass output through str()'),
49    ('includeRawExprInFilterArgs', True, ''),
50    ('useLegacyImportMode', True, 'All #import statements are relocated to the top of the generated Python module'),
51    ('prioritizeSearchListOverSelf', False, 'When iterating the searchList, look into the searchList passed into the initializer instead of Template members first'),
52
53    ('autoAssignDummyTransactionToSelf', False, ''),
54    ('useKWsDictArgForPassingTrans', True, ''),
55
56    ('commentOffset', 1, ''),
57    ('outputRowColComments', True, ''),
58    ('includeBlockMarkers', False, 'Wrap #block\'s in a comment in the template\'s output'),
59    ('blockMarkerStart', ('\n<!-- START BLOCK: ',' -->\n'), ''),
60    ('blockMarkerEnd', ('\n<!-- END BLOCK: ',' -->\n'), ''),
61    ('defDocStrMsg', 'Autogenerated by Cheetah: The Python-Powered Template Engine', ''),
62    ('setup__str__method', False, ''),
63    ('mainMethodName', 'respond', ''),
64    ('mainMethodNameForSubclasses', 'writeBody', ''),
65    ('indentationStep', ' ' * 4, ''),
66    ('initialMethIndentLevel', 2, ''),
67    ('monitorSrcFile', False, ''),
68    ('outputMethodsBeforeAttributes', True, ''),
69    ('addTimestampsToCompilerOutput', True, ''),
70
71    ## Customizing the #extends directive
72    ('autoImportForExtendsDirective', True, ''),
73    ('handlerForExtendsDirective', None, ''),
74
75    ('disabledDirectives', [], 'List of directive keys to disable (without starting "#")'),
76    ('enabledDirectives', [], 'List of directive keys to enable (without starting "#")'),
77    ('disabledDirectiveHooks', [], 'callable(parser, directiveKey)'),
78    ('preparseDirectiveHooks', [], 'callable(parser, directiveKey)'),
79    ('postparseDirectiveHooks', [], 'callable(parser, directiveKey)'),
80    ('preparsePlaceholderHooks', [], 'callable(parser)'),
81    ('postparsePlaceholderHooks', [], 'callable(parser)'),
82    ('expressionFilterHooks', [], '''callable(parser, expr, exprType, rawExpr=None, startPos=None), exprType is the name of the directive, "psp" or "placeholder" The filters *must* return the expr or raise an expression, they can modify the expr if needed'''),
83    ('templateMetaclass', None, 'Strictly optional, only will work with new-style basecalsses as well'),
84    ('i18NFunctionName', 'self.i18n', ''),
85
86    ('cheetahVarStartToken', '$', ''),
87    ('commentStartToken', '##', ''),
88    ('multiLineCommentStartToken', '#*', ''),
89    ('multiLineCommentEndToken', '*#', ''),
90    ('gobbleWhitespaceAroundMultiLineComments', True, ''),
91    ('directiveStartToken', '#', ''),
92    ('directiveEndToken', '#', ''),
93    ('allowWhitespaceAfterDirectiveStartToken', False, ''),
94    ('PSPStartToken', '<%', ''),
95    ('PSPEndToken', '%>', ''),
96    ('EOLSlurpToken', '#', ''),
97    ('gettextTokens', ["_", "N_", "ngettext"], ''),
98    ('allowExpressionsInExtendsDirective', False, ''),
99    ('allowEmptySingleLineMethods', False, ''),
100    ('allowNestedDefScopes', True, ''),
101    ('allowPlaceholderFilterArgs', True, ''),
102]
103
104DEFAULT_COMPILER_SETTINGS = dict([(v[0], v[1]) for v in _DEFAULT_COMPILER_SETTINGS])
105
106
107
108class GenUtils(object):
109    """An abstract baseclass for the Compiler classes that provides methods that
110    perform generic utility functions or generate pieces of output code from
111    information passed in by the Parser baseclass.  These methods don't do any
112    parsing themselves.
113    """
114
115    def genTimeInterval(self, timeString):
116        ##@@ TR: need to add some error handling here
117        if timeString[-1] == 's':
118            interval = float(timeString[:-1])
119        elif timeString[-1] == 'm':
120            interval = float(timeString[:-1])*60
121        elif timeString[-1] == 'h':
122            interval = float(timeString[:-1])*60*60
123        elif timeString[-1] == 'd':
124            interval = float(timeString[:-1])*60*60*24
125        elif timeString[-1] == 'w':
126            interval = float(timeString[:-1])*60*60*24*7
127        else:                       # default to minutes
128            interval = float(timeString)*60
129        return interval
130
131    def genCacheInfo(self, cacheTokenParts):
132        """Decipher a placeholder cachetoken
133        """
134        cacheInfo = {}
135        if cacheTokenParts['REFRESH_CACHE']:
136            cacheInfo['type'] = REFRESH_CACHE
137            cacheInfo['interval'] = self.genTimeInterval(cacheTokenParts['interval'])
138        elif cacheTokenParts['STATIC_CACHE']:
139            cacheInfo['type'] = STATIC_CACHE
140        return cacheInfo                # is empty if no cache
141
142    def genCacheInfoFromArgList(self, argList):
143        cacheInfo = {'type':REFRESH_CACHE}
144        for key, val in argList:
145            if val[0] in '"\'':
146                val = val[1:-1]
147
148            if key == 'timer':
149                key = 'interval'
150                val = self.genTimeInterval(val)
151               
152            cacheInfo[key] = val
153        return cacheInfo
154       
155    def genCheetahVar(self, nameChunks, plain=False):
156        if nameChunks[0][0] in self.setting('gettextTokens'):
157            self.addGetTextVar(nameChunks)
158        if self.setting('useNameMapper') and not plain:
159            return self.genNameMapperVar(nameChunks)
160        else:
161            return self.genPlainVar(nameChunks)
162
163    def addGetTextVar(self, nameChunks):
164        """Output something that gettext can recognize.
165       
166        This is a harmless side effect necessary to make gettext work when it
167        is scanning compiled templates for strings marked for translation.
168
169        @@TR: another marginally more efficient approach would be to put the
170        output in a dummy method that is never called.
171        """
172        # @@TR: this should be in the compiler not here
173        self.addChunk("if False:")
174        self.indent()
175        self.addChunk(self.genPlainVar(nameChunks[:]))
176        self.dedent()
177
178    def genPlainVar(self, nameChunks):       
179        """Generate Python code for a Cheetah $var without using NameMapper
180        (Unified Dotted Notation with the SearchList).
181        """
182        nameChunks.reverse()
183        chunk = nameChunks.pop()
184        pythonCode = chunk[0] + chunk[2]
185        while nameChunks:
186            chunk = nameChunks.pop()
187            pythonCode = (pythonCode + '.' + chunk[0] + chunk[2])
188        return pythonCode
189
190    def genNameMapperVar(self, nameChunks):
191        """Generate valid Python code for a Cheetah $var, using NameMapper
192        (Unified Dotted Notation with the SearchList).
193
194        nameChunks = list of var subcomponents represented as tuples
195          [ (name,useAC,remainderOfExpr),
196          ]
197        where:
198          name = the dotted name base
199          useAC = where NameMapper should use autocalling on namemapperPart
200          remainderOfExpr = any arglist, index, or slice
201
202        If remainderOfExpr contains a call arglist (e.g. '(1234)') then useAC
203        is False, otherwise it defaults to True. It is overridden by the global
204        setting 'useAutocalling' if this setting is False.
205
206        EXAMPLE
207        ------------------------------------------------------------------------
208        if the raw Cheetah Var is
209          $a.b.c[1].d().x.y.z
210         
211        nameChunks is the list
212          [ ('a.b.c',True,'[1]'), # A
213            ('d',False,'()'),     # B
214            ('x.y.z',True,''),    # C
215          ]
216       
217        When this method is fed the list above it returns
218          VFN(VFN(VFFSL(SL, 'a.b.c',True)[1], 'd',False)(), 'x.y.z',True)
219        which can be represented as
220          VFN(B`, name=C[0], executeCallables=(useAC and C[1]))C[2]
221        where:
222          VFN = NameMapper.valueForName
223          VFFSL = NameMapper.valueFromFrameOrSearchList
224          VFSL = NameMapper.valueFromSearchList # optionally used instead of VFFSL
225          SL = self.searchList()
226          useAC = self.setting('useAutocalling') # True in this example
227         
228          A = ('a.b.c',True,'[1]')
229          B = ('d',False,'()')
230          C = ('x.y.z',True,'')
231
232          C` = VFN( VFN( VFFSL(SL, 'a.b.c',True)[1],
233                         'd',False)(),
234                    'x.y.z',True)
235             = VFN(B`, name='x.y.z', executeCallables=True)
236             
237          B` = VFN(A`, name=B[0], executeCallables=(useAC and B[1]))B[2]
238          A` = VFFSL(SL, name=A[0], executeCallables=(useAC and A[1]))A[2]
239
240
241        Note, if the compiler setting useStackFrames=False (default is true)
242        then
243          A` = VFSL([locals()]+SL+[globals(), __builtin__], name=A[0], executeCallables=(useAC and A[1]))A[2]
244        This option allows Cheetah to be used with Psyco, which doesn't support
245        stack frame introspection.
246        """
247        defaultUseAC = self.setting('useAutocalling')
248        useSearchList = self.setting('useSearchList')
249
250        nameChunks.reverse()
251        name, useAC, remainder = nameChunks.pop()
252       
253        if not useSearchList:
254            firstDotIdx = name.find('.')
255            if firstDotIdx != -1 and firstDotIdx < len(name):
256                beforeFirstDot, afterDot = name[:firstDotIdx], name[firstDotIdx+1:]
257                pythonCode = ('VFN(' + beforeFirstDot +
258                              ',"' + afterDot +
259                              '",' + repr(defaultUseAC and useAC) + ')'
260                              + remainder)
261            else:
262                pythonCode = name+remainder
263        elif self.setting('useStackFrames'):
264            pythonCode = ('VFFSL(SL,'
265                          '"'+ name + '",'
266                          + repr(defaultUseAC and useAC) + ')'
267                          + remainder)
268        else:
269            pythonCode = ('VFSL([locals()]+SL+[globals(), __builtin__],'
270                          '"'+ name + '",'
271                          + repr(defaultUseAC and useAC) + ')'
272                          + remainder)
273        ##   
274        while nameChunks:
275            name, useAC, remainder = nameChunks.pop()
276            pythonCode = ('VFN(' + pythonCode +
277                          ',"' + name +
278                          '",' + repr(defaultUseAC and useAC) + ')'
279                          + remainder)
280        return pythonCode
281   
282##################################################
283## METHOD COMPILERS
284
285class MethodCompiler(GenUtils):
286    def __init__(self, methodName, classCompiler,
287                 initialMethodComment=None,
288                 decorators=None):
289        self._settingsManager = classCompiler
290        self._classCompiler = classCompiler
291        self._moduleCompiler = classCompiler._moduleCompiler
292        self._methodName = methodName
293        self._initialMethodComment = initialMethodComment
294        self._setupState()
295        self._decorators = decorators or []
296
297    def setting(self, key):
298        return self._settingsManager.setting(key)
299
300    def _setupState(self):
301        self._indent = self.setting('indentationStep')
302        self._indentLev = self.setting('initialMethIndentLevel')
303        self._pendingStrConstChunks = []
304        self._methodSignature = None
305        self._methodDef = None
306        self._docStringLines = []
307        self._methodBodyChunks = []
308
309        self._cacheRegionsStack = []
310        self._callRegionsStack = []
311        self._captureRegionsStack = []
312        self._filterRegionsStack = []
313
314        self._isErrorCatcherOn = False
315
316        self._hasReturnStatement = False
317        self._isGenerator = False
318       
319       
320    def cleanupState(self):
321        """Called by the containing class compiler instance
322        """
323        pass
324
325    def methodName(self):
326        return self._methodName
327
328    def setMethodName(self, name):
329        self._methodName = name
330       
331    ## methods for managing indentation
332   
333    def indentation(self):
334        return self._indent * self._indentLev
335   
336    def indent(self):
337        self._indentLev +=1
338       
339    def dedent(self):
340        if self._indentLev:
341            self._indentLev -=1
342        else:
343            raise Error('Attempt to dedent when the indentLev is 0')
344
345    ## methods for final code wrapping
346
347    def methodDef(self):
348        if self._methodDef:
349            return self._methodDef
350        else:
351            return self.wrapCode()
352
353    __str__ = methodDef
354    __unicode__ = methodDef
355   
356    def wrapCode(self):
357        self.commitStrConst()
358        methodDefChunks = (
359            self.methodSignature(),
360            '\n',
361            self.docString(),
362            self.methodBody() )
363        methodDef = ''.join(methodDefChunks)
364        self._methodDef = methodDef
365        return methodDef
366
367    def methodSignature(self):
368        return self._indent + self._methodSignature + ':'
369
370    def setMethodSignature(self, signature):
371        self._methodSignature = signature
372
373    def methodBody(self):
374        return ''.join( self._methodBodyChunks )
375
376    def docString(self):
377        if not self._docStringLines:
378            return ''
379       
380        ind = self._indent*2       
381        docStr = (ind + '"""\n' + ind +
382                  ('\n' + ind).join([ln.replace('"""',"'''") for ln in self._docStringLines]) +
383                  '\n' + ind + '"""\n')
384        return  docStr
385
386    ## methods for adding code
387    def addMethDocString(self, line):
388        self._docStringLines.append(line.replace('%','%%'))
389       
390    def addChunk(self, chunk):
391        self.commitStrConst()
392        chunk = "\n" + self.indentation() + chunk
393        self._methodBodyChunks.append(chunk)
394
395    def appendToPrevChunk(self, appendage):
396        self._methodBodyChunks[-1] = self._methodBodyChunks[-1] + appendage
397
398    def addWriteChunk(self, chunk):
399        self.addChunk('write(' + chunk + ')')
400
401    def addFilteredChunk(self, chunk, filterArgs=None, rawExpr=None, lineCol=None):
402        if filterArgs is None:
403            filterArgs = ''
404        if self.setting('includeRawExprInFilterArgs') and rawExpr:
405            filterArgs += ', rawExpr=%s'%repr(rawExpr)
406
407        if self.setting('alwaysFilterNone'):
408            if rawExpr and rawExpr.find('\n')==-1 and rawExpr.find('\r')==-1:
409                self.addChunk("_v = %s # %r"%(chunk, rawExpr))
410                if lineCol:
411                    self.appendToPrevChunk(' on line %s, col %s'%lineCol)
412            else:
413                self.addChunk("_v = %s"%chunk)
414               
415            if self.setting('useFilters'):
416                self.addChunk("if _v is not None: write(_filter(_v%s))"%filterArgs)
417            else:
418                self.addChunk("if _v is not None: write(str(_v))")
419        else:
420            if self.setting('useFilters'):
421                self.addChunk("write(_filter(%s%s))"%(chunk,filterArgs))
422            else:
423                self.addChunk("write(str(%s))"%chunk)
424
425    def _appendToPrevStrConst(self, strConst):
426        if self._pendingStrConstChunks:
427            self._pendingStrConstChunks.append(strConst)
428        else:
429            self._pendingStrConstChunks = [strConst]
430
431    def _unescapeCheetahVars(self, theString):
432        """Unescape any escaped Cheetah \$vars in the string.
433        """
434       
435        token = self.setting('cheetahVarStartToken')
436        return theString.replace('\\' + token, token)
437
438    def _unescapeDirectives(self, theString):
439        """Unescape any escaped Cheetah \$vars in the string.
440        """
441       
442        token = self.setting('directiveStartToken')
443        return theString.replace('\\' + token, token)
444       
445    def commitStrConst(self):
446        """Add the code for outputting the pending strConst without chopping off
447        any whitespace from it.
448        """
449        if self._pendingStrConstChunks:
450            strConst = self._unescapeCheetahVars(''.join(self._pendingStrConstChunks))
451            strConst = self._unescapeDirectives(strConst)
452            self._pendingStrConstChunks = []
453            if not strConst:
454                return
455            else:
456                reprstr = repr(strConst).replace('\\012','\n')
457                i = 0
458                out = []
459                if reprstr.startswith('u'):
460                    i = 1
461                    out = ['u']
462                body = escapedNewlineRE.sub('\n', reprstr[i+1:-1])
463               
464                if reprstr[i]=="'":
465                    out.append("'''")
466                    out.append(body)
467                    out.append("'''")
468                else:
469                    out.append('"""')
470                    out.append(body)
471                    out.append('"""')
472                self.addWriteChunk(''.join(out))
473
474    def handleWSBeforeDirective(self):
475        """Truncate the pending strCont to the beginning of the current line.
476        """
477        if self._pendingStrConstChunks:
478            src = self._pendingStrConstChunks[-1]
479            BOL = max(src.rfind('\n')+1, src.rfind('\r')+1, 0)
480            if BOL < len(src):
481                self._pendingStrConstChunks[-1] = src[:BOL]
482
483
484
485    def isErrorCatcherOn(self):
486        return self._isErrorCatcherOn
487   
488    def turnErrorCatcherOn(self):
489        self._isErrorCatcherOn = True
490
491    def turnErrorCatcherOff(self):
492        self._isErrorCatcherOn = False
493           
494    # @@TR: consider merging the next two methods into one
495    def addStrConst(self, strConst):
496        self._appendToPrevStrConst(strConst)
497
498    def addRawText(self, text):
499        self.addStrConst(text)
500       
501    def addMethComment(self, comm):
502        offSet = self.setting('commentOffset')
503        self.addChunk('#' + ' '*offSet + comm)
504
505    def addPlaceholder(self, expr, filterArgs, rawPlaceholder,
506                       cacheTokenParts, lineCol,
507                       silentMode=False):
508        cacheInfo = self.genCacheInfo(cacheTokenParts)
509        if cacheInfo:
510            cacheInfo['ID'] = repr(rawPlaceholder)[1:-1]
511            self.startCacheRegion(cacheInfo, lineCol, rawPlaceholder=rawPlaceholder)
512
513        if self.isErrorCatcherOn():
514            methodName = self._classCompiler.addErrorCatcherCall(
515                expr, rawCode=rawPlaceholder, lineCol=lineCol)
516            expr = 'self.' + methodName + '(localsDict=locals())'
517
518        if silentMode:
519            self.addChunk('try:')
520            self.indent()           
521            self.addFilteredChunk(expr, filterArgs, rawPlaceholder, lineCol=lineCol)
522            self.dedent()
523            self.addChunk('except NotFound: pass')           
524        else:
525            self.addFilteredChunk(expr, filterArgs, rawPlaceholder, lineCol=lineCol)
526
527        if self.setting('outputRowColComments'):
528            self.appendToPrevChunk(' # from line %s, col %s' % lineCol + '.')
529        if cacheInfo:
530            self.endCacheRegion()
531
532    def addSilent(self, expr):
533        self.addChunk( expr )
534
535    def addEcho(self, expr, rawExpr=None):
536        self.addFilteredChunk(expr, rawExpr=rawExpr)
537       
538    def addSet(self, expr, exprComponents, setStyle):
539        if setStyle is SET_GLOBAL:
540            (LVALUE, OP, RVALUE) = (exprComponents.LVALUE,
541                                    exprComponents.OP,
542                                    exprComponents.RVALUE)
543            # we need to split the LVALUE to deal with globalSetVars
544            splitPos1 = LVALUE.find('.')
545            splitPos2 = LVALUE.find('[')
546            if splitPos1 > 0 and splitPos2==-1:
547                splitPos = splitPos1
548            elif splitPos1 > 0 and splitPos1 < max(splitPos2,0):
549                splitPos = splitPos1
550            else:
551                splitPos = splitPos2
552
553            if splitPos >0:
554                primary = LVALUE[:splitPos]
555                secondary = LVALUE[splitPos:]
556            else:
557                primary = LVALUE
558                secondary = ''           
559            LVALUE = 'self._CHEETAH__globalSetVars["' + primary + '"]' + secondary
560            expr = LVALUE + ' ' + OP + ' ' + RVALUE.strip()
561
562        if setStyle is SET_MODULE:
563            self._moduleCompiler.addModuleGlobal(expr)
564        else:
565            self.addChunk(expr)
566
567    def addInclude(self, sourceExpr, includeFrom, isRaw):
568        self.addChunk('self._handleCheetahInclude(' + sourceExpr +
569                           ', trans=trans, ' +
570                           'includeFrom="' + includeFrom + '", raw=' +
571                           repr(isRaw) + ')')
572
573    def addWhile(self, expr, lineCol=None):
574        self.addIndentingDirective(expr, lineCol=lineCol)
575       
576    def addFor(self, expr, lineCol=None):
577        self.addIndentingDirective(expr, lineCol=lineCol)
578
579    def addRepeat(self, expr, lineCol=None):
580        #the _repeatCount stuff here allows nesting of #repeat directives       
581        self._repeatCount = getattr(self, "_repeatCount", -1) + 1
582        self.addFor('for __i%s in range(%s)' % (self._repeatCount,expr), lineCol=lineCol)
583
584    def addIndentingDirective(self, expr, lineCol=None):
585        if expr and not expr[-1] == ':':
586            expr = expr  + ':'
587        self.addChunk( expr )
588        if lineCol:
589            self.appendToPrevChunk(' # generated from line %s, col %s'%lineCol )
590        self.indent()
591
592    def addReIndentingDirective(self, expr, dedent=True, lineCol=None):
593        self.commitStrConst()
594        if dedent:
595            self.dedent()
596        if not expr[-1] == ':':
597            expr = expr  + ':'
598           
599        self.addChunk( expr )
600        if lineCol:
601            self.appendToPrevChunk(' # generated from line %s, col %s'%lineCol )
602        self.indent()
603
604    def addIf(self, expr, lineCol=None):
605        """For a full #if ... #end if directive
606        """
607        self.addIndentingDirective(expr, lineCol=lineCol)
608
609    def addOneLineIf(self, expr, lineCol=None):
610        """For a full #if ... #end if directive
611        """
612        self.addIndentingDirective(expr, lineCol=lineCol)
613
614    def addTernaryExpr(self, conditionExpr, trueExpr, falseExpr, lineCol=None):
615        """For a single-lie #if ... then .... else ... directive
616        <condition> then <trueExpr> else <falseExpr>
617        """
618        self.addIndentingDirective(conditionExpr, lineCol=lineCol)           
619        self.addFilteredChunk(trueExpr)
620        self.dedent()
621        self.addIndentingDirective('else')           
622        self.addFilteredChunk(falseExpr)
623        self.dedent()
624
625    def addElse(self, expr, dedent=True, lineCol=None):
626        expr = re.sub(r'else[ \f\t]+if','elif', expr)
627        self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol)
628
629    def addElif(self, expr, dedent=True, lineCol=None):
630        self.addElse(expr, dedent=dedent, lineCol=lineCol)
631       
632    def addUnless(self, expr, lineCol=None):
633        self.addIf('if not (' + expr + ')')
634
635    def addClosure(self, functionName, argsList, parserComment):
636        argStringChunks = []
637        for arg in argsList:
638            chunk = arg[0]
639            if not arg[1] == None:
640                chunk += '=' + arg[1]
641            argStringChunks.append(chunk)
642        signature = "def " + functionName + "(" + ','.join(argStringChunks) + "):"
643        self.addIndentingDirective(signature)
644        self.addChunk('#'+parserComment)
645
646    def addTry(self, expr, lineCol=None):
647        self.addIndentingDirective(expr, lineCol=lineCol)
648       
649    def addExcept(self, expr, dedent=True, lineCol=None):
650        self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol)
651       
652    def addFinally(self, expr, dedent=True, lineCol=None):
653        self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol)
654           
655    def addReturn(self, expr):
656        assert not self._isGenerator
657        self.addChunk(expr)
658        self._hasReturnStatement = True
659
660    def addYield(self, expr):
661        assert not self._hasReturnStatement
662        self._isGenerator = True
663        if expr.replace('yield','').strip():
664            self.addChunk(expr)
665        else:
666            self.addChunk('if _dummyTrans:')
667            self.indent()
668            self.addChunk('yield trans.response().getvalue()')
669            self.addChunk('trans = DummyTransaction()')
670            self.addChunk('write = trans.response().write')
671            self.dedent()
672            self.addChunk('else:')
673            self.indent()
674            self.addChunk(
675                'raise TypeError("This method cannot be called with a trans arg")')
676            self.dedent()
677           
678
679    def addPass(self, expr):
680        self.addChunk(expr)
681
682    def addDel(self, expr):
683        self.addChunk(expr)
684
685    def addAssert(self, expr):
686        self.addChunk(expr)
687
688    def addRaise(self, expr):
689        self.addChunk(expr)
690
691    def addBreak(self, expr):
692        self.addChunk(expr)
693
694    def addContinue(self, expr):
695        self.addChunk(expr)
696
697    def addPSP(self, PSP):
698        self.commitStrConst()
699        autoIndent = False
700        if PSP[0] == '=':
701            PSP = PSP[1:]
702            if PSP:
703                self.addWriteChunk('_filter(' + PSP + ')')
704            return
705                   
706        elif PSP.lower() == 'end':
707            self.dedent()
708            return
709        elif PSP[-1] == '$':
710            autoIndent = True
711            PSP = PSP[:-1]
712        elif PSP[-1] == ':':
713            autoIndent = True
714           
715        for line in PSP.splitlines():
716            self.addChunk(line)
717           
718        if autoIndent:
719            self.indent()
720   
721    def nextCacheID(self):
722        return ('_'+str(random.randrange(100, 999))
723                + str(random.randrange(10000, 99999)))
724       
725    def startCacheRegion(self, cacheInfo, lineCol, rawPlaceholder=None):
726
727        # @@TR: we should add some runtime logging to this
728       
729        ID = self.nextCacheID()
730        interval = cacheInfo.get('interval',None)
731        test = cacheInfo.get('test',None)
732        customID = cacheInfo.get('id',None)
733        if customID:
734            ID = customID
735        varyBy = cacheInfo.get('varyBy', repr(ID))
736        self._cacheRegionsStack.append(ID) # attrib of current methodCompiler
737
738        # @@TR: add this to a special class var as well
739        self.addChunk('')
740
741        self.addChunk('## START CACHE REGION: ID='+ID+
742                      '. line %s, col %s'%lineCol + ' in the source.')
743       
744        self.addChunk('_RECACHE_%(ID)s = False'%locals())
745        self.addChunk('_cacheRegion_%(ID)s = self.getCacheRegion(regionID='%locals()
746                      + repr(ID)
747                      + ', cacheInfo=%r'%cacheInfo
748                      + ')')
749        self.addChunk('if _cacheRegion_%(ID)s.isNew():'%locals())
750        self.indent()
751        self.addChunk('_RECACHE_%(ID)s = True'%locals())
752        self.dedent()
753       
754        self.addChunk('_cacheItem_%(ID)s = _cacheRegion_%(ID)s.getCacheItem('%locals()
755                      +varyBy+')')
756
757        self.addChunk('if _cacheItem_%(ID)s.hasExpired():'%locals())
758        self.indent()
759        self.addChunk('_RECACHE_%(ID)s = True'%locals())
760        self.dedent()
761           
762        if test:
763            self.addChunk('if ' + test + ':')
764            self.indent()
765            self.addChunk('_RECACHE_%(ID)s = True'%locals())
766            self.dedent()
767
768        self.addChunk('if (not _RECACHE_%(ID)s) and _cacheItem_%(ID)s.getRefreshTime():'%locals())
769        self.indent()
770        #self.addChunk('print "DEBUG"+"-"*50')
771        self.addChunk('try:')
772        self.indent()
773        self.addChunk('_output = _cacheItem_%(ID)s.renderOutput()'%locals())       
774        self.dedent()               
775        self.addChunk('except KeyError:')
776        self.indent()
777        self.addChunk('_RECACHE_%(ID)s = True'%locals())
778        #self.addChunk('print "DEBUG"+"*"*50')
779        self.dedent()               
780        self.addChunk('else:')
781        self.indent()
782        self.addWriteChunk('_output')
783        self.addChunk('del _output')
784        self.dedent()               
785
786        self.dedent()               
787
788        self.addChunk('if _RECACHE_%(ID)s or not _cacheItem_%(ID)s.getRefreshTime():'%locals())
789        self.indent()
790        self.addChunk('_orig_trans%(ID)s = trans'%locals())
791        self.addChunk('trans = _cacheCollector_%(ID)s = DummyTransaction()'%locals())
792        self.addChunk('write = _cacheCollector_%(ID)s.response().write'%locals())
793        if interval:
794            self.addChunk(("_cacheItem_%(ID)s.setExpiryTime(currentTime() +"%locals())
795                          + str(interval) + ")")
796       
797    def endCacheRegion(self):
798        ID = self._cacheRegionsStack.pop()
799        self.addChunk('trans = _orig_trans%(ID)s'%locals())
800        self.addChunk('write = trans.response().write')
801        self.addChunk('_cacheData = _cacheCollector_%(ID)s.response().getvalue()'%locals())
802        self.addChunk('_cacheItem_%(ID)s.setData(_cacheData)'%locals())
803        self.addWriteChunk('_cacheData')
804        self.addChunk('del _cacheData')       
805        self.addChunk('del _cacheCollector_%(ID)s'%locals())
806        self.addChunk('del _orig_trans%(ID)s'%locals())
807        self.dedent()
808        self.addChunk('## END CACHE REGION: '+ID)
809        self.addChunk('')
810
811    def nextCallRegionID(self):
812        return self.nextCacheID()
813
814    def startCallRegion(self, functionName, args, lineCol, regionTitle='CALL'):
815        class CallDetails(object):
816            pass
817        callDetails = CallDetails()
818        callDetails.ID = ID = self.nextCallRegionID()
819        callDetails.functionName = functionName
820        callDetails.args = args
821        callDetails.lineCol = lineCol
822        callDetails.usesKeywordArgs = False
823        self._callRegionsStack.append((ID, callDetails)) # attrib of current methodCompiler
824
825        self.addChunk('## START %(regionTitle)s REGION: '%locals()
826                      +ID
827                      +' of '+functionName
828                      +' at line %s, col %s'%lineCol + ' in the source.')
829        self.addChunk('_orig_trans%(ID)s = trans'%locals())
830        self.addChunk('_wasBuffering%(ID)s = self._CHEETAH__isBuffering'%locals())
831        self.addChunk('self._CHEETAH__isBuffering = True')
832        self.addChunk('trans = _callCollector%(ID)s = DummyTransaction()'%locals())
833        self.addChunk('write = _callCollector%(ID)s.response().write'%locals())
834
835    def setCallArg(self, argName, lineCol):
836        ID, callDetails = self._callRegionsStack[-1]
837        argName = str(argName)
838        if callDetails.usesKeywordArgs:
839            self._endCallArg()
840        else:
841            callDetails.usesKeywordArgs = True
842            self.addChunk('_callKws%(ID)s = {}'%locals())
843            self.addChunk('_currentCallArgname%(ID)s = %(argName)r'%locals())
844        callDetails.currentArgname = argName
845       
846    def _endCallArg(self):
847        ID, callDetails = self._callRegionsStack[-1]
848        currCallArg = callDetails.currentArgname
849        self.addChunk(('_callKws%(ID)s[%(currCallArg)r] ='
850                       ' _callCollector%(ID)s.response().getvalue()')%locals())
851        self.addChunk('del _callCollector%(ID)s'%locals())
852        self.addChunk('trans = _callCollector%(ID)s = DummyTransaction()'%locals())
853        self.addChunk('write = _callCollector%(ID)s.response().write'%locals())
854   
855    def endCallRegion(self, regionTitle='CALL'):
856        ID, callDetails = self._callRegionsStack[-1]
857        functionName, initialKwArgs, lineCol = (
858            callDetails.functionName, callDetails.args, callDetails.lineCol)
859
860        def reset(ID=ID):
861            self.addChunk('trans = _orig_trans%(ID)s'%locals())
862            self.addChunk('write = trans.response().write')
863            self.addChunk('self._CHEETAH__isBuffering = _wasBuffering%(ID)s '%locals())
864            self.addChunk('del _wasBuffering%(ID)s'%locals())
865            self.addChunk('del _orig_trans%(ID)s'%locals())
866
867        if not callDetails.usesKeywordArgs:
868            reset()
869            self.addChunk('_callArgVal%(ID)s = _callCollector%(ID)s.response().getvalue()'%locals())
870            self.addChunk('del _callCollector%(ID)s'%locals())
871            if initialKwArgs:
872                initialKwArgs = ', '+initialKwArgs           
873            self.addFilteredChunk('%(functionName)s(_callArgVal%(ID)s%(initialKwArgs)s)'%locals())
874            self.addChunk('del _callArgVal%(ID)s'%locals())
875        else:
876            if initialKwArgs:
877                initialKwArgs = initialKwArgs+', '
878            self._endCallArg()
879            reset()
880            self.addFilteredChunk('%(functionName)s(%(initialKwArgs)s**_callKws%(ID)s)'%locals())
881            self.addChunk('del _callKws%(ID)s'%locals())
882        self.addChunk('## END %(regionTitle)s REGION: '%locals()
883                      +ID
884                      +' of '+functionName
885                      +' at line %s, col %s'%lineCol + ' in the source.')       
886        self.addChunk('')
887        self._callRegionsStack.pop() # attrib of current methodCompiler
888
889    def nextCaptureRegionID(self):
890        return self.nextCacheID()
891
892    def startCaptureRegion(self, assignTo, lineCol):
893        class CaptureDetails: pass
894        captureDetails = CaptureDetails()
895        captureDetails.ID = ID = self.nextCaptureRegionID()
896        captureDetails.assignTo = assignTo
897        captureDetails.lineCol = lineCol
898       
899        self._captureRegionsStack.append((ID,captureDetails)) # attrib of current methodCompiler
900        self.addChunk('## START CAPTURE REGION: '+ID
901                      +' '+assignTo
902                      +' at line %s, col %s'%lineCol + ' in the source.')
903        self.addChunk('_orig_trans%(ID)s = trans'%locals())
904        self.addChunk('_wasBuffering%(ID)s = self._CHEETAH__isBuffering'%locals())
905        self.addChunk('self._CHEETAH__isBuffering = True')
906        self.addChunk('trans = _captureCollector%(ID)s = DummyTransaction()'%locals())
907        self.addChunk('write = _captureCollector%(ID)s.response().write'%locals())
908
909    def endCaptureRegion(self):
910        ID, captureDetails = self._captureRegionsStack.pop()
911        assignTo, lineCol = (captureDetails.assignTo, captureDetails.lineCol)
912        self.addChunk('trans = _orig_trans%(ID)s'%locals())
913        self.addChunk('write = trans.response().write')
914        self.addChunk('self._CHEETAH__isBuffering = _wasBuffering%(ID)s '%locals())
915        self.addChunk('%(assignTo)s = _captureCollector%(ID)s.response().getvalue()'%locals())
916        self.addChunk('del _orig_trans%(ID)s'%locals())
917        self.addChunk('del _captureCollector%(ID)s'%locals())
918        self.addChunk('del _wasBuffering%(ID)s'%locals())
919       
920    def setErrorCatcher(self, errorCatcherName):
921        self.turnErrorCatcherOn()       
922
923        self.addChunk('if self._CHEETAH__errorCatchers.has_key("' + errorCatcherName + '"):')
924        self.indent()
925        self.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["' +
926            errorCatcherName + '"]')
927        self.dedent()
928        self.addChunk('else:')
929        self.indent()
930        self.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["'
931                      + errorCatcherName + '"] = ErrorCatchers.'
932                      + errorCatcherName + '(self)'
933                      )
934        self.dedent()
935
936    def nextFilterRegionID(self):
937        return self.nextCacheID()
938
939    def setTransform(self, transformer, isKlass):
940        self.addChunk('trans = TransformerTransaction()')
941        self.addChunk('trans._response = trans.response()')
942        self.addChunk('trans._response._filter = %s' % transformer)
943        self.addChunk('write = trans._response.write')
944       
945    def setFilter(self, theFilter, isKlass):
946        class FilterDetails:
947            pass
948        filterDetails = FilterDetails()
949        filterDetails.ID = ID = self.nextFilterRegionID()
950        filterDetails.theFilter = theFilter
951        filterDetails.isKlass = isKlass
952        self._filterRegionsStack.append((ID, filterDetails)) # attrib of current methodCompiler
953
954        self.addChunk('_orig_filter%(ID)s = _filter'%locals())
955        if isKlass:
956            self.addChunk('_filter = self._CHEETAH__currentFilter = ' + theFilter.strip() +
957                          '(self).filter')
958        else:
959            if theFilter.lower() == 'none':
960                self.addChunk('_filter = self._CHEETAH__initialFilter')
961            else:
962                # is string representing the name of a builtin filter
963                self.addChunk('filterName = ' + repr(theFilter))
964                self.addChunk('if self._CHEETAH__filters.has_key("' + theFilter + '"):')
965                self.indent()
966                self.addChunk('_filter = self._CHEETAH__currentFilter = self._CHEETAH__filters[filterName]')
967                self.dedent()
968                self.addChunk('else:')
969                self.indent()
970                self.addChunk('_filter = self._CHEETAH__currentFilter'
971                              +' = \\\n\t\t\tself._CHEETAH__filters[filterName] = '
972                              + 'getattr(self._CHEETAH__filtersLib, filterName)(self).filter')
973                self.dedent()
974               
975    def closeFilterBlock(self):
976        ID, filterDetails = self._filterRegionsStack.pop()
977        #self.addChunk('_filter = self._CHEETAH__initialFilter')
978        #self.addChunk('_filter = _orig_filter%(ID)s'%locals())
979        self.addChunk('_filter = self._CHEETAH__currentFilter = _orig_filter%(ID)s'%locals())
980
981class AutoMethodCompiler(MethodCompiler):
982
983    def _setupState(self):
984        MethodCompiler._setupState(self)
985        self._argStringList = [ ("self",None) ]
986        self._streamingEnabled = True
987        self._isClassMethod = None
988        self._isStaticMethod = None
989
990    def _useKWsDictArgForPassingTrans(self):
991        alreadyHasTransArg = [argname for argname,defval in self._argStringList
992                              if argname=='trans']
993        return (self.methodName()!='respond'
994                and not alreadyHasTransArg
995                and self.setting('useKWsDictArgForPassingTrans'))
996
997    def isClassMethod(self):
998        if self._isClassMethod is None:
999            self._isClassMethod = '@classmethod' in self._decorators
1000        return self._isClassMethod
1001
1002    def isStaticMethod(self):
1003        if self._isStaticMethod is None:
1004            self._isStaticMethod = '@staticmethod' in self._decorators
1005        return self._isStaticMethod
1006   
1007    def cleanupState(self):
1008        MethodCompiler.cleanupState(self)
1009        self.commitStrConst()
1010        if self._cacheRegionsStack:
1011            self.endCacheRegion()
1012        if self._callRegionsStack:
1013            self.endCallRegion()
1014           
1015        if self._streamingEnabled:
1016            kwargsName = None
1017            positionalArgsListName = None
1018            for argname,defval in self._argStringList:
1019                if argname.strip().startswith('**'):
1020                    kwargsName = argname.strip().replace('**','')
1021                    break
1022                elif argname.strip().startswith('*'):
1023                    positionalArgsListName = argname.strip().replace('*','')
1024                   
1025            if not kwargsName and self._useKWsDictArgForPassingTrans():
1026                kwargsName = 'KWS'
1027                self.addMethArg('**KWS', None)
1028            self._kwargsName = kwargsName
1029
1030            if not self._useKWsDictArgForPassingTrans():
1031                if not kwargsName and not positionalArgsListName:
1032                    self.addMethArg('trans', 'None')       
1033                else:
1034                    self._streamingEnabled = False
1035               
1036        self._indentLev = self.setting('initialMethIndentLevel')
1037        mainBodyChunks = self._methodBodyChunks
1038        self._methodBodyChunks = []
1039        self._addAutoSetupCode()
1040        self._methodBodyChunks.extend(mainBodyChunks)
1041        self._addAutoCleanupCode()
1042       
1043    def _addAutoSetupCode(self):
1044        if self._initialMethodComment:
1045            self.addChunk(self._initialMethodComment)
1046           
1047        if self._streamingEnabled and not self.isClassMethod() and not self.isStaticMethod():
1048            if self._useKWsDictArgForPassingTrans() and self._kwargsName:
1049                self.addChunk('trans = %s.get("trans")'%self._kwargsName)           
1050            self.addChunk('if (not trans and not self._CHEETAH__isBuffering'
1051                          ' and not callable(self.transaction)):')
1052            self.indent()
1053            self.addChunk('trans = self.transaction'
1054                          ' # is None unless self.awake() was called')
1055            self.dedent()
1056            self.addChunk('if not trans:')
1057            self.indent()
1058            self.addChunk('trans = DummyTransaction()')
1059            if self.setting('autoAssignDummyTransactionToSelf'):
1060                self.addChunk('self.transaction = trans')           
1061            self.addChunk('_dummyTrans = True')
1062            self.dedent()
1063            self.addChunk('else: _dummyTrans = False')
1064        else:
1065            self.addChunk('trans = DummyTransaction()')
1066            self.addChunk('_dummyTrans = True')
1067        self.addChunk('write = trans.response().write')
1068        if self.setting('useNameMapper'):
1069            argNames = [arg[0] for arg in self._argStringList]
1070            allowSearchListAsMethArg = self.setting('allowSearchListAsMethArg')           
1071            if allowSearchListAsMethArg and 'SL' in argNames:
1072                pass
1073            elif allowSearchListAsMethArg and 'searchList' in argNames:
1074                self.addChunk('SL = searchList')
1075            elif not self.isClassMethod() and not self.isStaticMethod():
1076                self.addChunk('SL = self._CHEETAH__searchList')               
1077            else:
1078                self.addChunk('SL = [KWS]')
1079        if self.setting('useFilters'):
1080            if self.isClassMethod() or self.isStaticMethod():
1081                self.addChunk('_filter = lambda x, **kwargs: unicode(x)')
1082            else:
1083                self.addChunk('_filter = self._CHEETAH__currentFilter')
1084        self.addChunk('')
1085        self.addChunk("#" *40)
1086        self.addChunk('## START - generated method body')
1087        self.addChunk('')
1088
1089    def _addAutoCleanupCode(self):
1090        self.addChunk('')
1091        self.addChunk("#" *40)
1092        self.addChunk('## END - generated method body')
1093        self.addChunk('')
1094
1095        if not self._isGenerator:
1096            self.addStop()
1097        self.addChunk('')
1098       
1099    def addStop(self, expr=None):
1100        self.addChunk('return _dummyTrans and trans.response().getvalue() or ""')
1101
1102    def addMethArg(self, name, defVal=None):
1103        self._argStringList.append( (name,defVal) )
1104       
1105    def methodSignature(self):
1106        argStringChunks = []
1107        for arg in self._argStringList:
1108            chunk = arg[0]
1109            if chunk == 'self' and self.isClassMethod():
1110                chunk = 'cls'
1111            if chunk == 'self' and self.isStaticMethod():
1112                # Skip the "self" method for @staticmethod decorators
1113                continue
1114            if not arg[1] == None:
1115                chunk += '=' + arg[1]
1116            argStringChunks.append(chunk)
1117        argString = (', ').join(argStringChunks)
1118
1119        output = []
1120        if self._decorators:
1121            output.append(''.join([self._indent + decorator + '\n'
1122                                   for decorator in self._decorators]))
1123        output.append(self._indent + "def "
1124                      + self.methodName() + "(" +
1125                      argString + "):\n\n")
1126        return ''.join(output)
1127
1128
1129##################################################
1130## CLASS COMPILERS
1131
1132_initMethod_initCheetah = """\
1133if not self._CHEETAH__instanceInitialized:
1134    cheetahKWArgs = {}
1135    allowedKWs = 'searchList namespaces filter filtersLib errorCatcher'.split()
1136    for k,v in KWs.items():
1137        if k in allowedKWs: cheetahKWArgs[k] = v
1138    self._initCheetahInstance(**cheetahKWArgs)
1139""".replace('\n','\n'+' '*8)
1140
1141class ClassCompiler(GenUtils):
1142    methodCompilerClass = AutoMethodCompiler
1143    methodCompilerClassForInit = MethodCompiler
1144       
1145    def __init__(self, className, mainMethodName='respond',
1146                 moduleCompiler=None,
1147                 fileName=None,
1148                 settingsManager=None):
1149
1150        self._settingsManager = settingsManager
1151        self._fileName = fileName
1152        self._className = className
1153        self._moduleCompiler = moduleCompiler
1154        self._mainMethodName = mainMethodName
1155        self._setupState()
1156        methodCompiler = self._spawnMethodCompiler(
1157            mainMethodName,
1158            initialMethodComment='## CHEETAH: main method generated for this template')
1159
1160        self._setActiveMethodCompiler(methodCompiler)
1161        if fileName and self.setting('monitorSrcFile'):
1162            self._addSourceFileMonitoring(fileName)
1163
1164    def setting(self, key):
1165        return self._settingsManager.setting(key)
1166
1167    def __getattr__(self, name):
1168        """Provide access to the methods and attributes of the MethodCompiler
1169        at the top of the activeMethods stack: one-way namespace sharing
1170
1171       
1172        WARNING: Use .setMethods to assign the attributes of the MethodCompiler
1173        from the methods of this class!!! or you will be assigning to attributes
1174        of this object instead."""
1175       
1176        if self.__dict__.has_key(name):
1177            return self.__dict__[name]
1178        elif hasattr(self.__class__, name):
1179            return getattr(self.__class__, name)
1180        elif self._activeMethodsList and hasattr(self._activeMethodsList[-1], name):
1181            return getattr(self._activeMethodsList[-1], name)
1182        else:
1183            raise AttributeError, name
1184
1185    def _setupState(self):
1186        self._classDef = None
1187        self._decoratorsForNextMethod = []
1188        self._activeMethodsList = []        # stack while parsing/generating
1189        self._finishedMethodsList = []      # store by order
1190        self._methodsIndex = {}      # store by name
1191        self._baseClass = 'Template'
1192        self._classDocStringLines = []
1193        # printed after methods in the gen class def:
1194        self._generatedAttribs = ['_CHEETAH__instanceInitialized = False']
1195        self._generatedAttribs.append('_CHEETAH_version = __CHEETAH_version__')
1196        self._generatedAttribs.append(
1197            '_CHEETAH_versionTuple = __CHEETAH_versionTuple__')
1198
1199        if self.setting('addTimestampsToCompilerOutput'):
1200            self._generatedAttribs.append('_CHEETAH_genTime = __CHEETAH_genTime__')
1201            self._generatedAttribs.append('_CHEETAH_genTimestamp = __CHEETAH_genTimestamp__')
1202
1203        self._generatedAttribs.append('_CHEETAH_src = __CHEETAH_src__')
1204        self._generatedAttribs.append(
1205            '_CHEETAH_srcLastModified = __CHEETAH_srcLastModified__')
1206
1207        if self.setting('templateMetaclass'):
1208            self._generatedAttribs.append('__metaclass__ = '+self.setting('templateMetaclass'))
1209        self._initMethChunks = []       
1210        self._blockMetaData = {}
1211        self._errorCatcherCount = 0
1212        self._placeholderToErrorCatcherMap = {}
1213
1214    def cleanupState(self):
1215        while self._activeMethodsList:
1216            methCompiler = self._popActiveMethodCompiler()
1217            self._swallowMethodCompiler(methCompiler)
1218        self._setupInitMethod()
1219        if self._mainMethodName == 'respond':
1220            if self.setting('setup__str__method'):
1221                self._generatedAttribs.append('def __str__(self): return self.respond()')
1222        self.addAttribute('_mainCheetahMethod_for_' + self._className +
1223                           '= ' + repr(self._mainMethodName) )
1224
1225    def _setupInitMethod(self):
1226        __init__ = self._spawnMethodCompiler('__init__',
1227                                             klass=self.methodCompilerClassForInit)
1228        __init__.setMethodSignature("def __init__(self, *args, **KWs)")
1229        __init__.addChunk('super(%s, self).__init__(*args, **KWs)' % self._className)
1230        __init__.addChunk(_initMethod_initCheetah % {'className' : self._className})
1231        for chunk in self._initMethChunks:
1232            __init__.addChunk(chunk)
1233        __init__.cleanupState()
1234        self._swallowMethodCompiler(__init__, pos=0)
1235
1236    def _addSourceFileMonitoring(self, fileName):
1237        # @@TR: this stuff needs auditing for Cheetah 2.0       
1238        # the first bit is added to init
1239        self.addChunkToInit('self._filePath = ' + repr(fileName))
1240        self.addChunkToInit('self._fileMtime = ' + str(getmtime(fileName)) )
1241
1242        # the rest is added to the main output method of the class ('mainMethod')
1243        self.addChunk('if exists(self._filePath) and ' +
1244                      'getmtime(self._filePath) > self._fileMtime:')
1245        self.indent()
1246        self.addChunk('self._compile(file=self._filePath, moduleName='+self._className + ')')
1247        self.addChunk(
1248            'write(getattr(self, self._mainCheetahMethod_for_' + self._className +
1249            ')(trans=trans))')           
1250        self.addStop()
1251        self.dedent()
1252   
1253    def setClassName(self, name):
1254        self._className = name
1255
1256    def className(self):
1257        return self._className
1258
1259    def setBaseClass(self, baseClassName):
1260        self._baseClass = baseClassName
1261               
1262    def setMainMethodName(self, methodName):
1263        if methodName == self._mainMethodName:
1264            return
1265        ## change the name in the methodCompiler and add new reference
1266        mainMethod = self._methodsIndex[self._mainMethodName]
1267        mainMethod.setMethodName(methodName)
1268        self._methodsIndex[methodName] = mainMethod
1269
1270        ## make sure that fileUpdate code still works properly:
1271        chunkToChange = ('write(self.' + self._mainMethodName + '(trans=trans))')
1272        chunks = mainMethod._methodBodyChunks
1273        if chunkToChange in chunks:
1274            for i in range(len(chunks)):
1275                if chunks[i] == chunkToChange:
1276                    chunks[i] = ('write(self.' + methodName + '(trans=trans))')
1277        ## get rid of the old reference and update self._mainMethodName                   
1278        del self._methodsIndex[self._mainMethodName]
1279        self._mainMethodName = methodName
1280
1281    def setMainMethodArgs(self, argsList):
1282        mainMethodCompiler = self._methodsIndex[self._mainMethodName]
1283        for argName, defVal in argsList:
1284            mainMethodCompiler.addMethArg(argName, defVal)
1285       
1286         
1287    def _spawnMethodCompiler(self, methodName, klass=None,
1288                             initialMethodComment=None):
1289        if klass is None:
1290            klass = self.methodCompilerClass
1291
1292        decorators = self._decoratorsForNextMethod or []
1293        self._decoratorsForNextMethod = []
1294        methodCompiler = klass(methodName, classCompiler=self,
1295                               decorators=decorators,
1296                               initialMethodComment=initialMethodComment)
1297        self._methodsIndex[methodName] = methodCompiler
1298        return methodCompiler
1299
1300    def _setActiveMethodCompiler(self, methodCompiler):
1301        self._activeMethodsList.append(methodCompiler)
1302
1303    def _getActiveMethodCompiler(self):
1304        return self._activeMethodsList[-1]
1305
1306    def _popActiveMethodCompiler(self):
1307        return self._activeMethodsList.pop()
1308
1309    def _swallowMethodCompiler(self, methodCompiler, pos=None):
1310        methodCompiler.cleanupState()
1311        if pos==None:
1312            self._finishedMethodsList.append( methodCompiler )
1313        else:
1314            self._finishedMethodsList.insert(pos, methodCompiler)
1315        return methodCompiler
1316
1317    def startMethodDef(self, methodName, argsList, parserComment):
1318        methodCompiler = self._spawnMethodCompiler(
1319            methodName, initialMethodComment=parserComment)
1320        self._setActiveMethodCompiler(methodCompiler)       
1321        for argName, defVal in argsList:
1322            methodCompiler.addMethArg(argName, defVal)
1323       
1324    def _finishedMethods(self):
1325        return self._finishedMethodsList
1326
1327    def addDecorator(self, decoratorExpr):
1328        """Set the decorator to be used with the next method in the source.
1329
1330        See _spawnMethodCompiler() and MethodCompiler for the details of how
1331        this is used.
1332        """
1333        self._decoratorsForNextMethod.append(decoratorExpr)
1334
1335    def addClassDocString(self, line):
1336        self._classDocStringLines.append( line.replace('%','%%'))
1337
1338    def addChunkToInit(self,chunk):
1339        self._initMethChunks.append(chunk)
1340
1341    def addAttribute(self, attribExpr):
1342        ## first test to make sure that the user hasn't used any fancy Cheetah syntax
1343        #  (placeholders, directives, etc.) inside the expression
1344        if attribExpr.find('VFN(') != -1 or attribExpr.find('VFFSL(') != -1:
1345            raise ParseError(self,
1346                             'Invalid #attr directive.' +
1347                             ' It should only contain simple Python literals.')
1348        ## now add the attribute
1349        self._generatedAttribs.append(attribExpr)
1350
1351    def addSuper(self, argsList, parserComment=None):       
1352        className = self._className #self._baseClass
1353        methodName = self._getActiveMethodCompiler().methodName()
1354
1355        argStringChunks = []
1356        for arg in argsList:
1357            chunk = arg[0]
1358            if not arg[1] == None:
1359                chunk += '=' + arg[1]
1360            argStringChunks.append(chunk)
1361        argString = ','.join(argStringChunks)
1362
1363        self.addFilteredChunk(
1364            'super(%(className)s, self).%(methodName)s(%(argString)s)'%locals())
1365
1366    def addErrorCatcherCall(self, codeChunk, rawCode='', lineCol=''):
1367        if self._placeholderToErrorCatcherMap.has_key(rawCode):
1368            methodName = self._placeholderToErrorCatcherMap[rawCode]
1369            if not self.setting('outputRowColComments'):
1370                self._methodsIndex[methodName].addMethDocString(
1371                    'plus at line %s, col %s'%lineCol)
1372            return methodName
1373
1374        self._errorCatcherCount += 1
1375        methodName = '__errorCatcher' + str(self._errorCatcherCount)
1376        self._placeholderToErrorCatcherMap[rawCode] = methodName
1377       
1378        catcherMeth = self._spawnMethodCompiler(
1379            methodName,
1380            klass=MethodCompiler,
1381            initialMethodComment=('## CHEETAH: Generated from ' + rawCode +
1382                                  ' at line %s, col %s'%lineCol + '.')
1383            )       
1384        catcherMeth.setMethodSignature('def ' + methodName +
1385                                       '(self, localsDict={})')
1386                                        # is this use of localsDict right?
1387        catcherMeth.addChunk('try:')
1388        catcherMeth.indent()
1389        catcherMeth.addChunk("return eval('''" + codeChunk +
1390                             "''', globals(), localsDict)")
1391        catcherMeth.dedent()
1392        catcherMeth.addChunk('except self._CHEETAH__errorCatcher.exceptions(), e:')
1393        catcherMeth.indent()       
1394        catcherMeth.addChunk("return self._CHEETAH__errorCatcher.warn(exc_val=e, code= " +
1395                             repr(codeChunk) + " , rawCode= " +
1396                             repr(rawCode) + " , lineCol=" + str(lineCol) +")")
1397       
1398        catcherMeth.cleanupState()
1399       
1400        self._swallowMethodCompiler(catcherMeth)
1401        return methodName
1402
1403    def closeDef(self):
1404        self.commitStrConst()
1405        methCompiler = self._popActiveMethodCompiler()
1406        self._swallowMethodCompiler(methCompiler)
1407
1408    def closeBlock(self):
1409        self.commitStrConst()
1410        methCompiler = self._popActiveMethodCompiler()
1411        methodName = methCompiler.methodName()
1412        if self.setting('includeBlockMarkers'):
1413            endMarker = self.setting('blockMarkerEnd')
1414            methCompiler.addStrConst(endMarker[0] + methodName + endMarker[1])
1415        self._swallowMethodCompiler(methCompiler)
1416       
1417        #metaData = self._blockMetaData[methodName]
1418        #rawDirective = metaData['raw']
1419        #lineCol = metaData['lineCol']
1420       
1421        ## insert the code to call the block, caching if #cache directive is on
1422        codeChunk = 'self.' + methodName + '(trans=trans)'
1423        self.addChunk(codeChunk)
1424       
1425        #self.appendToPrevChunk(' # generated from ' + repr(rawDirective) )
1426        #if self.setting('outputRowColComments'):
1427        #    self.appendToPrevChunk(' at line %s, col %s' % lineCol + '.')
1428
1429
1430    ## code wrapping methods
1431   
1432    def classDef(self):
1433        if self._classDef:
1434            return self._classDef
1435        else:
1436            return self.wrapClassDef()
1437
1438    __str__ = classDef
1439    __unicode__ = classDef
1440   
1441    def wrapClassDef(self):
1442        ind = self.setting('indentationStep')
1443        classDefChunks = [self.classSignature(),
1444                          self.classDocstring(),
1445                          ]
1446        def addMethods():
1447            classDefChunks.extend([
1448                ind + '#'*50,
1449                ind + '## CHEETAH GENERATED METHODS',
1450                '\n',
1451                self.methodDefs(),
1452                ])
1453        def addAttributes():
1454            classDefChunks.extend([
1455                ind + '#'*50,
1456                ind + '## CHEETAH GENERATED ATTRIBUTES',
1457                '\n',
1458                self.attributes(),
1459                ])           
1460        if self.setting('outputMethodsBeforeAttributes'):
1461            addMethods()
1462            addAttributes()
1463        else:
1464            addAttributes()
1465            addMethods()
1466           
1467        classDef = '\n'.join(classDefChunks)
1468        self._classDef = classDef
1469        return classDef
1470
1471
1472    def classSignature(self):
1473        return "class %s(%s):" % (self.className(), self._baseClass)
1474       
1475    def classDocstring(self):
1476        if not self._classDocStringLines:
1477            return ''
1478        ind = self.setting('indentationStep')
1479        docStr = ('%(ind)s"""\n%(ind)s' +
1480                  '\n%(ind)s'.join(self._classDocStringLines) +
1481                  '\n%(ind)s"""\n'
1482                  ) % {'ind':ind}
1483        return  docStr
1484
1485    def methodDefs(self):
1486        methodDefs = [methGen.methodDef() for methGen in self._finishedMethods()]
1487        return '\n\n'.join(methodDefs)
1488
1489    def attributes(self):
1490        attribs = [self.setting('indentationStep') + str(attrib)
1491                      for attrib in self._generatedAttribs ]
1492        return '\n\n'.join(attribs)
1493 
1494class AutoClassCompiler(ClassCompiler):
1495    pass
1496
1497##################################################
1498## MODULE COMPILERS
1499
1500class ModuleCompiler(SettingsManager, GenUtils):
1501
1502    parserClass = Parser
1503    classCompilerClass = AutoClassCompiler
1504   
1505    def __init__(self, source=None, file=None,
1506                 moduleName='DynamicallyCompiledCheetahTemplate',                 
1507                 mainClassName=None, # string
1508                 mainMethodName=None, # string
1509                 baseclassName=None, # string
1510                 extraImportStatements=None, # list of strings
1511                 settings=None # dict
1512                 ):
1513        super(ModuleCompiler, self).__init__()
1514        if settings:
1515            self.updateSettings(settings)
1516        # disable useStackFrames if the C version of NameMapper isn't compiled
1517        # it's painfully slow in the Python version and bites Windows users all
1518        # the time:
1519        if not NameMapper.C_VERSION:
1520            if not sys.platform.startswith('java'):
1521                warnings.warn(
1522                    "\nYou don't have the C version of NameMapper installed! "
1523                    "I'm disabling Cheetah's useStackFrames option as it is "
1524                    "painfully slow with the Python version of NameMapper. "
1525                    "You should get a copy of Cheetah with the compiled C version of NameMapper."
1526                    )
1527            self.setSetting('useStackFrames', False)                   
1528
1529        self._compiled = False
1530        self._moduleName = moduleName
1531        if not mainClassName:
1532            self._mainClassName = moduleName
1533        else:
1534            self._mainClassName = mainClassName
1535        self._mainMethodNameArg = mainMethodName
1536        if mainMethodName:
1537            self.setSetting('mainMethodName', mainMethodName)
1538        self._baseclassName = baseclassName
1539       
1540        self._filePath = None
1541        self._fileMtime = None
1542       
1543        if source and file:
1544            raise TypeError("Cannot compile from a source string AND file.")
1545        elif isinstance(file, basestring): # it's a filename.
1546            f = open(file) # Raises IOError.
1547            source = f.read()
1548            f.close()
1549            self._filePath = file
1550            self._fileMtime = os.path.getmtime(file)
1551        elif hasattr(file, 'read'):
1552            source = file.read()  # Can't set filename or mtime--they're not accessible.
1553        elif file:
1554            raise TypeError("'file' argument must be a filename string or file-like object")
1555               
1556        if self._filePath:
1557            self._fileDirName, self._fileBaseName = os.path.split(self._filePath)
1558            self._fileBaseNameRoot, self._fileBaseNameExt = os.path.splitext(self._fileBaseName)
1559
1560        if not isinstance(source, basestring):
1561            source = unicode(source)
1562            # by converting to string here we allow objects such as other Templates
1563            # to be passed in
1564
1565        # Handle the #indent directive by converting it to other directives.
1566        # (Over the long term we'll make it a real directive.)
1567        if source == "":
1568            warnings.warn("You supplied an empty string for the source!", )
1569       
1570        else:
1571            unicodeMatch = unicodeDirectiveRE.search(source)
1572            encodingMatch = encodingDirectiveRE.match(source)
1573            if unicodeMatch:
1574                if encodingMatch:
1575                    raise ParseError(
1576                        self, "#encoding and #unicode are mutually exclusive! "
1577                        "Use one or the other.")
1578                source = unicodeDirectiveRE.sub('', source)
1579                if isinstance(source, str):
1580                    encoding = unicodeMatch.group(1) or 'ascii'
1581                    source = unicode(source, encoding)
1582            elif encodingMatch:
1583                encodings = encodingMatch.groups()
1584                if len(encodings):
1585                    encoding = encodings[0]
1586                    source = source.decode(encoding)
1587            else:
1588                source = unicode(source)
1589
1590        if source.find('#indent') != -1: #@@TR: undocumented hack
1591            source = indentize(source)
1592
1593        self._parser = self.parserClass(source, filename=self._filePath, compiler=self)
1594        self._setupCompilerState()
1595       
1596    def __getattr__(self, name):
1597        """Provide one-way access to the methods and attributes of the
1598        ClassCompiler, and thereby the MethodCompilers as well.
1599
1600        WARNING: Use .setMethods to assign the attributes of the ClassCompiler
1601        from the methods of this class!!! or you will be assigning to attributes
1602        of this object instead.
1603        """
1604        if self.__dict__.has_key(name):
1605            return self.__dict__[name]
1606        elif hasattr(self.__class__, name):
1607            return getattr(self.__class__, name)
1608        elif self._activeClassesList and hasattr(self._activeClassesList[-1], name):
1609            return getattr(self._activeClassesList[-1], name)
1610        else:
1611            raise AttributeError, name
1612
1613    def _initializeSettings(self):
1614        self.updateSettings(copy.deepcopy(DEFAULT_COMPILER_SETTINGS))
1615       
1616    def _setupCompilerState(self):
1617        self._activeClassesList = []
1618        self._finishedClassesList = []      # listed by ordered
1619        self._finishedClassIndex = {}  # listed by name
1620        self._moduleDef = None
1621        self._moduleShBang = '#!/usr/bin/env python'
1622        self._moduleEncoding = 'ascii'
1623        self._moduleEncodingStr = ''
1624        self._moduleHeaderLines = []
1625        self._moduleDocStringLines = []
1626        self._specialVars = {}
1627        self._importStatements = [
1628            "import sys",
1629            "import os",
1630            "import os.path",
1631            "import __builtin__",
1632            "from os.path import getmtime, exists",
1633            "import time",
1634            "import types",
1635            "from Cheetah.Version import MinCompatibleVersion as RequiredCheetahVersion",           
1636            "from Cheetah.Version import MinCompatibleVersionTuple as RequiredCheetahVersionTuple",
1637            "from Cheetah.Template import Template",
1638            "from Cheetah.DummyTransaction import *",
1639            "from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList",
1640            "from Cheetah.CacheRegion import CacheRegion",
1641            "import Cheetah.Filters as Filters",
1642            "import Cheetah.ErrorCatchers as ErrorCatchers",
1643            ]       
1644
1645        self._importedVarNames = ['sys',
1646                                  'os',
1647                                  'os.path',
1648                                  'time',
1649                                  'types',
1650                                  'Template',
1651                                  'DummyTransaction',
1652                                  'NotFound',
1653                                  'Filters',
1654                                  'ErrorCatchers',
1655                                  'CacheRegion',
1656                                  ]
1657       
1658        self._moduleConstants = [
1659            "VFFSL=valueFromFrameOrSearchList",
1660            "VFSL=valueFromSearchList",
1661            "VFN=valueForName",
1662            "currentTime=time.time",
1663            ]
1664       
1665    def compile(self):
1666        classCompiler = self._spawnClassCompiler(self._mainClassName)           
1667        if self._baseclassName:
1668            classCompiler.setBaseClass(self._baseclassName)
1669        self._addActiveClassCompiler(classCompiler)
1670        self._parser.parse()
1671        self._swallowClassCompiler(self._popActiveClassCompiler())
1672        self._compiled = True
1673        self._parser.cleanup()
1674       
1675    def _spawnClassCompiler(self, className, klass=None):
1676        if klass is None:
1677            klass = self.classCompilerClass
1678        classCompiler = klass(className,
1679                              moduleCompiler=self,
1680                              mainMethodName=self.setting('mainMethodName'),
1681                              fileName=self._filePath,
1682                              settingsManager=self,
1683                              )
1684        return classCompiler
1685
1686    def _addActiveClassCompiler(self, classCompiler):
1687        self._activeClassesList.append(classCompiler)
1688
1689    def _getActiveClassCompiler(self):
1690        return self._activeClassesList[-1]
1691
1692    def _popActiveClassCompiler(self):
1693        return self._activeClassesList.pop()
1694
1695    def _swallowClassCompiler(self, classCompiler):
1696        classCompiler.cleanupState()
1697        self._finishedClassesList.append( classCompiler )
1698        self._finishedClassIndex[classCompiler.className()] = classCompiler
1699        return classCompiler
1700
1701    def _finishedClasses(self):
1702        return self._finishedClassesList
1703
1704    def importedVarNames(self):
1705        return self._importedVarNames
1706   
1707    def addImportedVarNames(self, varNames, raw_statement=None):
1708        settings = self.settings()
1709        if not varNames:
1710            return
1711        if not settings.get('useLegacyImportMode'):
1712            if raw_statement and getattr(self, '_methodBodyChunks'):
1713                self.addChunk(raw_statement)
1714        else:
1715            self._importedVarNames.extend(varNames)
1716
1717    ## methods for adding stuff to the module and class definitions
1718
1719    def setBaseClass(self, baseClassName):
1720        if self._mainMethodNameArg:
1721            self.setMainMethodName(self._mainMethodNameArg)
1722        else:
1723            self.setMainMethodName(self.setting('mainMethodNameForSubclasses'))
1724       
1725        if self.setting('handlerForExtendsDirective'):
1726            handler = self.setting('handlerForExtendsDirective')
1727            baseClassName = handler(compiler=self, baseClassName=baseClassName)
1728            self._getActiveClassCompiler().setBaseClass(baseClassName)
1729        elif (not self.setting('autoImportForExtendsDirective')
1730            or baseClassName=='object' or baseClassName in self.importedVarNames()):
1731            self._getActiveClassCompiler().setBaseClass(baseClassName)
1732            # no need to import
1733        else:
1734            ##################################################
1735            ## If the #extends directive contains a classname or modulename that isn't
1736            #  in self.importedVarNames() already, we assume that we need to add
1737            #  an implied 'from ModName import ClassName' where ModName == ClassName.
1738            #  - This is the case in WebKit servlet modules.
1739            #  - We also assume that the final . separates the classname from the
1740            #    module name.  This might break if people do something really fancy
1741            #    with their dots and namespaces.
1742            baseclasses = baseClassName.split(',')
1743            for klass in baseclasses:
1744                chunks = klass.split('.')
1745                if len(chunks)==1:
1746                    self._getActiveClassCompiler().setBaseClass(klass)
1747                    if klass not in self.importedVarNames():
1748                        modName = klass
1749                        # we assume the class name to be the module name
1750                        # and that it's not a builtin:
1751                        importStatement = "from %s import %s" % (modName, klass)
1752                        self.addImportStatement(importStatement)
1753                        self.addImportedVarNames((klass,))
1754                else:
1755                    needToAddImport = True
1756                    modName = chunks[0]
1757                    #print chunks, ':', self.importedVarNames()
1758                    for chunk in chunks[1:-1]:
1759                        if modName in self.importedVarNames():
1760                            needToAddImport = False
1761                            finalBaseClassName = klass.replace(modName+'.', '')
1762                            self._getActiveClassCompiler().setBaseClass(finalBaseClassName)
1763                            break
1764                        else:
1765                            modName += '.'+chunk                       
1766                    if needToAddImport:
1767                        modName, finalClassName = '.'.join(chunks[:-1]), chunks[-1]               
1768                        #if finalClassName != chunks[:-1][-1]:
1769                        if finalClassName != chunks[-2]:
1770                            # we assume the class name to be the module name
1771                            modName = '.'.join(chunks)
1772                        self._getActiveClassCompiler().setBaseClass(finalClassName)                       
1773                        importStatement = "from %s import %s" % (modName, finalClassName)
1774                        self.addImportStatement(importStatement)
1775                        self.addImportedVarNames( [finalClassName,] )
1776           
1777    def setCompilerSetting(self, key, valueExpr):
1778        self.setSetting(key, eval(valueExpr) )
1779        self._parser.configureParser()
1780
1781    def setCompilerSettings(self, keywords, settingsStr):
1782        KWs = keywords
1783        merge = True
1784        if 'nomerge' in KWs:
1785            merge = False
1786           
1787        if 'reset' in KWs:
1788            # @@TR: this is actually caught by the parser at the moment.
1789            # subject to change in the future
1790            self._initializeSettings()
1791            self._parser.configureParser()
1792            return
1793        elif 'python' in KWs:
1794            settingsReader = self.updateSettingsFromPySrcStr
1795            # this comes from SettingsManager
1796        else:
1797            # this comes from SettingsManager
1798            settingsReader = self.updateSettingsFromConfigStr
1799
1800        settingsReader(settingsStr)
1801        self._parser.configureParser()
1802       
1803    def setShBang(self, shBang):
1804        self._moduleShBang = shBang
1805   
1806    def setModuleEncoding(self, encoding):
1807        self._moduleEncoding = encoding
1808
1809    def getModuleEncoding(self):
1810        return self._moduleEncoding
1811
1812    def addModuleHeader(self, line):
1813        """Adds a header comment to the top of the generated module.
1814        """
1815        self._moduleHeaderLines.append(line)
1816       
1817    def addModuleDocString(self, line):       
1818        """Adds a line to the generated module docstring.
1819        """
1820        self._moduleDocStringLines.append(line)
1821
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.
1825        """
1826        self._moduleConstants.append(line)
1827
1828    def addSpecialVar(self, basename, contents, includeUnderscores=True):
1829        """Adds module __specialConstant__ to the module globals.
1830        """
1831        name = includeUnderscores and '__'+basename+'__' or basename
1832        self._specialVars[name] = contents.strip()
1833
1834    def addImportStatement(self, impStatement):
1835        settings = self.settings()
1836        if not self._methodBodyChunks or settings.get('useLegacyImportMode'):
1837            # In the case where we are importing inline in the middle of a source block
1838            # we don't want to inadvertantly import the module at the top of the file either
1839            self._importStatements.append(impStatement)
1840
1841        #@@TR 2005-01-01: there's almost certainly a cleaner way to do this!
1842        importVarNames = impStatement[impStatement.find('import') + len('import'):].split(',')
1843        importVarNames = [var.split()[-1] for var in importVarNames] # handles aliases
1844        importVarNames = [var for var in importVarNames if not var == '*']
1845        self.addImportedVarNames(importVarNames, raw_statement=impStatement) #used by #extend for auto-imports
1846
1847    def addAttribute(self, attribName, expr):
1848        self._getActiveClassCompiler().addAttribute(attribName + ' =' + expr)
1849       
1850    def addComment(self, comm):
1851        if re.match(r'#+$',comm):      # skip bar comments
1852            return
1853       
1854        specialVarMatch = specialVarRE.match(comm)
1855        if specialVarMatch:
1856            # @@TR: this is a bit hackish and is being replaced with
1857            # #set module varName = ...
1858            return self.addSpecialVar(specialVarMatch.group(1),
1859                                      comm[specialVarMatch.end():])
1860        elif comm.startswith('doc:'):
1861            addLine = self.addMethDocString
1862            comm = comm[len('doc:'):].strip()
1863        elif comm.startswith('doc-method:'):
1864            addLine = self.addMethDocString
1865            comm = comm[len('doc-method:'):].strip()
1866        elif comm.startswith('doc-module:'):
1867            addLine = self.addModuleDocString
1868            comm = comm[len('doc-module:'):].strip()
1869        elif comm.startswith('doc-class:'):
1870            addLine = self.addClassDocString
1871            comm = comm[len('doc-class:'):].strip()
1872        elif comm.startswith('header:'):
1873            addLine = self.addModuleHeader
1874            comm = comm[len('header:'):].strip()
1875        else:
1876            addLine = self.addMethComment
1877
1878        for line in comm.splitlines():
1879            addLine(line)
1880
1881    ## methods for module code wrapping
1882   
1883    def getModuleCode(self):
1884        if not self._compiled:
1885            self.compile()
1886        if self._moduleDef:
1887            return self._moduleDef
1888        else:
1889            return self.wrapModuleDef()
1890       
1891    __str__ = getModuleCode
1892
1893    def wrapModuleDef(self):
1894        self.addSpecialVar('CHEETAH_docstring', self.setting('defDocStrMsg'))
1895        self.addModuleGlobal('__CHEETAH_version__ = %r'%Version)
1896        self.addModuleGlobal('__CHEETAH_versionTuple__ = %r'%(VersionTuple,))       
1897        if self.setting('addTimestampsToCompilerOutput'):
1898            self.addModuleGlobal('__CHEETAH_genTime__ = %r'%time.time())
1899            self.addModuleGlobal('__CHEETAH_genTimestamp__ = %r'%self.timestamp())
1900        if self._filePath:
1901            timestamp = self.timestamp(self._fileMtime)
1902            self.addModuleGlobal('__CHEETAH_src__ = %r'%self._filePath)
1903            self.addModuleGlobal('__CHEETAH_srcLastModified__ = %r'%timestamp)
1904        else:
1905            self.addModuleGlobal('__CHEETAH_src__ = None')
1906            self.addModuleGlobal('__CHEETAH_srcLastModified__ = None')           
1907
1908        moduleDef = """%(header)s
1909%(docstring)s
1910
1911##################################################
1912## DEPENDENCIES
1913%(imports)s
1914
1915##################################################
1916## MODULE CONSTANTS
1917%(constants)s
1918%(specialVars)s
1919
1920if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple:
1921    raise AssertionError(
1922      'This template was compiled with Cheetah version'
1923      ' %%s. Templates compiled before version %%s must be recompiled.'%%(
1924         __CHEETAH_version__, RequiredCheetahVersion))
1925
1926##################################################
1927## CLASSES
1928
1929%(classes)s
1930
1931## END CLASS DEFINITION
1932
1933if not hasattr(%(mainClassName)s, '_initCheetahAttributes'):
1934    templateAPIClass = getattr(%(mainClassName)s, '_CHEETAH_templateClass', Template)
1935    templateAPIClass._addCheetahPlumbingCodeToClass(%(mainClassName)s)
1936
1937%(footer)s
1938""" %   {'header':self.moduleHeader(),
1939         'docstring':self.moduleDocstring(),
1940         'specialVars':self.specialVars(),
1941         'imports':self.importStatements(),
1942         'constants':self.moduleConstants(),
1943         'classes':self.classDefs(),
1944         'footer':self.moduleFooter(),
1945         'mainClassName':self._mainClassName,
1946         }
1947       
1948        self._moduleDef = moduleDef
1949        return moduleDef
1950
1951    def timestamp(self, theTime=None):
1952        if not theTime:
1953            theTime = time.time()
1954        return time.asctime(time.localtime(theTime))
1955   
1956    def moduleHeader(self):
1957        header = self._moduleShBang + '\n'
1958        header += self._moduleEncodingStr + '\n'
1959        if self._moduleHeaderLines:
1960            offSet = self.setting('commentOffset')
1961       
1962            header += (
1963                '#' + ' '*offSet +
1964                ('\n#'+ ' '*offSet).join(self._moduleHeaderLines) + '\n')
1965
1966        return header
1967
1968    def moduleDocstring(self):
1969        if not self._moduleDocStringLines:
1970            return ''
1971       
1972        return ('"""' +
1973                '\n'.join(self._moduleDocStringLines) +
1974                '\n"""\n')
1975
1976    def specialVars(self):
1977        chunks = []
1978        theVars = self._specialVars
1979        keys = theVars.keys()
1980        keys.sort()
1981        for key in keys:
1982            chunks.append(key + ' = ' + repr(theVars[key])  )
1983        return '\n'.join(chunks)
1984       
1985    def importStatements(self):
1986        return '\n'.join(self._importStatements)
1987       
1988    def moduleConstants(self):
1989        return '\n'.join(self._moduleConstants)
1990
1991    def classDefs(self):
1992        classDefs = [klass.classDef() for klass in self._finishedClasses()]
1993        return '\n\n'.join(classDefs)
1994
1995    def moduleFooter(self):
1996        return """
1997# CHEETAH was developed by Tavis Rudd and Mike Orr
1998# with code, advice and input from many other volunteers.
1999# For more information visit http://www.CheetahTemplate.org/
2000
2001##################################################
2002## if run from command line:
2003if __name__ == '__main__':
2004    from Cheetah.TemplateCmdLineIface import CmdLineIface
2005    CmdLineIface(templateObj=%(className)s()).run()
2006
2007""" % {'className':self._mainClassName}
2008
2009
2010##################################################
2011## Make Compiler an alias for ModuleCompiler
2012   
2013Compiler = ModuleCompiler
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。