Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Change tool name to healthchecker

Also, fix the cheetah package upper/lower case issue
when using in case sensitive environment such as linux
  • Loading branch information...
commit 2c1724eac2bfe2a7b7283f5cb9abe0d1494897bc 1 parent b7d35d1
@bcui6611 bcui6611 authored
Showing with 3,752 additions and 53 deletions.
  1. 0  {cheetah → Cheetah}/CacheRegion.py
  2. 0  {cheetah → Cheetah}/CacheStore.py
  3. +634 −0 Cheetah/CheetahWrapper.py
  4. +2,014 −0 Cheetah/Compiler.py
  5. 0  {cheetah → Cheetah}/DirectiveAnalyzer.py
  6. 0  {cheetah → Cheetah}/Django.py
  7. 0  {cheetah → Cheetah}/DummyTransaction.py
  8. 0  {cheetah → Cheetah}/ErrorCatchers.py
  9. 0  {cheetah → Cheetah}/FileUtils.py
  10. 0  {cheetah → Cheetah}/Filters.py
  11. 0  {cheetah → Cheetah}/ImportHooks.py
  12. 0  {cheetah → Cheetah}/ImportManager.py
  13. 0  {cheetah → Cheetah}/Macros/I18n.py
  14. 0  {cheetah/Utils → Cheetah/Macros}/__init__.py
  15. +366 −0 Cheetah/NameMapper.py
  16. 0  {cheetah → Cheetah}/Parser.py
  17. 0  {cheetah → Cheetah}/Servlet.py
  18. 0  {cheetah → Cheetah}/SettingsManager.py
  19. 0  {cheetah → Cheetah}/SourceReader.py
  20. 0  {cheetah → Cheetah}/Template.py
  21. 0  {cheetah → Cheetah}/TemplateCmdLineIface.py
  22. 0  {cheetah → Cheetah}/Templates/SkeletonPage.py
  23. 0  {cheetah → Cheetah}/Templates/SkeletonPage.tmpl
  24. 0  {cheetah → Cheetah}/Templates/_SkeletonPage.py
  25. 0  {cheetah → Cheetah}/Templates/__init__.py
  26. 0  {cheetah → Cheetah}/Tests/Analyzer.py
  27. 0  {cheetah → Cheetah}/Tests/CheetahWrapper.py
  28. 0  {cheetah → Cheetah}/Tests/Cheps.py
  29. 0  {cheetah → Cheetah}/Tests/Filters.py
  30. 0  {cheetah → Cheetah}/Tests/Misc.py
  31. 0  {cheetah → Cheetah}/Tests/NameMapper.py
  32. 0  {cheetah → Cheetah}/Tests/Parser.py
  33. 0  {cheetah → Cheetah}/Tests/Performance.py
  34. 0  {cheetah → Cheetah}/Tests/Regressions.py
  35. 0  {cheetah → Cheetah}/Tests/SyntaxAndOutput.py
  36. 0  {cheetah → Cheetah}/Tests/Template.py
  37. 0  {cheetah → Cheetah}/Tests/Test.py
  38. 0  {cheetah → Cheetah}/Tests/Unicode.py
  39. 0  {cheetah/Tools/turbocheetah/tests → Cheetah/Tests}/__init__.py
  40. 0  {cheetah → Cheetah}/Tests/xmlrunner.py
  41. 0  {cheetah → Cheetah}/Tools/CGITemplate.py
  42. 0  {cheetah → Cheetah}/Tools/MondoReport.py
  43. 0  {cheetah → Cheetah}/Tools/MondoReportDoc.txt
  44. 0  {cheetah → Cheetah}/Tools/RecursiveNull.py
  45. 0  {cheetah → Cheetah}/Tools/SiteHierarchy.py
  46. 0  {cheetah → Cheetah}/Tools/__init__.py
  47. 0  {cheetah → Cheetah}/Tools/turbocheetah/__init__.py
  48. 0  {cheetah → Cheetah}/Tools/turbocheetah/cheetahsupport.py
  49. 0  {cheetah/Tests → Cheetah/Tools/turbocheetah/tests}/__init__.py
  50. 0  {cheetah → Cheetah}/Tools/turbocheetah/tests/test_template.py
  51. 0  {cheetah → Cheetah}/Unspecified.py
  52. 0  {cheetah → Cheetah}/Utils/Indenter.py
  53. 0  {cheetah → Cheetah}/Utils/Misc.py
  54. 0  {cheetah → Cheetah}/Utils/WebInputMixin.py
  55. 0  {cheetah/Macros → Cheetah/Utils}/__init__.py
  56. 0  {cheetah → Cheetah}/Utils/htmlDecode.py
  57. 0  {cheetah → Cheetah}/Utils/htmlEncode.py
  58. 0  {cheetah → Cheetah}/Utils/statprof.py
  59. +1 −1  {cheetah → Cheetah}/Version.py
  60. 0  {cheetah → Cheetah}/__init__.py
  61. +93 −0 Cheetah/c/_filters.c
  62. +505 −0 Cheetah/c/_namemapper.c
  63. +52 −0 Cheetah/c/_template.c
  64. +78 −0 Cheetah/c/cheetah.h
  65. +5 −1 {cheetah → Cheetah}/convertTmplPathToModuleName.py
  66. +3 −3 Makefile.am
  67. +1 −1  analyzer.py
  68. +0 −47 cheetah/c/Cheetah.h
  69. 0  healthChecker → healthchecker
View
0  cheetah/CacheRegion.py → Cheetah/CacheRegion.py
File renamed without changes
View
0  cheetah/CacheStore.py → Cheetah/CacheStore.py
File renamed without changes
View
634 Cheetah/CheetahWrapper.py
@@ -0,0 +1,634 @@
+# $Id: CheetahWrapper.py,v 1.26 2007/10/02 01:22:04 tavis_rudd Exp $
+"""Cheetah command-line interface.
+
+2002-09-03 MSO: Total rewrite.
+2002-09-04 MSO: Bugfix, compile command was using wrong output ext.
+2002-11-08 MSO: Another rewrite.
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com> and Mike Orr <sluggoster@gmail.com>>
+Version: $Revision: 1.26 $
+Start Date: 2001/03/30
+Last Revision Date: $Date: 2007/10/02 01:22:04 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com> and Mike Orr <sluggoster@gmail.com>"
+__revision__ = "$Revision: 1.26 $"[11:-2]
+
+import getopt, glob, os, pprint, re, shutil, sys
+import cPickle as pickle
+from optparse import OptionParser
+
+from Cheetah.Version import Version
+from Cheetah.Template import Template, DEFAULT_COMPILER_SETTINGS
+from Cheetah.Utils.Misc import mkdirsWithPyInitFiles
+
+optionDashesRE = re.compile( R"^-{1,2}" )
+moduleNameRE = re.compile( R"^[a-zA-Z_][a-zA-Z_0-9]*$" )
+
+def fprintfMessage(stream, format, *args):
+ if format[-1:] == '^':
+ format = format[:-1]
+ else:
+ format += '\n'
+ if args:
+ message = format % args
+ else:
+ message = format
+ stream.write(message)
+
+class Error(Exception):
+ pass
+
+
+class Bundle:
+ """Wrap the source, destination and backup paths in one neat little class.
+ Used by CheetahWrapper.getBundles().
+ """
+ def __init__(self, **kw):
+ self.__dict__.update(kw)
+
+ def __repr__(self):
+ return "<Bundle %r>" % self.__dict__
+
+
+##################################################
+## USAGE FUNCTION & MESSAGES
+
+def usage(usageMessage, errorMessage="", out=sys.stderr):
+ """Write help text, an optional error message, and abort the program.
+ """
+ out.write(WRAPPER_TOP)
+ out.write(usageMessage)
+ exitStatus = 0
+ if errorMessage:
+ out.write('\n')
+ out.write("*** USAGE ERROR ***: %s\n" % errorMessage)
+ exitStatus = 1
+ sys.exit(exitStatus)
+
+
+WRAPPER_TOP = """\
+ __ ____________ __
+ \ \/ \/ /
+ \/ * * \/ CHEETAH %(Version)s Command-Line Tool
+ \ | /
+ \ ==----== / by Tavis Rudd <tavis@damnsimple.com>
+ \__________/ and Mike Orr <sluggoster@gmail.com>
+
+""" % globals()
+
+
+HELP_PAGE1 = """\
+USAGE:
+------
+ cheetah compile [options] [FILES ...] : Compile template definitions
+ cheetah fill [options] [FILES ...] : Fill template definitions
+ cheetah help : Print this help message
+ cheetah options : Print options help message
+ cheetah test [options] : Run Cheetah's regression tests
+ : (same as for unittest)
+ cheetah version : Print Cheetah version number
+
+You may abbreviate the command to the first letter; e.g., 'h' == 'help'.
+If FILES is a single "-", read standard input and write standard output.
+Run "cheetah options" for the list of valid options.
+"""
+
+##################################################
+## CheetahWrapper CLASS
+
+class CheetahWrapper(object):
+ MAKE_BACKUPS = True
+ BACKUP_SUFFIX = ".bak"
+ _templateClass = None
+ _compilerSettings = None
+
+ def __init__(self):
+ self.progName = None
+ self.command = None
+ self.opts = None
+ self.pathArgs = None
+ self.sourceFiles = []
+ self.searchList = []
+ self.parser = None
+
+ ##################################################
+ ## MAIN ROUTINE
+
+ def main(self, argv=None):
+ """The main program controller."""
+
+ if argv is None:
+ argv = sys.argv
+
+ # Step 1: Determine the command and arguments.
+ try:
+ self.progName = progName = os.path.basename(argv[0])
+ self.command = command = optionDashesRE.sub("", argv[1])
+ if command == 'test':
+ self.testOpts = argv[2:]
+ else:
+ self.parseOpts(argv[2:])
+ except IndexError:
+ usage(HELP_PAGE1, "not enough command-line arguments")
+
+ # Step 2: Call the command
+ meths = (self.compile, self.fill, self.help, self.options,
+ self.test, self.version)
+ for meth in meths:
+ methName = meth.__name__
+ # Or meth.im_func.func_name
+ # Or meth.func_name (Python >= 2.1 only, sometimes works on 2.0)
+ methInitial = methName[0]
+ if command in (methName, methInitial):
+ sys.argv[0] += (" " + methName)
+ # @@MO: I don't necessarily agree sys.argv[0] should be
+ # modified.
+ meth()
+ return
+ # If none of the commands matched.
+ usage(HELP_PAGE1, "unknown command '%s'" % command)
+
+ def parseOpts(self, args):
+ C, D, W = self.chatter, self.debug, self.warn
+ self.isCompile = isCompile = self.command[0] == 'c'
+ defaultOext = isCompile and ".py" or ".html"
+ self.parser = OptionParser()
+ pao = self.parser.add_option
+ pao("--idir", action="store", dest="idir", default='', help='Input directory (defaults to current directory)')
+ pao("--odir", action="store", dest="odir", default="", help='Output directory (defaults to current directory)')
+ pao("--iext", action="store", dest="iext", default=".tmpl", help='File input extension (defaults: compile: .tmpl, fill: .tmpl)')
+ pao("--oext", action="store", dest="oext", default=defaultOext, help='File output extension (defaults: compile: .py, fill: .html)')
+ pao("-R", action="store_true", dest="recurse", default=False, help='Recurse through subdirectories looking for input files')
+ pao("--stdout", "-p", action="store_true", dest="stdout", default=False, help='Send output to stdout instead of writing to a file')
+ pao("--quiet", action="store_false", dest="verbose", default=True, help='Do not print informational messages to stdout')
+ pao("--debug", action="store_true", dest="debug", default=False, help='Print diagnostic/debug information to stderr')
+ pao("--env", action="store_true", dest="env", default=False, help='Pass the environment into the search list')
+ pao("--pickle", action="store", dest="pickle", default="", help='Unpickle FILE and pass it through in the search list')
+ pao("--flat", action="store_true", dest="flat", default=False, help='Do not build destination subdirectories')
+ pao("--nobackup", action="store_true", dest="nobackup", default=False, help='Do not make backup files when generating new ones')
+ pao("--settings", action="store", dest="compilerSettingsString", default=None, help='String of compiler settings to pass through, e.g. --settings="useNameMapper=False,useFilters=False"')
+ pao('--print-settings', action='store_true', dest='print_settings', help='Print out the list of available compiler settings')
+ pao("--templateAPIClass", action="store", dest="templateClassName", default=None, help='Name of a subclass of Cheetah.Template.Template to use for compilation, e.g. MyTemplateClass')
+ pao("--parallel", action="store", type="int", dest="parallel", default=1, help='Compile/fill templates in parallel, e.g. --parallel=4')
+ pao('--shbang', dest='shbang', default='#!/usr/bin/env python', help='Specify the shbang to place at the top of compiled templates, e.g. --shbang="#!/usr/bin/python2.6"')
+ pao('--encoding', dest='encoding', default=None, help='Specify the encoding of source files (e.g. \'utf-8\' to force input files to be interpreted as UTF-8)')
+
+ opts, files = self.parser.parse_args(args)
+ self.opts = opts
+ if sys.platform == "win32":
+ new_files = []
+ for spec in files:
+ file_list = glob.glob(spec)
+ if file_list:
+ new_files.extend(file_list)
+ else:
+ new_files.append(spec)
+ files = new_files
+ self.pathArgs = files
+
+ D("""\
+cheetah compile %s
+Options are
+%s
+Files are %s""", args, pprint.pformat(vars(opts)), files)
+
+
+ if opts.print_settings:
+ print()
+ print('>> Available Cheetah compiler settings:')
+ from Cheetah.Compiler import _DEFAULT_COMPILER_SETTINGS
+ listing = _DEFAULT_COMPILER_SETTINGS
+ listing.sort(key=lambda l: l[0][0].lower())
+
+ for l in listing:
+ print('\t%s (default: "%s")\t%s' % l)
+ sys.exit(0)
+
+ #cleanup trailing path separators
+ seps = [sep for sep in [os.sep, os.altsep] if sep]
+ for attr in ['idir', 'odir']:
+ for sep in seps:
+ path = getattr(opts, attr, None)
+ if path and path.endswith(sep):
+ path = path[:-len(sep)]
+ setattr(opts, attr, path)
+ break
+
+ self._fixExts()
+ if opts.env:
+ self.searchList.insert(0, os.environ)
+ if opts.pickle:
+ f = open(opts.pickle, 'rb')
+ unpickled = pickle.load(f)
+ f.close()
+ self.searchList.insert(0, unpickled)
+
+ ##################################################
+ ## COMMAND METHODS
+
+ def compile(self):
+ self._compileOrFill()
+
+ def fill(self):
+ from Cheetah.ImportHooks import install
+ install()
+ self._compileOrFill()
+
+ def help(self):
+ usage(HELP_PAGE1, "", sys.stdout)
+
+ def options(self):
+ return self.parser.print_help()
+
+ def test(self):
+ # @@MO: Ugly kludge.
+ TEST_WRITE_FILENAME = 'cheetah_test_file_creation_ability.tmp'
+ try:
+ f = open(TEST_WRITE_FILENAME, 'w')
+ except:
+ sys.exit("""\
+Cannot run the tests because you don't have write permission in the current
+directory. The tests need to create temporary files. Change to a directory
+you do have write permission to and re-run the tests.""")
+ else:
+ f.close()
+ os.remove(TEST_WRITE_FILENAME)
+ # @@MO: End ugly kludge.
+ from Cheetah.Tests import Test
+ import unittest
+ verbosity = 1
+ if '-q' in self.testOpts:
+ verbosity = 0
+ if '-v' in self.testOpts:
+ verbosity = 2
+ runner = unittest.TextTestRunner(verbosity=verbosity)
+ runner.run(unittest.TestSuite(Test.suites))
+ results = runner.run(unittest.TestSuite(Test.suites))
+ exit(int(not results.wasSuccessful()))
+
+ def version(self):
+ print(Version)
+
+ # If you add a command, also add it to the 'meths' variable in main().
+ ##################################################
+ ## LOGGING METHODS
+
+ def chatter(self, format, *args):
+ """Print a verbose message to stdout. But don't if .opts.stdout is
+ true or .opts.verbose is false.
+ """
+ if self.opts.stdout or not self.opts.verbose:
+ return
+ fprintfMessage(sys.stdout, format, *args)
+
+
+ def debug(self, format, *args):
+ """Print a debugging message to stderr, but don't if .debug is
+ false.
+ """
+ if self.opts.debug:
+ fprintfMessage(sys.stderr, format, *args)
+
+ def warn(self, format, *args):
+ """Always print a warning message to stderr.
+ """
+ fprintfMessage(sys.stderr, format, *args)
+
+ def error(self, format, *args):
+ """Always print a warning message to stderr and exit with an error code.
+ """
+ fprintfMessage(sys.stderr, format, *args)
+ sys.exit(1)
+
+ ##################################################
+ ## HELPER METHODS
+
+
+ def _fixExts(self):
+ assert self.opts.oext, "oext is empty!"
+ iext, oext = self.opts.iext, self.opts.oext
+ if iext and not iext.startswith("."):
+ self.opts.iext = "." + iext
+ if oext and not oext.startswith("."):
+ self.opts.oext = "." + oext
+
+
+
+ def _compileOrFill(self):
+ C, D, W = self.chatter, self.debug, self.warn
+ opts, files = self.opts, self.pathArgs
+ if files == ["-"]:
+ self._compileOrFillStdin()
+ return
+ elif not files and opts.recurse:
+ which = opts.idir and "idir" or "current"
+ C("Drilling down recursively from %s directory.", which)
+ sourceFiles = []
+ dir = os.path.join(self.opts.idir, os.curdir)
+ os.path.walk(dir, self._expandSourceFilesWalk, sourceFiles)
+ elif not files:
+ usage(HELP_PAGE1, "Neither files nor -R specified!")
+ else:
+ sourceFiles = self._expandSourceFiles(files, opts.recurse, True)
+ sourceFiles = [os.path.normpath(x) for x in sourceFiles]
+ D("All source files found: %s", sourceFiles)
+ bundles = self._getBundles(sourceFiles)
+ D("All bundles: %s", pprint.pformat(bundles))
+ if self.opts.flat:
+ self._checkForCollisions(bundles)
+
+ # In parallel mode a new process is forked for each template
+ # compilation, out of a pool of size self.opts.parallel. This is not
+ # really optimal in all cases (e.g. probably wasteful for small
+ # templates), but seems to work well in real life for me.
+ #
+ # It also won't work for Windows users, but I'm not going to lose any
+ # sleep over that.
+ if self.opts.parallel > 1:
+ bad_child_exit = 0
+ pid_pool = set()
+
+ def child_wait():
+ pid, status = os.wait()
+ pid_pool.remove(pid)
+ return os.WEXITSTATUS(status)
+
+ while bundles:
+ b = bundles.pop()
+ pid = os.fork()
+ if pid:
+ pid_pool.add(pid)
+ else:
+ self._compileOrFillBundle(b)
+ sys.exit(0)
+
+ if len(pid_pool) == self.opts.parallel:
+ bad_child_exit = child_wait()
+ if bad_child_exit:
+ break
+
+ while pid_pool:
+ child_exit = child_wait()
+ if not bad_child_exit:
+ bad_child_exit = child_exit
+
+ if bad_child_exit:
+ sys.exit("Child process failed, exited with code %d" % bad_child_exit)
+
+ else:
+ for b in bundles:
+ self._compileOrFillBundle(b)
+
+ def _checkForCollisions(self, bundles):
+ """Check for multiple source paths writing to the same destination
+ path.
+ """
+ C, D, W = self.chatter, self.debug, self.warn
+ isError = False
+ dstSources = {}
+ for b in bundles:
+ if b.dst in dstSources:
+ dstSources[b.dst].append(b.src)
+ else:
+ dstSources[b.dst] = [b.src]
+ keys = sorted(dstSources.keys())
+ for dst in keys:
+ sources = dstSources[dst]
+ if len(sources) > 1:
+ isError = True
+ sources.sort()
+ fmt = "Collision: multiple source files %s map to one destination file %s"
+ W(fmt, sources, dst)
+ if isError:
+ what = self.isCompile and "Compilation" or "Filling"
+ sys.exit("%s aborted due to collisions" % what)
+
+
+ def _expandSourceFilesWalk(self, arg, dir, files):
+ """Recursion extension for .expandSourceFiles().
+ This method is a callback for os.path.walk().
+ 'arg' is a list to which successful paths will be appended.
+ """
+ iext = self.opts.iext
+ for f in files:
+ path = os.path.join(dir, f)
+ if path.endswith(iext) and os.path.isfile(path):
+ arg.append(path)
+ elif os.path.islink(path) and os.path.isdir(path):
+ os.path.walk(path, self._expandSourceFilesWalk, arg)
+ # If is directory, do nothing; 'walk' will eventually get it.
+
+
+ def _expandSourceFiles(self, files, recurse, addIextIfMissing):
+ """Calculate source paths from 'files' by applying the
+ command-line options.
+ """
+ C, D, W = self.chatter, self.debug, self.warn
+ idir = self.opts.idir
+ iext = self.opts.iext
+ files = []
+ for f in self.pathArgs:
+ oldFilesLen = len(files)
+ D("Expanding %s", f)
+ path = os.path.join(idir, f)
+ pathWithExt = path + iext # May or may not be valid.
+ if os.path.isdir(path):
+ if recurse:
+ os.path.walk(path, self._expandSourceFilesWalk, files)
+ else:
+ raise Error("source file '%s' is a directory" % path)
+ elif os.path.isfile(path):
+ files.append(path)
+ elif (addIextIfMissing and not path.endswith(iext) and
+ os.path.isfile(pathWithExt)):
+ files.append(pathWithExt)
+ # Do not recurse directories discovered by iext appending.
+ elif os.path.exists(path):
+ W("Skipping source file '%s', not a plain file.", path)
+ else:
+ W("Skipping source file '%s', not found.", path)
+ if len(files) > oldFilesLen:
+ D(" ... found %s", files[oldFilesLen:])
+ return files
+
+
+ def _getBundles(self, sourceFiles):
+ flat = self.opts.flat
+ idir = self.opts.idir
+ iext = self.opts.iext
+ nobackup = self.opts.nobackup
+ odir = self.opts.odir
+ oext = self.opts.oext
+ idirSlash = idir + os.sep
+ bundles = []
+ for src in sourceFiles:
+ # 'base' is the subdirectory plus basename.
+ base = src
+ if idir and src.startswith(idirSlash):
+ base = src[len(idirSlash):]
+ if iext and base.endswith(iext):
+ base = base[:-len(iext)]
+ basename = os.path.basename(base)
+ if flat:
+ dst = os.path.join(odir, basename + oext)
+ else:
+ dbn = basename
+ if odir and base.startswith(os.sep):
+ odd = odir
+ while odd != '':
+ idx = base.find(odd)
+ if idx == 0:
+ dbn = base[len(odd):]
+ if dbn[0] == '/':
+ dbn = dbn[1:]
+ break
+ odd = os.path.dirname(odd)
+ if odd == '/':
+ break
+ dst = os.path.join(odir, dbn + oext)
+ else:
+ dst = os.path.join(odir, base + oext)
+ bak = dst + self.BACKUP_SUFFIX
+ b = Bundle(src=src, dst=dst, bak=bak, base=base, basename=basename)
+ bundles.append(b)
+ return bundles
+
+
+ def _getTemplateClass(self):
+ C, D, W = self.chatter, self.debug, self.warn
+ modname = None
+ if self._templateClass:
+ return self._templateClass
+
+ modname = self.opts.templateClassName
+
+ if not modname:
+ return Template
+ p = modname.rfind('.')
+ if ':' not in modname:
+ self.error('The value of option --templateAPIClass is invalid\n'
+ 'It must be in the form "module:class", '
+ 'e.g. "Cheetah.Template:Template"')
+
+ modname, classname = modname.split(':')
+
+ C('using --templateAPIClass=%s:%s'%(modname, classname))
+
+ if p >= 0:
+ mod = getattr(__import__(modname[:p], {}, {}, [modname[p+1:]]), modname[p+1:])
+ else:
+ mod = __import__(modname, {}, {}, [])
+
+ klass = getattr(mod, classname, None)
+ if klass:
+ self._templateClass = klass
+ return klass
+ else:
+ self.error('**Template class specified in option --templateAPIClass not found\n'
+ '**Falling back on Cheetah.Template:Template')
+
+
+ def _getCompilerSettings(self):
+ if self._compilerSettings:
+ return self._compilerSettings
+
+ def getkws(**kws):
+ return kws
+ if self.opts.compilerSettingsString:
+ try:
+ exec('settings = getkws(%s)'%self.opts.compilerSettingsString)
+ except:
+ self.error("There's an error in your --settings option."
+ "It must be valid Python syntax.\n"
+ +" --settings='%s'\n"%self.opts.compilerSettingsString
+ +" %s: %s"%sys.exc_info()[:2]
+ )
+
+ validKeys = DEFAULT_COMPILER_SETTINGS.keys()
+ if [k for k in settings.keys() if k not in validKeys]:
+ self.error(
+ 'The --setting "%s" is not a valid compiler setting name.'%k)
+
+ self._compilerSettings = settings
+ return settings
+ else:
+ return {}
+
+ def _compileOrFillStdin(self):
+ TemplateClass = self._getTemplateClass()
+ compilerSettings = self._getCompilerSettings()
+ if self.isCompile:
+ pysrc = TemplateClass.compile(file=sys.stdin,
+ compilerSettings=compilerSettings,
+ returnAClass=False)
+ output = pysrc
+ else:
+ output = str(TemplateClass(file=sys.stdin, compilerSettings=compilerSettings))
+ sys.stdout.write(output)
+
+ def _compileOrFillBundle(self, b):
+ C, D, W = self.chatter, self.debug, self.warn
+ TemplateClass = self._getTemplateClass()
+ compilerSettings = self._getCompilerSettings()
+ src = b.src
+ dst = b.dst
+ base = b.base
+ basename = b.basename
+ dstDir = os.path.dirname(dst)
+ what = self.isCompile and "Compiling" or "Filling"
+ C("%s %s -> %s^", what, src, dst) # No trailing newline.
+ if os.path.exists(dst) and not self.opts.nobackup:
+ bak = b.bak
+ C(" (backup %s)", bak) # On same line as previous message.
+ else:
+ bak = None
+ C("")
+ if self.isCompile:
+ if not moduleNameRE.match(basename):
+ tup = basename, src
+ raise Error("""\
+%s: base name %s contains invalid characters. It must
+be named according to the same rules as Python modules.""" % tup)
+ pysrc = TemplateClass.compile(file=src, returnAClass=False,
+ moduleName=basename,
+ className=basename,
+ commandlineopts=self.opts,
+ compilerSettings=compilerSettings)
+ output = pysrc
+ else:
+ #output = str(TemplateClass(file=src, searchList=self.searchList))
+ tclass = TemplateClass.compile(file=src, compilerSettings=compilerSettings)
+ output = str(tclass(searchList=self.searchList))
+
+ if bak:
+ shutil.copyfile(dst, bak)
+ if dstDir and not os.path.exists(dstDir):
+ if self.isCompile:
+ mkdirsWithPyInitFiles(dstDir)
+ else:
+ os.makedirs(dstDir)
+ if self.opts.stdout:
+ sys.stdout.write(output)
+ else:
+ f = open(dst, 'w')
+ f.write(output)
+ f.close()
+
+
+# Called when invoked as `cheetah`
+def _cheetah():
+ CheetahWrapper().main()
+
+# Called when invoked as `cheetah-compile`
+def _cheetah_compile():
+ sys.argv.insert(1, "compile")
+ CheetahWrapper().main()
+
+
+##################################################
+## if run from the command line
+if __name__ == '__main__': CheetahWrapper().main()
+
+# vim: shiftwidth=4 tabstop=4 expandtab
View
2,014 Cheetah/Compiler.py
@@ -0,0 +1,2014 @@
+'''
+ Compiler classes for Cheetah:
+ ModuleCompiler aka 'Compiler'
+ ClassCompiler
+ MethodCompiler
+
+ If you are trying to grok this code start with ModuleCompiler.__init__,
+ ModuleCompiler.compile, and ModuleCompiler.__getattr__.
+'''
+
+import sys
+import os
+import os.path
+from os.path import getmtime, exists
+import re
+import types
+import time
+import random
+import warnings
+import copy
+import codecs
+
+from Cheetah.Version import Version, VersionTuple
+from Cheetah.SettingsManager import SettingsManager
+from Cheetah.Utils.Indenter import indentize # an undocumented preprocessor
+from Cheetah import ErrorCatchers
+from Cheetah import NameMapper
+from Cheetah.Parser import Parser, ParseError, specialVarRE, \
+ STATIC_CACHE, REFRESH_CACHE, SET_LOCAL, SET_GLOBAL, SET_MODULE, \
+ unicodeDirectiveRE, encodingDirectiveRE, escapedNewlineRE
+
+from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList
+VFFSL=valueFromFrameOrSearchList
+VFSL=valueFromSearchList
+VFN=valueForName
+currentTime=time.time
+
+class Error(Exception): pass
+
+# Settings format: (key, default, docstring)
+_DEFAULT_COMPILER_SETTINGS = [
+ ('useNameMapper', True, 'Enable NameMapper for dotted notation and searchList support'),
+ ('useSearchList', True, 'Enable the searchList, requires useNameMapper=True, if disabled, first portion of the $variable is a global, builtin, or local variable that doesn\'t need looking up in the searchList'),
+ ('allowSearchListAsMethArg', True, ''),
+ ('useAutocalling', True, 'Detect and call callable objects in searchList, requires useNameMapper=True'),
+ ('useStackFrames', True, 'Used for NameMapper.valueFromFrameOrSearchList rather than NameMapper.valueFromSearchList'),
+ ('useErrorCatcher', False, 'Turn on the #errorCatcher directive for catching NameMapper errors, etc'),
+ ('alwaysFilterNone', True, 'Filter out None prior to calling the #filter'),
+ ('useFilters', True, 'If False, pass output through str()'),
+ ('includeRawExprInFilterArgs', True, ''),
+ ('useLegacyImportMode', True, 'All #import statements are relocated to the top of the generated Python module'),
+ ('prioritizeSearchListOverSelf', False, 'When iterating the searchList, look into the searchList passed into the initializer instead of Template members first'),
+
+ ('autoAssignDummyTransactionToSelf', False, ''),
+ ('useKWsDictArgForPassingTrans', True, ''),
+
+ ('commentOffset', 1, ''),
+ ('outputRowColComments', True, ''),
+ ('includeBlockMarkers', False, 'Wrap #block\'s in a comment in the template\'s output'),
+ ('blockMarkerStart', ('\n<!-- START BLOCK: ', ' -->\n'), ''),
+ ('blockMarkerEnd', ('\n<!-- END BLOCK: ', ' -->\n'), ''),
+ ('defDocStrMsg', 'Autogenerated by Cheetah: The Python-Powered Template Engine', ''),
+ ('setup__str__method', False, ''),
+ ('mainMethodName', 'respond', ''),
+ ('mainMethodNameForSubclasses', 'writeBody', ''),
+ ('indentationStep', ' ' * 4, ''),
+ ('initialMethIndentLevel', 2, ''),
+ ('monitorSrcFile', False, ''),
+ ('outputMethodsBeforeAttributes', True, ''),
+ ('addTimestampsToCompilerOutput', True, ''),
+
+ ## Customizing the #extends directive
+ ('autoImportForExtendsDirective', True, ''),
+ ('handlerForExtendsDirective', None, ''),
+
+ ('disabledDirectives', [], 'List of directive keys to disable (without starting "#")'),
+ ('enabledDirectives', [], 'List of directive keys to enable (without starting "#")'),
+ ('disabledDirectiveHooks', [], 'callable(parser, directiveKey)'),
+ ('preparseDirectiveHooks', [], 'callable(parser, directiveKey)'),
+ ('postparseDirectiveHooks', [], 'callable(parser, directiveKey)'),
+ ('preparsePlaceholderHooks', [], 'callable(parser)'),
+ ('postparsePlaceholderHooks', [], 'callable(parser)'),
+ ('expressionFilterHooks', [], '''callable(parser, expr, exprType, rawExpr=None, startPos=None), exprType is the name of the directive, "psp" or "placeholder" The filters *must* return the expr or raise an expression, they can modify the expr if needed'''),
+ ('templateMetaclass', None, 'Strictly optional, only will work with new-style basecalsses as well'),
+ ('i18NFunctionName', 'self.i18n', ''),
+
+ ('cheetahVarStartToken', '$', ''),
+ ('commentStartToken', '##', ''),
+ ('multiLineCommentStartToken', '#*', ''),
+ ('multiLineCommentEndToken', '*#', ''),
+ ('gobbleWhitespaceAroundMultiLineComments', True, ''),
+ ('directiveStartToken', '#', ''),
+ ('directiveEndToken', '#', ''),
+ ('allowWhitespaceAfterDirectiveStartToken', False, ''),
+ ('PSPStartToken', '<%', ''),
+ ('PSPEndToken', '%>', ''),
+ ('EOLSlurpToken', '#', ''),
+ ('gettextTokens', ["_", "N_", "ngettext"], ''),
+ ('allowExpressionsInExtendsDirective', False, ''),
+ ('allowEmptySingleLineMethods', False, ''),
+ ('allowNestedDefScopes', True, ''),
+ ('allowPlaceholderFilterArgs', True, ''),
+ ('encoding', None, 'The encoding to read input files as (or None for ASCII)'),
+]
+
+DEFAULT_COMPILER_SETTINGS = dict([(v[0], v[1]) for v in _DEFAULT_COMPILER_SETTINGS])
+
+
+
+class GenUtils(object):
+ """An abstract baseclass for the Compiler classes that provides methods that
+ perform generic utility functions or generate pieces of output code from
+ information passed in by the Parser baseclass. These methods don't do any
+ parsing themselves.
+ """
+
+ def genTimeInterval(self, timeString):
+ ##@@ TR: need to add some error handling here
+ if timeString[-1] == 's':
+ interval = float(timeString[:-1])
+ elif timeString[-1] == 'm':
+ interval = float(timeString[:-1])*60
+ elif timeString[-1] == 'h':
+ interval = float(timeString[:-1])*60*60
+ elif timeString[-1] == 'd':
+ interval = float(timeString[:-1])*60*60*24
+ elif timeString[-1] == 'w':
+ interval = float(timeString[:-1])*60*60*24*7
+ else: # default to minutes
+ interval = float(timeString)*60
+ return interval
+
+ def genCacheInfo(self, cacheTokenParts):
+ """Decipher a placeholder cachetoken
+ """
+ cacheInfo = {}
+ if cacheTokenParts['REFRESH_CACHE']:
+ cacheInfo['type'] = REFRESH_CACHE
+ cacheInfo['interval'] = self.genTimeInterval(cacheTokenParts['interval'])
+ elif cacheTokenParts['STATIC_CACHE']:
+ cacheInfo['type'] = STATIC_CACHE
+ return cacheInfo # is empty if no cache
+
+ def genCacheInfoFromArgList(self, argList):
+ cacheInfo = {'type':REFRESH_CACHE}
+ for key, val in argList:
+ if val[0] in '"\'':
+ val = val[1:-1]
+
+ if key == 'timer':
+ key = 'interval'
+ val = self.genTimeInterval(val)
+
+ cacheInfo[key] = val
+ return cacheInfo
+
+ def genCheetahVar(self, nameChunks, plain=False):
+ if nameChunks[0][0] in self.setting('gettextTokens'):
+ self.addGetTextVar(nameChunks)
+ if self.setting('useNameMapper') and not plain:
+ return self.genNameMapperVar(nameChunks)
+ else:
+ return self.genPlainVar(nameChunks)
+
+ def addGetTextVar(self, nameChunks):
+ """Output something that gettext can recognize.
+
+ This is a harmless side effect necessary to make gettext work when it
+ is scanning compiled templates for strings marked for translation.
+
+ @@TR: another marginally more efficient approach would be to put the
+ output in a dummy method that is never called.
+ """
+ # @@TR: this should be in the compiler not here
+ self.addChunk("if False:")
+ self.indent()
+ self.addChunk(self.genPlainVar(nameChunks[:]))
+ self.dedent()
+
+ def genPlainVar(self, nameChunks):
+ """Generate Python code for a Cheetah $var without using NameMapper
+ (Unified Dotted Notation with the SearchList).
+ """
+ nameChunks.reverse()
+ chunk = nameChunks.pop()
+ pythonCode = chunk[0] + chunk[2]
+ while nameChunks:
+ chunk = nameChunks.pop()
+ pythonCode = (pythonCode + '.' + chunk[0] + chunk[2])
+ return pythonCode
+
+ def genNameMapperVar(self, nameChunks):
+ """Generate valid Python code for a Cheetah $var, using NameMapper
+ (Unified Dotted Notation with the SearchList).
+
+ nameChunks = list of var subcomponents represented as tuples
+ [ (name,useAC,remainderOfExpr),
+ ]
+ where:
+ name = the dotted name base
+ useAC = where NameMapper should use autocalling on namemapperPart
+ remainderOfExpr = any arglist, index, or slice
+
+ If remainderOfExpr contains a call arglist (e.g. '(1234)') then useAC
+ is False, otherwise it defaults to True. It is overridden by the global
+ setting 'useAutocalling' if this setting is False.
+
+ EXAMPLE
+ ------------------------------------------------------------------------
+ if the raw Cheetah Var is
+ $a.b.c[1].d().x.y.z
+
+ nameChunks is the list
+ [ ('a.b.c',True,'[1]'), # A
+ ('d',False,'()'), # B
+ ('x.y.z',True,''), # C
+ ]
+
+ When this method is fed the list above it returns
+ VFN(VFN(VFFSL(SL, 'a.b.c',True)[1], 'd',False)(), 'x.y.z',True)
+ which can be represented as
+ VFN(B`, name=C[0], executeCallables=(useAC and C[1]))C[2]
+ where:
+ VFN = NameMapper.valueForName
+ VFFSL = NameMapper.valueFromFrameOrSearchList
+ VFSL = NameMapper.valueFromSearchList # optionally used instead of VFFSL
+ SL = self.searchList()
+ useAC = self.setting('useAutocalling') # True in this example
+
+ A = ('a.b.c',True,'[1]')
+ B = ('d',False,'()')
+ C = ('x.y.z',True,'')
+
+ C` = VFN( VFN( VFFSL(SL, 'a.b.c',True)[1],
+ 'd',False)(),
+ 'x.y.z',True)
+ = VFN(B`, name='x.y.z', executeCallables=True)
+
+ B` = VFN(A`, name=B[0], executeCallables=(useAC and B[1]))B[2]
+ A` = VFFSL(SL, name=A[0], executeCallables=(useAC and A[1]))A[2]
+
+
+ Note, if the compiler setting useStackFrames=False (default is true)
+ then
+ A` = VFSL([locals()]+SL+[globals(), __builtin__], name=A[0], executeCallables=(useAC and A[1]))A[2]
+ This option allows Cheetah to be used with Psyco, which doesn't support
+ stack frame introspection.
+ """
+ defaultUseAC = self.setting('useAutocalling')
+ useSearchList = self.setting('useSearchList')
+
+ nameChunks.reverse()
+ name, useAC, remainder = nameChunks.pop()
+
+ if not useSearchList:
+ firstDotIdx = name.find('.')
+ if firstDotIdx != -1 and firstDotIdx < len(name):
+ beforeFirstDot, afterDot = name[:firstDotIdx], name[firstDotIdx+1:]
+ pythonCode = ('VFN(' + beforeFirstDot +
+ ',"' + afterDot +
+ '",' + repr(defaultUseAC and useAC) + ')'
+ + remainder)
+ else:
+ pythonCode = name+remainder
+ elif self.setting('useStackFrames'):
+ pythonCode = ('VFFSL(SL,'
+ '"'+ name + '",'
+ + repr(defaultUseAC and useAC) + ')'
+ + remainder)
+ else:
+ pythonCode = ('VFSL([locals()]+SL+[globals(), builtin],'
+ '"'+ name + '",'
+ + repr(defaultUseAC and useAC) + ')'
+ + remainder)
+ ##
+ while nameChunks:
+ name, useAC, remainder = nameChunks.pop()
+ pythonCode = ('VFN(' + pythonCode +
+ ',"' + name +
+ '",' + repr(defaultUseAC and useAC) + ')'
+ + remainder)
+ return pythonCode
+
+##################################################
+## METHOD COMPILERS
+
+class MethodCompiler(GenUtils):
+ def __init__(self, methodName, classCompiler,
+ initialMethodComment=None,
+ decorators=None):
+ self._settingsManager = classCompiler
+ self._classCompiler = classCompiler
+ self._moduleCompiler = classCompiler._moduleCompiler
+ self._methodName = methodName
+ self._initialMethodComment = initialMethodComment
+ self._setupState()
+ self._decorators = decorators or []
+
+ def setting(self, key):
+ return self._settingsManager.setting(key)
+
+ def _setupState(self):
+ self._indent = self.setting('indentationStep')
+ self._indentLev = self.setting('initialMethIndentLevel')
+ self._pendingStrConstChunks = []
+ self._methodSignature = None
+ self._methodDef = None
+ self._docStringLines = []
+ self._methodBodyChunks = []
+
+ self._cacheRegionsStack = []
+ self._callRegionsStack = []
+ self._captureRegionsStack = []
+ self._filterRegionsStack = []
+
+ self._isErrorCatcherOn = False
+
+ self._hasReturnStatement = False
+ self._isGenerator = False
+
+
+ def cleanupState(self):
+ """Called by the containing class compiler instance
+ """
+ pass
+
+ def methodName(self):
+ return self._methodName
+
+ def setMethodName(self, name):
+ self._methodName = name
+
+ ## methods for managing indentation
+
+ def indentation(self):
+ return self._indent * self._indentLev
+
+ def indent(self):
+ self._indentLev +=1
+
+ def dedent(self):
+ if self._indentLev:
+ self._indentLev -=1
+ else:
+ raise Error('Attempt to dedent when the indentLev is 0')
+
+ ## methods for final code wrapping
+
+ def methodDef(self):
+ if self._methodDef:
+ return self._methodDef
+ else:
+ return self.wrapCode()
+
+ __str__ = methodDef
+ __unicode__ = methodDef
+
+ def wrapCode(self):
+ self.commitStrConst()
+ methodDefChunks = (
+ self.methodSignature(),
+ '\n',
+ self.docString(),
+ self.methodBody() )
+ methodDef = ''.join(methodDefChunks)
+ self._methodDef = methodDef
+ return methodDef
+
+ def methodSignature(self):
+ return self._indent + self._methodSignature + ':'
+
+ def setMethodSignature(self, signature):
+ self._methodSignature = signature
+
+ def methodBody(self):
+ return ''.join( self._methodBodyChunks )
+
+ def docString(self):
+ if not self._docStringLines:
+ return ''
+
+ ind = self._indent*2
+ docStr = (ind + '"""\n' + ind +
+ ('\n' + ind).join([ln.replace('"""', "'''") for ln in self._docStringLines]) +
+ '\n' + ind + '"""\n')
+ return docStr
+
+ ## methods for adding code
+ def addMethDocString(self, line):
+ self._docStringLines.append(line.replace('%', '%%'))
+
+ def addChunk(self, chunk):
+ self.commitStrConst()
+ chunk = "\n" + self.indentation() + chunk
+ self._methodBodyChunks.append(chunk)
+
+ def appendToPrevChunk(self, appendage):
+ self._methodBodyChunks[-1] = self._methodBodyChunks[-1] + appendage
+
+ def addWriteChunk(self, chunk):
+ self.addChunk('write(' + chunk + ')')
+
+ def addFilteredChunk(self, chunk, filterArgs=None, rawExpr=None, lineCol=None):
+ if filterArgs is None:
+ filterArgs = ''
+ if self.setting('includeRawExprInFilterArgs') and rawExpr:
+ filterArgs += ', rawExpr=%s'%repr(rawExpr)
+
+ if self.setting('alwaysFilterNone'):
+ if rawExpr and rawExpr.find('\n')==-1 and rawExpr.find('\r')==-1:
+ self.addChunk("_v = %s # %r"%(chunk, rawExpr))
+ if lineCol:
+ self.appendToPrevChunk(' on line %s, col %s'%lineCol)
+ else:
+ self.addChunk("_v = %s"%chunk)
+
+ if self.setting('useFilters'):
+ self.addChunk("if _v is not None: write(_filter(_v%s))"%filterArgs)
+ else:
+ self.addChunk("if _v is not None: write(str(_v))")
+ else:
+ if self.setting('useFilters'):
+ self.addChunk("write(_filter(%s%s))"%(chunk, filterArgs))
+ else:
+ self.addChunk("write(str(%s))"%chunk)
+
+ def _appendToPrevStrConst(self, strConst):
+ if self._pendingStrConstChunks:
+ self._pendingStrConstChunks.append(strConst)
+ else:
+ self._pendingStrConstChunks = [strConst]
+
+ def commitStrConst(self):
+ """Add the code for outputting the pending strConst without chopping off
+ any whitespace from it.
+ """
+ if not self._pendingStrConstChunks:
+ return
+
+ strConst = ''.join(self._pendingStrConstChunks)
+ self._pendingStrConstChunks = []
+ if not strConst:
+ return
+
+ reprstr = repr(strConst)
+ i = 0
+ out = []
+ if reprstr.startswith('u'):
+ i = 1
+ out = ['u']
+ body = escapedNewlineRE.sub('\\1\n', reprstr[i+1:-1])
+
+ if reprstr[i]=="'":
+ out.append("'''")
+ out.append(body)
+ out.append("'''")
+ else:
+ out.append('"""')
+ out.append(body)
+ out.append('"""')
+ self.addWriteChunk(''.join(out))
+
+ def handleWSBeforeDirective(self):
+ """Truncate the pending strCont to the beginning of the current line.
+ """
+ if self._pendingStrConstChunks:
+ src = self._pendingStrConstChunks[-1]
+ BOL = max(src.rfind('\n')+1, src.rfind('\r')+1, 0)
+ if BOL < len(src):
+ self._pendingStrConstChunks[-1] = src[:BOL]
+
+
+
+ def isErrorCatcherOn(self):
+ return self._isErrorCatcherOn
+
+ def turnErrorCatcherOn(self):
+ self._isErrorCatcherOn = True
+
+ def turnErrorCatcherOff(self):
+ self._isErrorCatcherOn = False
+
+ # @@TR: consider merging the next two methods into one
+ def addStrConst(self, strConst):
+ self._appendToPrevStrConst(strConst)
+
+ def addRawText(self, text):
+ self.addStrConst(text)
+
+ def addMethComment(self, comm):
+ offSet = self.setting('commentOffset')
+ self.addChunk('#' + ' '*offSet + comm)
+
+ def addPlaceholder(self, expr, filterArgs, rawPlaceholder,
+ cacheTokenParts, lineCol,
+ silentMode=False):
+ cacheInfo = self.genCacheInfo(cacheTokenParts)
+ if cacheInfo:
+ cacheInfo['ID'] = repr(rawPlaceholder)[1:-1]
+ self.startCacheRegion(cacheInfo, lineCol, rawPlaceholder=rawPlaceholder)
+
+ if self.isErrorCatcherOn():
+ methodName = self._classCompiler.addErrorCatcherCall(
+ expr, rawCode=rawPlaceholder, lineCol=lineCol)
+ expr = 'self.' + methodName + '(localsDict=locals())'
+
+ if silentMode:
+ self.addChunk('try:')
+ self.indent()
+ self.addFilteredChunk(expr, filterArgs, rawPlaceholder, lineCol=lineCol)
+ self.dedent()
+ self.addChunk('except NotFound: pass')
+ else:
+ self.addFilteredChunk(expr, filterArgs, rawPlaceholder, lineCol=lineCol)
+
+ if self.setting('outputRowColComments'):
+ self.appendToPrevChunk(' # from line %s, col %s' % lineCol + '.')
+ if cacheInfo:
+ self.endCacheRegion()
+
+ def addSilent(self, expr):
+ self.addChunk( expr )
+
+ def addEcho(self, expr, rawExpr=None):
+ self.addFilteredChunk(expr, rawExpr=rawExpr)
+
+ def addSet(self, expr, exprComponents, setStyle):
+ if setStyle is SET_GLOBAL:
+ (LVALUE, OP, RVALUE) = (exprComponents.LVALUE,
+ exprComponents.OP,
+ exprComponents.RVALUE)
+ # we need to split the LVALUE to deal with globalSetVars
+ splitPos1 = LVALUE.find('.')
+ splitPos2 = LVALUE.find('[')
+ if splitPos1 > 0 and splitPos2==-1:
+ splitPos = splitPos1
+ elif splitPos1 > 0 and splitPos1 < max(splitPos2, 0):
+ splitPos = splitPos1
+ else:
+ splitPos = splitPos2
+
+ if splitPos >0:
+ primary = LVALUE[:splitPos]
+ secondary = LVALUE[splitPos:]
+ else:
+ primary = LVALUE
+ secondary = ''
+ LVALUE = 'self._CHEETAH__globalSetVars["' + primary + '"]' + secondary
+ expr = LVALUE + ' ' + OP + ' ' + RVALUE.strip()
+
+ if setStyle is SET_MODULE:
+ self._moduleCompiler.addModuleGlobal(expr)
+ else:
+ self.addChunk(expr)
+
+ def addInclude(self, sourceExpr, includeFrom, isRaw):
+ self.addChunk('self._handleCheetahInclude(' + sourceExpr +
+ ', trans=trans, ' +
+ 'includeFrom="' + includeFrom + '", raw=' +
+ repr(isRaw) + ')')
+
+ def addWhile(self, expr, lineCol=None):
+ self.addIndentingDirective(expr, lineCol=lineCol)
+
+ def addFor(self, expr, lineCol=None):
+ self.addIndentingDirective(expr, lineCol=lineCol)
+
+ def addRepeat(self, expr, lineCol=None):
+ #the _repeatCount stuff here allows nesting of #repeat directives
+ self._repeatCount = getattr(self, "_repeatCount", -1) + 1
+ self.addFor('for __i%s in range(%s)' % (self._repeatCount, expr), lineCol=lineCol)
+
+ def addIndentingDirective(self, expr, lineCol=None):
+ if expr and not expr[-1] == ':':
+ expr = expr + ':'
+ self.addChunk( expr )
+ if lineCol:
+ self.appendToPrevChunk(' # generated from line %s, col %s'%lineCol )
+ self.indent()
+
+ def addReIndentingDirective(self, expr, dedent=True, lineCol=None):
+ self.commitStrConst()
+ if dedent:
+ self.dedent()
+ if not expr[-1] == ':':
+ expr = expr + ':'
+
+ self.addChunk( expr )
+ if lineCol:
+ self.appendToPrevChunk(' # generated from line %s, col %s'%lineCol )
+ self.indent()
+
+ def addIf(self, expr, lineCol=None):
+ """For a full #if ... #end if directive
+ """
+ self.addIndentingDirective(expr, lineCol=lineCol)
+
+ def addOneLineIf(self, expr, lineCol=None):
+ """For a full #if ... #end if directive
+ """
+ self.addIndentingDirective(expr, lineCol=lineCol)
+
+ def addTernaryExpr(self, conditionExpr, trueExpr, falseExpr, lineCol=None):
+ """For a single-lie #if ... then .... else ... directive
+ <condition> then <trueExpr> else <falseExpr>
+ """
+ self.addIndentingDirective(conditionExpr, lineCol=lineCol)
+ self.addFilteredChunk(trueExpr)
+ self.dedent()
+ self.addIndentingDirective('else')
+ self.addFilteredChunk(falseExpr)
+ self.dedent()
+
+ def addElse(self, expr, dedent=True, lineCol=None):
+ expr = re.sub(r'else[ \f\t]+if', 'elif', expr)
+ self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol)
+
+ def addElif(self, expr, dedent=True, lineCol=None):
+ self.addElse(expr, dedent=dedent, lineCol=lineCol)
+
+ def addUnless(self, expr, lineCol=None):
+ self.addIf('if not (' + expr + ')')
+
+ def addClosure(self, functionName, argsList, parserComment):
+ argStringChunks = []
+ for arg in argsList:
+ chunk = arg[0]
+ if not arg[1] == None:
+ chunk += '=' + arg[1]
+ argStringChunks.append(chunk)
+ signature = "def " + functionName + "(" + ','.join(argStringChunks) + "):"
+ self.addIndentingDirective(signature)
+ self.addChunk('#'+parserComment)
+
+ def addTry(self, expr, lineCol=None):
+ self.addIndentingDirective(expr, lineCol=lineCol)
+
+ def addExcept(self, expr, dedent=True, lineCol=None):
+ self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol)
+
+ def addFinally(self, expr, dedent=True, lineCol=None):
+ self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol)
+
+ def addReturn(self, expr):
+ assert not self._isGenerator
+ self.addChunk(expr)
+ self._hasReturnStatement = True
+
+ def addYield(self, expr):
+ assert not self._hasReturnStatement
+ self._isGenerator = True
+ if expr.replace('yield', '').strip():
+ self.addChunk(expr)
+ else:
+ self.addChunk('if _dummyTrans:')
+ self.indent()
+ self.addChunk('yield trans.response().getvalue()')
+ self.addChunk('trans = DummyTransaction()')
+ self.addChunk('write = trans.response().write')
+ self.dedent()
+ self.addChunk('else:')
+ self.indent()
+ self.addChunk(
+ 'raise TypeError("This method cannot be called with a trans arg")')
+ self.dedent()
+
+
+ def addPass(self, expr):
+ self.addChunk(expr)
+
+ def addDel(self, expr):
+ self.addChunk(expr)
+
+ def addAssert(self, expr):
+ self.addChunk(expr)
+
+ def addRaise(self, expr):
+ self.addChunk(expr)
+
+ def addBreak(self, expr):
+ self.addChunk(expr)
+
+ def addContinue(self, expr):
+ self.addChunk(expr)
+
+ def addPSP(self, PSP):
+ self.commitStrConst()
+ autoIndent = False
+ if PSP[0] == '=':
+ PSP = PSP[1:]
+ if PSP:
+ self.addWriteChunk('_filter(' + PSP + ')')
+ return
+
+ elif PSP.lower() == 'end':
+ self.dedent()
+ return
+ elif PSP[-1] == '$':
+ autoIndent = True
+ PSP = PSP[:-1]
+ elif PSP[-1] == ':':
+ autoIndent = True
+
+ for line in PSP.splitlines():
+ self.addChunk(line)
+
+ if autoIndent:
+ self.indent()
+
+ def nextCacheID(self):
+ return ('_'+str(random.randrange(100, 999))
+ + str(random.randrange(10000, 99999)))
+
+ def startCacheRegion(self, cacheInfo, lineCol, rawPlaceholder=None):
+
+ # @@TR: we should add some runtime logging to this
+
+ ID = self.nextCacheID()
+ interval = cacheInfo.get('interval', None)
+ test = cacheInfo.get('test', None)
+ customID = cacheInfo.get('id', None)
+ if customID:
+ ID = customID
+ varyBy = cacheInfo.get('varyBy', repr(ID))
+ self._cacheRegionsStack.append(ID) # attrib of current methodCompiler
+
+ # @@TR: add this to a special class var as well
+ self.addChunk('')
+
+ self.addChunk('## START CACHE REGION: ID='+ID+
+ '. line %s, col %s'%lineCol + ' in the source.')
+
+ self.addChunk('_RECACHE_%(ID)s = False'%locals())
+ self.addChunk('_cacheRegion_%(ID)s = self.getCacheRegion(regionID='%locals()
+ + repr(ID)
+ + ', cacheInfo=%r'%cacheInfo
+ + ')')
+ self.addChunk('if _cacheRegion_%(ID)s.isNew():'%locals())
+ self.indent()
+ self.addChunk('_RECACHE_%(ID)s = True'%locals())
+ self.dedent()
+
+ self.addChunk('_cacheItem_%(ID)s = _cacheRegion_%(ID)s.getCacheItem('%locals()
+ +varyBy+')')
+
+ self.addChunk('if _cacheItem_%(ID)s.hasExpired():'%locals())
+ self.indent()
+ self.addChunk('_RECACHE_%(ID)s = True'%locals())
+ self.dedent()
+
+ if test:
+ self.addChunk('if ' + test + ':')
+ self.indent()
+ self.addChunk('_RECACHE_%(ID)s = True'%locals())
+ self.dedent()
+
+ self.addChunk('if (not _RECACHE_%(ID)s) and _cacheItem_%(ID)s.getRefreshTime():'%locals())
+ self.indent()
+ #self.addChunk('print "DEBUG"+"-"*50')
+ self.addChunk('try:')
+ self.indent()
+ self.addChunk('_output = _cacheItem_%(ID)s.renderOutput()'%locals())
+ self.dedent()
+ self.addChunk('except KeyError:')
+ self.indent()
+ self.addChunk('_RECACHE_%(ID)s = True'%locals())
+ #self.addChunk('print "DEBUG"+"*"*50')
+ self.dedent()
+ self.addChunk('else:')
+ self.indent()
+ self.addWriteChunk('_output')
+ self.addChunk('del _output')
+ self.dedent()
+
+ self.dedent()
+
+ self.addChunk('if _RECACHE_%(ID)s or not _cacheItem_%(ID)s.getRefreshTime():'%locals())
+ self.indent()
+ self.addChunk('_orig_trans%(ID)s = trans'%locals())
+ self.addChunk('trans = _cacheCollector_%(ID)s = DummyTransaction()'%locals())
+ self.addChunk('write = _cacheCollector_%(ID)s.response().write'%locals())
+ if interval:
+ self.addChunk(("_cacheItem_%(ID)s.setExpiryTime(currentTime() +"%locals())
+ + str(interval) + ")")
+
+ def endCacheRegion(self):
+ ID = self._cacheRegionsStack.pop()
+ self.addChunk('trans = _orig_trans%(ID)s'%locals())
+ self.addChunk('write = trans.response().write')
+ self.addChunk('_cacheData = _cacheCollector_%(ID)s.response().getvalue()'%locals())
+ self.addChunk('_cacheItem_%(ID)s.setData(_cacheData)'%locals())
+ self.addWriteChunk('_cacheData')
+ self.addChunk('del _cacheData')
+ self.addChunk('del _cacheCollector_%(ID)s'%locals())
+ self.addChunk('del _orig_trans%(ID)s'%locals())
+ self.dedent()
+ self.addChunk('## END CACHE REGION: '+ID)
+ self.addChunk('')
+
+ def nextCallRegionID(self):
+ return self.nextCacheID()
+
+ def startCallRegion(self, functionName, args, lineCol, regionTitle='CALL'):
+ class CallDetails(object):
+ pass
+ callDetails = CallDetails()
+ callDetails.ID = ID = self.nextCallRegionID()
+ callDetails.functionName = functionName
+ callDetails.args = args
+ callDetails.lineCol = lineCol
+ callDetails.usesKeywordArgs = False
+ self._callRegionsStack.append((ID, callDetails)) # attrib of current methodCompiler
+
+ self.addChunk('## START %(regionTitle)s REGION: '%locals()
+ +ID
+ +' of '+functionName
+ +' at line %s, col %s'%lineCol + ' in the source.')
+ self.addChunk('_orig_trans%(ID)s = trans'%locals())
+ self.addChunk('_wasBuffering%(ID)s = self._CHEETAH__isBuffering'%locals())
+ self.addChunk('self._CHEETAH__isBuffering = True')
+ self.addChunk('trans = _callCollector%(ID)s = DummyTransaction()'%locals())
+ self.addChunk('write = _callCollector%(ID)s.response().write'%locals())
+
+ def setCallArg(self, argName, lineCol):
+ ID, callDetails = self._callRegionsStack[-1]
+ argName = str(argName)
+ if callDetails.usesKeywordArgs:
+ self._endCallArg()
+ else:
+ callDetails.usesKeywordArgs = True
+ self.addChunk('_callKws%(ID)s = {}'%locals())
+ self.addChunk('_currentCallArgname%(ID)s = %(argName)r'%locals())
+ callDetails.currentArgname = argName
+
+ def _endCallArg(self):
+ ID, callDetails = self._callRegionsStack[-1]
+ currCallArg = callDetails.currentArgname
+ self.addChunk(('_callKws%(ID)s[%(currCallArg)r] ='
+ ' _callCollector%(ID)s.response().getvalue()')%locals())
+ self.addChunk('del _callCollector%(ID)s'%locals())
+ self.addChunk('trans = _callCollector%(ID)s = DummyTransaction()'%locals())
+ self.addChunk('write = _callCollector%(ID)s.response().write'%locals())
+
+ def endCallRegion(self, regionTitle='CALL'):
+ ID, callDetails = self._callRegionsStack[-1]
+ functionName, initialKwArgs, lineCol = (
+ callDetails.functionName, callDetails.args, callDetails.lineCol)
+
+ def reset(ID=ID):
+ self.addChunk('trans = _orig_trans%(ID)s'%locals())
+ self.addChunk('write = trans.response().write')
+ self.addChunk('self._CHEETAH__isBuffering = _wasBuffering%(ID)s '%locals())
+ self.addChunk('del _wasBuffering%(ID)s'%locals())
+ self.addChunk('del _orig_trans%(ID)s'%locals())
+
+ if not callDetails.usesKeywordArgs:
+ reset()
+ self.addChunk('_callArgVal%(ID)s = _callCollector%(ID)s.response().getvalue()'%locals())
+ self.addChunk('del _callCollector%(ID)s'%locals())
+ if initialKwArgs:
+ initialKwArgs = ', '+initialKwArgs
+ self.addFilteredChunk('%(functionName)s(_callArgVal%(ID)s%(initialKwArgs)s)'%locals())
+ self.addChunk('del _callArgVal%(ID)s'%locals())
+ else:
+ if initialKwArgs:
+ initialKwArgs = initialKwArgs+', '
+ self._endCallArg()
+ reset()
+ self.addFilteredChunk('%(functionName)s(%(initialKwArgs)s**_callKws%(ID)s)'%locals())
+ self.addChunk('del _callKws%(ID)s'%locals())
+ self.addChunk('## END %(regionTitle)s REGION: '%locals()
+ +ID
+ +' of '+functionName
+ +' at line %s, col %s'%lineCol + ' in the source.')
+ self.addChunk('')
+ self._callRegionsStack.pop() # attrib of current methodCompiler
+
+ def nextCaptureRegionID(self):
+ return self.nextCacheID()
+
+ def startCaptureRegion(self, assignTo, lineCol):
+ class CaptureDetails: pass
+ captureDetails = CaptureDetails()
+ captureDetails.ID = ID = self.nextCaptureRegionID()
+ captureDetails.assignTo = assignTo
+ captureDetails.lineCol = lineCol
+
+ self._captureRegionsStack.append((ID, captureDetails)) # attrib of current methodCompiler
+ self.addChunk('## START CAPTURE REGION: '+ID
+ +' '+assignTo
+ +' at line %s, col %s'%lineCol + ' in the source.')
+ self.addChunk('_orig_trans%(ID)s = trans'%locals())
+ self.addChunk('_wasBuffering%(ID)s = self._CHEETAH__isBuffering'%locals())
+ self.addChunk('self._CHEETAH__isBuffering = True')
+ self.addChunk('trans = _captureCollector%(ID)s = DummyTransaction()'%locals())
+ self.addChunk('write = _captureCollector%(ID)s.response().write'%locals())
+
+ def endCaptureRegion(self):
+ ID, captureDetails = self._captureRegionsStack.pop()
+ assignTo, lineCol = (captureDetails.assignTo, captureDetails.lineCol)
+ self.addChunk('trans = _orig_trans%(ID)s'%locals())
+ self.addChunk('write = trans.response().write')
+ self.addChunk('self._CHEETAH__isBuffering = _wasBuffering%(ID)s '%locals())
+ self.addChunk('%(assignTo)s = _captureCollector%(ID)s.response().getvalue()'%locals())
+ self.addChunk('del _orig_trans%(ID)s'%locals())
+ self.addChunk('del _captureCollector%(ID)s'%locals())
+ self.addChunk('del _wasBuffering%(ID)s'%locals())
+
+ def setErrorCatcher(self, errorCatcherName):
+ self.turnErrorCatcherOn()
+
+ self.addChunk('if self._CHEETAH__errorCatchers.has_key("' + errorCatcherName + '"):')
+ self.indent()
+ self.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["' +
+ errorCatcherName + '"]')
+ self.dedent()
+ self.addChunk('else:')
+ self.indent()
+ self.addChunk('self._CHEETAH__errorCatcher = self._CHEETAH__errorCatchers["'
+ + errorCatcherName + '"] = ErrorCatchers.'
+ + errorCatcherName + '(self)'
+ )
+ self.dedent()
+
+ def nextFilterRegionID(self):
+ return self.nextCacheID()
+
+ def setTransform(self, transformer, isKlass):
+ self.addChunk('trans = TransformerTransaction()')
+ self.addChunk('trans._response = trans.response()')
+ self.addChunk('trans._response._filter = %s' % transformer)
+ self.addChunk('write = trans._response.write')
+
+ def setFilter(self, theFilter, isKlass):
+ class FilterDetails:
+ pass
+ filterDetails = FilterDetails()
+ filterDetails.ID = ID = self.nextFilterRegionID()
+ filterDetails.theFilter = theFilter
+ filterDetails.isKlass = isKlass
+ self._filterRegionsStack.append((ID, filterDetails)) # attrib of current methodCompiler
+
+ self.addChunk('_orig_filter%(ID)s = _filter'%locals())
+ if isKlass:
+ self.addChunk('_filter = self._CHEETAH__currentFilter = ' + theFilter.strip() +
+ '(self).filter')
+ else:
+ if theFilter.lower() == 'none':
+ self.addChunk('_filter = self._CHEETAH__initialFilter')
+ else:
+ # is string representing the name of a builtin filter
+ self.addChunk('filterName = ' + repr(theFilter))
+ self.addChunk('if self._CHEETAH__filters.has_key("' + theFilter + '"):')
+ self.indent()
+ self.addChunk('_filter = self._CHEETAH__currentFilter = self._CHEETAH__filters[filterName]')
+ self.dedent()
+ self.addChunk('else:')
+ self.indent()
+ self.addChunk('_filter = self._CHEETAH__currentFilter'
+ +' = \\\n\t\t\tself._CHEETAH__filters[filterName] = '
+ + 'getattr(self._CHEETAH__filtersLib, filterName)(self).filter')
+ self.dedent()
+
+ def closeFilterBlock(self):
+ ID, filterDetails = self._filterRegionsStack.pop()
+ #self.addChunk('_filter = self._CHEETAH__initialFilter')
+ #self.addChunk('_filter = _orig_filter%(ID)s'%locals())
+ self.addChunk('_filter = self._CHEETAH__currentFilter = _orig_filter%(ID)s'%locals())
+
+class AutoMethodCompiler(MethodCompiler):
+
+ def _setupState(self):
+ MethodCompiler._setupState(self)
+ self._argStringList = [ ("self", None) ]
+ self._streamingEnabled = True
+ self._isClassMethod = None
+ self._isStaticMethod = None
+
+ def _useKWsDictArgForPassingTrans(self):
+ alreadyHasTransArg = [argname for argname, defval in self._argStringList
+ if argname=='trans']
+ return (self.methodName()!='respond'
+ and not alreadyHasTransArg
+ and self.setting('useKWsDictArgForPassingTrans'))
+
+ def isClassMethod(self):
+ if self._isClassMethod is None:
+ self._isClassMethod = '@classmethod' in self._decorators
+ return self._isClassMethod
+
+ def isStaticMethod(self):
+ if self._isStaticMethod is None:
+ self._isStaticMethod = '@staticmethod' in self._decorators
+ return self._isStaticMethod
+
+ def cleanupState(self):
+ MethodCompiler.cleanupState(self)
+ self.commitStrConst()
+ if self._cacheRegionsStack:
+ self.endCacheRegion()
+ if self._callRegionsStack:
+ self.endCallRegion()
+
+ if self._streamingEnabled:
+ kwargsName = None
+ positionalArgsListName = None
+ for argname, defval in self._argStringList:
+ if argname.strip().startswith('**'):
+ kwargsName = argname.strip().replace('**', '')
+ break
+ elif argname.strip().startswith('*'):
+ positionalArgsListName = argname.strip().replace('*', '')
+
+ if not kwargsName and self._useKWsDictArgForPassingTrans():
+ kwargsName = 'KWS'
+ self.addMethArg('**KWS', None)
+ self._kwargsName = kwargsName
+
+ if not self._useKWsDictArgForPassingTrans():
+ if not kwargsName and not positionalArgsListName:
+ self.addMethArg('trans', 'None')
+ else:
+ self._streamingEnabled = False
+
+ self._indentLev = self.setting('initialMethIndentLevel')
+ mainBodyChunks = self._methodBodyChunks
+ self._methodBodyChunks = []
+ self._addAutoSetupCode()
+ self._methodBodyChunks.extend(mainBodyChunks)
+ self._addAutoCleanupCode()
+
+ def _addAutoSetupCode(self):
+ if self._initialMethodComment:
+ self.addChunk(self._initialMethodComment)
+
+ if self._streamingEnabled and not self.isClassMethod() and not self.isStaticMethod():
+ if self._useKWsDictArgForPassingTrans() and self._kwargsName:
+ self.addChunk('trans = %s.get("trans")'%self._kwargsName)
+ self.addChunk('if (not trans and not self._CHEETAH__isBuffering'
+ ' and not callable(self.transaction)):')
+ self.indent()
+ self.addChunk('trans = self.transaction'
+ ' # is None unless self.awake() was called')
+ self.dedent()
+ self.addChunk('if not trans:')
+ self.indent()
+ self.addChunk('trans = DummyTransaction()')
+ if self.setting('autoAssignDummyTransactionToSelf'):
+ self.addChunk('self.transaction = trans')
+ self.addChunk('_dummyTrans = True')
+ self.dedent()
+ self.addChunk('else: _dummyTrans = False')
+ else:
+ self.addChunk('trans = DummyTransaction()')
+ self.addChunk('_dummyTrans = True')
+ self.addChunk('write = trans.response().write')
+ if self.setting('useNameMapper'):
+ argNames = [arg[0] for arg in self._argStringList]
+ allowSearchListAsMethArg = self.setting('allowSearchListAsMethArg')
+ if allowSearchListAsMethArg and 'SL' in argNames:
+ pass
+ elif allowSearchListAsMethArg and 'searchList' in argNames:
+ self.addChunk('SL = searchList')
+ elif not self.isClassMethod() and not self.isStaticMethod():
+ self.addChunk('SL = self._CHEETAH__searchList')
+ else:
+ self.addChunk('SL = [KWS]')
+ if self.setting('useFilters'):
+ if self.isClassMethod() or self.isStaticMethod():
+ self.addChunk('_filter = lambda x, **kwargs: unicode(x)')
+ else:
+ self.addChunk('_filter = self._CHEETAH__currentFilter')
+ self.addChunk('')
+ self.addChunk("#" *40)
+ self.addChunk('## START - generated method body')
+ self.addChunk('')
+
+ def _addAutoCleanupCode(self):
+ self.addChunk('')
+ self.addChunk("#" *40)
+ self.addChunk('## END - generated method body')
+ self.addChunk('')
+
+ if not self._isGenerator:
+ self.addStop()
+ self.addChunk('')
+
+ def addStop(self, expr=None):
+ self.addChunk('return _dummyTrans and trans.response().getvalue() or ""')
+
+ def addMethArg(self, name, defVal=None):
+ self._argStringList.append( (name, defVal) )
+
+ def methodSignature(self):
+ argStringChunks = []
+ for arg in self._argStringList:
+ chunk = arg[0]
+ if chunk == 'self' and self.isClassMethod():
+ chunk = 'cls'
+ if chunk == 'self' and self.isStaticMethod():
+ # Skip the "self" method for @staticmethod decorators
+ continue
+ if not arg[1] == None:
+ chunk += '=' + arg[1]
+ argStringChunks.append(chunk)
+ argString = (', ').join(argStringChunks)
+
+ output = []
+ if self._decorators:
+ output.append(''.join([self._indent + decorator + '\n'
+ for decorator in self._decorators]))
+ output.append(self._indent + "def "
+ + self.methodName() + "(" +
+ argString + "):\n\n")
+ return ''.join(output)
+
+
+##################################################
+## CLASS COMPILERS
+
+_initMethod_initCheetah = """\
+if not self._CHEETAH__instanceInitialized:
+ cheetahKWArgs = {}
+ allowedKWs = 'searchList namespaces filter filtersLib errorCatcher'.split()
+ for k,v in KWs.items():
+ if k in allowedKWs: cheetahKWArgs[k] = v
+ self._initCheetahInstance(**cheetahKWArgs)
+""".replace('\n', '\n'+' '*8)
+
+class ClassCompiler(GenUtils):
+ methodCompilerClass = AutoMethodCompiler
+ methodCompilerClassForInit = MethodCompiler
+
+ def __init__(self, className, mainMethodName='respond',
+ moduleCompiler=None,
+ fileName=None,
+ settingsManager=None):
+
+ self._settingsManager = settingsManager
+ self._fileName = fileName
+ self._className = className
+ self._moduleCompiler = moduleCompiler
+ self._mainMethodName = mainMethodName
+ self._setupState()
+ methodCompiler = self._spawnMethodCompiler(
+ mainMethodName,
+ initialMethodComment='## CHEETAH: main method generated for this template')
+
+ self._setActiveMethodCompiler(methodCompiler)
+ if fileName and self.setting('monitorSrcFile'):
+ self._addSourceFileMonitoring(fileName)
+
+ def setting(self, key):
+ return self._settingsManager.setting(key)
+
+ def __getattr__(self, name):
+ """Provide access to the methods and attributes of the MethodCompiler
+ at the top of the activeMethods stack: one-way namespace sharing
+
+
+ WARNING: Use .setMethods to assign the attributes of the MethodCompiler
+ from the methods of this class!!! or you will be assigning to attributes
+ of this object instead."""
+
+ if name in self.__dict__:
+ return self.__dict__[name]
+ elif hasattr(self.__class__, name):
+ return getattr(self.__class__, name)
+ elif self._activeMethodsList and hasattr(self._activeMethodsList[-1], name):
+ return getattr(self._activeMethodsList[-1], name)
+ else:
+ raise AttributeError(name)
+
+ def _setupState(self):
+ self._classDef = None
+ self._decoratorsForNextMethod = []
+ self._activeMethodsList = [] # stack while parsing/generating
+ self._finishedMethodsList = [] # store by order
+ self._methodsIndex = {} # store by name
+ self._baseClass = 'Template'
+ self._classDocStringLines = []
+ # printed after methods in the gen class def:
+ self._generatedAttribs = ['_CHEETAH__instanceInitialized = False']
+ self._generatedAttribs.append('_CHEETAH_version = __CHEETAH_version__')
+ self._generatedAttribs.append(
+ '_CHEETAH_versionTuple = __CHEETAH_versionTuple__')
+
+ if self.setting('addTimestampsToCompilerOutput'):
+ self._generatedAttribs.append('_CHEETAH_genTime = __CHEETAH_genTime__')
+ self._generatedAttribs.append('_CHEETAH_genTimestamp = __CHEETAH_genTimestamp__')
+
+ self._generatedAttribs.append('_CHEETAH_src = __CHEETAH_src__')
+ self._generatedAttribs.append(
+ '_CHEETAH_srcLastModified = __CHEETAH_srcLastModified__')
+
+ if self.setting('templateMetaclass'):
+ self._generatedAttribs.append('__metaclass__ = '+self.setting('templateMetaclass'))
+ self._initMethChunks = []
+ self._blockMetaData = {}
+ self._errorCatcherCount = 0
+ self._placeholderToErrorCatcherMap = {}
+
+ def cleanupState(self):
+ while self._activeMethodsList:
+ methCompiler = self._popActiveMethodCompiler()
+ self._swallowMethodCompiler(methCompiler)
+ self._setupInitMethod()
+ if self._mainMethodName == 'respond':
+ if self.setting('setup__str__method'):
+ self._generatedAttribs.append('def __str__(self): return self.respond()')
+ self.addAttribute('_mainCheetahMethod_for_' + self._className +
+ '= ' + repr(self._mainMethodName) )
+
+ def _setupInitMethod(self):
+ __init__ = self._spawnMethodCompiler('__init__',
+ klass=self.methodCompilerClassForInit)
+ __init__.setMethodSignature("def __init__(self, *args, **KWs)")
+ __init__.addChunk('super(%s, self).__init__(*args, **KWs)' % self._className)
+ __init__.addChunk(_initMethod_initCheetah % {'className' : self._className})
+ for chunk in self._initMethChunks:
+ __init__.addChunk(chunk)
+ __init__.cleanupState()
+ self._swallowMethodCompiler(__init__, pos=0)
+
+ def _addSourceFileMonitoring(self, fileName):
+ # @@TR: this stuff needs auditing for Cheetah 2.0
+ # the first bit is added to init
+ self.addChunkToInit('self._filePath = ' + repr(fileName))
+ self.addChunkToInit('self._fileMtime = ' + str(getmtime(fileName)) )
+
+ # the rest is added to the main output method of the class ('mainMethod')
+ self.addChunk('if exists(self._filePath) and ' +
+ 'getmtime(self._filePath) > self._fileMtime:')
+ self.indent()
+ self.addChunk('self._compile(file=self._filePath, moduleName='+self._className + ')')
+ self.addChunk(
+ 'write(getattr(self, self._mainCheetahMethod_for_' + self._className +
+ ')(trans=trans))')
+ self.addStop()
+ self.dedent()
+
+ def setClassName(self, name):
+ self._className = name
+
+ def className(self):
+ return self._className
+
+ def setBaseClass(self, baseClassName):
+ self._baseClass = baseClassName
+
+ def setMainMethodName(self, methodName):
+ if methodName == self._mainMethodName:
+ return
+ ## change the name in the methodCompiler and add new reference
+ mainMethod = self._methodsIndex[self._mainMethodName]
+ mainMethod.setMethodName(methodName)
+ self._methodsIndex[methodName] = mainMethod
+
+ ## make sure that fileUpdate code still works properly:
+ chunkToChange = ('write(self.' + self._mainMethodName + '(trans=trans))')
+ chunks = mainMethod._methodBodyChunks
+ if chunkToChange in chunks:
+ for i in range(len(chunks)):
+ if chunks[i] == chunkToChange:
+ chunks[i] = ('write(self.' + methodName + '(trans=trans))')
+ ## get rid of the old reference and update self._mainMethodName
+ del self._methodsIndex[self._mainMethodName]
+ self._mainMethodName = methodName
+
+ def setMainMethodArgs(self, argsList):
+ mainMethodCompiler = self._methodsIndex[self._mainMethodName]
+ for argName, defVal in argsList:
+ mainMethodCompiler.addMethArg(argName, defVal)
+
+
+ def _spawnMethodCompiler(self, methodName, klass=None,
+ initialMethodComment=None):
+ if klass is None:
+ klass = self.methodCompilerClass
+
+ decorators = self._decoratorsForNextMethod or []
+ self._decoratorsForNextMethod = []
+ methodCompiler = klass(methodName, classCompiler=self,
+ decorators=decorators,
+ initialMethodComment=initialMethodComment)
+ self._methodsIndex[methodName] = methodCompiler
+ return methodCompiler
+
+ def _setActiveMethodCompiler(self, methodCompiler):
+ self._activeMethodsList.append(methodCompiler)
+
+ def _getActiveMethodCompiler(self):
+ return self._activeMethodsList[-1]
+
+ def _popActiveMethodCompiler(self):
+ return self._activeMethodsList.pop()
+
+ def _swallowMethodCompiler(self, methodCompiler, pos=None):
+ methodCompiler.cleanupState()
+ if pos==None:
+ self._finishedMethodsList.append( methodCompiler )
+ else:
+ self._finishedMethodsList.insert(pos, methodCompiler)
+ return methodCompiler
+
+ def startMethodDef(self, methodName, argsList, parserComment):
+ methodCompiler = self._spawnMethodCompiler(
+ methodName, initialMethodComment=parserComment)
+ self._setActiveMethodCompiler(methodCompiler)
+ for argName, defVal in argsList:
+ methodCompiler.addMethArg(argName, defVal)
+
+ def _finishedMethods(self):
+ return self._finishedMethodsList
+
+ def addDecorator(self, decoratorExpr):
+ """Set the decorator to be used with the next method in the source.
+
+ See _spawnMethodCompiler() and MethodCompiler for the details of how
+ this is used.
+ """
+ self._decoratorsForNextMethod.append(decoratorExpr)
+
+ def addClassDocString(self, line):
+ self._classDocStringLines.append( line.replace('%', '%%'))
+
+ def addChunkToInit(self, chunk):
+ self._initMethChunks.append(chunk)
+
+ def addAttribute(self, attribExpr):
+ ## first test to make sure that the user hasn't used any fancy Cheetah syntax
+ # (placeholders, directives, etc.) inside the expression
+ if attribExpr.find('VFN(') != -1 or attribExpr.find('VFFSL(') != -1:
+ raise ParseError(self,
+ 'Invalid #attr directive.' +
+ ' It should only contain simple Python literals.')
+ ## now add the attribute
+ self._generatedAttribs.append(attribExpr)
+
+ def addSuper(self, argsList, parserComment=None):
+ className = self._className #self._baseClass
+ methodName = self._getActiveMethodCompiler().methodName()
+
+ argStringChunks = []
+ for arg in argsList:
+ chunk = arg[0]
+ if not arg[1] == None:
+ chunk += '=' + arg[1]
+ argStringChunks.append(chunk)
+ argString = ','.join(argStringChunks)
+
+ self.addFilteredChunk(
+ 'super(%(className)s, self).%(methodName)s(%(argString)s)'%locals())
+
+ def addErrorCatcherCall(self, codeChunk, rawCode='', lineCol=''):
+ if rawCode in self._placeholderToErrorCatcherMap:
+ methodName = self._placeholderToErrorCatcherMap[rawCode]
+ if not self.setting('outputRowColComments'):
+ self._methodsIndex[methodName].addMethDocString(
+ 'plus at line %s, col %s'%lineCol)
+ return methodName
+
+ self._errorCatcherCount += 1
+ methodName = '__errorCatcher' + str(self._errorCatcherCount)
+ self._placeholderToErrorCatcherMap[rawCode] = methodName
+
+ catcherMeth = self._spawnMethodCompiler(
+ methodName,
+ klass=MethodCompiler,
+ initialMethodComment=('## CHEETAH: Generated from ' + rawCode +
+ ' at line %s, col %s'%lineCol + '.')
+ )
+ catcherMeth.setMethodSignature('def ' + methodName +
+ '(self, localsDict={})')
+ # is this use of localsDict right?
+ catcherMeth.addChunk('try:')
+ catcherMeth.indent()
+ catcherMeth.addChunk("return eval('''" + codeChunk +
+ "''', globals(), localsDict)")
+ catcherMeth.dedent()
+ catcherMeth.addChunk('except self._CHEETAH__errorCatcher.exceptions(), e:')
+ catcherMeth.indent()
+ catcherMeth.addChunk("return self._CHEETAH__errorCatcher.warn(exc_val=e, code= " +
+ repr(codeChunk) + " , rawCode= " +
+ repr(rawCode) + " , lineCol=" + str(lineCol) +")")
+
+ catcherMeth.cleanupState()
+
+ self._swallowMethodCompiler(catcherMeth)
+ return methodName
+
+ def closeDef(self):
+ self.commitStrConst()
+ methCompiler = self._popActiveMethodCompiler()
+ self._swallowMethodCompiler(methCompiler)
+
+ def closeBlock(self):
+ self.commitStrConst()
+ methCompiler = self._popActiveMethodCompiler()
+ methodName = methCompiler.methodName()
+ if self.setting('includeBlockMarkers'):
+ endMarker = self.setting('blockMarkerEnd')
+ methCompiler.addStrConst(endMarker[0] + methodName + endMarker[1])
+ self._swallowMethodCompiler(methCompiler)
+
+ #metaData = self._blockMetaData[methodName]
+ #rawDirective = metaData['raw']
+ #lineCol = metaData['lineCol']
+
+ ## insert the code to call the block, caching if #cache directive is on
+ codeChunk = 'self.' + methodName + '(trans=trans)'
+ self.addChunk(codeChunk)
+
+ #self.appendToPrevChunk(' # generated from ' + repr(rawDirective) )
+ #if self.setting('outputRowColComments'):
+ # self.appendToPrevChunk(' at line %s, col %s' % lineCol + '.')
+
+
+ ## code wrapping methods
+
+ def classDef(self):
+ if self._classDef:
+ return self._classDef
+ else:
+ return self.wrapClassDef()
+
+ __str__ = classDef
+ __unicode__ = classDef
+
+ def wrapClassDef(self):
+ ind = self.setting('indentationStep')
+ classDefChunks = [self.classSignature(),
+ self.classDocstring(),
+ ]
+ def addMethods():
+ classDefChunks.extend([
+ ind + '#'*50,
+ ind + '## CHEETAH GENERATED METHODS',
+ '\n',
+ self.methodDefs(),
+ ])
+ def addAttributes():
+ classDefChunks.extend([
+ ind + '#'*50,
+ ind + '## CHEETAH GENERATED ATTRIBUTES',
+ '\n',
+ self.attributes(),
+ ])
+ if self.setting('outputMethodsBeforeAttributes'):
+ addMethods()
+ addAttributes()
+ else:
+ addAttributes()
+ addMethods()
+
+ classDef = '\n'.join(classDefChunks)
+ self._classDef = classDef
+ return classDef
+
+
+ def classSignature(self):
+ return "class %s(%s):" % (self.className(), self._baseClass)
+
+ def classDocstring(self):
+ if not self._classDocStringLines:
+ return ''
+ ind = self.setting('indentationStep')
+ docStr = ('%(ind)s"""\n%(ind)s' +
+ '\n%(ind)s'.join(self._classDocStringLines) +
+ '\n%(ind)s"""\n'
+ ) % {'ind':ind}
+ return docStr
+
+ def methodDefs(self):
+ methodDefs = [methGen.methodDef() for methGen in self._finishedMethods()]
+ return '\n\n'.join(methodDefs)
+
+ def attributes(self):
+ try:
+ attribs = [self.setting('indentationStep') + str(attrib)
+ for attrib in self._generatedAttribs ]
+ except UnicodeEncodeError:
+ attribs = [self.setting('indentationStep') + unicode(attrib)
+ for attrib in self._generatedAttribs ]
+ return '\n\n'.join(attribs)
+
+class AutoClassCompiler(ClassCompiler):
+ pass
+
+##################################################
+## MODULE COMPILERS
+
+class ModuleCompiler(SettingsManager, GenUtils):
+
+ parserClass = Parser
+ classCompilerClass = AutoClassCompiler
+
+ def __init__(self, source=None, file=None,
+ moduleName='DynamicallyCompiledCheetahTemplate',
+ mainClassName=None, # string
+ mainMethodName=None, # string
+ baseclassName=None, # string
+ extraImportStatements=None, # list of strings
+ settings=None # dict
+ ):
+ super(ModuleCompiler, self).__init__()
+ if settings:
+ self.updateSettings(settings)
+ # disable useStackFrames if the C version of NameMapper isn't compiled
+ # it's painfully slow in the Python version and bites Windows users all
+ # the time:
+ if not NameMapper.C_VERSION:
+ # if not sys.platform.startswith('java'):
+ # warnings.warn(
+ # "\nYou don't have the C version of NameMapper installed! "
+ # "I'm disabling Cheetah's useStackFrames option as it is "
+ # "painfully slow with the Python version of NameMapper. "
+ # "You should get a copy of Cheetah with the compiled C version of NameMapper."
+ # )
+ self.setSetting('useStackFrames', False)
+
+ self._compiled = False
+ self._moduleName = moduleName
+ if not mainClassName:
+ self._mainClassName = moduleName
+ else:
+ self._mainClassName = mainClassName
+ self._mainMethodNameArg = mainMethodName
+ if mainMethodName:
+ self.setSetting('mainMethodName', mainMethodName)
+ self._baseclassName = baseclassName
+
+ self._filePath = None
+ self._fileMtime = None
+
+ if source and file:
+ raise TypeError("Cannot compile from a source string AND file.")
+ elif isinstance(file, basestring): # it's a filename.
+ encoding = settings.get('encoding')
+ if encoding:
+ f = codecs.open(file, 'r', encoding=encoding)
+ else:
+ f = open(file, 'r') # if no encoding is specified, use the
+ # builtin open function, which will
+ # effectively read data as a bytestream
+ source = f.read()
+ f.close()
+ self._filePath = file
+ self._fileMtime = os.path.getmtime(file)
+ elif hasattr(file, 'read'):
+ source = file.read() # Can't set filename or mtime--they're not accessible.
+ elif file:
+ raise TypeError("'file' argument must be a filename string or file-like object")
+
+ if self._filePath:
+ self._fileDirName, self._fileBaseName = os.path.split(self._filePath)
+ self._fileBaseNameRoot, self._fileBaseNameExt = os.path.splitext(self._fileBaseName)
+
+ if not isinstance(source, basestring):
+ source = unicode(source)
+ # by converting to string here we allow objects such as other Templates
+ # to be passed in