"Fossies" - the Fresh Open Source Software Archive

Member "cheetah3-3.2.6.post2/Cheetah/Compiler.py" (20 Apr 2021, 82955 Bytes) of package /linux/www/cheetah3-3.2.6.post2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "Compiler.py" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 3-3.2.2_vs_3-3.2.3.

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