summaryrefslogtreecommitdiffstats
path: root/cobbler
diff options
context:
space:
mode:
authorMichael DeHaan <mdehaan@redhat.com>2007-02-19 12:51:28 -0500
committerJim Meyering <jim@meyering.net>2007-02-19 12:51:28 -0500
commitfbd232abc00ea43d64f7e30ee03052d10b83328f (patch)
tree8b9dbdf017673811bc877612c7b5febc676d5756 /cobbler
parent102483e058ecffd07641482758b652ae540f5f7f (diff)
downloadthird_party-cobbler-fbd232abc00ea43d64f7e30ee03052d10b83328f.tar.gz
third_party-cobbler-fbd232abc00ea43d64f7e30ee03052d10b83328f.tar.xz
third_party-cobbler-fbd232abc00ea43d64f7e30ee03052d10b83328f.zip
Bundling Cheetah for backwards compatibility, and associated changes to specfiles/etc.
Diffstat (limited to 'cobbler')
-rw-r--r--cobbler/Cheetah/CacheRegion.py138
-rw-r--r--cobbler/Cheetah/CacheStore.py108
-rw-r--r--cobbler/Cheetah/CheetahWrapper.py589
-rw-r--r--cobbler/Cheetah/Compiler.py1975
-rw-r--r--cobbler/Cheetah/DummyTransaction.py58
-rw-r--r--cobbler/Cheetah/ErrorCatchers.py63
-rw-r--r--cobbler/Cheetah/FileUtils.py374
-rw-r--r--cobbler/Cheetah/Filters.py290
-rw-r--r--cobbler/Cheetah/ImportHooks.py139
-rw-r--r--cobbler/Cheetah/ImportManager.py562
-rw-r--r--cobbler/Cheetah/Macros/I18n.py67
-rw-r--r--cobbler/Cheetah/Macros/__init__.py1
-rw-r--r--cobbler/Cheetah/NameMapper.py355
-rw-r--r--cobbler/Cheetah/Parser.py2558
-rw-r--r--cobbler/Cheetah/Servlet.py126
-rw-r--r--cobbler/Cheetah/SettingsManager.py623
-rw-r--r--cobbler/Cheetah/SourceReader.py305
-rw-r--r--cobbler/Cheetah/Template.py1858
-rw-r--r--cobbler/Cheetah/TemplateCmdLineIface.py108
-rw-r--r--cobbler/Cheetah/Templates/SkeletonPage.py273
-rw-r--r--cobbler/Cheetah/Templates/SkeletonPage.tmpl44
-rw-r--r--cobbler/Cheetah/Templates/_SkeletonPage.py216
-rw-r--r--cobbler/Cheetah/Templates/__init__.py1
-rw-r--r--cobbler/Cheetah/Tests/CheetahWrapper.py596
-rw-r--r--cobbler/Cheetah/Tests/FileRefresh.py55
-rw-r--r--cobbler/Cheetah/Tests/NameMapper.py539
-rw-r--r--cobbler/Cheetah/Tests/SyntaxAndOutput.py3170
-rw-r--r--cobbler/Cheetah/Tests/Template.py312
-rw-r--r--cobbler/Cheetah/Tests/Test.py70
-rw-r--r--cobbler/Cheetah/Tests/__init__.py1
-rw-r--r--cobbler/Cheetah/Tests/unittest_local_copy.py977
-rw-r--r--cobbler/Cheetah/Tools/CGITemplate.py78
-rw-r--r--cobbler/Cheetah/Tools/MondoReport.py464
-rw-r--r--cobbler/Cheetah/Tools/MondoReportDoc.txt391
-rw-r--r--cobbler/Cheetah/Tools/RecursiveNull.py23
-rw-r--r--cobbler/Cheetah/Tools/SiteHierarchy.py183
-rw-r--r--cobbler/Cheetah/Tools/__init__.py8
-rw-r--r--cobbler/Cheetah/Unspecified.py9
-rw-r--r--cobbler/Cheetah/Utils/Indenter.py135
-rw-r--r--cobbler/Cheetah/Utils/Misc.py82
-rw-r--r--cobbler/Cheetah/Utils/VerifyType.py82
-rw-r--r--cobbler/Cheetah/Utils/WebInputMixin.py103
-rw-r--r--cobbler/Cheetah/Utils/__init__.py1
-rw-r--r--cobbler/Cheetah/Utils/htmlDecode.py14
-rw-r--r--cobbler/Cheetah/Utils/htmlEncode.py21
-rw-r--r--cobbler/Cheetah/Utils/memcache.py625
-rw-r--r--cobbler/Cheetah/Utils/optik/__init__.py32
-rw-r--r--cobbler/Cheetah/Utils/optik/errors.py52
-rw-r--r--cobbler/Cheetah/Utils/optik/option.py354
-rw-r--r--cobbler/Cheetah/Utils/optik/option_parser.py667
-rw-r--r--cobbler/Cheetah/Version.py58
-rw-r--r--cobbler/Cheetah/__init__.py27
-rwxr-xr-xcobbler/Cheetah/_namemapper.sobin0 -> 10144 bytes
-rw-r--r--cobbler/Cheetah/convertTmplPathToModuleName.py15
-rw-r--r--cobbler/action_import.py2
-rw-r--r--cobbler/action_sync.py28
56 files changed, 19995 insertions, 10 deletions
diff --git a/cobbler/Cheetah/CacheRegion.py b/cobbler/Cheetah/CacheRegion.py
new file mode 100644
index 0000000..16de188
--- /dev/null
+++ b/cobbler/Cheetah/CacheRegion.py
@@ -0,0 +1,138 @@
+# $Id: CacheRegion.py,v 1.3 2006/01/28 04:19:30 tavis_rudd Exp $
+"""Cache holder classes for Cheetah:
+
+Cache regions are defined using the #cache Cheetah directive. Each
+cache region can be viewed as a dictionary (keyed by cacheRegionID)
+handling at least one cache item (the default one). It's possible to add
+cacheItems in a region by using the `varyBy` #cache directive parameter as
+in the following example::
+ #def getArticle
+ this is the article content.
+ #end def
+
+ #cache varyBy=$getArticleID()
+ $getArticle($getArticleID())
+ #end cache
+
+The code above will generate a CacheRegion and add new cacheItem for each value
+of $getArticleID().
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com> and Philippe Normand <phil@base-art.net>
+Version: $Revision: 1.3 $
+Start Date: 2005/06/20
+Last Revision Date: $Date: 2006/01/28 04:19:30 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com> and Philippe Normand <phil@base-art.net>"
+__revision__ = "$Revision: 1.3 $"[11:-2]
+
+import md5
+from time import time as currentTime
+from Cheetah.CacheStore import MemoryCacheStore
+
+class CacheItem:
+ """A CacheItem is a container storing:
+
+ - cacheID (string)
+ - refreshTime (timestamp or None) : last time the cache was refreshed
+ - data (string) : the content of the cache
+ """
+
+ def __init__(self, cacheItemID, cacheStore):
+ self._cacheItemID = cacheItemID
+ self._cacheStore = cacheStore
+ self._refreshTime = None
+ self._expiryTime = 0
+
+ def hasExpired(self):
+ return (self._expiryTime and currentTime() > self._expiryTime)
+
+ def setExpiryTime(self, time):
+ self._expiryTime = time
+
+ def getExpiryTime(self):
+ return self._expiryTime
+
+ def setData(self, data):
+ self._refreshTime = currentTime()
+ self._cacheStore.set(self._cacheItemID, data, self._expiryTime)
+
+ def getRefreshTime(self):
+ return self._refreshTime
+
+ def getData(self):
+ assert self._refreshTime
+ return self._cacheStore.get(self._cacheItemID)
+
+ def renderOutput(self):
+ """Can be overridden to implement edge-caching"""
+ return self.getData() or ""
+
+ def clear(self):
+ self._cacheStore.delete(self._cacheItemID)
+ self._refreshTime = None
+
+class _CacheDataStoreWrapper:
+ def __init__(self, dataStore, keyPrefix):
+ self._dataStore = dataStore
+ self._keyPrefix = keyPrefix
+
+ def get(self, key):
+ return self._dataStore.get(self._keyPrefix+key)
+
+ def delete(self, key):
+ self._dataStore.delete(self._keyPrefix+key)
+
+ def set(self, key, val, time=0):
+ self._dataStore.set(self._keyPrefix+key, val, time=time)
+
+class CacheRegion:
+ """ A `CacheRegion` stores some `CacheItem` instances.
+
+ This implementation stores the data in the memory of the current process.
+ If you need a more advanced data store, create a cacheStore class that works
+ with Cheetah's CacheStore protocol and provide it as the cacheStore argument
+ to __init__. For example you could use
+ Cheetah.CacheStore.MemcachedCacheStore, a wrapper around the Python
+ memcached API (http://www.danga.com/memcached).
+ """
+ _cacheItemClass = CacheItem
+
+ def __init__(self, regionID, templateCacheIdPrefix='', cacheStore=None):
+ self._isNew = True
+ self._regionID = regionID
+ self._templateCacheIdPrefix = templateCacheIdPrefix
+ if not cacheStore:
+ cacheStore = MemoryCacheStore()
+ self._cacheStore = cacheStore
+ self._wrappedCacheDataStore = _CacheDataStoreWrapper(
+ cacheStore, keyPrefix=templateCacheIdPrefix+':'+regionID+':')
+ self._cacheItems = {}
+
+ def isNew(self):
+ return self._isNew
+
+ def clear(self):
+ " drop all the caches stored in this cache region "
+ for cacheItemId in self._cacheItems.keys():
+ cacheItem = self._cacheItems[cacheItemId]
+ cacheItem.clear()
+ del self._cacheItems[cacheItemId]
+
+ def getCacheItem(self, cacheItemID):
+ """ Lazy access to a cacheItem
+
+ Try to find a cache in the stored caches. If it doesn't
+ exist, it's created.
+
+ Returns a `CacheItem` instance.
+ """
+ cacheItemID = md5.new(str(cacheItemID)).hexdigest()
+
+ if not self._cacheItems.has_key(cacheItemID):
+ cacheItem = self._cacheItemClass(
+ cacheItemID=cacheItemID, cacheStore=self._wrappedCacheDataStore)
+ self._cacheItems[cacheItemID] = cacheItem
+ self._isNew = False
+ return self._cacheItems[cacheItemID]
diff --git a/cobbler/Cheetah/CacheStore.py b/cobbler/Cheetah/CacheStore.py
new file mode 100644
index 0000000..f1a1840
--- /dev/null
+++ b/cobbler/Cheetah/CacheStore.py
@@ -0,0 +1,108 @@
+"""Provides several CacheStore backends for Cheetah's caching framework. The
+methods provided by these classes have the same semantics as those in the
+python-memcached API, except for their return values:
+
+set(key, val, time=0)
+ set the value unconditionally
+add(key, val, time=0)
+ set only if the server doesn't already have this key
+replace(key, val, time=0)
+ set only if the server already have this key
+get(key, val)
+ returns val or raises a KeyError
+delete(key)
+ deletes or raises a KeyError
+
+"""
+from time import time as currentTime
+
+from Cheetah.Utils.memcache import Client as MemcachedClient
+
+class Error(Exception):
+ pass
+
+class AbstractCacheStore(object):
+
+ def set(self, key, val, time=None):
+ raise NotImplementedError
+
+ def add(self, key, val, time=None):
+ raise NotImplementedError
+
+ def replace(self, key, val, time=None):
+ raise NotImplementedError
+
+ def delete(self, key):
+ raise NotImplementedError
+
+ def get(self, key):
+ raise NotImplementedError
+
+class MemoryCacheStore(AbstractCacheStore):
+ def __init__(self):
+ self._data = {}
+
+ def set(self, key, val, time=0):
+ self._data[key] = (val, time)
+
+ def add(self, key, val, time=0):
+ if self._data.has_key(key):
+ raise Error('a value for key %r is already in the cache'%key)
+ self._data[key] = (val, time)
+
+ def replace(self, key, val, time=0):
+ if self._data.has_key(key):
+ raise Error('a value for key %r is already in the cache'%key)
+ self._data[key] = (val, time)
+
+ def delete(self, key):
+ del self._data[key]
+
+ def get(self, key):
+ (val, exptime) = self._data[key]
+ if exptime and currentTime() > exptime:
+ del self._data[key]
+ raise KeyError(key)
+ else:
+ return val
+
+ def clear(self):
+ self._data.clear()
+
+class MemcachedCacheStore(AbstractCacheStore):
+ servers = ('127.0.0.1:11211')
+ def __init__(self, servers=None, debug=False):
+ if servers is None:
+ servers = self.servers
+
+ self._client = MemcachedClient(servers, debug)
+
+ def set(self, key, val, time=0):
+ self._client.set(key, val, time)
+
+ def add(self, key, val, time=0):
+ res = self._client.add(key, val, time)
+ if not res:
+ raise Error('a value for key %r is already in the cache'%key)
+ self._data[key] = (val, time)
+
+ def replace(self, key, val, time=0):
+ res = self._client.replace(key, val, time)
+ if not res:
+ raise Error('a value for key %r is already in the cache'%key)
+ self._data[key] = (val, time)
+
+ def delete(self, key):
+ res = self._client.delete(key, time=0)
+ if not res:
+ raise KeyError(key)
+
+ def get(self, key):
+ val = self._client.get(key)
+ if val is None:
+ raise KeyError(key)
+ else:
+ return val
+
+ def clear(self):
+ self._client.flush_all()
diff --git a/cobbler/Cheetah/CheetahWrapper.py b/cobbler/Cheetah/CheetahWrapper.py
new file mode 100644
index 0000000..68a8598
--- /dev/null
+++ b/cobbler/Cheetah/CheetahWrapper.py
@@ -0,0 +1,589 @@
+#!/usr/bin/env python
+# $Id: CheetahWrapper.py,v 1.25 2006/02/04 00:59:46 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 <iron@mso.oz.net>
+Version: $Revision: 1.25 $
+Start Date: 2001/03/30
+Last Revision Date: $Date: 2006/02/04 00:59:46 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com> and Mike Orr <iron@mso.oz.net>"
+__revision__ = "$Revision: 1.25 $"[11:-2]
+
+import getopt, glob, os, pprint, re, shutil, sys
+import cPickle as pickle
+
+from Cheetah.Version import Version
+from Cheetah.Template import Template, DEFAULT_COMPILER_SETTINGS
+from Cheetah.Utils.Misc import mkdirsWithPyInitFiles
+from Cheetah.Utils.optik import OptionParser
+
+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__
+
+
+class MyOptionParser(OptionParser):
+ standard_option_list = [] # We use commands for Optik's standard options.
+
+ def error(self, msg):
+ """Print our usage+error page."""
+ usage(HELP_PAGE2, msg)
+
+ def print_usage(self, file=None):
+ """Our usage+error page already has this."""
+ pass
+
+
+##################################################
+## 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 <iron@mso.oz.net>
+
+""" % 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.
+"""
+
+HELP_PAGE2 = """\
+OPTIONS FOR "compile" AND "fill":
+---------------------------------
+ --idir DIR, --odir DIR : input/output directories (default: current dir)
+ --iext EXT, --oext EXT : input/output filename extensions
+ (default for compile: tmpl/py, fill: tmpl/html)
+ -R : recurse subdirectories looking for input files
+ --debug : print lots of diagnostic output to standard error
+ --env : put the environment in the searchList
+ --flat : no destination subdirectories
+ --nobackup : don't make backups
+ --pickle FILE : unpickle FILE and put that object in the searchList
+ --stdout, -p : output to standard output (pipe)
+ --settings : a string representing the compiler settings to use
+ e.g. --settings='useNameMapper=False,useFilters=False'
+ This string is eval'd in Python so it should contain
+ valid Python syntax.
+ --templateAPIClass : a string representing a subclass of
+ Cheetah.Template:Template to use for compilation
+
+Run "cheetah help" for the main help screen.
+"""
+
+##################################################
+## CheetahWrapper CLASS
+
+class CheetahWrapper:
+ 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 = []
+
+ ##################################################
+ ## 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"
+ parser = MyOptionParser()
+ pao = parser.add_option
+ pao("--idir", action="store", dest="idir", default="")
+ pao("--odir", action="store", dest="odir", default="")
+ pao("--iext", action="store", dest="iext", default=".tmpl")
+ pao("--oext", action="store", dest="oext", default=defaultOext)
+ pao("-R", action="store_true", dest="recurse", default=False)
+ pao("--stdout", "-p", action="store_true", dest="stdout", default=False)
+ pao("--debug", action="store_true", dest="debug", default=False)
+ pao("--env", action="store_true", dest="env", default=False)
+ pao("--pickle", action="store", dest="pickle", default="")
+ pao("--flat", action="store_true", dest="flat", default=False)
+ pao("--nobackup", action="store_true", dest="nobackup", default=False)
+ pao("--settings", action="store", dest="compilerSettingsString", default=None)
+ pao("--templateAPIClass", action="store", dest="templateClassName", default=None)
+
+ self.opts, self.pathArgs = opts, files = parser.parse_args(args)
+ D("""\
+cheetah compile %s
+Options are
+%s
+Files are %s""", args, pprint.pformat(vars(opts)), files)
+
+
+ #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)
+ opts.verbose = not opts.stdout
+
+ ##################################################
+ ## 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):
+ usage(HELP_PAGE2, "", sys.stdout)
+
+ 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 Cheetah.Tests.unittest_local_copy as unittest
+ del sys.argv[1:] # Prevent unittest from misinterpreting options.
+ sys.argv.extend(self.testOpts)
+ #unittest.main(testSuite=Test.testSuite)
+ #unittest.main(testSuite=Test.testSuite)
+ unittest.main(module=Test)
+
+ 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)
+ 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 dstSources.has_key(b.dst):
+ dstSources[b.dst].append(b.src)
+ else:
+ dstSources[b.dst] = [b.src]
+ keys = dstSources.keys()
+ keys.sort()
+ 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,
+ 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()
+
+
+##################################################
+## if run from the command line
+if __name__ == '__main__': CheetahWrapper().main()
+
+# vim: shiftwidth=4 tabstop=4 expandtab
diff --git a/cobbler/Cheetah/Compiler.py b/cobbler/Cheetah/Compiler.py
new file mode 100644
index 0000000..a74267d
--- /dev/null
+++ b/cobbler/Cheetah/Compiler.py
@@ -0,0 +1,1975 @@
+#!/usr/bin/env python
+# $Id: Compiler.py,v 1.148 2006/06/22 00:18:22 tavis_rudd Exp $
+"""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__.
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com>
+Version: $Revision: 1.148 $
+Start Date: 2001/09/19
+Last Revision Date: $Date: 2006/06/22 00:18:22 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.148 $"[11:-2]
+
+import sys
+import os
+import os.path
+from os.path import getmtime, exists
+import re
+import types
+import time
+import random
+import warnings
+import __builtin__
+import copy
+
+from Cheetah.Version import Version, VersionTuple
+from Cheetah.SettingsManager import SettingsManager
+from Cheetah.Parser import Parser, ParseError, specialVarRE, \
+ STATIC_CACHE, REFRESH_CACHE, SET_LOCAL, SET_GLOBAL,SET_MODULE
+from Cheetah.Utils.Indenter import indentize # an undocumented preprocessor
+from Cheetah import ErrorCatchers
+from Cheetah import NameMapper
+
+from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList
+VFFSL=valueFromFrameOrSearchList
+VFSL=valueFromSearchList
+VFN=valueForName
+currentTime=time.time
+
+class Error(Exception): pass
+
+DEFAULT_COMPILER_SETTINGS = {
+ ## controlling the handling of Cheetah $placeholders
+ 'useNameMapper': True, # Unified dotted notation and the searchList
+ 'useSearchList': True, # if false, assume the first
+ # portion of the $variable (before the first dot) is a global,
+ # builtin, or local var that doesn't need
+ # looking up in the searchlist BUT use
+ # namemapper on the rest of the lookup
+ 'allowSearchListAsMethArg': True,
+ 'useAutocalling': True, # detect and call callable()'s, requires NameMapper
+ 'useStackFrames': True, # use NameMapper.valueFromFrameOrSearchList
+ # rather than NameMapper.valueFromSearchList
+ 'useErrorCatcher':False,
+ 'alwaysFilterNone':True, # filter out None, before the filter is called
+ 'useFilters':True, # use str instead if =False
+ 'includeRawExprInFilterArgs':True,
+
+
+ #'lookForTransactionAttr':False,
+ 'autoAssignDummyTransactionToSelf':False,
+ 'useKWsDictArgForPassingTrans':True,
+
+ ## controlling the aesthetic appearance / behaviour of generated code
+ 'commentOffset': 1,
+ # should shorter str constant chunks be printed using repr rather than ''' quotes
+ 'reprShortStrConstants': True,
+ 'reprNewlineThreshold':3,
+ 'outputRowColComments':True,
+ # should #block's be wrapped in a comment in the template's output
+ 'includeBlockMarkers': False,
+ '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,
+
+
+ ## customizing the #extends directive
+ 'autoImportForExtendsDirective':True,
+ 'handlerForExtendsDirective':None, # baseClassName = handler(compiler, baseClassName)
+ # a callback hook for customizing the
+ # #extends directive. It can manipulate
+ # the compiler's state if needed.
+ # also see allowExpressionsInExtendsDirective
+
+
+ # input filtering/restriction
+ # use lower case keys here!!
+ 'disabledDirectives':[], # list of directive keys, without the start token
+ 'enabledDirectives':[], # list of directive keys, without the start token
+
+ 'disabledDirectiveHooks':[], # callable(parser, directiveKey)
+ 'preparseDirectiveHooks':[], # callable(parser, directiveKey)
+ 'postparseDirectiveHooks':[], # callable(parser, directiveKey)
+ 'preparsePlaceholderHooks':[], # callable(parser)
+ 'postparsePlaceholderHooks':[], # callable(parser)
+ # the above hooks don't need to return anything
+
+ 'expressionFilterHooks':[], # callable(parser, expr, exprType, rawExpr=None, startPos=None)
+ # exprType is the name of the directive, 'psp', or 'placeholder'. all
+ # lowercase. The filters *must* return the expr or raise an exception.
+ # They can modify the expr if needed.
+
+ 'templateMetaclass':None, # strictly optional. Only works with new-style baseclasses
+
+
+ 'i18NFunctionName':'self.i18n',
+
+ ## These are used in the parser, but I've put them here for the time being to
+ ## facilitate separating the parser and compiler:
+ 'cheetahVarStartToken':'$',
+ 'commentStartToken':'##',
+ 'multiLineCommentStartToken':'#*',
+ 'multiLineCommentEndToken':'*#',
+ 'gobbleWhitespaceAroundMultiLineComments':True,
+ 'directiveStartToken':'#',
+ 'directiveEndToken':'#',
+ 'allowWhitespaceAfterDirectiveStartToken':False,
+ 'PSPStartToken':'<%',
+ 'PSPEndToken':'%>',
+ 'EOLSlurpToken':'#',
+ 'gettextTokens': ["_", "N_", "ngettext"],
+ 'allowExpressionsInExtendsDirective': False, # the default restricts it to
+ # accepting dotted names
+ 'allowEmptySingleLineMethods': False,
+ 'allowNestedDefScopes': True,
+ 'allowPlaceholderFilterArgs': True,
+
+ ## See Parser.initDirectives() for the use of the next 3
+ #'directiveNamesAndParsers':{}
+ #'endDirectiveNamesAndHandlers':{}
+ #'macroDirectives':{}
+
+ }
+
+
+
+class GenUtils:
+ """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,
+ decorator=None):
+ self._settingsManager = classCompiler
+ self._classCompiler = classCompiler
+ self._moduleCompiler = classCompiler._moduleCompiler
+ self._methodName = methodName
+ self._initialMethodComment = initialMethodComment
+ self._setupState()
+ self._decorator = decorator
+
+ 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
+
+ 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 _unescapeCheetahVars(self, theString):
+ """Unescape any escaped Cheetah \$vars in the string.
+ """
+
+ token = self.setting('cheetahVarStartToken')
+ return theString.replace('\\' + token, token)
+
+ def _unescapeDirectives(self, theString):
+ """Unescape any escaped Cheetah \$vars in the string.
+ """
+
+ token = self.setting('directiveStartToken')
+ return theString.replace('\\' + token, token)
+
+ def commitStrConst(self):
+ """Add the code for outputting the pending strConst without chopping off
+ any whitespace from it.
+ """
+ if self._pendingStrConstChunks:
+ strConst = self._unescapeCheetahVars(''.join(self._pendingStrConstChunks))
+ strConst = self._unescapeDirectives(strConst)
+ self._pendingStrConstChunks = []
+ if not strConst:
+ return
+ if self.setting('reprShortStrConstants') and \
+ strConst.count('\n') < self.setting('reprNewlineThreshold'):
+ self.addWriteChunk( repr(strConst).replace('\\012','\\n'))
+ else:
+ strConst = strConst.replace('\\','\\\\').replace("'''","'\'\'\'")
+ if strConst[0] == "'":
+ strConst = '\\' + strConst
+ if strConst[-1] == "'":
+ strConst = strConst[:-1] + '\\' + strConst[-1]
+
+ self.addWriteChunk("'''" + strConst + "'''" )
+
+ 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: 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]
+ 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 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())
+
+class AutoMethodCompiler(MethodCompiler):
+
+ def _setupState(self):
+ MethodCompiler._setupState(self)
+ self._argStringList = [ ("self",None) ]
+ self._streamingEnabled = True
+
+ 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 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:
+ 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')
+ else:
+ self.addChunk('SL = self._CHEETAH__searchList')
+ if self.setting('useFilters'):
+ 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 not arg[1] == None:
+ chunk += '=' + arg[1]
+ argStringChunks.append(chunk)
+ argString = (', ').join(argStringChunks)
+
+ output = []
+ if self._decorator:
+ output.append(self._indent + self._decorator+'\n')
+ 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 self.__dict__.has_key(name):
+ 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._decoratorForNextMethod = None
+ 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__')
+ 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("%s.__init__(self, *args, **KWs)" % self._baseClass)
+ __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='+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
+
+ decorator = None
+ if self._decoratorForNextMethod:
+ decorator = self._decoratorForNextMethod
+ self._decoratorForNextMethod = None
+ methodCompiler = klass(methodName, classCompiler=self,
+ decorator=decorator,
+ 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._decoratorForNextMethod = 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 addErrorCatcherCall(self, codeChunk, rawCode='', lineCol=''):
+ if self._placeholderToErrorCatcherMap.has_key(rawCode):
+ 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
+
+ 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 = [str(methGen) for methGen in self._finishedMethods() ]
+ return '\n\n'.join(methodDefs)
+
+ def attributes(self):
+ attribs = [self.setting('indentationStep') + str(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
+ ):
+ SettingsManager.__init__(self)
+ 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:
+ # disable warning noise as Cobbler is noarch, hence no C version -- mdehaan
+ #
+ # 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, types.StringType) or isinstance(file, types.UnicodeType): # it's a filename.
+
+ f = open(file) # Raises IOError.
+ 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, str) or isinstance(source, unicode)):
+ source = str( source )
+ # by converting to string here we allow objects such as other Templates
+ # to be passed in
+
+ # Handle the #indent directive by converting it to other directives.
+ # (Over the long term we'll make it a real directive.)
+ if source == "":
+ warnings.warn("You supplied an empty string for the source!", )
+
+ if source.find('#indent') != -1: #@@TR: undocumented hack
+ source = indentize(source)
+
+ self._parser = self.parserClass(source, filename=self._filePath, compiler=self)
+ self._setupCompilerState()
+
+ def __getattr__(self, name):
+ """Provide one-way access to the methods and attributes of the
+ ClassCompiler, and thereby the MethodCompilers as well.
+
+ WARNING: Use .setMethods to assign the attributes of the ClassCompiler
+ from the methods of this class!!! or you will be assigning to attributes
+ of this object instead.
+ """
+ if self.__dict__.has_key(name):
+ return self.__dict__[name]
+ elif hasattr(self.__class__, name):
+ return getattr(self.__class__, name)
+ elif self._activeClassesList and hasattr(self._activeClassesList[-1], name):
+ return getattr(self._activeClassesList[-1], name)
+ else:
+ raise AttributeError, name
+
+ def _initializeSettings(self):
+ self.updateSettings(copy.deepcopy(DEFAULT_COMPILER_SETTINGS))
+
+ def _setupCompilerState(self):
+ self._activeClassesList = []
+ self._finishedClassesList = [] # listed by ordered
+ self._finishedClassIndex = {} # listed by name
+ self._moduleDef = None
+ self._moduleShBang = '#!/usr/bin/env python'
+ self._moduleEncoding = 'ascii'
+ self._moduleEncodingStr = ''
+ self._moduleHeaderLines = []
+ self._moduleDocStringLines = []
+ self._specialVars = {}
+ self._importStatements = [
+ "import sys",
+ "import os",
+ "import os.path",
+ "from os.path import getmtime, exists",
+ "import time",
+ "import types",
+ "import __builtin__",
+ "from Cheetah.Version import MinCompatibleVersion as RequiredCheetahVersion",
+ "from Cheetah.Version import MinCompatibleVersionTuple as RequiredCheetahVersionTuple",
+ "from Cheetah.Template import Template",
+ "from Cheetah.DummyTransaction import DummyTransaction",
+ "from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList",
+ "from Cheetah.CacheRegion import CacheRegion",
+ "import Cheetah.Filters as Filters",
+ "import Cheetah.ErrorCatchers as ErrorCatchers",
+ ]
+
+ self._importedVarNames = ['sys',
+ 'os',
+ 'os.path',
+ 'time',
+ 'types',
+ 'Template',
+ 'DummyTransaction',
+ 'NotFound',
+ 'Filters',
+ 'ErrorCatchers',
+ 'CacheRegion',
+ ]
+
+ self._moduleConstants = [
+ "try:",
+ " True, False",
+ "except NameError:",
+ " True, False = (1==1), (1==0)",
+ "VFFSL=valueFromFrameOrSearchList",
+ "VFSL=valueFromSearchList",
+ "VFN=valueForName",
+ "currentTime=time.time",
+ ]
+
+ def compile(self):
+ classCompiler = self._spawnClassCompiler(self._mainClassName)
+ if self._baseclassName:
+ classCompiler.setBaseClass(self._baseclassName)
+ self._addActiveClassCompiler(classCompiler)
+ self._parser.parse()
+ self._swallowClassCompiler(self._popActiveClassCompiler())
+ self._compiled = True
+ self._parser.cleanup()
+
+ def _spawnClassCompiler(self, className, klass=None):
+ if klass is None:
+ klass = self.classCompilerClass
+ classCompiler = klass(className,
+ moduleCompiler=self,
+ mainMethodName=self.setting('mainMethodName'),
+ fileName=self._filePath,
+ settingsManager=self,
+ )
+ return classCompiler
+
+ def _addActiveClassCompiler(self, classCompiler):
+ self._activeClassesList.append(classCompiler)
+
+ def _getActiveClassCompiler(self):
+ return self._activeClassesList[-1]
+
+ def _popActiveClassCompiler(self):
+ return self._activeClassesList.pop()
+
+ def _swallowClassCompiler(self, classCompiler):
+ classCompiler.cleanupState()
+ self._finishedClassesList.append( classCompiler )
+ self._finishedClassIndex[classCompiler.className()] = classCompiler
+ return classCompiler
+
+ def _finishedClasses(self):
+ return self._finishedClassesList
+
+ def importedVarNames(self):
+ return self._importedVarNames
+
+ def addImportedVarNames(self, varNames):
+ self._importedVarNames.extend(varNames)
+
+ ## methods for adding stuff to the module and class definitions
+
+ def setBaseClass(self, baseClassName):
+ if self._mainMethodNameArg:
+ self.setMainMethodName(self._mainMethodNameArg)
+ else:
+ self.setMainMethodName(self.setting('mainMethodNameForSubclasses'))
+
+ if self.setting('handlerForExtendsDirective'):
+ handler = self.setting('handlerForExtendsDirective')
+ baseClassName = handler(compiler=self, baseClassName=baseClassName)
+ self._getActiveClassCompiler().setBaseClass(baseClassName)
+ elif (not self.setting('autoImportForExtendsDirective')
+ or baseClassName=='object' or baseClassName in self.importedVarNames()):
+ self._getActiveClassCompiler().setBaseClass(baseClassName)
+ # no need to import
+ else:
+ ##################################################
+ ## If the #extends directive contains a classname or modulename that isn't
+ # in self.importedVarNames() already, we assume that we need to add
+ # an implied 'from ModName import ClassName' where ModName == ClassName.
+ # - This is the case in WebKit servlet modules.
+ # - We also assume that the final . separates the classname from the
+ # module name. This might break if people do something really fancy
+ # with their dots and namespaces.
+ chunks = baseClassName.split('.')
+ if len(chunks)==1:
+ self._getActiveClassCompiler().setBaseClass(baseClassName)
+ if baseClassName not in self.importedVarNames():
+ modName = baseClassName
+ # we assume the class name to be the module name
+ # and that it's not a builtin:
+ importStatement = "from %s import %s" % (modName, baseClassName)
+ self.addImportStatement(importStatement)
+ self.addImportedVarNames( [baseClassName,] )
+ else:
+ needToAddImport = True
+ modName = chunks[0]
+ #print chunks, ':', self.importedVarNames()
+ for chunk in chunks[1:-1]:
+ if modName in self.importedVarNames():
+ needToAddImport = False
+ finalBaseClassName = baseClassName.replace(modName+'.', '')
+ self._getActiveClassCompiler().setBaseClass(finalBaseClassName)
+ break
+ else:
+ modName += '.'+chunk
+ if needToAddImport:
+ modName, finalClassName = '.'.join(chunks[:-1]), chunks[-1]
+ #if finalClassName != chunks[:-1][-1]:
+ if finalClassName != chunks[-2]:
+ # we assume the class name to be the module name
+ modName = '.'.join(chunks)
+ self._getActiveClassCompiler().setBaseClass(finalClassName)
+ importStatement = "from %s import %s" % (modName, finalClassName)
+ self.addImportStatement(importStatement)
+ self.addImportedVarNames( [finalClassName,] )
+
+ def setCompilerSetting(self, key, valueExpr):
+ self.setSetting(key, eval(valueExpr) )
+ self._parser.configureParser()
+
+ def setCompilerSettings(self, keywords, settingsStr):
+ KWs = keywords
+ merge = True
+ if 'nomerge' in KWs:
+ merge = False
+
+ if 'reset' in KWs:
+ # @@TR: this is actually caught by the parser at the moment.
+ # subject to change in the future
+ self._initializeSettings()
+ self._parser.configureParser()
+ return
+ elif 'python' in KWs:
+ settingsReader = self.updateSettingsFromPySrcStr
+ # this comes from SettingsManager
+ else:
+ # this comes from SettingsManager
+ settingsReader = self.updateSettingsFromConfigStr
+
+ settingsReader(settingsStr)
+ self._parser.configureParser()
+
+ def setShBang(self, shBang):
+ self._moduleShBang = shBang
+
+ def setModuleEncoding(self, encoding):
+ self._moduleEncoding = encoding
+ self._moduleEncodingStr = '# -*- coding: %s -*-' %encoding
+
+ def getModuleEncoding(self):
+ return self._moduleEncoding
+
+ def addModuleHeader(self, line):
+ """Adds a header comment to the top of the generated module.
+ """
+ self._moduleHeaderLines.append(line)
+
+ def addModuleDocString(self, line):
+ """Adds a line to the generated module docstring.
+ """
+ self._moduleDocStringLines.append(line)
+
+ def addModuleGlobal(self, line):
+ """Adds a line of global module code. It is inserted after the import
+ statements and Cheetah default module constants.
+ """
+ self._moduleConstants.append(line)
+
+ def addSpecialVar(self, basename, contents, includeUnderscores=True):
+ """Adds module __specialConstant__ to the module globals.
+ """
+ name = includeUnderscores and '__'+basename+'__' or basename
+ self._specialVars[name] = contents.strip()
+
+ def addImportStatement(self, impStatement):
+ self._importStatements.append(impStatement)
+
+ #@@TR 2005-01-01: there's almost certainly a cleaner way to do this!
+ importVarNames = impStatement[impStatement.find('import') + len('import'):].split(',')
+ importVarNames = [var.split()[-1] for var in importVarNames] # handles aliases
+ importVarNames = [var for var in importVarNames if var!='*']
+ self.addImportedVarNames(importVarNames) #used by #extend for auto-imports
+
+ def addAttribute(self, attribName, expr):
+ self._getActiveClassCompiler().addAttribute(attribName + ' =' + expr)
+
+ def addComment(self, comm):
+ if re.match(r'#+$',comm): # skip bar comments
+ return
+
+ specialVarMatch = specialVarRE.match(comm)
+ if specialVarMatch:
+ # @@TR: this is a bit hackish and is being replaced with
+ # #set module varName = ...
+ return self.addSpecialVar(specialVarMatch.group(1),
+ comm[specialVarMatch.end():])
+ elif comm.startswith('doc:'):
+ addLine = self.addMethDocString
+ comm = comm[len('doc:'):].strip()
+ elif comm.startswith('doc-method:'):
+ addLine = self.addMethDocString
+ comm = comm[len('doc-method:'):].strip()
+ elif comm.startswith('doc-module:'):
+ addLine = self.addModuleDocString
+ comm = comm[len('doc-module:'):].strip()
+ elif comm.startswith('doc-class:'):
+ addLine = self.addClassDocString
+ comm = comm[len('doc-class:'):].strip()
+ elif comm.startswith('header:'):
+ addLine = self.addModuleHeader
+ comm = comm[len('header:'):].strip()
+ else:
+ addLine = self.addMethComment
+
+ for line in comm.splitlines():
+ addLine(line)
+
+ ## methods for module code wrapping
+
+ def getModuleCode(self):
+ if not self._compiled:
+ self.compile()
+ if self._moduleDef:
+ return self._moduleDef
+ else:
+ return self.wrapModuleDef()
+
+ __str__ = getModuleCode
+
+ def wrapModuleDef(self):
+ self.addSpecialVar('CHEETAH_docstring', self.setting('defDocStrMsg'))
+ self.addModuleGlobal('__CHEETAH_version__ = %r'%Version)
+ self.addModuleGlobal('__CHEETAH_versionTuple__ = %r'%(VersionTuple,))
+ self.addModuleGlobal('__CHEETAH_genTime__ = %r'%time.time())
+ self.addModuleGlobal('__CHEETAH_genTimestamp__ = %r'%self.timestamp())
+ if self._filePath:
+ timestamp = self.timestamp(self._fileMtime)
+ self.addModuleGlobal('__CHEETAH_src__ = %r'%self._filePath)
+ self.addModuleGlobal('__CHEETAH_srcLastModified__ = %r'%timestamp)
+ else:
+ self.addModuleGlobal('__CHEETAH_src__ = None')
+ self.addModuleGlobal('__CHEETAH_srcLastModified__ = None')
+
+ moduleDef = """%(header)s
+%(docstring)s
+
+##################################################
+## DEPENDENCIES
+%(imports)s
+
+##################################################
+## MODULE CONSTANTS
+%(constants)s
+%(specialVars)s
+
+if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple:
+ raise AssertionError(
+ 'This template was compiled with Cheetah version'
+ ' %%s. Templates compiled before version %%s must be recompiled.'%%(
+ __CHEETAH_version__, RequiredCheetahVersion))
+
+##################################################
+## CLASSES
+
+%(classes)s
+
+## END CLASS DEFINITION
+
+if not hasattr(%(mainClassName)s, '_initCheetahAttributes'):
+ templateAPIClass = getattr(%(mainClassName)s, '_CHEETAH_templateClass', Template)
+ templateAPIClass._addCheetahPlumbingCodeToClass(%(mainClassName)s)
+
+%(footer)s
+""" % {'header':self.moduleHeader(),
+ 'docstring':self.moduleDocstring(),
+ 'specialVars':self.specialVars(),
+ 'imports':self.importStatements(),
+ 'constants':self.moduleConstants(),
+ 'classes':self.classDefs(),
+ 'footer':self.moduleFooter(),
+ 'mainClassName':self._mainClassName,
+ }
+
+ self._moduleDef = moduleDef
+ return moduleDef
+
+ def timestamp(self, theTime=None):
+ if not theTime:
+ theTime = time.time()
+ return time.asctime(time.localtime(theTime))
+
+ def moduleHeader(self):
+ header = self._moduleShBang + '\n'
+ header += self._moduleEncodingStr + '\n'
+ if self._moduleHeaderLines:
+ offSet = self.setting('commentOffset')
+
+ header += (
+ '#' + ' '*offSet +
+ ('\n#'+ ' '*offSet).join(self._moduleHeaderLines) + '\n')
+
+ return header
+
+ def moduleDocstring(self):
+ if not self._moduleDocStringLines:
+ return ''
+
+ return ('"""' +
+ '\n'.join(self._moduleDocStringLines) +
+ '\n"""\n')
+
+ def specialVars(self):
+ chunks = []
+ theVars = self._specialVars
+ keys = theVars.keys()
+ keys.sort()
+ for key in keys:
+ chunks.append(key + ' = ' + repr(theVars[key]) )
+ return '\n'.join(chunks)
+
+ def importStatements(self):
+ return '\n'.join(self._importStatements)
+
+ def moduleConstants(self):
+ return '\n'.join(self._moduleConstants)
+
+ def classDefs(self):
+ classDefs = [str(klass) for klass in self._finishedClasses() ]
+ return '\n\n'.join(classDefs)
+
+ def moduleFooter(self):
+ return """
+# CHEETAH was developed by Tavis Rudd and Mike Orr
+# with code, advice and input from many other volunteers.
+# For more information visit http://www.CheetahTemplate.org/
+
+##################################################
+## if run from command line:
+if __name__ == '__main__':
+ from Cheetah.TemplateCmdLineIface import CmdLineIface
+ CmdLineIface(templateObj=%(className)s()).run()
+
+""" % {'className':self._mainClassName}
+
+
+##################################################
+## Make Compiler an alias for ModuleCompiler
+
+Compiler = ModuleCompiler
diff --git a/cobbler/Cheetah/DummyTransaction.py b/cobbler/Cheetah/DummyTransaction.py
new file mode 100644
index 0000000..8ebe33d
--- /dev/null
+++ b/cobbler/Cheetah/DummyTransaction.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+# $Id: DummyTransaction.py,v 1.13 2005/11/13 01:12:13 tavis_rudd Exp $
+
+"""Provides dummy Transaction and Response classes is used by Cheetah in place
+of real Webware transactions when the Template obj is not used directly as a
+Webware servlet.
+
+Meta-Data
+==========
+Author: Tavis Rudd <tavis@damnsimple.com>
+Version: $Revision: 1.13 $
+Start Date: 2001/08/30
+Last Revision Date: $Date: 2005/11/13 01:12:13 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.13 $"[11:-2]
+
+def flush():
+ pass
+
+class DummyResponse:
+
+ """A dummy Response class is used by Cheetah in place of real Webware
+ Response objects when the Template obj is not used directly as a Webware
+ servlet. """
+
+
+ def __init__(self):
+ self._outputChunks = outputChunks = []
+ self.write = write = outputChunks.append
+ def getvalue(outputChunks=outputChunks):
+ return ''.join(outputChunks)
+ self.getvalue = getvalue
+
+ def writeln(txt):
+ write(txt)
+ write('\n')
+ self.writeln = writeln
+ self.flush = flush
+
+ def writelines(self, *lines):
+ ## not used
+ [self.writeln(ln) for ln in lines]
+
+class DummyTransaction:
+
+ """A dummy Transaction class is used by Cheetah in place of real Webware
+ transactions when the Template obj is not used directly as a Webware
+ servlet.
+
+ It only provides a response object and method. All other methods and
+ attributes make no sense in this context.
+ """
+
+ def __init__(self, DummyResponse=DummyResponse):
+ def response(resp=DummyResponse()):
+ return resp
+ self.response = response
diff --git a/cobbler/Cheetah/ErrorCatchers.py b/cobbler/Cheetah/ErrorCatchers.py
new file mode 100644
index 0000000..d33b979
--- /dev/null
+++ b/cobbler/Cheetah/ErrorCatchers.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+# $Id: ErrorCatchers.py,v 1.7 2005/01/03 19:59:07 tavis_rudd Exp $
+"""ErrorCatcher class for Cheetah Templates
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com>
+Version: $Revision: 1.7 $
+Start Date: 2001/08/01
+Last Revision Date: $Date: 2005/01/03 19:59:07 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.7 $"[11:-2]
+
+import time
+from Cheetah.NameMapper import NotFound
+
+class Error(Exception):
+ pass
+
+class ErrorCatcher:
+ _exceptionsToCatch = (NotFound,)
+
+ def __init__(self, templateObj):
+ pass
+
+ def exceptions(self):
+ return self._exceptionsToCatch
+
+ def warn(self, exc_val, code, rawCode, lineCol):
+ return rawCode
+## make an alias
+Echo = ErrorCatcher
+
+class BigEcho(ErrorCatcher):
+ def warn(self, exc_val, code, rawCode, lineCol):
+ return "="*15 + "&lt;" + rawCode + " could not be found&gt;" + "="*15
+
+class KeyError(ErrorCatcher):
+ def warn(self, exc_val, code, rawCode, lineCol):
+ raise KeyError("no '%s' in this Template Object's Search List" % rawCode)
+
+class ListErrors(ErrorCatcher):
+ """Accumulate a list of errors."""
+ _timeFormat = "%c"
+
+ def __init__(self, templateObj):
+ ErrorCatcher.__init__(self, templateObj)
+ self._errors = []
+
+ def warn(self, exc_val, code, rawCode, lineCol):
+ dict = locals().copy()
+ del dict['self']
+ dict['time'] = time.strftime(self._timeFormat,
+ time.localtime(time.time()))
+ self._errors.append(dict)
+ return rawCode
+
+ def listErrors(self):
+ """Return the list of errors."""
+ return self._errors
+
+
diff --git a/cobbler/Cheetah/FileUtils.py b/cobbler/Cheetah/FileUtils.py
new file mode 100644
index 0000000..c3749f5
--- /dev/null
+++ b/cobbler/Cheetah/FileUtils.py
@@ -0,0 +1,374 @@
+#!/usr/bin/env python
+# $Id: FileUtils.py,v 1.12 2005/11/02 22:26:07 tavis_rudd Exp $
+"""File utitilies for Python:
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com>
+License: This software is released for unlimited distribution under the
+ terms of the MIT license. See the LICENSE file.
+Version: $Revision: 1.12 $
+Start Date: 2001/09/26
+Last Revision Date: $Date: 2005/11/02 22:26:07 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.12 $"[11:-2]
+
+
+from glob import glob
+import os
+from os import listdir
+import os.path
+import re
+from types import StringType
+from tempfile import mktemp
+
+def _escapeRegexChars(txt,
+ escapeRE=re.compile(r'([\$\^\*\+\.\?\{\}\[\]\(\)\|\\])')):
+ return escapeRE.sub(r'\\\1' , txt)
+
+def findFiles(*args, **kw):
+ """Recursively find all the files matching a glob pattern.
+
+ This function is a wrapper around the FileFinder class. See its docstring
+ for details about the accepted arguments, etc."""
+
+ return FileFinder(*args, **kw).files()
+
+def replaceStrInFiles(files, theStr, repl):
+
+ """Replace all instances of 'theStr' with 'repl' for each file in the 'files'
+ list. Returns a dictionary with data about the matches found.
+
+ This is like string.replace() on a multi-file basis.
+
+ This function is a wrapper around the FindAndReplace class. See its
+ docstring for more details."""
+
+ pattern = _escapeRegexChars(theStr)
+ return FindAndReplace(files, pattern, repl).results()
+
+def replaceRegexInFiles(files, pattern, repl):
+
+ """Replace all instances of regex 'pattern' with 'repl' for each file in the
+ 'files' list. Returns a dictionary with data about the matches found.
+
+ This is like re.sub on a multi-file basis.
+
+ This function is a wrapper around the FindAndReplace class. See its
+ docstring for more details."""
+
+ return FindAndReplace(files, pattern, repl).results()
+
+
+##################################################
+## CLASSES
+
+class FileFinder:
+
+ """Traverses a directory tree and finds all files in it that match one of
+ the specified glob patterns."""
+
+ def __init__(self, rootPath,
+ globPatterns=('*',),
+ ignoreBasenames=('CVS','.svn'),
+ ignoreDirs=(),
+ ):
+
+ self._rootPath = rootPath
+ self._globPatterns = globPatterns
+ self._ignoreBasenames = ignoreBasenames
+ self._ignoreDirs = ignoreDirs
+ self._files = []
+
+ self.walkDirTree(rootPath)
+
+ def walkDirTree(self, dir='.',
+
+ listdir=os.listdir,
+ isdir=os.path.isdir,
+ join=os.path.join,
+ ):
+
+ """Recursively walk through a directory tree and find matching files."""
+ processDir = self.processDir
+ filterDir = self.filterDir
+
+ pendingDirs = [dir]
+ addDir = pendingDirs.append
+ getDir = pendingDirs.pop
+
+ while pendingDirs:
+ dir = getDir()
+ ## process this dir
+ processDir(dir)
+
+ ## and add sub-dirs
+ for baseName in listdir(dir):
+ fullPath = join(dir, baseName)
+ if isdir(fullPath):
+ if filterDir(baseName, fullPath):
+ addDir( fullPath )
+
+ def filterDir(self, baseName, fullPath):
+
+ """A hook for filtering out certain dirs. """
+
+ return not (baseName in self._ignoreBasenames or
+ fullPath in self._ignoreDirs)
+
+ def processDir(self, dir, glob=glob):
+ extend = self._files.extend
+ for pattern in self._globPatterns:
+ extend( glob(os.path.join(dir, pattern)) )
+
+ def files(self):
+ return self._files
+
+class _GenSubberFunc:
+
+ """Converts a 'sub' string in the form that one feeds to re.sub (backrefs,
+ groups, etc.) into a function that can be used to do the substitutions in
+ the FindAndReplace class."""
+
+ backrefRE = re.compile(r'\\([1-9][0-9]*)')
+ groupRE = re.compile(r'\\g<([a-zA-Z_][a-zA-Z_]*)>')
+
+ def __init__(self, replaceStr):
+ self._src = replaceStr
+ self._pos = 0
+ self._codeChunks = []
+ self.parse()
+
+ def src(self):
+ return self._src
+
+ def pos(self):
+ return self._pos
+
+ def setPos(self, pos):
+ self._pos = pos
+
+ def atEnd(self):
+ return self._pos >= len(self._src)
+
+ def advance(self, offset=1):
+ self._pos += offset
+
+ def readTo(self, to, start=None):
+ if start == None:
+ start = self._pos
+ self._pos = to
+ if self.atEnd():
+ return self._src[start:]
+ else:
+ return self._src[start:to]
+
+ ## match and get methods
+
+ def matchBackref(self):
+ return self.backrefRE.match(self.src(), self.pos())
+
+ def getBackref(self):
+ m = self.matchBackref()
+ self.setPos(m.end())
+ return m.group(1)
+
+ def matchGroup(self):
+ return self.groupRE.match(self.src(), self.pos())
+
+ def getGroup(self):
+ m = self.matchGroup()
+ self.setPos(m.end())
+ return m.group(1)
+
+ ## main parse loop and the eat methods
+
+ def parse(self):
+ while not self.atEnd():
+ if self.matchBackref():
+ self.eatBackref()
+ elif self.matchGroup():
+ self.eatGroup()
+ else:
+ self.eatStrConst()
+
+ def eatStrConst(self):
+ startPos = self.pos()
+ while not self.atEnd():
+ if self.matchBackref() or self.matchGroup():
+ break
+ else:
+ self.advance()
+ strConst = self.readTo(self.pos(), start=startPos)
+ self.addChunk(repr(strConst))
+
+ def eatBackref(self):
+ self.addChunk( 'm.group(' + self.getBackref() + ')' )
+
+ def eatGroup(self):
+ self.addChunk( 'm.group("' + self.getGroup() + '")' )
+
+ def addChunk(self, chunk):
+ self._codeChunks.append(chunk)
+
+ ## code wrapping methods
+
+ def codeBody(self):
+ return ', '.join(self._codeChunks)
+
+ def code(self):
+ return "def subber(m):\n\treturn ''.join([%s])\n" % (self.codeBody())
+
+ def subberFunc(self):
+ exec self.code()
+ return subber
+
+
+class FindAndReplace:
+
+ """Find and replace all instances of 'patternOrRE' with 'replacement' for
+ each file in the 'files' list. This is a multi-file version of re.sub().
+
+ 'patternOrRE' can be a raw regex pattern or
+ a regex object as generated by the re module. 'replacement' can be any
+ string that would work with patternOrRE.sub(replacement, fileContents).
+ """
+
+ def __init__(self, files, patternOrRE, replacement,
+ recordResults=True):
+
+
+ if type(patternOrRE) == StringType:
+ self._regex = re.compile(patternOrRE)
+ else:
+ self._regex = patternOrRE
+ if type(replacement) == StringType:
+ self._subber = _GenSubberFunc(replacement).subberFunc()
+ else:
+ self._subber = replacement
+
+ self._pattern = pattern = self._regex.pattern
+ self._files = files
+ self._results = {}
+ self._recordResults = recordResults
+
+ ## see if we should use pgrep to do the file matching
+ self._usePgrep = False
+ if (os.popen3('pgrep')[2].read()).startswith('Usage:'):
+ ## now check to make sure pgrep understands the pattern
+ tmpFile = mktemp()
+ open(tmpFile, 'w').write('#')
+ if not (os.popen3('pgrep "' + pattern + '" ' + tmpFile)[2].read()):
+ # it didn't print an error msg so we're ok
+ self._usePgrep = True
+ os.remove(tmpFile)
+
+ self._run()
+
+ def results(self):
+ return self._results
+
+ def _run(self):
+ regex = self._regex
+ subber = self._subDispatcher
+ usePgrep = self._usePgrep
+ pattern = self._pattern
+ for file in self._files:
+ if not os.path.isfile(file):
+ continue # skip dirs etc.
+
+ self._currFile = file
+ found = False
+ if locals().has_key('orig'):
+ del orig
+ if self._usePgrep:
+ if os.popen('pgrep "' + pattern + '" ' + file ).read():
+ found = True
+ else:
+ orig = open(file).read()
+ if regex.search(orig):
+ found = True
+ if found:
+ if not locals().has_key('orig'):
+ orig = open(file).read()
+ new = regex.sub(subber, orig)
+ open(file, 'w').write(new)
+
+ def _subDispatcher(self, match):
+ if self._recordResults:
+ if not self._results.has_key(self._currFile):
+ res = self._results[self._currFile] = {}
+ res['count'] = 0
+ res['matches'] = []
+ else:
+ res = self._results[self._currFile]
+ res['count'] += 1
+ res['matches'].append({'contents':match.group(),
+ 'start':match.start(),
+ 'end':match.end(),
+ }
+ )
+ return self._subber(match)
+
+
+class SourceFileStats:
+
+ """
+ """
+
+ _fileStats = None
+
+ def __init__(self, files):
+ self._fileStats = stats = {}
+ for file in files:
+ stats[file] = self.getFileStats(file)
+
+ def rawStats(self):
+ return self._fileStats
+
+ def summary(self):
+ codeLines = 0
+ blankLines = 0
+ commentLines = 0
+ totalLines = 0
+ for fileStats in self.rawStats().values():
+ codeLines += fileStats['codeLines']
+ blankLines += fileStats['blankLines']
+ commentLines += fileStats['commentLines']
+ totalLines += fileStats['totalLines']
+
+ stats = {'codeLines':codeLines,
+ 'blankLines':blankLines,
+ 'commentLines':commentLines,
+ 'totalLines':totalLines,
+ }
+ return stats
+
+ def printStats(self):
+ pass
+
+ def getFileStats(self, fileName):
+ codeLines = 0
+ blankLines = 0
+ commentLines = 0
+ commentLineRe = re.compile(r'\s#.*$')
+ blankLineRe = re.compile('\s$')
+ lines = open(fileName).read().splitlines()
+ totalLines = len(lines)
+
+ for line in lines:
+ if commentLineRe.match(line):
+ commentLines += 1
+ elif blankLineRe.match(line):
+ blankLines += 1
+ else:
+ codeLines += 1
+
+ stats = {'codeLines':codeLines,
+ 'blankLines':blankLines,
+ 'commentLines':commentLines,
+ 'totalLines':totalLines,
+ }
+
+ return stats
diff --git a/cobbler/Cheetah/Filters.py b/cobbler/Cheetah/Filters.py
new file mode 100644
index 0000000..2bf4784
--- /dev/null
+++ b/cobbler/Cheetah/Filters.py
@@ -0,0 +1,290 @@
+#!/usr/bin/env python
+# $Id: Filters.py,v 1.28 2006/06/16 20:15:24 hierro Exp $
+"""Filters for the #filter directive; output filters Cheetah's $placeholders .
+
+Filters may now be used standalone, for debugging or for use outside Cheetah.
+Class DummyTemplate, instance _dummyTemplateObj and class NoDefault exist only
+for this, to provide a default argument for the filter constructors (which
+would otherwise require a real template object).
+
+The default filter is now RawOrEncodedUnicode. Please use this as a base class instead of Filter because it handles non-ASCII characters better.
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com>
+Version: $Revision: 1.28 $
+Start Date: 2001/08/01
+Last Revision Date: $Date: 2006/06/16 20:15:24 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.28 $"[11:-2]
+
+from StringIO import StringIO # not cStringIO because of unicode support
+
+# Additional entities WebSafe knows how to transform. No need to include
+# '<', '>' or '&' since those will have been done already.
+webSafeEntities = {' ': '&nbsp;', '"': '&quot;'}
+
+class Error(Exception):
+ pass
+
+class NoDefault:
+ pass
+
+
+class DummyTemplate:
+ """Fake template class to allow filters to be used standalone.
+
+ This is provides only the level of Template compatibility required by the
+ standard filters. Namely, the get-settings interface works but there are
+ no settings. Other aspects of Template are not implemented.
+ """
+ def setting(self, name, default=NoDefault):
+ if default is NoDefault:
+ raise KeyError(name)
+ else:
+ return default
+
+ def settings(self):
+ return {}
+
+_dummyTemplateObj = DummyTemplate()
+
+
+##################################################
+## BASE CLASS
+
+class Filter(object):
+ """A baseclass for the Cheetah Filters."""
+
+ def __init__(self, templateObj=_dummyTemplateObj):
+ """Setup a ref to the templateObj. Subclasses should call this method.
+ """
+ if hasattr(templateObj, 'setting'):
+ self.setting = templateObj.setting
+ else:
+ self.setting = lambda k: None
+
+ if hasattr(templateObj, 'settings'):
+ self.settings = templateObj.settings
+ else:
+ self.settings = lambda: {}
+
+ def generateAutoArgs(self):
+
+ """This hook allows the filters to generate an arg-list that will be
+ appended to the arg-list of a $placeholder tag when it is being
+ translated into Python code during the template compilation process. See
+ the 'Pager' filter class for an example."""
+
+ return ''
+
+ def filter(self, val, **kw):
+
+ """Reimplement this method if you want more advanced filterting."""
+
+ return str(val)
+
+
+##################################################
+## ENHANCED FILTERS
+
+#####
+class ReplaceNone(Filter):
+ def filter(self, val, **kw):
+
+ """Replace None with an empty string. Reimplement this method if you
+ want more advanced filterting."""
+
+ if val is None:
+ return ''
+ return str(val)
+#####
+class EncodeUnicode(Filter):
+ def filter(self, val,
+ encoding='utf8',
+ str=str, type=type, unicodeType=type(u''),
+ **kw):
+ """Encode Unicode strings, by default in UTF-8.
+
+ >>> import Cheetah.Template
+ >>> t = Cheetah.Template.Template('''
+ ... $myvar
+ ... ${myvar, encoding='utf16'}
+ ... ''', searchList=[{'myvar': u'Asni\xe8res'}],
+ ... filter='EncodeUnicode')
+ >>> print t
+ """
+ if type(val)==unicodeType:
+ filtered = val.encode(encoding)
+ elif val is None:
+ filtered = ''
+ else:
+ filtered = str(val)
+ return filtered
+
+class RawOrEncodedUnicode(Filter):
+ def filter(self, val,
+ #encoding='utf8',
+ encoding=None,
+ str=str, type=type, unicodeType=type(u''),
+ **kw):
+ """Pass Unicode strings through unmolested, unless an encoding is specified.
+ """
+ if type(val)==unicodeType:
+ if encoding:
+ filtered = val.encode(encoding)
+ else:
+ filtered = val
+ elif val is None:
+ filtered = ''
+ else:
+ filtered = str(val)
+ return filtered
+
+#####
+class MaxLen(RawOrEncodedUnicode):
+ def filter(self, val, **kw):
+ """Replace None with '' and cut off at maxlen."""
+
+ output = super(MaxLen, self).filter(val, **kw)
+ if kw.has_key('maxlen') and len(output) > kw['maxlen']:
+ return output[:kw['maxlen']]
+ return output
+
+
+#####
+class Pager(RawOrEncodedUnicode):
+ def __init__(self, templateObj=_dummyTemplateObj):
+ Filter.__init__(self, templateObj)
+ self._IDcounter = 0
+
+ def buildQString(self,varsDict, updateDict):
+ finalDict = varsDict.copy()
+ finalDict.update(updateDict)
+ qString = '?'
+ for key, val in finalDict.items():
+ qString += str(key) + '=' + str(val) + '&'
+ return qString
+
+ def generateAutoArgs(self):
+ ID = str(self._IDcounter)
+ self._IDcounter += 1
+ return ', trans=trans, ID=' + ID
+
+ def filter(self, val, **kw):
+ """Replace None with '' and cut off at maxlen."""
+ output = super(Pager, self).filter(val, **kw)
+ if kw.has_key('trans') and kw['trans']:
+ ID = kw['ID']
+ marker = kw.get('marker', '<split>')
+ req = kw['trans'].request()
+ URI = req.environ()['SCRIPT_NAME'] + req.environ()['PATH_INFO']
+ queryVar = 'pager' + str(ID) + '_page'
+ fields = req.fields()
+ page = int(fields.get( queryVar, 1))
+ pages = output.split(marker)
+ output = pages[page-1]
+ output += '<BR>'
+ if page > 1:
+ output +='<A HREF="' + URI + self.buildQString(fields, {queryVar:max(page-1,1)}) + \
+ '">Previous Page</A>&nbsp;&nbsp;&nbsp;'
+ if page < len(pages):
+ output += '<A HREF="' + URI + self.buildQString(
+ fields,
+ {queryVar:
+ min(page+1,len(pages))}) + \
+ '">Next Page</A>'
+
+ return output
+ return output
+
+
+#####
+class WebSafe(RawOrEncodedUnicode):
+ """Escape HTML entities in $placeholders.
+ """
+ def filter(self, val, **kw):
+ s = super(WebSafe, self).filter(val, **kw)
+ # These substitutions are copied from cgi.escape().
+ s = s.replace("&", "&amp;") # Must be done first!
+ s = s.replace("<", "&lt;")
+ s = s.replace(">", "&gt;")
+ # Process the additional transformations if any.
+ if kw.has_key('also'):
+ also = kw['also']
+ entities = webSafeEntities # Global variable.
+ for k in also:
+ if entities.has_key(k):
+ v = entities[k]
+ else:
+ v = "&#%s;" % ord(k)
+ s = s.replace(k, v)
+ # Return the puppy.
+ return s
+
+
+#####
+class Strip(RawOrEncodedUnicode):
+ """Strip leading/trailing whitespace but preserve newlines.
+
+ This filter goes through the value line by line, removing leading and
+ trailing whitespace on each line. It does not strip newlines, so every
+ input line corresponds to one output line, with its trailing newline intact.
+
+ We do not use val.split('\n') because that would squeeze out consecutive
+ blank lines. Instead, we search for each newline individually. This
+ makes us unable to use the fast C .split method, but it makes the filter
+ much more widely useful.
+
+ This filter is intended to be usable both with the #filter directive and
+ with the proposed #sed directive (which has not been ratified yet.)
+ """
+ def filter(self, val, **kw):
+ s = super(Strip, self).filter(val, **kw)
+ result = []
+ start = 0 # The current line will be s[start:end].
+ while 1: # Loop through each line.
+ end = s.find('\n', start) # Find next newline.
+ if end == -1: # If no more newlines.
+ break
+ chunk = s[start:end].strip()
+ result.append(chunk)
+ result.append('\n')
+ start = end + 1
+ # Write the unfinished portion after the last newline, if any.
+ chunk = s[start:].strip()
+ result.append(chunk)
+ return "".join(result)
+
+#####
+class StripSqueeze(RawOrEncodedUnicode):
+ """Canonicalizes every chunk of whitespace to a single space.
+
+ Strips leading/trailing whitespace. Removes all newlines, so multi-line
+ input is joined into one ling line with NO trailing newline.
+ """
+ def filter(self, val, **kw):
+ s = super(StripSqueeze, self).filter(val, **kw)
+ s = s.split()
+ return " ".join(s)
+
+##################################################
+## MAIN ROUTINE -- testing
+
+def test():
+ s1 = "abc <=> &"
+ s2 = " asdf \n\t 1 2 3\n"
+ print "WebSafe INPUT:", `s1`
+ print " WebSafe:", `WebSafe().filter(s1)`
+
+ print
+ print " Strip INPUT:", `s2`
+ print " Strip:", `Strip().filter(s2)`
+ print "StripSqueeze:", `StripSqueeze().filter(s2)`
+
+ print "Unicode:", `EncodeUnicode().filter(u'aoeu12345\u1234')`
+
+if __name__ == "__main__": test()
+
+# vim: shiftwidth=4 tabstop=4 expandtab
diff --git a/cobbler/Cheetah/ImportHooks.py b/cobbler/Cheetah/ImportHooks.py
new file mode 100644
index 0000000..9d50119
--- /dev/null
+++ b/cobbler/Cheetah/ImportHooks.py
@@ -0,0 +1,139 @@
+#!/usr/bin/env python
+# $Id: ImportHooks.py,v 1.25 2006/06/20 19:23:27 tavis_rudd Exp $
+
+"""Provides some import hooks to allow Cheetah's .tmpl files to be imported
+directly like Python .py modules.
+
+To use these:
+ import Cheetah.ImportHooks
+ Cheetah.ImportHooks.install()
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com>
+License: This software is released for unlimited distribution under the
+ terms of the MIT license. See the LICENSE file.
+Version: $Revision: 1.25 $
+Start Date: 2001/03/30
+Last Revision Date: $Date: 2006/06/20 19:23:27 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.25 $"[11:-2]
+
+import sys
+import os.path
+import types
+import __builtin__
+import new
+import imp
+from threading import Lock
+import string
+import traceback
+from Cheetah import ImportManager
+from Cheetah.ImportManager import DirOwner
+from Cheetah.Compiler import Compiler
+from Cheetah.convertTmplPathToModuleName import convertTmplPathToModuleName
+
+_installed = False
+
+##################################################
+## HELPER FUNCS
+
+_cacheDir = []
+def setCacheDir(cacheDir):
+ global _cacheDir
+ _cacheDir.append(cacheDir)
+
+##################################################
+## CLASSES
+
+class CheetahDirOwner(DirOwner):
+ _lock = Lock()
+ _acquireLock = _lock.acquire
+ _releaseLock = _lock.release
+
+ templateFileExtensions = ('.tmpl',)
+
+ def getmod(self, name):
+ try:
+ self._acquireLock()
+ mod = DirOwner.getmod(self, name)
+ if mod:
+ return mod
+
+ for ext in self.templateFileExtensions:
+ tmplPath = os.path.join(self.path, name + ext)
+ if os.path.exists(tmplPath):
+ try:
+ return self._compile(name, tmplPath)
+ except:
+ # @@TR: log the error
+ exc_txt = traceback.format_exc()
+ exc_txt =' '+(' \n'.join(exc_txt.splitlines()))
+ raise ImportError(
+ 'Error while compiling Cheetah module'
+ ' %(name)s, original traceback follows:\n%(exc_txt)s'%locals())
+ ##
+ return None
+
+ finally:
+ self._releaseLock()
+
+ def _compile(self, name, tmplPath):
+ ## @@ consider adding an ImportError raiser here
+ code = str(Compiler(file=tmplPath, moduleName=name,
+ mainClassName=name))
+ if _cacheDir:
+ __file__ = os.path.join(_cacheDir[0],
+ convertTmplPathToModuleName(tmplPath)) + '.py'
+ try:
+ open(__file__, 'w').write(code)
+ except OSError:
+ ## @@ TR: need to add some error code here
+ traceback.print_exc(file=sys.stderr)
+ __file__ = tmplPath
+ else:
+ __file__ = tmplPath
+ co = compile(code+'\n', __file__, 'exec')
+
+ mod = imp.new_module(name)
+ mod.__file__ = co.co_filename
+ if _cacheDir:
+ mod.__orig_file__ = tmplPath # @@TR: this is used in the WebKit
+ # filemonitoring code
+ mod.__co__ = co
+ return mod
+
+
+##################################################
+## FUNCTIONS
+
+def install(templateFileExtensions=('.tmpl',)):
+ """Install the Cheetah Import Hooks"""
+
+ global _installed
+ if not _installed:
+ CheetahDirOwner.templateFileExtensions = templateFileExtensions
+ import __builtin__
+ if type(__builtin__.__import__) == types.BuiltinFunctionType:
+ global __oldimport__
+ __oldimport__ = __builtin__.__import__
+ ImportManager._globalOwnerTypes.insert(0, CheetahDirOwner)
+ #ImportManager._globalOwnerTypes.append(CheetahDirOwner)
+ global _manager
+ _manager=ImportManager.ImportManager()
+ _manager.setThreaded()
+ _manager.install()
+
+def uninstall():
+ """Uninstall the Cheetah Import Hooks"""
+ global _installed
+ if not _installed:
+ import __builtin__
+ if type(__builtin__.__import__) == types.MethodType:
+ __builtin__.__import__ = __oldimport__
+ global _manager
+ del _manager
+
+if __name__ == '__main__':
+ install()
diff --git a/cobbler/Cheetah/ImportManager.py b/cobbler/Cheetah/ImportManager.py
new file mode 100644
index 0000000..3ff1dbe
--- /dev/null
+++ b/cobbler/Cheetah/ImportManager.py
@@ -0,0 +1,562 @@
+#!/usr/bin/env python
+# $Id: ImportManager.py,v 1.5 2006/01/27 19:00:20 tavis_rudd Exp $
+
+"""Provides an emulator/replacement for Python's standard import system.
+
+@@TR: Be warned that Import Hooks are in the deepest, darkest corner of Python's
+jungle. If you need to start hacking with this, be prepared to get lost for a
+while. Also note, this module predates the newstyle import hooks in Python 2.3
+http://www.python.org/peps/pep-0302.html.
+
+
+This is a hacked/documented version of Gordon McMillan's iu.py. I have:
+
+ - made it a little less terse
+
+ - added docstrings and explanatations
+
+ - standardized the variable naming scheme
+
+ - reorganized the code layout to enhance readability
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com> based on Gordon McMillan's iu.py
+License: This software is released for unlimited distribution under the
+ terms of the MIT license. See the LICENSE file.
+Version: $Revision: 1.5 $
+Start Date: 2001/03/30
+Last Revision Date: $Date: 2006/01/27 19:00:20 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.5 $"[11:-2]
+
+##################################################
+## DEPENDENCIES
+
+import sys
+import imp
+import marshal
+
+##################################################
+## CONSTANTS & GLOBALS
+
+try:
+ True,False
+except NameError:
+ True, False = (1==1),(1==0)
+
+_installed = False
+
+STRINGTYPE = type('')
+
+# _globalOwnerTypes is defined at the bottom of this file
+
+_os_stat = _os_path_join = _os_getcwd = _os_path_dirname = None
+
+##################################################
+## FUNCTIONS
+
+def _os_bootstrap():
+ """Set up 'os' module replacement functions for use during import bootstrap."""
+
+ names = sys.builtin_module_names
+
+ join = dirname = None
+ if 'posix' in names:
+ sep = '/'
+ from posix import stat, getcwd
+ elif 'nt' in names:
+ sep = '\\'
+ from nt import stat, getcwd
+ elif 'dos' in names:
+ sep = '\\'
+ from dos import stat, getcwd
+ elif 'os2' in names:
+ sep = '\\'
+ from os2 import stat, getcwd
+ elif 'mac' in names:
+ from mac import stat, getcwd
+ def join(a, b):
+ if a == '':
+ return b
+ path = s
+ if ':' not in a:
+ a = ':' + a
+ if a[-1:] != ':':
+ a = a + ':'
+ return a + b
+ else:
+ raise ImportError, 'no os specific module found'
+
+ if join is None:
+ def join(a, b, sep=sep):
+ if a == '':
+ return b
+ lastchar = a[-1:]
+ if lastchar == '/' or lastchar == sep:
+ return a + b
+ return a + sep + b
+
+ if dirname is None:
+ def dirname(a, sep=sep):
+ for i in range(len(a)-1, -1, -1):
+ c = a[i]
+ if c == '/' or c == sep:
+ return a[:i]
+ return ''
+
+ global _os_stat
+ _os_stat = stat
+
+ global _os_path_join
+ _os_path_join = join
+
+ global _os_path_dirname
+ _os_path_dirname = dirname
+
+ global _os_getcwd
+ _os_getcwd = getcwd
+
+_os_bootstrap()
+
+def packageName(s):
+ for i in range(len(s)-1, -1, -1):
+ if s[i] == '.':
+ break
+ else:
+ return ''
+ return s[:i]
+
+def nameSplit(s):
+ rslt = []
+ i = j = 0
+ for j in range(len(s)):
+ if s[j] == '.':
+ rslt.append(s[i:j])
+ i = j+1
+ if i < len(s):
+ rslt.append(s[i:])
+ return rslt
+
+def getPathExt(fnm):
+ for i in range(len(fnm)-1, -1, -1):
+ if fnm[i] == '.':
+ return fnm[i:]
+ return ''
+
+def pathIsDir(pathname):
+ "Local replacement for os.path.isdir()."
+ try:
+ s = _os_stat(pathname)
+ except OSError:
+ return None
+ return (s[0] & 0170000) == 0040000
+
+def getDescr(fnm):
+ ext = getPathExt(fnm)
+ for (suffix, mode, typ) in imp.get_suffixes():
+ if suffix == ext:
+ return (suffix, mode, typ)
+
+##################################################
+## CLASSES
+
+class Owner:
+
+ """An Owner does imports from a particular piece of turf That is, there's
+ an Owner for each thing on sys.path There are owners for directories and
+ .pyz files. There could be owners for zip files, or even URLs. A
+ shadowpath (a dictionary mapping the names in sys.path to their owners) is
+ used so that sys.path (or a package's __path__) is still a bunch of strings,
+ """
+
+ def __init__(self, path):
+ self.path = path
+
+ def __str__(self):
+ return self.path
+
+ def getmod(self, nm):
+ return None
+
+class DirOwner(Owner):
+
+ def __init__(self, path):
+ if path == '':
+ path = _os_getcwd()
+ if not pathIsDir(path):
+ raise ValueError, "%s is not a directory" % path
+ Owner.__init__(self, path)
+
+ def getmod(self, nm,
+ getsuffixes=imp.get_suffixes, loadco=marshal.loads, newmod=imp.new_module):
+
+ pth = _os_path_join(self.path, nm)
+
+ possibles = [(pth, 0, None)]
+ if pathIsDir(pth):
+ possibles.insert(0, (_os_path_join(pth, '__init__'), 1, pth))
+ py = pyc = None
+ for pth, ispkg, pkgpth in possibles:
+ for ext, mode, typ in getsuffixes():
+ attempt = pth+ext
+ try:
+ st = _os_stat(attempt)
+ except:
+ pass
+ else:
+ if typ == imp.C_EXTENSION:
+ fp = open(attempt, 'rb')
+ mod = imp.load_module(nm, fp, attempt, (ext, mode, typ))
+ mod.__file__ = attempt
+ return mod
+ elif typ == imp.PY_SOURCE:
+ py = (attempt, st)
+ else:
+ pyc = (attempt, st)
+ if py or pyc:
+ break
+ if py is None and pyc is None:
+ return None
+ while 1:
+ if pyc is None or py and pyc[1][8] < py[1][8]:
+ try:
+ co = compile(open(py[0], 'r').read()+'\n', py[0], 'exec')
+ break
+ except SyntaxError, e:
+ print "Invalid syntax in %s" % py[0]
+ print e.args
+ raise
+ elif pyc:
+ stuff = open(pyc[0], 'rb').read()
+ try:
+ co = loadco(stuff[8:])
+ break
+ except (ValueError, EOFError):
+ pyc = None
+ else:
+ return None
+ mod = newmod(nm)
+ mod.__file__ = co.co_filename
+ if ispkg:
+ mod.__path__ = [pkgpth]
+ subimporter = PathImportDirector(mod.__path__)
+ mod.__importsub__ = subimporter.getmod
+ mod.__co__ = co
+ return mod
+
+
+class ImportDirector(Owner):
+ """ImportDirectors live on the metapath There's one for builtins, one for
+ frozen modules, and one for sys.path Windows gets one for modules gotten
+ from the Registry Mac would have them for PY_RESOURCE modules etc. A
+ generalization of Owner - their concept of 'turf' is broader"""
+
+ pass
+
+class BuiltinImportDirector(ImportDirector):
+ """Directs imports of builtin modules"""
+ def __init__(self):
+ self.path = 'Builtins'
+
+ def getmod(self, nm, isbuiltin=imp.is_builtin):
+ if isbuiltin(nm):
+ mod = imp.load_module(nm, None, nm, ('','',imp.C_BUILTIN))
+ return mod
+ return None
+
+class FrozenImportDirector(ImportDirector):
+ """Directs imports of frozen modules"""
+
+ def __init__(self):
+ self.path = 'FrozenModules'
+
+ def getmod(self, nm,
+ isFrozen=imp.is_frozen, loadMod=imp.load_module):
+ if isFrozen(nm):
+ mod = loadMod(nm, None, nm, ('','',imp.PY_FROZEN))
+ if hasattr(mod, '__path__'):
+ mod.__importsub__ = lambda name, pname=nm, owner=self: owner.getmod(pname+'.'+name)
+ return mod
+ return None
+
+
+class RegistryImportDirector(ImportDirector):
+ """Directs imports of modules stored in the Windows Registry"""
+
+ def __init__(self):
+ self.path = "WindowsRegistry"
+ self.map = {}
+ try:
+ import win32api
+ ## import win32con
+ except ImportError:
+ pass
+ else:
+ HKEY_CURRENT_USER = -2147483647
+ HKEY_LOCAL_MACHINE = -2147483646
+ KEY_ALL_ACCESS = 983103
+ subkey = r"Software\Python\PythonCore\%s\Modules" % sys.winver
+ for root in (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE):
+ try:
+ hkey = win32api.RegOpenKeyEx(root, subkey, 0, KEY_ALL_ACCESS)
+ except:
+ pass
+ else:
+ numsubkeys, numvalues, lastmodified = win32api.RegQueryInfoKey(hkey)
+ for i in range(numsubkeys):
+ subkeyname = win32api.RegEnumKey(hkey, i)
+ hskey = win32api.RegOpenKeyEx(hkey, subkeyname, 0, KEY_ALL_ACCESS)
+ val = win32api.RegQueryValueEx(hskey, '')
+ desc = getDescr(val[0])
+ self.map[subkeyname] = (val[0], desc)
+ hskey.Close()
+ hkey.Close()
+ break
+
+ def getmod(self, nm):
+ stuff = self.map.get(nm)
+ if stuff:
+ fnm, desc = stuff
+ fp = open(fnm, 'rb')
+ mod = imp.load_module(nm, fp, fnm, desc)
+ mod.__file__ = fnm
+ return mod
+ return None
+
+class PathImportDirector(ImportDirector):
+ """Directs imports of modules stored on the filesystem."""
+
+ def __init__(self, pathlist=None, importers=None, ownertypes=None):
+ if pathlist is None:
+ self.path = sys.path
+ else:
+ self.path = pathlist
+ if ownertypes == None:
+ self._ownertypes = _globalOwnerTypes
+ else:
+ self._ownertypes = ownertypes
+ if importers:
+ self._shadowPath = importers
+ else:
+ self._shadowPath = {}
+ self._inMakeOwner = False
+ self._building = {}
+
+ def getmod(self, nm):
+ mod = None
+ for thing in self.path:
+ if type(thing) is STRINGTYPE:
+ owner = self._shadowPath.get(thing, -1)
+ if owner == -1:
+ owner = self._shadowPath[thing] = self._makeOwner(thing)
+ if owner:
+ mod = owner.getmod(nm)
+ else:
+ mod = thing.getmod(nm)
+ if mod:
+ break
+ return mod
+
+ def _makeOwner(self, path):
+ if self._building.get(path):
+ return None
+ self._building[path] = 1
+ owner = None
+ for klass in self._ownertypes:
+ try:
+ # this may cause an import, which may cause recursion
+ # hence the protection
+ owner = klass(path)
+ except:
+ pass
+ else:
+ break
+ del self._building[path]
+ return owner
+
+#=================ImportManager============================#
+# The one-and-only ImportManager
+# ie, the builtin import
+
+UNTRIED = -1
+
+class ImportManager:
+ # really the equivalent of builtin import
+ def __init__(self):
+ self.metapath = [
+ BuiltinImportDirector(),
+ FrozenImportDirector(),
+ RegistryImportDirector(),
+ PathImportDirector()
+ ]
+ self.threaded = 0
+ self.rlock = None
+ self.locker = None
+ self.setThreaded()
+
+ def setThreaded(self):
+ thread = sys.modules.get('thread', None)
+ if thread and not self.threaded:
+ self.threaded = 1
+ self.rlock = thread.allocate_lock()
+ self._get_ident = thread.get_ident
+
+ def install(self):
+ import __builtin__
+ __builtin__.__import__ = self.importHook
+ __builtin__.reload = self.reloadHook
+
+ def importHook(self, name, globals=None, locals=None, fromlist=None):
+ # first see if we could be importing a relative name
+ #print "importHook(%s, %s, locals, %s)" % (name, globals['__name__'], fromlist)
+ _sys_modules_get = sys.modules.get
+ contexts = [None]
+ if globals:
+ importernm = globals.get('__name__', '')
+ if importernm:
+ if hasattr(_sys_modules_get(importernm), '__path__'):
+ contexts.insert(0,importernm)
+ else:
+ pkgnm = packageName(importernm)
+ if pkgnm:
+ contexts.insert(0,pkgnm)
+ # so contexts is [pkgnm, None] or just [None]
+ # now break the name being imported up so we get:
+ # a.b.c -> [a, b, c]
+ nmparts = nameSplit(name)
+ _self_doimport = self.doimport
+ threaded = self.threaded
+ for context in contexts:
+ ctx = context
+ for i in range(len(nmparts)):
+ nm = nmparts[i]
+ #print " importHook trying %s in %s" % (nm, ctx)
+ if ctx:
+ fqname = ctx + '.' + nm
+ else:
+ fqname = nm
+ if threaded:
+ self._acquire()
+ mod = _sys_modules_get(fqname, UNTRIED)
+ if mod is UNTRIED:
+ mod = _self_doimport(nm, ctx, fqname)
+ if threaded:
+ self._release()
+ if mod:
+ ctx = fqname
+ else:
+ break
+ else:
+ # no break, point i beyond end
+ i = i + 1
+ if i:
+ break
+
+ if i<len(nmparts):
+ if ctx and hasattr(sys.modules[ctx], nmparts[i]):
+ #print "importHook done with %s %s %s (case 1)" % (name, globals['__name__'], fromlist)
+ return sys.modules[nmparts[0]]
+ del sys.modules[fqname]
+ raise ImportError, "No module named %s" % fqname
+ if fromlist is None:
+ #print "importHook done with %s %s %s (case 2)" % (name, globals['__name__'], fromlist)
+ if context:
+ return sys.modules[context+'.'+nmparts[0]]
+ return sys.modules[nmparts[0]]
+ bottommod = sys.modules[ctx]
+ if hasattr(bottommod, '__path__'):
+ fromlist = list(fromlist)
+ i = 0
+ while i < len(fromlist):
+ nm = fromlist[i]
+ if nm == '*':
+ fromlist[i:i+1] = list(getattr(bottommod, '__all__', []))
+ if i >= len(fromlist):
+ break
+ nm = fromlist[i]
+ i = i + 1
+ if not hasattr(bottommod, nm):
+ if self.threaded:
+ self._acquire()
+ mod = self.doimport(nm, ctx, ctx+'.'+nm)
+ if self.threaded:
+ self._release()
+ if not mod:
+ raise ImportError, "%s not found in %s" % (nm, ctx)
+ #print "importHook done with %s %s %s (case 3)" % (name, globals['__name__'], fromlist)
+ return bottommod
+
+ def doimport(self, nm, parentnm, fqname):
+ # Not that nm is NEVER a dotted name at this point
+ #print "doimport(%s, %s, %s)" % (nm, parentnm, fqname)
+ if parentnm:
+ parent = sys.modules[parentnm]
+ if hasattr(parent, '__path__'):
+ importfunc = getattr(parent, '__importsub__', None)
+ if not importfunc:
+ subimporter = PathImportDirector(parent.__path__)
+ importfunc = parent.__importsub__ = subimporter.getmod
+ mod = importfunc(nm)
+ if mod:
+ setattr(parent, nm, mod)
+ else:
+ #print "..parent not a package"
+ return None
+ else:
+ # now we're dealing with an absolute import
+ for director in self.metapath:
+ mod = director.getmod(nm)
+ if mod:
+ break
+ if mod:
+ mod.__name__ = fqname
+ sys.modules[fqname] = mod
+ if hasattr(mod, '__co__'):
+ co = mod.__co__
+ del mod.__co__
+ exec co in mod.__dict__
+ if fqname == 'thread' and not self.threaded:
+## print "thread detected!"
+ self.setThreaded()
+ else:
+ sys.modules[fqname] = None
+ #print "..found %s" % mod
+ return mod
+
+ def reloadHook(self, mod):
+ fqnm = mod.__name__
+ nm = nameSplit(fqnm)[-1]
+ parentnm = packageName(fqnm)
+ newmod = self.doimport(nm, parentnm, fqnm)
+ mod.__dict__.update(newmod.__dict__)
+## return newmod
+
+ def _acquire(self):
+ if self.rlock.locked():
+ if self.locker == self._get_ident():
+ self.lockcount = self.lockcount + 1
+## print "_acquire incrementing lockcount to", self.lockcount
+ return
+ self.rlock.acquire()
+ self.locker = self._get_ident()
+ self.lockcount = 0
+## print "_acquire first time!"
+
+ def _release(self):
+ if self.lockcount:
+ self.lockcount = self.lockcount - 1
+## print "_release decrementing lockcount to", self.lockcount
+ else:
+ self.rlock.release()
+## print "_release releasing lock!"
+
+
+##################################################
+## MORE CONSTANTS & GLOBALS
+
+_globalOwnerTypes = [
+ DirOwner,
+ Owner,
+]
diff --git a/cobbler/Cheetah/Macros/I18n.py b/cobbler/Cheetah/Macros/I18n.py
new file mode 100644
index 0000000..7c2b1ef
--- /dev/null
+++ b/cobbler/Cheetah/Macros/I18n.py
@@ -0,0 +1,67 @@
+import gettext
+_ = gettext.gettext
+class I18n(object):
+ def __init__(self, parser):
+ pass
+
+## junk I'm playing with to test the macro framework
+# def parseArgs(self, parser, startPos):
+# parser.getWhiteSpace()
+# args = parser.getExpression(useNameMapper=False,
+# pyTokensToBreakAt=[':']).strip()
+# return args
+#
+# def convertArgStrToDict(self, args, parser=None, startPos=None):
+# def getArgs(*pargs, **kws):
+# return pargs, kws
+# exec 'positionalArgs, kwArgs = getArgs(%(args)s)'%locals()
+# return kwArgs
+
+ def __call__(self,
+ src, # aka message,
+ plural=None,
+ n=None, # should be a string representing the name of the
+ # '$var' rather than $var itself
+ id=None,
+ domain=None,
+ source=None,
+ target=None,
+ comment=None,
+
+ # args that are automatically supplied by the parser when the
+ # macro is called:
+ parser=None,
+ macros=None,
+ isShortForm=False,
+ EOLCharsInShortForm=None,
+ startPos=None,
+ endPos=None,
+ ):
+ """This is just a stub at this time.
+
+ plural = the plural form of the message
+ n = a sized argument to distinguish between single and plural forms
+
+ id = msgid in the translation catalog
+ domain = translation domain
+ source = source lang
+ target = a specific target lang
+ comment = a comment to the translation team
+
+ See the following for some ideas
+ http://www.zope.org/DevHome/Wikis/DevSite/Projects/ComponentArchitecture/ZPTInternationalizationSupport
+
+ Other notes:
+ - There is no need to replicate the i18n:name attribute from plone / PTL,
+ as cheetah placeholders serve the same purpose
+
+
+ """
+
+ #print macros['i18n']
+ src = _(src)
+ if isShortForm and endPos<len(parser):
+ return src+EOLCharsInShortForm
+ else:
+ return src
+
diff --git a/cobbler/Cheetah/Macros/__init__.py b/cobbler/Cheetah/Macros/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/cobbler/Cheetah/Macros/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/cobbler/Cheetah/NameMapper.py b/cobbler/Cheetah/NameMapper.py
new file mode 100644
index 0000000..2d6d953
--- /dev/null
+++ b/cobbler/Cheetah/NameMapper.py
@@ -0,0 +1,355 @@
+#!/usr/bin/env python
+# $Id: NameMapper.py,v 1.29 2006/01/15 20:27:42 tavis_rudd Exp $
+
+"""This module supports Cheetah's optional NameMapper syntax.
+
+Overview
+================================================================================
+
+NameMapper provides a simple syntax for accessing Python data structures,
+functions, and methods from Cheetah. It's called NameMapper because it 'maps'
+simple 'names' in Cheetah templates to possibly more complex syntax in Python.
+
+Its purpose is to make working with Cheetah easy for non-programmers.
+Specifically, non-programmers using Cheetah should NOT need to be taught (a)
+what the difference is between an object and a dictionary, (b) what functions
+and methods are, and (c) what 'self' is. A further aim (d) is to buffer the
+code in Cheetah templates from changes in the implementation of the Python data
+structures behind them.
+
+Consider this scenario:
+
+You are building a customer information system. The designers with you want to
+use information from your system on the client's website --AND-- they want to
+understand the display code and so they can maintian it themselves.
+
+You write a UI class with a 'customers' method that returns a dictionary of all
+the customer objects. Each customer object has an 'address' method that returns
+the a dictionary with information about the customer's address. The designers
+want to be able to access that information.
+
+Using PSP, the display code for the website would look something like the
+following, assuming your servlet subclasses the class you created for managing
+customer information:
+
+ <%= self.customer()[ID].address()['city'] %> (42 chars)
+
+Using Cheetah's NameMapper syntax it could be any of the following:
+
+ $self.customers()[$ID].address()['city'] (39 chars)
+ --OR--
+ $customers()[$ID].address()['city']
+ --OR--
+ $customers()[$ID].address().city
+ --OR--
+ $customers()[$ID].address.city
+ --OR--
+ $customers()[$ID].address.city
+ --OR--
+ $customers[$ID].address.city (27 chars)
+
+
+Which of these would you prefer to explain to the designers, who have no
+programming experience? The last form is 15 characters shorter than the PSP
+and, conceptually, is far more accessible. With PHP or ASP, the code would be
+even messier than the PSP
+
+This is a rather extreme example and, of course, you could also just implement
+'$getCustomer($ID).city' and obey the Law of Demeter (search Google for more on that).
+But good object orientated design isn't the point here.
+
+Details
+================================================================================
+The parenthesized letters below correspond to the aims in the second paragraph.
+
+DICTIONARY ACCESS (a)
+---------------------
+
+NameMapper allows access to items in a dictionary using the same dotted notation
+used to access object attributes in Python. This aspect of NameMapper is known
+as 'Unified Dotted Notation'.
+
+For example, with Cheetah it is possible to write:
+ $customers()['kerr'].address() --OR-- $customers().kerr.address()
+where the second form is in NameMapper syntax.
+
+This only works with dictionary keys that are also valid python identifiers:
+ regex = '[a-zA-Z_][a-zA-Z_0-9]*'
+
+
+AUTOCALLING (b,d)
+-----------------
+
+NameMapper automatically detects functions and methods in Cheetah $vars and calls
+them if the parentheses have been left off.
+
+For example if 'a' is an object, 'b' is a method
+ $a.b
+is equivalent to
+ $a.b()
+
+If b returns a dictionary, then following variations are possible
+ $a.b.c --OR-- $a.b().c --OR-- $a.b()['c']
+where 'c' is a key in the dictionary that a.b() returns.
+
+Further notes:
+* NameMapper autocalls the function or method without any arguments. Thus
+autocalling can only be used with functions or methods that either have no
+arguments or have default values for all arguments.
+
+* NameMapper only autocalls functions and methods. Classes and callable object instances
+will not be autocalled.
+
+* Autocalling can be disabled using Cheetah's 'useAutocalling' setting.
+
+LEAVING OUT 'self' (c,d)
+------------------------
+
+NameMapper makes it possible to access the attributes of a servlet in Cheetah
+without needing to include 'self' in the variable names. See the NAMESPACE
+CASCADING section below for details.
+
+NAMESPACE CASCADING (d)
+--------------------
+...
+
+Implementation details
+================================================================================
+
+* NameMapper's search order is dictionary keys then object attributes
+
+* NameMapper.NotFound is raised if a value can't be found for a name.
+
+Performance and the C version
+================================================================================
+
+Cheetah comes with both a C version and a Python version of NameMapper. The C
+version is significantly faster and the exception tracebacks are much easier to
+read. It's still slower than standard Python syntax, but you won't notice the
+difference in realistic usage scenarios.
+
+Cheetah uses the optimized C version (_namemapper.c) if it has
+been compiled or falls back to the Python version if not.
+
+Meta-Data
+================================================================================
+Authors: Tavis Rudd <tavis@damnsimple.com>,
+ Chuck Esterbrook <echuck@mindspring.com>
+Version: $Revision: 1.29 $
+Start Date: 2001/04/03
+Last Revision Date: $Date: 2006/01/15 20:27:42 $
+"""
+from __future__ import generators
+__author__ = "Tavis Rudd <tavis@damnsimple.com>," +\
+ "\nChuck Esterbrook <echuck@mindspring.com>"
+__revision__ = "$Revision: 1.29 $"[11:-2]
+import types
+from types import StringType, InstanceType, ClassType, TypeType
+from pprint import pformat
+import inspect
+
+_INCLUDE_NAMESPACE_REPR_IN_NOTFOUND_EXCEPTIONS = False
+_ALLOW_WRAPPING_OF_NOTFOUND_EXCEPTIONS = True
+__all__ = ['NotFound',
+ 'hasKey',
+ 'valueForKey',
+ 'valueForName',
+ 'valueFromSearchList',
+ 'valueFromFrameOrSearchList',
+ 'valueFromFrame',
+ ]
+
+
+## N.B. An attempt is made at the end of this module to import C versions of
+## these functions. If _namemapper.c has been compiled succesfully and the
+## import goes smoothly, the Python versions defined here will be replaced with
+## the C versions.
+
+class NotFound(LookupError):
+ pass
+
+def _raiseNotFoundException(key, namespace):
+ excString = "cannot find '%s'"%key
+ if _INCLUDE_NAMESPACE_REPR_IN_NOTFOUND_EXCEPTIONS:
+ excString += ' in the namespace %s'%pformat(namespace)
+ raise NotFound(excString)
+
+def _wrapNotFoundException(exc, fullName, namespace):
+ if not _ALLOW_WRAPPING_OF_NOTFOUND_EXCEPTIONS:
+ raise
+ else:
+ excStr = exc.args[0]
+ if excStr.find('while searching')==-1: # only wrap once!
+ excStr +=" while searching for '%s'"%fullName
+ if _INCLUDE_NAMESPACE_REPR_IN_NOTFOUND_EXCEPTIONS:
+ excString += ' in the namespace %s'%pformat(namespace)
+ exc.args = (excStr,)
+ raise
+
+def hasKey(obj, key):
+ """Determine if 'obj' has 'key' """
+ if hasattr(obj,'has_key') and obj.has_key(key):
+ return True
+ elif hasattr(obj, key):
+ return True
+ else:
+ return False
+
+def valueForKey(obj, key):
+ if hasattr(obj, 'has_key') and obj.has_key(key):
+ return obj[key]
+ elif hasattr(obj, key):
+ return getattr(obj, key)
+ else:
+ _raiseNotFoundException(key, obj)
+
+def _valueForName(obj, name, executeCallables=False):
+ nameChunks=name.split('.')
+ for i in range(len(nameChunks)):
+ key = nameChunks[i]
+ if hasattr(obj, 'has_key') and obj.has_key(key):
+ nextObj = obj[key]
+ elif hasattr(obj, key):
+ nextObj = getattr(obj, key)
+ else:
+ _raiseNotFoundException(key, obj)
+
+ if (executeCallables and callable(nextObj)
+ and (type(nextObj) not in (InstanceType, ClassType))):
+ obj = nextObj()
+ else:
+ obj = nextObj
+ return obj
+
+def valueForName(obj, name, executeCallables=False):
+ try:
+ return _valueForName(obj, name, executeCallables)
+ except NotFound, e:
+ _wrapNotFoundException(e, fullName=name, namespace=obj)
+
+def valueFromSearchList(searchList, name, executeCallables=False):
+ key = name.split('.')[0]
+ for namespace in searchList:
+ if hasKey(namespace, key):
+ return _valueForName(namespace, name,
+ executeCallables=executeCallables)
+ _raiseNotFoundException(key, searchList)
+
+def _namespaces(callerFrame, searchList=None):
+ yield callerFrame.f_locals
+ if searchList:
+ for namespace in searchList:
+ yield namespace
+ yield callerFrame.f_globals
+ yield __builtins__
+
+def valueFromFrameOrSearchList(searchList, name, executeCallables=False,
+ frame=None):
+ def __valueForName():
+ try:
+ return _valueForName(namespace, name, executeCallables=executeCallables)
+ except NotFound, e:
+ _wrapNotFoundException(e, fullName=name, namespace=searchList)
+ try:
+ if not frame:
+ frame = inspect.stack()[1][0]
+ key = name.split('.')[0]
+ for namespace in _namespaces(frame, searchList):
+ if hasKey(namespace, key): return __valueForName()
+ _raiseNotFoundException(key, searchList)
+ finally:
+ del frame
+
+def valueFromFrame(name, executeCallables=False, frame=None):
+ # @@TR consider implementing the C version the same way
+ # at the moment it provides a seperate but mirror implementation
+ # to valueFromFrameOrSearchList
+ try:
+ if not frame:
+ frame = inspect.stack()[1][0]
+ return valueFromFrameOrSearchList(searchList=None,
+ name=name,
+ executeCallables=executeCallables,
+ frame=frame)
+ finally:
+ del frame
+
+def hasName(obj, name):
+ #Not in the C version
+ """Determine if 'obj' has the 'name' """
+ key = name.split('.')[0]
+ if not hasKey(obj, key):
+ return False
+ try:
+ valueForName(obj, name)
+ return True
+ except NotFound:
+ return False
+try:
+ from _namemapper import NotFound, valueForKey, valueForName, \
+ valueFromSearchList, valueFromFrameOrSearchList, valueFromFrame
+ # it is possible with Jython or Windows, for example, that _namemapper.c hasn't been compiled
+ C_VERSION = True
+except:
+ C_VERSION = False
+
+##################################################
+## CLASSES
+
+class Mixin:
+ """@@ document me"""
+ def valueForName(self, name):
+ return valueForName(self, name)
+
+ def valueForKey(self, key):
+ return valueForKey(self, key)
+
+##################################################
+## if run from the command line ##
+
+def example():
+ class A(Mixin):
+ classVar = 'classVar val'
+ def method(self,arg='method 1 default arg'):
+ return arg
+
+ def method2(self, arg='meth 2 default arg'):
+ return {'item1':arg}
+
+ def method3(self, arg='meth 3 default'):
+ return arg
+
+ class B(A):
+ classBvar = 'classBvar val'
+
+ a = A()
+ a.one = 'valueForOne'
+ def function(whichOne='default'):
+ values = {
+ 'default': 'default output',
+ 'one': 'output option one',
+ 'two': 'output option two'
+ }
+ return values[whichOne]
+
+ a.dic = {
+ 'func': function,
+ 'method': a.method3,
+ 'item': 'itemval',
+ 'subDict': {'nestedMethod':a.method3}
+ }
+ b = 'this is local b'
+
+ print valueForKey(a.dic,'subDict')
+ print valueForName(a, 'dic.item')
+ print valueForName(vars(), 'b')
+ print valueForName(__builtins__, 'dir')()
+ print valueForName(vars(), 'a.classVar')
+ print valueForName(vars(), 'a.dic.func', executeCallables=True)
+ print valueForName(vars(), 'a.method2.item1', executeCallables=True)
+
+if __name__ == '__main__':
+ example()
+
+
+
diff --git a/cobbler/Cheetah/Parser.py b/cobbler/Cheetah/Parser.py
new file mode 100644
index 0000000..a72e702
--- /dev/null
+++ b/cobbler/Cheetah/Parser.py
@@ -0,0 +1,2558 @@
+#!/usr/bin/env python
+# $Id: Parser.py,v 1.130 2006/06/21 23:49:14 tavis_rudd Exp $
+"""Parser classes for Cheetah's Compiler
+
+Classes:
+ ParseError( Exception )
+ _LowLevelParser( Cheetah.SourceReader.SourceReader ), basically a lexer
+ _HighLevelParser( _LowLevelParser )
+ Parser === _HighLevelParser (an alias)
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com>
+Version: $Revision: 1.130 $
+Start Date: 2001/08/01
+Last Revision Date: $Date: 2006/06/21 23:49:14 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.130 $"[11:-2]
+
+import os
+import sys
+import re
+from re import DOTALL, MULTILINE
+from types import StringType, ListType, TupleType, ClassType, TypeType
+import time
+from tokenize import pseudoprog
+import inspect
+import new
+import traceback
+
+from Cheetah.SourceReader import SourceReader
+from Cheetah import Filters
+from Cheetah import ErrorCatchers
+from Cheetah.Unspecified import Unspecified
+
+# re tools
+def escapeRegexChars(txt,
+ escapeRE=re.compile(r'([\$\^\*\+\.\?\{\}\[\]\(\)\|\\])')):
+
+ """Return a txt with all special regular expressions chars escaped."""
+
+ return escapeRE.sub(r'\\\1' , txt)
+
+def group(*choices): return '(' + '|'.join(choices) + ')'
+def nongroup(*choices): return '(?:' + '|'.join(choices) + ')'
+def namedGroup(name, *choices): return '(P:<' + name +'>' + '|'.join(choices) + ')'
+def any(*choices): return apply(group, choices) + '*'
+def maybe(*choices): return apply(group, choices) + '?'
+
+##################################################
+## CONSTANTS & GLOBALS ##
+
+NO_CACHE = 0
+STATIC_CACHE = 1
+REFRESH_CACHE = 2
+
+SET_LOCAL = 0
+SET_GLOBAL = 1
+SET_MODULE = 2
+
+##################################################
+## Tokens for the parser ##
+
+#generic
+identchars = "abcdefghijklmnopqrstuvwxyz" \
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ_"
+namechars = identchars + "0123456789"
+
+#operators
+powerOp = '**'
+unaryArithOps = ('+', '-', '~')
+binaryArithOps = ('+', '-', '/', '//','%')
+shiftOps = ('>>','<<')
+bitwiseOps = ('&','|','^')
+assignOp = '='
+augAssignOps = ('+=','-=','/=','*=', '**=','^=','%=',
+ '>>=','<<=','&=','|=', )
+assignmentOps = (assignOp,) + augAssignOps
+
+compOps = ('<','>','==','!=','<=','>=', '<>', 'is', 'in',)
+booleanOps = ('and','or','not')
+operators = (powerOp,) + unaryArithOps + binaryArithOps \
+ + shiftOps + bitwiseOps + assignmentOps \
+ + compOps + booleanOps
+
+delimeters = ('(',')','{','}','[',']',
+ ',','.',':',';','=','`') + augAssignOps
+
+
+keywords = ('and', 'del', 'for', 'is', 'raise',
+ 'assert', 'elif', 'from', 'lambda', 'return',
+ 'break', 'else', 'global', 'not', 'try',
+ 'class', 'except', 'if', 'or', 'while',
+ 'continue', 'exec', 'import', 'pass',
+ 'def', 'finally', 'in', 'print',
+ )
+
+single3 = "'''"
+double3 = '"""'
+
+tripleQuotedStringStarts = ("'''", '"""',
+ "r'''", 'r"""', "R'''", 'R"""',
+ "u'''", 'u"""', "U'''", 'U"""',
+ "ur'''", 'ur"""', "Ur'''", 'Ur"""',
+ "uR'''", 'uR"""', "UR'''", 'UR"""')
+
+tripleQuotedStringPairs = {"'''": single3, '"""': double3,
+ "r'''": single3, 'r"""': double3,
+ "u'''": single3, 'u"""': double3,
+ "ur'''": single3, 'ur"""': double3,
+ "R'''": single3, 'R"""': double3,
+ "U'''": single3, 'U"""': double3,
+ "uR'''": single3, 'uR"""': double3,
+ "Ur'''": single3, 'Ur"""': double3,
+ "UR'''": single3, 'UR"""': double3,
+ }
+
+closurePairs= {')':'(',']':'[','}':'{'}
+closurePairsRev= {'(':')','[':']','{':'}'}
+
+##################################################
+## Regex chunks for the parser ##
+
+tripleQuotedStringREs = {}
+def makeTripleQuoteRe(start, end):
+ start = escapeRegexChars(start)
+ end = escapeRegexChars(end)
+ return re.compile(r'(?:' + start + r').*?' + r'(?:' + end + r')', re.DOTALL)
+
+for start, end in tripleQuotedStringPairs.items():
+ tripleQuotedStringREs[start] = makeTripleQuoteRe(start, end)
+
+WS = r'[ \f\t]*'
+EOL = r'\r\n|\n|\r'
+EOLZ = EOL + r'|\Z'
+escCharLookBehind = nongroup(r'(?<=\A)',r'(?<!\\)')
+nameCharLookAhead = r'(?=[A-Za-z_])'
+identRE=re.compile(r'[a-zA-Z_][a-zA-Z_0-9]*')
+EOLre=re.compile(r'(?:\r\n|\r|\n)')
+
+specialVarRE=re.compile(r'([a-zA-z_]+)@') # for matching specialVar comments
+# e.g. ##author@ Tavis Rudd
+
+directiveNamesAndParsers = {
+ # importing and inheritance
+ 'import':None,
+ 'from':None,
+ 'extends': 'eatExtends',
+ 'implements': 'eatImplements',
+
+ # output, filtering, and caching
+ 'slurp': 'eatSlurp',
+ 'raw': 'eatRaw',
+ 'include': 'eatInclude',
+ 'cache': 'eatCache',
+ 'filter': 'eatFilter',
+ 'echo': None,
+ 'silent': None,
+
+ 'call': 'eatCall',
+ 'arg': 'eatCallArg',
+
+ 'capture': 'eatCapture',
+
+ # declaration, assignment, and deletion
+ 'attr': 'eatAttr',
+ 'def': 'eatDef',
+ 'block': 'eatBlock',
+ '@': 'eatDecorator',
+ 'defmacro': 'eatDefMacro',
+
+ 'closure': 'eatClosure',
+
+ 'set': 'eatSet',
+ 'del': None,
+
+ # flow control
+ 'if': 'eatIf',
+ 'while': None,
+ 'for': None,
+ 'else': None,
+ 'elif': None,
+ 'pass': None,
+ 'break': None,
+ 'continue': None,
+ 'stop': None,
+ 'return': None,
+ 'yield': None,
+
+ # little wrappers
+ 'repeat': None,
+ 'unless': None,
+
+ # error handling
+ 'assert': None,
+ 'raise': None,
+ 'try': None,
+ 'except': None,
+ 'finally': None,
+ 'errorCatcher': 'eatErrorCatcher',
+
+ # intructions to the parser and compiler
+ 'breakpoint': 'eatBreakPoint',
+ 'compiler': 'eatCompiler',
+ 'compiler-settings': 'eatCompilerSettings',
+
+ # misc
+ 'shBang': 'eatShbang',
+ 'encoding': 'eatEncoding',
+
+ 'end': 'eatEndDirective',
+ }
+
+endDirectiveNamesAndHandlers = {
+ 'def': 'handleEndDef', # has short-form
+ 'block': None, # has short-form
+ 'closure': None, # has short-form
+ 'cache': None, # has short-form
+ 'call': None, # has short-form
+ 'capture': None, # has short-form
+ 'filter': None,
+ 'errorCatcher':None,
+ 'while': None, # has short-form
+ 'for': None, # has short-form
+ 'if': None, # has short-form
+ 'try': None, # has short-form
+ 'repeat': None, # has short-form
+ 'unless': None, # has short-form
+ }
+
+##################################################
+## CLASSES ##
+
+# @@TR: SyntaxError doesn't call exception.__str__ for some reason!
+#class ParseError(SyntaxError):
+class ParseError(ValueError):
+ def __init__(self, stream, msg='Invalid Syntax', extMsg='', lineno=None, col=None):
+ self.stream = stream
+ if stream.pos() >= len(stream):
+ stream.setPos(len(stream) -1)
+ self.msg = msg
+ self.extMsg = extMsg
+ self.lineno = lineno
+ self.col = col
+
+ def __str__(self):
+ return self.report()
+
+ def report(self):
+ stream = self.stream
+ if stream.filename():
+ f = " in file %s" % stream.filename()
+ else:
+ f = ''
+ report = ''
+ if self.lineno:
+ lineno = self.lineno
+ row, col, line = (lineno, (self.col or 0),
+ self.stream.splitlines()[lineno-1])
+ else:
+ row, col, line = self.stream.getRowColLine()
+
+ ## get the surrounding lines
+ lines = stream.splitlines()
+ prevLines = [] # (rowNum, content)
+ for i in range(1,4):
+ if row-1-i <=0:
+ break
+ prevLines.append( (row-i,lines[row-1-i]) )
+
+ nextLines = [] # (rowNum, content)
+ for i in range(1,4):
+ if not row-1+i < len(lines):
+ break
+ nextLines.append( (row+i,lines[row-1+i]) )
+ nextLines.reverse()
+
+ ## print the main message
+ report += "\n\n%s\n" %self.msg
+ report += "Line %i, column %i%s\n\n" % (row, col, f)
+ report += 'Line|Cheetah Code\n'
+ report += '----|-------------------------------------------------------------\n'
+ while prevLines:
+ lineInfo = prevLines.pop()
+ report += "%(row)-4d|%(line)s\n"% {'row':lineInfo[0], 'line':lineInfo[1]}
+ report += "%(row)-4d|%(line)s\n"% {'row':row, 'line':line}
+ report += ' '*5 +' '*(col-1) + "^\n"
+
+ while nextLines:
+ lineInfo = nextLines.pop()
+ report += "%(row)-4d|%(line)s\n"% {'row':lineInfo[0], 'line':lineInfo[1]}
+ ## add the extra msg
+ if self.extMsg:
+ report += self.extMsg + '\n'
+
+ return report
+
+class ForbiddenSyntax(ParseError): pass
+class ForbiddenExpression(ForbiddenSyntax): pass
+class ForbiddenDirective(ForbiddenSyntax): pass
+
+class CheetahVariable:
+ def __init__(self, nameChunks, useNameMapper=True, cacheToken=None,
+ rawSource=None):
+ self.nameChunks = nameChunks
+ self.useNameMapper = useNameMapper
+ self.cacheToken = cacheToken
+ self.rawSource = rawSource
+
+class Placeholder(CheetahVariable): pass
+
+class ArgList:
+ """Used by _LowLevelParser.getArgList()"""
+
+ def __init__(self):
+ self.argNames = []
+ self.defVals = []
+ self.i = 0
+
+ def addArgName(self, name):
+ self.argNames.append( name )
+ self.defVals.append( None )
+
+ def next(self):
+ self.i += 1
+
+ def addToDefVal(self, token):
+ i = self.i
+ if self.defVals[i] == None:
+ self.defVals[i] = ''
+ self.defVals[i] += token
+
+ def merge(self):
+ defVals = self.defVals
+ for i in range(len(defVals)):
+ if type(defVals[i]) == StringType:
+ defVals[i] = defVals[i].strip()
+
+ return map(None, [i.strip() for i in self.argNames], defVals)
+
+ def __str__(self):
+ return str(self.merge())
+
+class _LowLevelParser(SourceReader):
+ """This class implements the methods to match or extract ('get*') the basic
+ elements of Cheetah's grammar. It does NOT handle any code generation or
+ state management.
+ """
+
+ _settingsManager = None
+
+ def setSettingsManager(self, settingsManager):
+ self._settingsManager = settingsManager
+
+ def setting(self, key, default=Unspecified):
+ if default is Unspecified:
+ return self._settingsManager.setting(key)
+ else:
+ return self._settingsManager.setting(key, default=default)
+
+ def setSetting(self, key, val):
+ self._settingsManager.setSetting(key, val)
+
+ def settings(self):
+ return self._settingsManager.settings()
+
+ def updateSettings(self, settings):
+ self._settingsManager.updateSettings(settings)
+
+ def _initializeSettings(self):
+ self._settingsManager._initializeSettings()
+
+ def configureParser(self):
+ """Is called by the Compiler instance after the parser has had a
+ settingsManager assigned with self.setSettingsManager()
+ """
+ self._makeCheetahVarREs()
+ self._makeCommentREs()
+ self._makeDirectiveREs()
+ self._makePspREs()
+ self._possibleNonStrConstantChars = (
+ self.setting('commentStartToken')[0] +
+ self.setting('multiLineCommentStartToken')[0] +
+ self.setting('cheetahVarStartToken')[0] +
+ self.setting('directiveStartToken')[0] +
+ self.setting('PSPStartToken')[0])
+ self._nonStrConstMatchers = [
+ self.matchCommentStartToken,
+ self.matchMultiLineCommentStartToken,
+ self.matchVariablePlaceholderStart,
+ self.matchExpressionPlaceholderStart,
+ self.matchDirective,
+ self.matchPSPStartToken,
+ self.matchEOLSlurpToken,
+ ]
+
+ ## regex setup ##
+
+ def _makeCheetahVarREs(self):
+
+ """Setup the regexs for Cheetah $var parsing."""
+
+ num = r'[0-9\.]+'
+ interval = (r'(?P<interval>' +
+ num + r's|' +
+ num + r'm|' +
+ num + r'h|' +
+ num + r'd|' +
+ num + r'w|' +
+ num + ')'
+ )
+
+ cacheToken = (r'(?:' +
+ r'(?P<REFRESH_CACHE>\*' + interval + '\*)'+
+ '|' +
+ r'(?P<STATIC_CACHE>\*)' +
+ '|' +
+ r'(?P<NO_CACHE>)' +
+ ')')
+ self.cacheTokenRE = re.compile(cacheToken)
+
+ silentPlaceholderToken = (r'(?:' +
+ r'(?P<SILENT>' +escapeRegexChars('!')+')'+
+ '|' +
+ r'(?P<NOT_SILENT>)' +
+ ')')
+ self.silentPlaceholderTokenRE = re.compile(silentPlaceholderToken)
+
+ self.cheetahVarStartRE = re.compile(
+ escCharLookBehind +
+ r'(?P<startToken>'+escapeRegexChars(self.setting('cheetahVarStartToken'))+')'+
+ r'(?P<silenceToken>'+silentPlaceholderToken+')'+
+ r'(?P<cacheToken>'+cacheToken+')'+
+ r'(?P<enclosure>|(?:(?:\{|\(|\[)[ \t\f]*))' + # allow WS after enclosure
+ r'(?=[A-Za-z_])')
+ validCharsLookAhead = r'(?=[A-Za-z_\*!\{\(\[])'
+ self.cheetahVarStartToken = self.setting('cheetahVarStartToken')
+ self.cheetahVarStartTokenRE = re.compile(
+ escCharLookBehind +
+ escapeRegexChars(self.setting('cheetahVarStartToken'))
+ +validCharsLookAhead
+ )
+
+ self.cheetahVarInExpressionStartTokenRE = re.compile(
+ escapeRegexChars(self.setting('cheetahVarStartToken'))
+ +r'(?=[A-Za-z_])'
+ )
+
+ self.expressionPlaceholderStartRE = re.compile(
+ escCharLookBehind +
+ r'(?P<startToken>' + escapeRegexChars(self.setting('cheetahVarStartToken')) + ')' +
+ r'(?P<cacheToken>' + cacheToken + ')' +
+ #r'\[[ \t\f]*'
+ r'(?:\{|\(|\[)[ \t\f]*'
+ + r'(?=[^\)\}\]])'
+ )
+
+ if self.setting('EOLSlurpToken'):
+ self.EOLSlurpRE = re.compile(
+ escapeRegexChars(self.setting('EOLSlurpToken'))
+ + r'[ \t\f]*'
+ + r'(?:'+EOL+')'
+ )
+ else:
+ self.EOLSlurpRE = None
+
+
+ def _makeCommentREs(self):
+ """Construct the regex bits that are used in comment parsing."""
+ startTokenEsc = escapeRegexChars(self.setting('commentStartToken'))
+ self.commentStartTokenRE = re.compile(escCharLookBehind + startTokenEsc)
+ del startTokenEsc
+
+ startTokenEsc = escapeRegexChars(
+ self.setting('multiLineCommentStartToken'))
+ endTokenEsc = escapeRegexChars(
+ self.setting('multiLineCommentEndToken'))
+ self.multiLineCommentTokenStartRE = re.compile(escCharLookBehind +
+ startTokenEsc)
+ self.multiLineCommentEndTokenRE = re.compile(escCharLookBehind +
+ endTokenEsc)
+
+ def _makeDirectiveREs(self):
+ """Construct the regexs that are used in directive parsing."""
+ startToken = self.setting('directiveStartToken')
+ endToken = self.setting('directiveEndToken')
+ startTokenEsc = escapeRegexChars(startToken)
+ endTokenEsc = escapeRegexChars(endToken)
+ validSecondCharsLookAhead = r'(?=[A-Za-z_@])'
+ reParts = [escCharLookBehind, startTokenEsc]
+ if self.setting('allowWhitespaceAfterDirectiveStartToken'):
+ reParts.append('[ \t]*')
+ reParts.append(validSecondCharsLookAhead)
+ self.directiveStartTokenRE = re.compile(''.join(reParts))
+ self.directiveEndTokenRE = re.compile(escCharLookBehind + endTokenEsc)
+
+ def _makePspREs(self):
+ """Setup the regexs for PSP parsing."""
+ startToken = self.setting('PSPStartToken')
+ startTokenEsc = escapeRegexChars(startToken)
+ self.PSPStartTokenRE = re.compile(escCharLookBehind + startTokenEsc)
+ endToken = self.setting('PSPEndToken')
+ endTokenEsc = escapeRegexChars(endToken)
+ self.PSPEndTokenRE = re.compile(escCharLookBehind + endTokenEsc)
+
+
+ def isLineClearToStartToken(self, pos=None):
+ return self.isLineClearToPos(pos)
+
+ def matchTopLevelToken(self):
+ """Returns the first match found from the following methods:
+ self.matchCommentStartToken
+ self.matchMultiLineCommentStartToken
+ self.matchVariablePlaceholderStart
+ self.matchExpressionPlaceholderStart
+ self.matchDirective
+ self.matchPSPStartToken
+ self.matchEOLSlurpToken
+
+ Returns None if no match.
+ """
+ match = None
+ if self.peek() in self._possibleNonStrConstantChars:
+ for matcher in self._nonStrConstMatchers:
+ match = matcher()
+ if match:
+ break
+ return match
+
+ def matchPyToken(self):
+ match = pseudoprog.match(self.src(), self.pos())
+
+ if match and match.group() in tripleQuotedStringStarts:
+ TQSmatch = tripleQuotedStringREs[match.group()].match(self.src(), self.pos())
+ if TQSmatch:
+ return TQSmatch
+ return match
+
+ def getPyToken(self):
+ match = self.matchPyToken()
+ if match is None:
+ raise ParseError(self)
+ elif match.group() in tripleQuotedStringStarts:
+ raise ParseError(self, msg='Malformed triple-quoted string')
+ return self.readTo(match.end())
+
+ def matchEOLSlurpToken(self):
+ if self.EOLSlurpRE:
+ return self.EOLSlurpRE.match(self.src(), self.pos())
+
+ def getEOLSlurpToken(self):
+ match = self.matchEOLSlurpToken()
+ if not match:
+ raise ParseError(self, msg='Invalid EOL slurp token')
+ return self.readTo(match.end())
+
+ def matchCommentStartToken(self):
+ return self.commentStartTokenRE.match(self.src(), self.pos())
+
+ def getCommentStartToken(self):
+ match = self.matchCommentStartToken()
+ if not match:
+ raise ParseError(self, msg='Invalid single-line comment start token')
+ return self.readTo(match.end())
+
+ def matchMultiLineCommentStartToken(self):
+ return self.multiLineCommentTokenStartRE.match(self.src(), self.pos())
+
+ def getMultiLineCommentStartToken(self):
+ match = self.matchMultiLineCommentStartToken()
+ if not match:
+ raise ParseError(self, msg='Invalid multi-line comment start token')
+ return self.readTo(match.end())
+
+ def matchMultiLineCommentEndToken(self):
+ return self.multiLineCommentEndTokenRE.match(self.src(), self.pos())
+
+ def getMultiLineCommentEndToken(self):
+ match = self.matchMultiLineCommentEndToken()
+ if not match:
+ raise ParseError(self, msg='Invalid multi-line comment end token')
+ return self.readTo(match.end())
+
+ def getDottedName(self):
+ srcLen = len(self)
+ nameChunks = []
+
+ if not self.peek() in identchars:
+ raise ParseError(self)
+
+ while self.pos() < srcLen:
+ c = self.peek()
+ if c in namechars:
+ nameChunk = self.getIdentifier()
+ nameChunks.append(nameChunk)
+ elif c == '.':
+ if self.pos()+1 <srcLen and self.peek(1) in identchars:
+ nameChunks.append(self.getc())
+ else:
+ break
+ else:
+ break
+
+ return ''.join(nameChunks)
+
+ def matchIdentifier(self):
+ return identRE.match(self.src(), self.pos())
+
+ def getIdentifier(self):
+ match = self.matchIdentifier()
+ if not match:
+ raise ParseError(self, msg='Invalid identifier')
+ return self.readTo(match.end())
+
+ def matchOperator(self):
+ match = self.matchPyToken()
+ if match and match.group() not in operators:
+ match = None
+ return match
+
+ def getOperator(self):
+ match = self.matchOperator()
+ if not match:
+ raise ParseError(self, msg='Expected operator')
+ return self.readTo( match.end() )
+
+ def matchAssignmentOperator(self):
+ match = self.matchPyToken()
+ if match and match.group() not in assignmentOps:
+ match = None
+ return match
+
+ def getAssignmentOperator(self):
+ match = self.matchAssignmentOperator()
+ if not match:
+ raise ParseError(self, msg='Expected assignment operator')
+ return self.readTo( match.end() )
+
+ def matchDirective(self):
+ """Returns False or the name of the directive matched.
+ """
+ startPos = self.pos()
+ if not self.matchDirectiveStartToken():
+ return False
+ self.getDirectiveStartToken()
+ directiveName = self.matchDirectiveName()
+ self.setPos(startPos)
+ return directiveName
+
+ def matchDirectiveName(self, directiveNameChars=identchars+'0123456789-@'):
+ startPos = self.pos()
+ directives = self._directiveNamesAndParsers.keys()
+ possibleMatches = []
+ name = ''
+ while not self.atEnd():
+ c = self.getc()
+ if not c in directiveNameChars:
+ break
+ name += c
+ if name in directives:
+ possibleMatches.append(name)
+
+ possibleMatches.sort()
+ possibleMatches.reverse() # longest match first
+
+ directiveName = False
+ if possibleMatches:
+ directiveName = possibleMatches[0]
+
+ self.setPos(startPos)
+ return directiveName
+
+ def matchDirectiveStartToken(self):
+ return self.directiveStartTokenRE.match(self.src(), self.pos())
+
+ def getDirectiveStartToken(self):
+ match = self.matchDirectiveStartToken()
+ if not match:
+ raise ParseError(self, msg='Invalid directive start token')
+ return self.readTo(match.end())
+
+ def matchDirectiveEndToken(self):
+ return self.directiveEndTokenRE.match(self.src(), self.pos())
+
+ def getDirectiveEndToken(self):
+ match = self.matchDirectiveEndToken()
+ if not match:
+ raise ParseError(self, msg='Invalid directive end token')
+ return self.readTo(match.end())
+
+
+ def matchColonForSingleLineShortFormDirective(self):
+ if not self.atEnd() and self.peek()==':':
+ restOfLine = self[self.pos()+1:self.findEOL()]
+ restOfLine = restOfLine.strip()
+ if not restOfLine:
+ return False
+ elif self.commentStartTokenRE.match(restOfLine):
+ return False
+ else: # non-whitespace, non-commment chars found
+ return True
+ return False
+
+ def matchPSPStartToken(self):
+ return self.PSPStartTokenRE.match(self.src(), self.pos())
+
+ def matchPSPEndToken(self):
+ return self.PSPEndTokenRE.match(self.src(), self.pos())
+
+ def getPSPStartToken(self):
+ match = self.matchPSPStartToken()
+ if not match:
+ raise ParseError(self, msg='Invalid psp start token')
+ return self.readTo(match.end())
+
+ def getPSPEndToken(self):
+ match = self.matchPSPEndToken()
+ if not match:
+ raise ParseError(self, msg='Invalid psp end token')
+ return self.readTo(match.end())
+
+ def matchCheetahVarStart(self):
+ """includes the enclosure and cache token"""
+ return self.cheetahVarStartRE.match(self.src(), self.pos())
+
+ def matchCheetahVarStartToken(self):
+ """includes the enclosure and cache token"""
+ return self.cheetahVarStartTokenRE.match(self.src(), self.pos())
+
+ def matchCheetahVarInExpressionStartToken(self):
+ """no enclosures or cache tokens allowed"""
+ return self.cheetahVarInExpressionStartTokenRE.match(self.src(), self.pos())
+
+ def matchVariablePlaceholderStart(self):
+ """includes the enclosure and cache token"""
+ return self.cheetahVarStartRE.match(self.src(), self.pos())
+
+ def matchExpressionPlaceholderStart(self):
+ """includes the enclosure and cache token"""
+ return self.expressionPlaceholderStartRE.match(self.src(), self.pos())
+
+ def getCheetahVarStartToken(self):
+ """just the start token, not the enclosure or cache token"""
+ match = self.matchCheetahVarStartToken()
+ if not match:
+ raise ParseError(self, msg='Expected Cheetah $var start token')
+ return self.readTo( match.end() )
+
+
+ def getCacheToken(self):
+ try:
+ token = self.cacheTokenRE.match(self.src(), self.pos())
+ self.setPos( token.end() )
+ return token.group()
+ except:
+ raise ParseError(self, msg='Expected cache token')
+
+ def getSilentPlaceholderToken(self):
+ try:
+ token = self.silentPlaceholderTokenRE.match(self.src(), self.pos())
+ self.setPos( token.end() )
+ return token.group()
+ except:
+ raise ParseError(self, msg='Expected silent placeholder token')
+
+
+
+ def getTargetVarsList(self):
+ varnames = []
+ while not self.atEnd():
+ if self.peek() in ' \t\f':
+ self.getWhiteSpace()
+ elif self.peek() in '\r\n':
+ break
+ elif self.startswith(','):
+ self.advance()
+ elif self.startswith('in ') or self.startswith('in\t'):
+ break
+ #elif self.matchCheetahVarStart():
+ elif self.matchCheetahVarInExpressionStartToken():
+ self.getCheetahVarStartToken()
+ self.getSilentPlaceholderToken()
+ self.getCacheToken()
+ varnames.append( self.getDottedName() )
+ elif self.matchIdentifier():
+ varnames.append( self.getDottedName() )
+ else:
+ break
+ return varnames
+
+ def getCheetahVar(self, plain=False, skipStartToken=False):
+ """This is called when parsing inside expressions. Cache tokens are only
+ valid in placeholders so this method discards any cache tokens found.
+ """
+ if not skipStartToken:
+ self.getCheetahVarStartToken()
+ self.getSilentPlaceholderToken()
+ self.getCacheToken()
+ return self.getCheetahVarBody(plain=plain)
+
+ def getCheetahVarBody(self, plain=False):
+ # @@TR: this should be in the compiler
+ return self._compiler.genCheetahVar(self.getCheetahVarNameChunks(), plain=plain)
+
+ def getCheetahVarNameChunks(self):
+
+ """
+ nameChunks = list of Cheetah $var subcomponents represented as tuples
+ [ (namemapperPart,autoCall,restOfName),
+ ]
+ where:
+ namemapperPart = the dottedName base
+ autocall = where NameMapper should use autocalling on namemapperPart
+ restOfName = any arglist, index, or slice
+
+ If restOfName contains a call arglist (e.g. '(1234)') then autocall is
+ False, otherwise it defaults to True.
+
+ EXAMPLE
+ ------------------------------------------------------------------------
+
+ if the raw CheetahVar is
+ $a.b.c[1].d().x.y.z
+
+ nameChunks is the list
+ [ ('a.b.c',True,'[1]'),
+ ('d',False,'()'),
+ ('x.y.z',True,''),
+ ]
+
+ """
+
+ chunks = []
+ while self.pos() < len(self):
+ rest = ''
+ autoCall = True
+ if not self.peek() in identchars + '.':
+ break
+ elif self.peek() == '.':
+
+ if self.pos()+1 < len(self) and self.peek(1) in identchars:
+ self.advance() # discard the period as it isn't needed with NameMapper
+ else:
+ break
+
+ dottedName = self.getDottedName()
+ if not self.atEnd() and self.peek() in '([':
+ if self.peek() == '(':
+ rest = self.getCallArgString()
+ else:
+ rest = self.getExpression(enclosed=True)
+
+ period = max(dottedName.rfind('.'), 0)
+ if period:
+ chunks.append( (dottedName[:period], autoCall, '') )
+ dottedName = dottedName[period+1:]
+ if rest and rest[0]=='(':
+ autoCall = False
+ chunks.append( (dottedName, autoCall, rest) )
+
+ return chunks
+
+
+ def getCallArgString(self,
+ enclosures=[], # list of tuples (char, pos), where char is ({ or [
+ useNameMapper=Unspecified):
+
+ """ Get a method/function call argument string.
+
+ This method understands *arg, and **kw
+ """
+
+ # @@TR: this settings mangling should be removed
+ if useNameMapper is not Unspecified:
+ useNameMapper_orig = self.setting('useNameMapper')
+ self.setSetting('useNameMapper', useNameMapper)
+
+ if enclosures:
+ pass
+ else:
+ if not self.peek() == '(':
+ raise ParseError(self, msg="Expected '('")
+ startPos = self.pos()
+ self.getc()
+ enclosures = [('(', startPos),
+ ]
+
+ argStringBits = ['(']
+ addBit = argStringBits.append
+
+ while 1:
+ if self.atEnd():
+ open = enclosures[-1][0]
+ close = closurePairsRev[open]
+ self.setPos(enclosures[-1][1])
+ raise ParseError(
+ self, msg="EOF was reached before a matching '" + close +
+ "' was found for the '" + open + "'")
+
+ c = self.peek()
+ if c in ")}]": # get the ending enclosure and break
+ if not enclosures:
+ raise ParseError(self)
+ c = self.getc()
+ open = closurePairs[c]
+ if enclosures[-1][0] == open:
+ enclosures.pop()
+ addBit(')')
+ break
+ else:
+ raise ParseError(self)
+ elif c in " \t\f\r\n":
+ addBit(self.getc())
+ elif self.matchCheetahVarInExpressionStartToken():
+ startPos = self.pos()
+ codeFor1stToken = self.getCheetahVar()
+ WS = self.getWhiteSpace()
+ if not self.atEnd() and self.peek() == '=':
+ nextToken = self.getPyToken()
+ if nextToken == '=':
+ endPos = self.pos()
+ self.setPos(startPos)
+ codeFor1stToken = self.getCheetahVar(plain=True)
+ self.setPos(endPos)
+
+ ## finally
+ addBit( codeFor1stToken + WS + nextToken )
+ else:
+ addBit( codeFor1stToken + WS)
+ elif self.matchCheetahVarStart():
+ # it has syntax that is only valid at the top level
+ self._raiseErrorAboutInvalidCheetahVarSyntaxInExpr()
+ else:
+ beforeTokenPos = self.pos()
+ token = self.getPyToken()
+ if token in ('{','(','['):
+ self.rev()
+ token = self.getExpression(enclosed=True)
+ token = self.transformToken(token, beforeTokenPos)
+ addBit(token)
+
+ if useNameMapper is not Unspecified:
+ self.setSetting('useNameMapper', useNameMapper_orig) # @@TR: see comment above
+
+ return ''.join(argStringBits)
+
+ def getDefArgList(self, exitPos=None, useNameMapper=False):
+
+ """ Get an argument list. Can be used for method/function definition
+ argument lists or for #directive argument lists. Returns a list of
+ tuples in the form (argName, defVal=None) with one tuple for each arg
+ name.
+
+ These defVals are always strings, so (argName, defVal=None) is safe even
+ with a case like (arg1, arg2=None, arg3=1234*2), which would be returned as
+ [('arg1', None),
+ ('arg2', 'None'),
+ ('arg3', '1234*2'),
+ ]
+
+ This method understands *arg, and **kw
+
+ """
+
+ if self.peek() == '(':
+ self.advance()
+ else:
+ exitPos = self.findEOL() # it's a directive so break at the EOL
+ argList = ArgList()
+ onDefVal = False
+
+ # @@TR: this settings mangling should be removed
+ useNameMapper_orig = self.setting('useNameMapper')
+ self.setSetting('useNameMapper', useNameMapper)
+
+ while 1:
+ if self.atEnd():
+ self.setPos(enclosures[-1][1])
+ raise ParseError(
+ self, msg="EOF was reached before a matching ')'"+
+ " was found for the '('")
+
+ if self.pos() == exitPos:
+ break
+
+ c = self.peek()
+ if c == ")" or self.matchDirectiveEndToken():
+ break
+ elif c == ":":
+ break
+ elif c in " \t\f\r\n":
+ if onDefVal:
+ argList.addToDefVal(c)
+ self.advance()
+ elif c == '=':
+ onDefVal = True
+ self.advance()
+ elif c == ",":
+ argList.next()
+ onDefVal = False
+ self.advance()
+ elif self.startswith(self.cheetahVarStartToken) and not onDefVal:
+ self.advance(len(self.cheetahVarStartToken))
+ elif self.matchIdentifier() and not onDefVal:
+ argList.addArgName( self.getIdentifier() )
+ elif onDefVal:
+ if self.matchCheetahVarInExpressionStartToken():
+ token = self.getCheetahVar()
+ elif self.matchCheetahVarStart():
+ # it has syntax that is only valid at the top level
+ self._raiseErrorAboutInvalidCheetahVarSyntaxInExpr()
+ else:
+ beforeTokenPos = self.pos()
+ token = self.getPyToken()
+ if token in ('{','(','['):
+ self.rev()
+ token = self.getExpression(enclosed=True)
+ token = self.transformToken(token, beforeTokenPos)
+ argList.addToDefVal(token)
+ elif c == '*' and not onDefVal:
+ varName = self.getc()
+ if self.peek() == '*':
+ varName += self.getc()
+ if not self.matchIdentifier():
+ raise ParseError(self)
+ varName += self.getIdentifier()
+ argList.addArgName(varName)
+ else:
+ raise ParseError(self)
+
+
+ self.setSetting('useNameMapper', useNameMapper_orig) # @@TR: see comment above
+ return argList.merge()
+
+ def getExpressionParts(self,
+ enclosed=False,
+ enclosures=None, # list of tuples (char, pos), where char is ({ or [
+ pyTokensToBreakAt=None, # only works if not enclosed
+ useNameMapper=Unspecified,
+ ):
+
+ """ Get a Cheetah expression that includes $CheetahVars and break at
+ directive end tokens, the end of an enclosure, or at a specified
+ pyToken.
+ """
+
+ if useNameMapper is not Unspecified:
+ useNameMapper_orig = self.setting('useNameMapper')
+ self.setSetting('useNameMapper', useNameMapper)
+
+ if enclosures is None:
+ enclosures = []
+
+ srcLen = len(self)
+ exprBits = []
+ while 1:
+ if self.atEnd():
+ if enclosures:
+ open = enclosures[-1][0]
+ close = closurePairsRev[open]
+ self.setPos(enclosures[-1][1])
+ raise ParseError(
+ self, msg="EOF was reached before a matching '" + close +
+ "' was found for the '" + open + "'")
+ else:
+ break
+
+ c = self.peek()
+ if c in "{([":
+ exprBits.append(c)
+ enclosures.append( (c, self.pos()) )
+ self.advance()
+ elif enclosed and not enclosures:
+ break
+ elif c in "])}":
+ if not enclosures:
+ raise ParseError(self)
+ open = closurePairs[c]
+ if enclosures[-1][0] == open:
+ enclosures.pop()
+ exprBits.append(c)
+ else:
+ open = enclosures[-1][0]
+ close = closurePairsRev[open]
+ row, col = self.getRowCol()
+ self.setPos(enclosures[-1][1])
+ raise ParseError(
+ self, msg= "A '" + c + "' was found at line " + str(row) +
+ ", col " + str(col) +
+ " before a matching '" + close +
+ "' was found\nfor the '" + open + "'")
+ self.advance()
+
+ elif c in " \f\t":
+ exprBits.append(self.getWhiteSpace())
+ elif self.matchDirectiveEndToken() and not enclosures:
+ break
+ elif c == "\\" and self.pos()+1 < srcLen:
+ eolMatch = EOLre.match(self.src(), self.pos()+1)
+ if not eolMatch:
+ self.advance()
+ raise ParseError(self, msg='Line ending expected')
+ self.setPos( eolMatch.end() )
+ elif c in '\r\n':
+ if enclosures:
+ self.advance()
+ else:
+ break
+ elif self.matchCheetahVarInExpressionStartToken():
+ expr = self.getCheetahVar()
+ exprBits.append(expr)
+ elif self.matchCheetahVarStart():
+ # it has syntax that is only valid at the top level
+ self._raiseErrorAboutInvalidCheetahVarSyntaxInExpr()
+ else:
+ beforeTokenPos = self.pos()
+ token = self.getPyToken()
+ if (not enclosures
+ and pyTokensToBreakAt
+ and token in pyTokensToBreakAt):
+
+ self.setPos(beforeTokenPos)
+ break
+
+ token = self.transformToken(token, beforeTokenPos)
+
+ exprBits.append(token)
+ if identRE.match(token):
+ if token == 'for':
+ expr = self.getExpression(useNameMapper=False, pyTokensToBreakAt=['in'])
+ exprBits.append(expr)
+ else:
+ exprBits.append(self.getWhiteSpace())
+ if not self.atEnd() and self.peek() == '(':
+ exprBits.append(self.getCallArgString())
+ ##
+ if useNameMapper is not Unspecified:
+ self.setSetting('useNameMapper', useNameMapper_orig) # @@TR: see comment above
+ return exprBits
+
+ def getExpression(self,
+ enclosed=False,
+ enclosures=None, # list of tuples (char, pos), where # char is ({ or [
+ pyTokensToBreakAt=None,
+ useNameMapper=Unspecified,
+ ):
+ """Returns the output of self.getExpressionParts() as a concatenated
+ string rather than as a list.
+ """
+ return ''.join(self.getExpressionParts(
+ enclosed=enclosed, enclosures=enclosures,
+ pyTokensToBreakAt=pyTokensToBreakAt,
+ useNameMapper=useNameMapper))
+
+
+ def transformToken(self, token, beforeTokenPos):
+ """Takes a token from the expression being parsed and performs and
+ special transformations required by Cheetah.
+
+ At the moment only Cheetah's c'$placeholder strings' are transformed.
+ """
+ if token=='c' and not self.atEnd() and self.peek() in '\'"':
+ nextToken = self.getPyToken()
+ token = nextToken.upper()
+ theStr = eval(token)
+ endPos = self.pos()
+ if not theStr:
+ return
+
+ if token.startswith(single3) or token.startswith(double3):
+ startPosIdx = 3
+ else:
+ startPosIdx = 1
+ #print 'CHEETAH STRING', nextToken, theStr, startPosIdx
+ self.setPos(beforeTokenPos+startPosIdx+1)
+ outputExprs = []
+ strConst = ''
+ while self.pos() < (endPos-startPosIdx):
+ if self.matchCheetahVarStart() or self.matchExpressionPlaceholderStart():
+ if strConst:
+ outputExprs.append(repr(strConst))
+ strConst = ''
+ placeholderExpr = self.getPlaceholder()
+ outputExprs.append('str('+placeholderExpr+')')
+ else:
+ strConst += self.getc()
+ self.setPos(endPos)
+ if strConst:
+ outputExprs.append(repr(strConst))
+ #if not self.atEnd() and self.matches('.join('):
+ # print 'DEBUG***'
+ token = "''.join(["+','.join(outputExprs)+"])"
+ return token
+
+ def _raiseErrorAboutInvalidCheetahVarSyntaxInExpr(self):
+ match = self.matchCheetahVarStart()
+ groupdict = match.groupdict()
+ if groupdict.get('cacheToken'):
+ raise ParseError(
+ self,
+ msg='Cache tokens are not valid inside expressions. '
+ 'Use them in top-level $placeholders only.')
+ elif groupdict.get('enclosure'):
+ raise ParseError(
+ self,
+ msg='Long-form placeholders - ${}, $(), $[], etc. are not valid inside expressions. '
+ 'Use them in top-level $placeholders only.')
+ else:
+ raise ParseError(
+ self,
+ msg='This form of $placeholder syntax is not valid here.')
+
+
+ def getPlaceholder(self, allowCacheTokens=False, plain=False, returnEverything=False):
+ # filtered
+ for callback in self.setting('preparsePlaceholderHooks'):
+ callback(parser=self)
+
+ startPos = self.pos()
+ lineCol = self.getRowCol(startPos)
+ startToken = self.getCheetahVarStartToken()
+ silentPlaceholderToken = self.getSilentPlaceholderToken()
+ if silentPlaceholderToken:
+ isSilentPlaceholder = True
+ else:
+ isSilentPlaceholder = False
+
+
+ if allowCacheTokens:
+ cacheToken = self.getCacheToken()
+ cacheTokenParts = self.cacheTokenRE.match(cacheToken).groupdict()
+ else:
+ cacheTokenParts = {}
+
+ if self.peek() in '({[':
+ pos = self.pos()
+ enclosureOpenChar = self.getc()
+ enclosures = [ (enclosureOpenChar, pos) ]
+ self.getWhiteSpace()
+ else:
+ enclosures = []
+
+ filterArgs = None
+ if self.matchIdentifier():
+ nameChunks = self.getCheetahVarNameChunks()
+ expr = self._compiler.genCheetahVar(nameChunks[:], plain=plain)
+ restOfExpr = None
+ if enclosures:
+ WS = self.getWhiteSpace()
+ expr += WS
+ if self.setting('allowPlaceholderFilterArgs') and self.peek()==',':
+ filterArgs = self.getCallArgString(enclosures=enclosures)[1:-1]
+ else:
+ if self.peek()==closurePairsRev[enclosureOpenChar]:
+ self.getc()
+ else:
+ restOfExpr = self.getExpression(enclosed=True, enclosures=enclosures)
+ if restOfExpr[-1] == closurePairsRev[enclosureOpenChar]:
+ restOfExpr = restOfExpr[:-1]
+ expr += restOfExpr
+ rawPlaceholder = self[startPos: self.pos()]
+ else:
+ expr = self.getExpression(enclosed=True, enclosures=enclosures)
+ if expr[-1] == closurePairsRev[enclosureOpenChar]:
+ expr = expr[:-1]
+ rawPlaceholder=self[startPos: self.pos()]
+
+ expr = self._applyExpressionFilters(expr,'placeholder',
+ rawExpr=rawPlaceholder,startPos=startPos)
+ for callback in self.setting('postparsePlaceholderHooks'):
+ callback(parser=self)
+
+ if returnEverything:
+ return (expr, rawPlaceholder, lineCol, cacheTokenParts,
+ filterArgs, isSilentPlaceholder)
+ else:
+ return expr
+
+
+class _HighLevelParser(_LowLevelParser):
+ """This class is a StateMachine for parsing Cheetah source and
+ sending state dependent code generation commands to
+ Cheetah.Compiler.Compiler.
+ """
+ def __init__(self, src, filename=None, breakPoint=None, compiler=None):
+ _LowLevelParser.__init__(self, src, filename=filename, breakPoint=breakPoint)
+ self.setSettingsManager(compiler)
+ self._compiler = compiler
+ self.setupState()
+ self.configureParser()
+
+ def setupState(self):
+ self._macros = {}
+ self._macroDetails = {}
+ self._openDirectivesStack = []
+
+ def cleanup(self):
+ """Cleanup to remove any possible reference cycles
+ """
+ self._macros.clear()
+ for macroname, macroDetails in self._macroDetails.items():
+ macroDetails.template.shutdown()
+ del macroDetails.template
+ self._macroDetails.clear()
+
+ def configureParser(self):
+ _LowLevelParser.configureParser(self)
+ self._initDirectives()
+
+ def _initDirectives(self):
+ def normalizeParserVal(val):
+ if isinstance(val, (str,unicode)):
+ handler = getattr(self, val)
+ elif type(val) in (ClassType, TypeType):
+ handler = val(self)
+ elif callable(val):
+ handler = val
+ elif val is None:
+ handler = val
+ else:
+ raise Exception('Invalid parser/handler value %r for %s'%(val, name))
+ return handler
+
+ normalizeHandlerVal = normalizeParserVal
+
+ _directiveNamesAndParsers = directiveNamesAndParsers.copy()
+ customNamesAndParsers = self.setting('directiveNamesAndParsers',{})
+ _directiveNamesAndParsers.update(customNamesAndParsers)
+
+ _endDirectiveNamesAndHandlers = endDirectiveNamesAndHandlers.copy()
+ customNamesAndHandlers = self.setting('endDirectiveNamesAndHandlers',{})
+ _endDirectiveNamesAndHandlers.update(customNamesAndHandlers)
+
+ self._directiveNamesAndParsers = {}
+ for name, val in _directiveNamesAndParsers.items():
+ if val in (False, 0):
+ continue
+ self._directiveNamesAndParsers[name] = normalizeParserVal(val)
+
+ self._endDirectiveNamesAndHandlers = {}
+ for name, val in _endDirectiveNamesAndHandlers.items():
+ if val in (False, 0):
+ continue
+ self._endDirectiveNamesAndHandlers[name] = normalizeHandlerVal(val)
+
+ self._closeableDirectives = ['def','block','closure','defmacro',
+ 'call',
+ 'capture',
+ 'cache',
+ 'filter',
+ 'if','unless',
+ 'for','while','repeat',
+ 'try',
+ ]
+ for directiveName in self.setting('closeableDirectives',[]):
+ self._closeableDirectives.append(directiveName)
+
+
+
+ macroDirectives = self.setting('macroDirectives',{})
+ from Cheetah.Macros.I18n import I18n
+ macroDirectives['i18n'] = I18n
+
+
+ for macroName, callback in macroDirectives.items():
+ if type(callback) in (ClassType, TypeType):
+ callback = callback(parser=self)
+ assert callback
+ self._macros[macroName] = callback
+ self._directiveNamesAndParsers[macroName] = self.eatMacroCall
+
+ def _applyExpressionFilters(self, expr, exprType, rawExpr=None, startPos=None):
+ """Pipes cheetah expressions through a set of optional filter hooks.
+
+ The filters are functions which may modify the expressions or raise
+ a ForbiddenExpression exception if the expression is not allowed. They
+ are defined in the compiler setting 'expressionFilterHooks'.
+
+ Some intended use cases:
+
+ - to implement 'restricted execution' safeguards in cases where you
+ can't trust the author of the template.
+
+ - to enforce style guidelines
+
+ filter call signature: (parser, expr, exprType, rawExpr=None, startPos=None)
+ - parser is the Cheetah parser
+ - expr is the expression to filter. In some cases the parser will have
+ already modified it from the original source code form. For example,
+ placeholders will have been translated into namemapper calls. If you
+ need to work with the original source, see rawExpr.
+ - exprType is the name of the directive, 'psp', or 'placeholder'. All
+ lowercase. @@TR: These will eventually be replaced with a set of
+ constants.
+ - rawExpr is the original source string that Cheetah parsed. This
+ might be None in some cases.
+ - startPos is the character position in the source string/file
+ where the parser started parsing the current expression.
+
+ @@TR: I realize this use of the term 'expression' is a bit wonky as many
+ of the 'expressions' are actually statements, but I haven't thought of
+ a better name yet. Suggestions?
+ """
+ for callback in self.setting('expressionFilterHooks'):
+ expr = callback(parser=self, expr=expr, exprType=exprType,
+ rawExpr=rawExpr, startPos=startPos)
+ return expr
+
+ def _filterDisabledDirectives(self, directiveName):
+ directiveName = directiveName.lower()
+ if (directiveName in self.setting('disabledDirectives')
+ or (self.setting('enabledDirectives')
+ and directiveName not in self.setting('enabledDirectives'))):
+ for callback in self.setting('disabledDirectiveHooks'):
+ callback(parser=self, directiveName=directiveName)
+ raise ForbiddenDirective(self, msg='This %r directive is disabled'%directiveName)
+
+ ## main parse loop
+
+ def parse(self, breakPoint=None, assertEmptyStack=True):
+ if breakPoint:
+ origBP = self.breakPoint()
+ self.setBreakPoint(breakPoint)
+ assertEmptyStack = False
+
+ while not self.atEnd():
+ if self.matchCommentStartToken():
+ self.eatComment()
+ elif self.matchMultiLineCommentStartToken():
+ self.eatMultiLineComment()
+ elif self.matchVariablePlaceholderStart():
+ self.eatPlaceholder()
+ elif self.matchExpressionPlaceholderStart():
+ self.eatPlaceholder()
+ elif self.matchDirective():
+ self.eatDirective()
+ elif self.matchPSPStartToken():
+ self.eatPSP()
+ elif self.matchEOLSlurpToken():
+ self.eatEOLSlurpToken()
+ else:
+ self.eatPlainText()
+ if assertEmptyStack:
+ self.assertEmptyOpenDirectivesStack()
+ if breakPoint:
+ self.setBreakPoint(origBP)
+
+ ## non-directive eat methods
+
+ def eatPlainText(self):
+ startPos = self.pos()
+ match = None
+ while not self.atEnd():
+ match = self.matchTopLevelToken()
+ if match:
+ break
+ else:
+ self.advance()
+ strConst = self.readTo(self.pos(), start=startPos)
+ self._compiler.addStrConst(strConst)
+ return match
+
+ def eatComment(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ if isLineClearToStartToken:
+ self._compiler.handleWSBeforeDirective()
+ self.getCommentStartToken()
+ comm = self.readToEOL(gobble=isLineClearToStartToken)
+ self._compiler.addComment(comm)
+
+ def eatMultiLineComment(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLine = self.findEOL()
+
+ self.getMultiLineCommentStartToken()
+ endPos = startPos = self.pos()
+ level = 1
+ while 1:
+ endPos = self.pos()
+ if self.atEnd():
+ break
+ if self.matchMultiLineCommentStartToken():
+ self.getMultiLineCommentStartToken()
+ level += 1
+ elif self.matchMultiLineCommentEndToken():
+ self.getMultiLineCommentEndToken()
+ level -= 1
+ if not level:
+ break
+ self.advance()
+ comm = self.readTo(endPos, start=startPos)
+
+ if not self.atEnd():
+ self.getMultiLineCommentEndToken()
+
+ if (not self.atEnd()) and self.setting('gobbleWhitespaceAroundMultiLineComments'):
+ restOfLine = self[self.pos():self.findEOL()]
+ if not restOfLine.strip(): # WS only to EOL
+ self.readToEOL(gobble=isLineClearToStartToken)
+
+ if isLineClearToStartToken and (self.atEnd() or self.pos() > endOfFirstLine):
+ self._compiler.handleWSBeforeDirective()
+
+ self._compiler.addComment(comm)
+
+ def eatPlaceholder(self):
+ (expr, rawPlaceholder,
+ lineCol, cacheTokenParts,
+ filterArgs, isSilentPlaceholder) = self.getPlaceholder(
+ allowCacheTokens=True, returnEverything=True)
+
+ self._compiler.addPlaceholder(
+ expr,
+ filterArgs=filterArgs,
+ rawPlaceholder=rawPlaceholder,
+ cacheTokenParts=cacheTokenParts,
+ lineCol=lineCol,
+ silentMode=isSilentPlaceholder)
+ return
+
+ def eatPSP(self):
+ # filtered
+ self._filterDisabledDirectives(directiveName='psp')
+ self.getPSPStartToken()
+ endToken = self.setting('PSPEndToken')
+ startPos = self.pos()
+ while not self.atEnd():
+ if self.peek() == endToken[0]:
+ if self.matchPSPEndToken():
+ break
+ self.advance()
+ pspString = self.readTo(self.pos(), start=startPos).strip()
+ pspString = self._applyExpressionFilters(pspString, 'psp', startPos=startPos)
+ self._compiler.addPSP(pspString)
+ self.getPSPEndToken()
+
+ ## generic directive eat methods
+ _simpleIndentingDirectives = '''
+ else elif for while repeat unless try except finally'''.split()
+ _simpleExprDirectives = '''
+ pass continue stop return yield break
+ del assert raise
+ silent echo
+ import from'''.split()
+ _directiveHandlerNames = {'import':'addImportStatement',
+ 'from':'addImportStatement', }
+ def eatDirective(self):
+ directiveName = self.matchDirective()
+ self._filterDisabledDirectives(directiveName)
+
+ for callback in self.setting('preparseDirectiveHooks'):
+ callback(parser=self, directiveName=directiveName)
+
+ # subclasses can override the default behaviours here by providing an
+ # eater method in self._directiveNamesAndParsers[directiveName]
+ directiveParser = self._directiveNamesAndParsers.get(directiveName)
+ if directiveParser:
+ directiveParser()
+ elif directiveName in self._simpleIndentingDirectives:
+ handlerName = self._directiveHandlerNames.get(directiveName)
+ if not handlerName:
+ handlerName = 'add'+directiveName.capitalize()
+ handler = getattr(self._compiler, handlerName)
+ self.eatSimpleIndentingDirective(directiveName, callback=handler)
+ elif directiveName in self._simpleExprDirectives:
+ handlerName = self._directiveHandlerNames.get(directiveName)
+ if not handlerName:
+ handlerName = 'add'+directiveName.capitalize()
+ handler = getattr(self._compiler, handlerName)
+ if directiveName in ('silent', 'echo'):
+ includeDirectiveNameInExpr = False
+ else:
+ includeDirectiveNameInExpr = True
+ expr = self.eatSimpleExprDirective(
+ directiveName,
+ includeDirectiveNameInExpr=includeDirectiveNameInExpr)
+ handler(expr)
+ ##
+ for callback in self.setting('postparseDirectiveHooks'):
+ callback(parser=self, directiveName=directiveName)
+
+ def _eatRestOfDirectiveTag(self, isLineClearToStartToken, endOfFirstLinePos):
+ foundComment = False
+ if self.matchCommentStartToken():
+ pos = self.pos()
+ self.advance()
+ if not self.matchDirective():
+ self.setPos(pos)
+ foundComment = True
+ self.eatComment() # this won't gobble the EOL
+ else:
+ self.setPos(pos)
+
+ if not foundComment and self.matchDirectiveEndToken():
+ self.getDirectiveEndToken()
+ elif isLineClearToStartToken and (not self.atEnd()) and self.peek() in '\r\n':
+ # still gobble the EOL if a comment was found.
+ self.readToEOL(gobble=True)
+
+ if isLineClearToStartToken and (self.atEnd() or self.pos() > endOfFirstLinePos):
+ self._compiler.handleWSBeforeDirective()
+
+ def _eatToThisEndDirective(self, directiveName):
+ finalPos = endRawPos = startPos = self.pos()
+ directiveChar = self.setting('directiveStartToken')[0]
+ isLineClearToStartToken = False
+ while not self.atEnd():
+ if self.peek() == directiveChar:
+ if self.matchDirective() == 'end':
+ endRawPos = self.pos()
+ self.getDirectiveStartToken()
+ self.advance(len('end'))
+ self.getWhiteSpace()
+ if self.startswith(directiveName):
+ if self.isLineClearToStartToken(endRawPos):
+ isLineClearToStartToken = True
+ endRawPos = self.findBOL(endRawPos)
+ self.advance(len(directiveName)) # to end of directiveName
+ self.getWhiteSpace()
+ finalPos = self.pos()
+ break
+ self.advance()
+ finalPos = endRawPos = self.pos()
+
+ textEaten = self.readTo(endRawPos, start=startPos)
+ self.setPos(finalPos)
+
+ endOfFirstLinePos = self.findEOL()
+
+ if self.matchDirectiveEndToken():
+ self.getDirectiveEndToken()
+ elif isLineClearToStartToken and (not self.atEnd()) and self.peek() in '\r\n':
+ self.readToEOL(gobble=True)
+
+ if isLineClearToStartToken and self.pos() > endOfFirstLinePos:
+ self._compiler.handleWSBeforeDirective()
+ return textEaten
+
+
+ def eatSimpleExprDirective(self, directiveName, includeDirectiveNameInExpr=True):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLine = self.findEOL()
+ self.getDirectiveStartToken()
+ if not includeDirectiveNameInExpr:
+ self.advance(len(directiveName))
+ startPos = self.pos()
+ expr = self.getExpression().strip()
+ directiveName = expr.split()[0]
+ expr = self._applyExpressionFilters(expr, directiveName, startPos=startPos)
+ if directiveName in self._closeableDirectives:
+ self.pushToOpenDirectivesStack(directiveName)
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+ return expr
+
+ def eatSimpleIndentingDirective(self, directiveName, callback,
+ includeDirectiveNameInExpr=False):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ lineCol = self.getRowCol()
+ self.getDirectiveStartToken()
+ if directiveName not in 'else elif for while try except finally'.split():
+ self.advance(len(directiveName))
+ startPos = self.pos()
+
+ self.getWhiteSpace()
+
+ expr = self.getExpression(pyTokensToBreakAt=[':'])
+ expr = self._applyExpressionFilters(expr, directiveName, startPos=startPos)
+ if self.matchColonForSingleLineShortFormDirective():
+ self.advance() # skip over :
+ if directiveName in 'else elif except finally'.split():
+ callback(expr, dedent=False, lineCol=lineCol)
+ else:
+ callback(expr, lineCol=lineCol)
+
+ self.getWhiteSpace(max=1)
+ self.parse(breakPoint=self.findEOL(gobble=True))
+ self._compiler.commitStrConst()
+ self._compiler.dedent()
+ else:
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ if directiveName in self._closeableDirectives:
+ self.pushToOpenDirectivesStack(directiveName)
+ callback(expr, lineCol=lineCol)
+
+ def eatEndDirective(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ self.getDirectiveStartToken()
+ self.advance(3) # to end of 'end'
+ self.getWhiteSpace()
+ pos = self.pos()
+ directiveName = False
+ for key in self._endDirectiveNamesAndHandlers.keys():
+ if self.find(key, pos) == pos:
+ directiveName = key
+ break
+ if not directiveName:
+ raise ParseError(self, msg='Invalid end directive')
+
+ endOfFirstLinePos = self.findEOL()
+ self.getExpression() # eat in any extra comment-like crap
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ if directiveName in self._closeableDirectives:
+ self.popFromOpenDirectivesStack(directiveName)
+
+ # subclasses can override the default behaviours here by providing an
+ # end-directive handler in self._endDirectiveNamesAndHandlers[directiveName]
+ if self._endDirectiveNamesAndHandlers.get(directiveName):
+ handler = self._endDirectiveNamesAndHandlers[directiveName]
+ handler()
+ elif directiveName in 'block capture cache call filter errorCatcher'.split():
+ if key == 'block':
+ self._compiler.closeBlock()
+ elif key == 'capture':
+ self._compiler.endCaptureRegion()
+ elif key == 'cache':
+ self._compiler.endCacheRegion()
+ elif key == 'call':
+ self._compiler.endCallRegion()
+ elif key == 'filter':
+ self._compiler.closeFilterBlock()
+ elif key == 'errorCatcher':
+ self._compiler.turnErrorCatcherOff()
+ elif directiveName in 'while for if try repeat unless'.split():
+ self._compiler.commitStrConst()
+ self._compiler.dedent()
+ elif directiveName=='closure':
+ self._compiler.commitStrConst()
+ self._compiler.dedent()
+ # @@TR: temporary hack of useSearchList
+ self.setSetting('useSearchList', self._useSearchList_orig)
+
+ ## specific directive eat methods
+
+ def eatBreakPoint(self):
+ """Tells the parser to stop parsing at this point and completely ignore
+ everything else.
+
+ This is a debugging tool.
+ """
+ self.setBreakPoint(self.pos())
+
+ def eatShbang(self):
+ # filtered
+ self.getDirectiveStartToken()
+ self.advance(len('shBang'))
+ self.getWhiteSpace()
+ startPos = self.pos()
+ shBang = self.readToEOL()
+ shBang = self._applyExpressionFilters(shBang, 'shbang', startPos=startPos)
+ self._compiler.setShBang(shBang.strip())
+
+ def eatEncoding(self):
+ # filtered
+ self.getDirectiveStartToken()
+ self.advance(len('encoding'))
+ self.getWhiteSpace()
+ startPos = self.pos()
+ encoding = self.readToEOL()
+ encoding = self._applyExpressionFilters(encoding, 'encoding', startPos=startPos)
+ self._compiler.setModuleEncoding(encoding.strip())
+
+ def eatCompiler(self):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLine = self.findEOL()
+ startPos = self.pos()
+ self.getDirectiveStartToken()
+ self.advance(len('compiler')) # to end of 'compiler'
+ self.getWhiteSpace()
+
+ startPos = self.pos()
+ settingName = self.getIdentifier()
+
+ if settingName.lower() == 'reset':
+ self.getExpression() # gobble whitespace & junk
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+ self._initializeSettings()
+ self.configureParser()
+ return
+
+ self.getWhiteSpace()
+ if self.peek() == '=':
+ self.advance()
+ else:
+ raise ParserError(self)
+ valueExpr = self.getExpression()
+ endPos = self.pos()
+
+ # @@TR: it's unlikely that anyone apply filters would have left this
+ # directive enabled:
+ # @@TR: fix up filtering, regardless
+ self._applyExpressionFilters('%s=%r'%(settingName, valueExpr),
+ 'compiler', startPos=startPos)
+
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+ try:
+ self._compiler.setCompilerSetting(settingName, valueExpr)
+ except:
+ out = sys.stderr
+ print >> out, 'An error occurred while processing the following #compiler directive.'
+ print >> out, '-'*80
+ print >> out, self[startPos:endPos]
+ print >> out, '-'*80
+ print >> out, 'Please check the syntax of these settings.'
+ print >> out, 'A full Python exception traceback follows.'
+ raise
+
+
+ def eatCompilerSettings(self):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLine = self.findEOL()
+ self.getDirectiveStartToken()
+ self.advance(len('compiler-settings')) # to end of 'settings'
+
+ keywords = self.getTargetVarsList()
+ self.getExpression() # gobble any garbage
+
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+
+ if 'reset' in keywords:
+ self._compiler._initializeSettings()
+ self.configureParser()
+ # @@TR: this implies a single-line #compiler-settings directive, and
+ # thus we should parse forward for an end directive.
+ # Subject to change in the future
+ return
+ startPos = self.pos()
+ settingsStr = self._eatToThisEndDirective('compiler-settings')
+ settingsStr = self._applyExpressionFilters(settingsStr, 'compilerSettings',
+ startPos=startPos)
+ try:
+ self._compiler.setCompilerSettings(keywords=keywords, settingsStr=settingsStr)
+ except:
+ out = sys.stderr
+ print >> out, 'An error occurred while processing the following compiler settings.'
+ print >> out, '-'*80
+ print >> out, settingsStr.strip()
+ print >> out, '-'*80
+ print >> out, 'Please check the syntax of these settings.'
+ print >> out, 'A full Python exception traceback follows.'
+ raise
+
+ def eatAttr(self):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ startPos = self.pos()
+ self.getDirectiveStartToken()
+ self.advance(len('attr'))
+ self.getWhiteSpace()
+ startPos = self.pos()
+ if self.matchCheetahVarStart():
+ self.getCheetahVarStartToken()
+ attribName = self.getIdentifier()
+ self.getWhiteSpace()
+ self.getAssignmentOperator()
+ expr = self.getExpression()
+ expr = self._applyExpressionFilters(expr, 'attr', startPos=startPos)
+ self._compiler.addAttribute(attribName, expr)
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+
+ def eatDecorator(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ startPos = self.pos()
+ self.getDirectiveStartToken()
+ #self.advance() # eat @
+ startPos = self.pos()
+ decoratorExpr = self.getExpression()
+ decoratorExpr = self._applyExpressionFilters(decoratorExpr, 'decorator', startPos=startPos)
+ self._compiler.addDecorator(decoratorExpr)
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ self.getWhiteSpace()
+
+ directiveName = self.matchDirective()
+ if not directiveName or directiveName not in ('def', 'block', 'closure'):
+ raise ParseError(self, msg='Expected #def, #block or #closure')
+ self.eatDirective()
+
+ def eatDef(self):
+ # filtered
+ self._eatDefOrBlock('def')
+
+ def eatBlock(self):
+ # filtered
+ startPos = self.pos()
+ methodName, rawSignature = self._eatDefOrBlock('block')
+ self._compiler._blockMetaData[methodName] = {
+ 'raw':rawSignature,
+ 'lineCol':self.getRowCol(startPos),
+ }
+
+ def eatClosure(self):
+ # filtered
+ self._eatDefOrBlock('closure')
+
+ def _eatDefOrBlock(self, directiveName):
+ # filtered
+ assert directiveName in ('def','block','closure')
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ startPos = self.pos()
+ self.getDirectiveStartToken()
+ self.advance(len(directiveName))
+ self.getWhiteSpace()
+ if self.matchCheetahVarStart():
+ self.getCheetahVarStartToken()
+ methodName = self.getIdentifier()
+ self.getWhiteSpace()
+ if self.peek() == '(':
+ argsList = self.getDefArgList()
+ self.advance() # past the closing ')'
+ if argsList and argsList[0][0] == 'self':
+ del argsList[0]
+ else:
+ argsList=[]
+
+ def includeBlockMarkers():
+ if self.setting('includeBlockMarkers'):
+ startMarker = self.setting('blockMarkerStart')
+ self._compiler.addStrConst(startMarker[0] + methodName + startMarker[1])
+
+ # @@TR: fix up filtering
+ self._applyExpressionFilters(self[startPos:self.pos()], 'def', startPos=startPos)
+
+ if self.matchColonForSingleLineShortFormDirective():
+ isNestedDef = (self.setting('allowNestedDefScopes')
+ and [name for name in self._openDirectivesStack if name=='def'])
+ self.getc()
+ rawSignature = self[startPos:endOfFirstLinePos]
+ self._eatSingleLineDef(directiveName=directiveName,
+ methodName=methodName,
+ argsList=argsList,
+ startPos=startPos,
+ endPos=endOfFirstLinePos)
+ if directiveName == 'def' and not isNestedDef:
+ #@@TR: must come before _eatRestOfDirectiveTag ... for some reason
+ self._compiler.closeDef()
+ elif directiveName == 'block':
+ includeBlockMarkers()
+ self._compiler.closeBlock()
+ elif directiveName == 'closure' or isNestedDef:
+ self._compiler.dedent()
+
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ else:
+ if self.peek()==':':
+ self.getc()
+ self.pushToOpenDirectivesStack(directiveName)
+ rawSignature = self[startPos:self.pos()]
+ self._eatMultiLineDef(directiveName=directiveName,
+ methodName=methodName,
+ argsList=argsList,
+ startPos=startPos,
+ isLineClearToStartToken=isLineClearToStartToken)
+ if directiveName == 'block':
+ includeBlockMarkers()
+
+ return methodName, rawSignature
+
+ def _eatMultiLineDef(self, directiveName, methodName, argsList, startPos,
+ isLineClearToStartToken=False):
+ # filtered in calling method
+ self.getExpression() # slurp up any garbage left at the end
+ signature = self[startPos:self.pos()]
+ endOfFirstLinePos = self.findEOL()
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ parserComment = ('## CHEETAH: generated from ' + signature +
+ ' at line %s, col %s' % self.getRowCol(startPos)
+ + '.')
+
+ isNestedDef = (self.setting('allowNestedDefScopes')
+ and len([name for name in self._openDirectivesStack if name=='def'])>1)
+ if directiveName=='block' or (directiveName=='def' and not isNestedDef):
+ self._compiler.startMethodDef(methodName, argsList, parserComment)
+ else: #closure
+ self._useSearchList_orig = self.setting('useSearchList')
+ self.setSetting('useSearchList', False)
+ self._compiler.addClosure(methodName, argsList, parserComment)
+
+ return methodName
+
+ def _eatSingleLineDef(self, directiveName, methodName, argsList, startPos, endPos):
+ # filtered in calling method
+ fullSignature = self[startPos:endPos]
+ parserComment = ('## Generated from ' + fullSignature +
+ ' at line %s, col %s' % self.getRowCol(startPos)
+ + '.')
+ isNestedDef = (self.setting('allowNestedDefScopes')
+ and [name for name in self._openDirectivesStack if name=='def'])
+ if directiveName=='block' or (directiveName=='def' and not isNestedDef):
+ self._compiler.startMethodDef(methodName, argsList, parserComment)
+ else: #closure
+ # @@TR: temporary hack of useSearchList
+ useSearchList_orig = self.setting('useSearchList')
+ self.setSetting('useSearchList', False)
+ self._compiler.addClosure(methodName, argsList, parserComment)
+
+ self.getWhiteSpace(max=1)
+ self.parse(breakPoint=endPos)
+ if directiveName=='closure' or isNestedDef: # @@TR: temporary hack of useSearchList
+ self.setSetting('useSearchList', useSearchList_orig)
+
+ def eatExtends(self):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLine = self.findEOL()
+ self.getDirectiveStartToken()
+ self.advance(len('extends'))
+ self.getWhiteSpace()
+ startPos = self.pos()
+ if self.setting('allowExpressionsInExtendsDirective'):
+ baseName = self.getExpression()
+ else:
+ baseName = self.getDottedName()
+
+ baseName = self._applyExpressionFilters(baseName, 'extends', startPos=startPos)
+ self._compiler.setBaseClass(baseName) # in compiler
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+
+ def eatImplements(self):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLine = self.findEOL()
+ self.getDirectiveStartToken()
+ self.advance(len('implements'))
+ self.getWhiteSpace()
+ startPos = self.pos()
+ methodName = self.getIdentifier()
+ if not self.atEnd() and self.peek() == '(':
+ argsList = self.getDefArgList()
+ self.advance() # past the closing ')'
+ if argsList and argsList[0][0] == 'self':
+ del argsList[0]
+ else:
+ argsList=[]
+
+ # @@TR: need to split up filtering of the methodname and the args
+ #methodName = self._applyExpressionFilters(methodName, 'implements', startPos=startPos)
+ self._applyExpressionFilters(self[startPos:self.pos()], 'implements', startPos=startPos)
+
+ self._compiler.setMainMethodName(methodName)
+ self._compiler.setMainMethodArgs(argsList)
+
+ self.getExpression() # throw away and unwanted crap that got added in
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+
+ def eatSet(self):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLine = self.findEOL()
+ self.getDirectiveStartToken()
+ self.advance(3)
+ self.getWhiteSpace()
+ style = SET_LOCAL
+ if self.startswith('local'):
+ self.getIdentifier()
+ self.getWhiteSpace()
+ elif self.startswith('global'):
+ self.getIdentifier()
+ self.getWhiteSpace()
+ style = SET_GLOBAL
+ elif self.startswith('module'):
+ self.getIdentifier()
+ self.getWhiteSpace()
+ style = SET_MODULE
+
+ startsWithDollar = self.matchCheetahVarStart()
+ startPos = self.pos()
+ LVALUE = self.getExpression(pyTokensToBreakAt=assignmentOps, useNameMapper=False).strip()
+ OP = self.getAssignmentOperator()
+ RVALUE = self.getExpression()
+ expr = LVALUE + ' ' + OP + ' ' + RVALUE.strip()
+
+ expr = self._applyExpressionFilters(expr, 'set', startPos=startPos)
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+
+ class Components: pass # used for 'set global'
+ exprComponents = Components()
+ exprComponents.LVALUE = LVALUE
+ exprComponents.OP = OP
+ exprComponents.RVALUE = RVALUE
+ self._compiler.addSet(expr, exprComponents, style)
+
+ def eatSlurp(self):
+ if self.isLineClearToStartToken():
+ self._compiler.handleWSBeforeDirective()
+ self._compiler.commitStrConst()
+ self.readToEOL(gobble=True)
+
+ def eatEOLSlurpToken(self):
+ if self.isLineClearToStartToken():
+ self._compiler.handleWSBeforeDirective()
+ self._compiler.commitStrConst()
+ self.readToEOL(gobble=True)
+
+ def eatRaw(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ self.getDirectiveStartToken()
+ self.advance(len('raw'))
+ self.getWhiteSpace()
+ if self.matchColonForSingleLineShortFormDirective():
+ self.advance() # skip over :
+ self.getWhiteSpace(max=1)
+ rawBlock = self.readToEOL(gobble=False)
+ else:
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ rawBlock = self._eatToThisEndDirective('raw')
+ self._compiler.addRawText(rawBlock)
+
+ def eatInclude(self):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ self.getDirectiveStartToken()
+ self.advance(len('include'))
+
+ self.getWhiteSpace()
+ includeFrom = 'file'
+ isRaw = False
+ if self.startswith('raw'):
+ self.advance(3)
+ isRaw=True
+
+ self.getWhiteSpace()
+ if self.startswith('source'):
+ self.advance(len('source'))
+ includeFrom = 'str'
+ self.getWhiteSpace()
+ if not self.peek() == '=':
+ raise ParseError(self)
+ self.advance()
+ startPos = self.pos()
+ sourceExpr = self.getExpression()
+ sourceExpr = self._applyExpressionFilters(sourceExpr, 'include', startPos=startPos)
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ self._compiler.addInclude(sourceExpr, includeFrom, isRaw)
+
+
+ def eatDefMacro(self):
+ # @@TR: not filtered yet
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ self.getDirectiveStartToken()
+ self.advance(len('defmacro'))
+
+ self.getWhiteSpace()
+ if self.matchCheetahVarStart():
+ self.getCheetahVarStartToken()
+ macroName = self.getIdentifier()
+ self.getWhiteSpace()
+ if self.peek() == '(':
+ argsList = self.getDefArgList(useNameMapper=False)
+ self.advance() # past the closing ')'
+ if argsList and argsList[0][0] == 'self':
+ del argsList[0]
+ else:
+ argsList=[]
+
+ assert not self._directiveNamesAndParsers.has_key(macroName)
+ argsList.insert(0, ('src',None))
+ argsList.append(('parser','None'))
+ argsList.append(('macros','None'))
+ argsList.append(('compilerSettings','None'))
+ argsList.append(('isShortForm','None'))
+ argsList.append(('EOLCharsInShortForm','None'))
+ argsList.append(('startPos','None'))
+ argsList.append(('endPos','None'))
+
+ if self.matchColonForSingleLineShortFormDirective():
+ self.advance() # skip over :
+ self.getWhiteSpace(max=1)
+ macroSrc = self.readToEOL(gobble=False)
+ self.readToEOL(gobble=True)
+ else:
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ macroSrc = self._eatToThisEndDirective('defmacro')
+
+ #print argsList
+ normalizedMacroSrc = ''.join(
+ ['%def callMacro('+','.join([defv and '%s=%s'%(n,defv) or n
+ for n,defv in argsList])
+ +')\n',
+ macroSrc,
+ '%end def'])
+
+
+ from Cheetah.Template import Template
+ templateAPIClass = self.setting('templateAPIClassForDefMacro', default=Template)
+ compilerSettings = self.setting('compilerSettingsForDefMacro', default={})
+ searchListForMacros = self.setting('searchListForDefMacro', default=[])
+ searchListForMacros = list(searchListForMacros) # copy to avoid mutation bugs
+ searchListForMacros.append({'macros':self._macros,
+ 'parser':self,
+ 'compilerSettings':self.settings(),
+ })
+
+ templateAPIClass._updateSettingsWithPreprocessTokens(
+ compilerSettings, placeholderToken='@', directiveToken='%')
+ macroTemplateClass = templateAPIClass.compile(source=normalizedMacroSrc,
+ compilerSettings=compilerSettings)
+ #print normalizedMacroSrc
+ #t = macroTemplateClass()
+ #print t.callMacro('src')
+ #print t.generatedClassCode()
+
+ class MacroDetails: pass
+ macroDetails = MacroDetails()
+ macroDetails.macroSrc = macroSrc
+ macroDetails.argsList = argsList
+ macroDetails.template = macroTemplateClass(searchList=searchListForMacros)
+
+ self._macroDetails[macroName] = macroDetails
+ self._macros[macroName] = macroDetails.template.callMacro
+ self._directiveNamesAndParsers[macroName] = self.eatMacroCall
+
+ def eatMacroCall(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ startPos = self.pos()
+ self.getDirectiveStartToken()
+ macroName = self.getIdentifier()
+ macro = self._macros[macroName]
+ if hasattr(macro, 'parse'):
+ return macro.parse(parser=self, startPos=startPos)
+
+ if hasattr(macro, 'parseArgs'):
+ args = macro.parseArgs(parser=self, startPos=startPos)
+ else:
+ self.getWhiteSpace()
+ args = self.getExpression(useNameMapper=False,
+ pyTokensToBreakAt=[':']).strip()
+
+ if self.matchColonForSingleLineShortFormDirective():
+ isShortForm = True
+ self.advance() # skip over :
+ self.getWhiteSpace(max=1)
+ srcBlock = self.readToEOL(gobble=False)
+ EOLCharsInShortForm = self.readToEOL(gobble=True)
+ #self.readToEOL(gobble=False)
+ else:
+ isShortForm = False
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ srcBlock = self._eatToThisEndDirective(macroName)
+
+
+ if hasattr(macro, 'convertArgStrToDict'):
+ kwArgs = macro.convertArgStrToDict(args, parser=self, startPos=startPos)
+ else:
+ def getArgs(*pargs, **kws):
+ return pargs, kws
+ exec 'positionalArgs, kwArgs = getArgs(%(args)s)'%locals()
+
+ assert not kwArgs.has_key('src')
+ kwArgs['src'] = srcBlock
+
+ if type(macro)==new.instancemethod:
+ co = macro.im_func.func_code
+ elif (hasattr(macro, '__call__')
+ and hasattr(macro.__call__, 'im_func')):
+ co = macro.__call__.im_func.func_code
+ else:
+ co = macro.func_code
+ availableKwArgs = inspect.getargs(co)[0]
+
+ if 'parser' in availableKwArgs:
+ kwArgs['parser'] = self
+ if 'macros' in availableKwArgs:
+ kwArgs['macros'] = self._macros
+ if 'compilerSettings' in availableKwArgs:
+ kwArgs['compilerSettings'] = self.settings()
+ if 'isShortForm' in availableKwArgs:
+ kwArgs['isShortForm'] = isShortForm
+ if isShortForm and 'EOLCharsInShortForm' in availableKwArgs:
+ kwArgs['EOLCharsInShortForm'] = EOLCharsInShortForm
+
+ if 'startPos' in availableKwArgs:
+ kwArgs['startPos'] = startPos
+ if 'endPos' in availableKwArgs:
+ kwArgs['endPos'] = self.pos()
+
+ srcFromMacroOutput = macro(**kwArgs)
+
+ origParseSrc = self._src
+ origBreakPoint = self.breakPoint()
+ origPos = self.pos()
+ # add a comment to the output about the macro src that is being parsed
+ # or add a comment prefix to all the comments added by the compiler
+ self._src = srcFromMacroOutput
+ self.setPos(0)
+ self.setBreakPoint(len(srcFromMacroOutput))
+
+ self.parse(assertEmptyStack=False)
+
+ self._src = origParseSrc
+ self.setBreakPoint(origBreakPoint)
+ self.setPos(origPos)
+
+
+ #self._compiler.addRawText('end')
+
+ def eatCache(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ lineCol = self.getRowCol()
+ self.getDirectiveStartToken()
+ self.advance(len('cache'))
+
+ startPos = self.pos()
+ argList = self.getDefArgList(useNameMapper=True)
+ argList = self._applyExpressionFilters(argList, 'cache', startPos=startPos)
+
+ def startCache():
+ cacheInfo = self._compiler.genCacheInfoFromArgList(argList)
+ self._compiler.startCacheRegion(cacheInfo, lineCol)
+
+ if self.matchColonForSingleLineShortFormDirective():
+ self.advance() # skip over :
+ self.getWhiteSpace(max=1)
+ startCache()
+ self.parse(breakPoint=self.findEOL(gobble=True))
+ self._compiler.endCacheRegion()
+ else:
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ self.pushToOpenDirectivesStack('cache')
+ startCache()
+
+ def eatCall(self):
+ # @@TR: need to enable single line version of this
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ lineCol = self.getRowCol()
+ self.getDirectiveStartToken()
+ self.advance(len('call'))
+ startPos = self.pos()
+
+ useAutocallingOrig = self.setting('useAutocalling')
+ self.setSetting('useAutocalling', False)
+ self.getWhiteSpace()
+ if self.matchCheetahVarStart():
+ functionName = self.getCheetahVar()
+ else:
+ functionName = self.getCheetahVar(plain=True, skipStartToken=True)
+ self.setSetting('useAutocalling', useAutocallingOrig)
+ # @@TR: fix up filtering
+ self._applyExpressionFilters(self[startPos:self.pos()], 'call', startPos=startPos)
+
+ self.getWhiteSpace()
+ args = self.getExpression(pyTokensToBreakAt=[':']).strip()
+ if self.matchColonForSingleLineShortFormDirective():
+ self.advance() # skip over :
+ self._compiler.startCallRegion(functionName, args, lineCol)
+ self.getWhiteSpace(max=1)
+ self.parse(breakPoint=self.findEOL(gobble=False))
+ self._compiler.endCallRegion()
+ else:
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self.pushToOpenDirectivesStack("call")
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ self._compiler.startCallRegion(functionName, args, lineCol)
+
+ def eatCallArg(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ lineCol = self.getRowCol()
+ self.getDirectiveStartToken()
+
+ self.advance(len('arg'))
+ startPos = self.pos()
+ self.getWhiteSpace()
+ argName = self.getIdentifier()
+ self.getWhiteSpace()
+ argName = self._applyExpressionFilters(argName, 'arg', startPos=startPos)
+ self._compiler.setCallArg(argName, lineCol)
+ if self.peek() == ':':
+ self.getc()
+ else:
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+
+ def eatFilter(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+
+ self.getDirectiveStartToken()
+ self.advance(len('filter'))
+ self.getWhiteSpace()
+ startPos = self.pos()
+ if self.matchCheetahVarStart():
+ isKlass = True
+ theFilter = self.getExpression(pyTokensToBreakAt=[':'])
+ else:
+ isKlass = False
+ theFilter = self.getIdentifier()
+ self.getWhiteSpace()
+ theFilter = self._applyExpressionFilters(theFilter, 'filter', startPos=startPos)
+
+ if self.matchColonForSingleLineShortFormDirective():
+ self.advance() # skip over :
+ self.getWhiteSpace(max=1)
+ self._compiler.setFilter(theFilter, isKlass)
+ self.parse(breakPoint=self.findEOL(gobble=False))
+ self._compiler.closeFilterBlock()
+ else:
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self.pushToOpenDirectivesStack("filter")
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ self._compiler.setFilter(theFilter, isKlass)
+
+ def eatErrorCatcher(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ self.getDirectiveStartToken()
+ self.advance(len('errorCatcher'))
+ self.getWhiteSpace()
+ startPos = self.pos()
+ errorCatcherName = self.getIdentifier()
+ errorCatcherName = self._applyExpressionFilters(
+ errorCatcherName, 'errorcatcher', startPos=startPos)
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ self._compiler.setErrorCatcher(errorCatcherName)
+
+ def eatCapture(self):
+ # @@TR: this could be refactored to use the code in eatSimpleIndentingDirective
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ lineCol = self.getRowCol()
+
+ self.getDirectiveStartToken()
+ self.advance(len('capture'))
+ startPos = self.pos()
+ self.getWhiteSpace()
+
+ expr = self.getExpression(pyTokensToBreakAt=[':'])
+ expr = self._applyExpressionFilters(expr, 'capture', startPos=startPos)
+ if self.matchColonForSingleLineShortFormDirective():
+ self.advance() # skip over :
+ self._compiler.startCaptureRegion(assignTo=expr, lineCol=lineCol)
+ self.getWhiteSpace(max=1)
+ self.parse(breakPoint=self.findEOL(gobble=False))
+ self._compiler.endCaptureRegion()
+ else:
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ self.pushToOpenDirectivesStack("capture")
+ self._compiler.startCaptureRegion(assignTo=expr, lineCol=lineCol)
+
+
+ def eatIf(self):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLine = self.findEOL()
+ lineCol = self.getRowCol()
+ self.getDirectiveStartToken()
+ startPos = self.pos()
+
+ expressionParts = self.getExpressionParts(pyTokensToBreakAt=[':'])
+ expr = ''.join(expressionParts).strip()
+ expr = self._applyExpressionFilters(expr, 'if', startPos=startPos)
+
+ isTernaryExpr = ('then' in expressionParts and 'else' in expressionParts)
+ if isTernaryExpr:
+ conditionExpr = []
+ trueExpr = []
+ falseExpr = []
+ currentExpr = conditionExpr
+ for part in expressionParts:
+ if part.strip()=='then':
+ currentExpr = trueExpr
+ elif part.strip()=='else':
+ currentExpr = falseExpr
+ else:
+ currentExpr.append(part)
+
+ conditionExpr = ''.join(conditionExpr)
+ trueExpr = ''.join(trueExpr)
+ falseExpr = ''.join(falseExpr)
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+ self._compiler.addTernaryExpr(conditionExpr, trueExpr, falseExpr, lineCol=lineCol)
+ elif self.matchColonForSingleLineShortFormDirective():
+ self.advance() # skip over :
+ self._compiler.addIf(expr, lineCol=lineCol)
+ self.getWhiteSpace(max=1)
+ self.parse(breakPoint=self.findEOL(gobble=True))
+ self._compiler.commitStrConst()
+ self._compiler.dedent()
+ else:
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+ self.pushToOpenDirectivesStack('if')
+ self._compiler.addIf(expr, lineCol=lineCol)
+
+ ## end directive handlers
+ def handleEndDef(self):
+ isNestedDef = (self.setting('allowNestedDefScopes')
+ and [name for name in self._openDirectivesStack if name=='def'])
+ if not isNestedDef:
+ self._compiler.closeDef()
+ else:
+ # @@TR: temporary hack of useSearchList
+ self.setSetting('useSearchList', self._useSearchList_orig)
+ self._compiler.commitStrConst()
+ self._compiler.dedent()
+ ###
+
+ def pushToOpenDirectivesStack(self, directiveName):
+ assert directiveName in self._closeableDirectives
+ self._openDirectivesStack.append(directiveName)
+
+ def popFromOpenDirectivesStack(self, directiveName):
+ if not self._openDirectivesStack:
+ raise ParseError(self, msg="#end found, but nothing to end")
+
+ if self._openDirectivesStack[-1] == directiveName:
+ del self._openDirectivesStack[-1]
+ else:
+ raise ParseError(self, msg="#end %s found, expected #end %s" %(
+ directiveName, self._openDirectivesStack[-1]))
+
+ def assertEmptyOpenDirectivesStack(self):
+ if self._openDirectivesStack:
+ errorMsg = (
+ "Some #directives are missing their corresponding #end ___ tag: %s" %(
+ ', '.join(self._openDirectivesStack)))
+ raise ParseError(self, msg=errorMsg)
+
+##################################################
+## Make an alias to export
+Parser = _HighLevelParser
diff --git a/cobbler/Cheetah/Servlet.py b/cobbler/Cheetah/Servlet.py
new file mode 100644
index 0000000..5295d30
--- /dev/null
+++ b/cobbler/Cheetah/Servlet.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python
+# $Id: Servlet.py,v 1.40 2006/02/04 23:06:15 tavis_rudd Exp $
+"""Provides an abstract Servlet baseclass for Cheetah's Template class
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com>
+License: This software is released for unlimited distribution under the
+ terms of the MIT license. See the LICENSE file.
+Version: $Revision: 1.40 $
+Start Date: 2001/10/03
+Last Revision Date: $Date: 2006/02/04 23:06:15 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.40 $"[11:-2]
+
+import sys
+import os.path
+
+isWebwareInstalled = False
+try:
+ if 'ds.appserver' in sys.modules.keys():
+ from ds.appserver.Servlet import Servlet as BaseServlet
+ else:
+ from WebKit.Servlet import Servlet as BaseServlet
+ isWebwareInstalled = True
+
+ if not issubclass(BaseServlet, object):
+ class NewStyleBaseServlet(BaseServlet, object): pass
+ BaseServlet = NewStyleBaseServlet
+except:
+ class BaseServlet(object):
+ _reusable = 1
+ _threadSafe = 0
+
+ def __init__(self):
+ pass
+
+ def awake(self, transaction):
+ pass
+
+ def sleep(self, transaction):
+ pass
+
+ def shutdown(self):
+ pass
+
+##################################################
+## CLASSES
+
+class Servlet(BaseServlet):
+
+ """This class is an abstract baseclass for Cheetah.Template.Template.
+
+ It wraps WebKit.Servlet and provides a few extra convenience methods that
+ are also found in WebKit.Page. It doesn't do any of the HTTP method
+ resolution that is done in WebKit.HTTPServlet
+ """
+
+ transaction = None
+ application = None
+ request = None
+ session = None
+
+ def __init__(self):
+ BaseServlet.__init__(self)
+
+ # this default will be changed by the .awake() method
+ self._CHEETAH__isControlledByWebKit = False
+
+ ## methods called by Webware during the request-response
+
+ def awake(self, transaction):
+ BaseServlet.awake(self, transaction)
+
+ # a hack to signify that the servlet is being run directly from WebKit
+ self._CHEETAH__isControlledByWebKit = True
+
+ self.transaction = transaction
+ #self.application = transaction.application
+ self.response = response = transaction.response
+ self.request = transaction.request
+
+ # Temporary hack to accomodate bug in
+ # WebKit.Servlet.Servlet.serverSidePath: it uses
+ # self._request even though this attribute does not exist.
+ # This attribute WILL disappear in the future.
+ self._request = transaction.request()
+
+
+ self.session = transaction.session
+ self.write = response().write
+ #self.writeln = response.writeln
+
+ def respond(self, trans=None):
+ raise NotImplementedError("""\
+couldn't find the template's main method. If you are using #extends
+without #implements, try adding '#implements respond' to your template
+definition.""")
+
+ def sleep(self, transaction):
+ BaseServlet.sleep(self, transaction)
+ self.session = None
+ self.request = None
+ self._request = None
+ self.response = None
+ self.transaction = None
+
+ def shutdown(self):
+ pass
+
+ def serverSidePath(self, path=None,
+ normpath=os.path.normpath,
+ abspath=os.path.abspath
+ ):
+
+ if self._CHEETAH__isControlledByWebKit:
+ return BaseServlet.serverSidePath(self, path)
+ elif path:
+ return normpath(abspath(path.replace("\\",'/')))
+ elif hasattr(self, '_filePath') and self._filePath:
+ return normpath(abspath(self._filePath))
+ else:
+ return None
+
+# vim: shiftwidth=4 tabstop=4 expandtab
diff --git a/cobbler/Cheetah/SettingsManager.py b/cobbler/Cheetah/SettingsManager.py
new file mode 100644
index 0000000..0967155
--- /dev/null
+++ b/cobbler/Cheetah/SettingsManager.py
@@ -0,0 +1,623 @@
+#!/usr/bin/env python
+
+"""Provides a mixin/base class for collecting and managing application settings
+
+Meta-Data
+==========
+Author: Tavis Rudd <tavis@damnsimple.com>
+Version: $Revision: 1.28 $
+Start Date: 2001/05/30
+Last Revision Date: $Date: 2006/01/29 07:19:12 $
+"""
+
+# $Id: SettingsManager.py,v 1.28 2006/01/29 07:19:12 tavis_rudd Exp $
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.28 $"[11:-2]
+
+
+##################################################
+## DEPENDENCIES ##
+
+import sys
+import os.path
+import copy as copyModule
+from ConfigParser import ConfigParser
+import re
+from tokenize import Intnumber, Floatnumber, Number
+from types import *
+import types
+import new
+import tempfile
+import imp
+import time
+
+from StringIO import StringIO # not cStringIO because of unicode support
+
+import imp # used by SettingsManager.updateSettingsFromPySrcFile()
+
+try:
+ import threading
+ from threading import Lock # used for thread lock on sys.path manipulations
+except:
+ ## provide a dummy for non-threading Python systems
+ class Lock:
+ def acquire(self):
+ pass
+ def release(self):
+ pass
+
+class BaseErrorClass: pass
+
+##################################################
+## CONSTANTS & GLOBALS ##
+
+try:
+ True,False
+except NameError:
+ True, False = (1==1),(1==0)
+
+numberRE = re.compile(Number)
+complexNumberRE = re.compile('[\(]*' +Number + r'[ \t]*\+[ \t]*' + Number + '[\)]*')
+
+convertableToStrTypes = (StringType, IntType, FloatType,
+ LongType, ComplexType, NoneType,
+ UnicodeType)
+
+##################################################
+## FUNCTIONS ##
+
+def mergeNestedDictionaries(dict1, dict2, copy=False, deepcopy=False):
+
+ """Recursively merge the values of dict2 into dict1.
+
+ This little function is very handy for selectively overriding settings in a
+ settings dictionary that has a nested structure.
+ """
+
+ if copy:
+ dict1 = copyModule.copy(dict1)
+ elif deepcopy:
+ dict1 = copyModule.deepcopy(dict1)
+
+ for key,val in dict2.items():
+ if dict1.has_key(key) and type(val) == types.DictType and \
+ type(dict1[key]) == types.DictType:
+
+ dict1[key] = mergeNestedDictionaries(dict1[key], val)
+ else:
+ dict1[key] = val
+ return dict1
+
+def stringIsNumber(S):
+
+ """Return True if theString represents a Python number, False otherwise.
+ This also works for complex numbers and numbers with +/- in front."""
+
+ S = S.strip()
+
+ if S[0] in '-+' and len(S) > 1:
+ S = S[1:].strip()
+
+ match = complexNumberRE.match(S)
+ if not match:
+ match = numberRE.match(S)
+ if not match or (match.end() != len(S)):
+ return False
+ else:
+ return True
+
+def convStringToNum(theString):
+
+ """Convert a string representation of a Python number to the Python version"""
+
+ if not stringIsNumber(theString):
+ raise Error(theString + ' cannot be converted to a Python number')
+ return eval(theString, {}, {})
+
+
+
+######
+
+ident = r'[_a-zA-Z][_a-zA-Z0-9]*'
+firstChunk = r'^(?P<indent>\s*)(?P<class>[_a-zA-Z][_a-zA-Z0-9]*)'
+customClassRe = re.compile(firstChunk + r'\s*:')
+baseClasses = r'(?P<bases>\(\s*([_a-zA-Z][_a-zA-Z0-9]*\s*(,\s*[_a-zA-Z][_a-zA-Z0-9]*\s*)*)\))'
+customClassWithBasesRe = re.compile(firstChunk + baseClasses + '\s*:')
+
+def translateClassBasedConfigSyntax(src):
+
+ """Compiles a config file in the custom class-based SettingsContainer syntax
+ to Vanilla Python
+
+ # WebKit.config
+ Applications:
+ MyApp:
+ Dirs:
+ ROOT = '/home/www/Home'
+ Products = '/home/www/Products'
+ becomes:
+ # WebKit.config
+ from Cheetah.SettingsManager import SettingsContainer
+ class Applications(SettingsContainer):
+ class MyApp(SettingsContainer):
+ class Dirs(SettingsContainer):
+ ROOT = '/home/www/Home'
+ Products = '/home/www/Products'
+ """
+
+ outputLines = []
+ for line in src.splitlines():
+ if customClassRe.match(line) and \
+ line.strip().split(':')[0] not in ('else','try', 'except', 'finally'):
+
+ line = customClassRe.sub(
+ r'\g<indent>class \g<class>(SettingsContainer):', line)
+
+ elif customClassWithBasesRe.match(line) and not line.strip().startswith('except'):
+ line = customClassWithBasesRe.sub(
+ r'\g<indent>class \g<class>\g<bases>:', line)
+
+ outputLines.append(line)
+
+ ## prepend this to the first line to make sure that tracebacks report the right line nums
+ if outputLines[0].find('class ') == -1:
+ initLine = 'from Cheetah.SettingsManager import SettingsContainer; True, False = 1, 0; '
+ else:
+ initLine = 'from Cheetah.SettingsManager import SettingsContainer; True, False = 1, 0\n'
+ return initLine + '\n'.join(outputLines) + '\n'
+
+
+##################################################
+## CLASSES ##
+
+class Error(BaseErrorClass):
+ pass
+
+class NoDefault:
+ pass
+
+class ConfigParserCaseSensitive(ConfigParser):
+
+ """A case sensitive version of the standard Python ConfigParser."""
+
+ def optionxform(self, optionstr):
+
+ """Don't change the case as is done in the default implemenation."""
+
+ return optionstr
+
+class SettingsContainer:
+ """An abstract base class for 'classes' that are used to house settings."""
+ pass
+
+
+class _SettingsCollector:
+
+ """An abstract base class that provides the methods SettingsManager uses to
+ collect settings from config files and SettingsContainers.
+
+ This class only collects settings it doesn't modify the _settings dictionary
+ of SettingsManager instances in any way.
+
+ SettingsCollector is designed to:
+ - be able to read settings from Python src files (or strings) so that
+ complex Python objects can be stored in the application's settings
+ dictionary. For example, you might want to store references to various
+ classes that are used by the application and plugins to the application
+ might want to substitute one class for another.
+ - be able to read/write .ini style config files (or strings)
+ - allow sections in .ini config files to be extended by settings in Python
+ src files
+ - allow python literals to be used values in .ini config files
+ - maintain the case of setting names, unlike the ConfigParser module
+
+ """
+
+ _sysPathLock = Lock() # used by the updateSettingsFromPySrcFile() method
+ _ConfigParserClass = ConfigParserCaseSensitive
+
+
+ def __init__(self):
+ pass
+
+ def normalizePath(self, path):
+
+ """A hook for any neccessary path manipulations.
+
+ For example, when this is used with WebKit servlets all relative paths
+ must be converted so they are relative to the servlet's directory rather
+ than relative to the program's current working dir.
+
+ The default implementation just normalizes the path for the current
+ operating system."""
+
+ return os.path.normpath(path.replace("\\",'/'))
+
+
+ def readSettingsFromContainer(self, container, ignoreUnderscored=True):
+
+ """Returns all settings from a SettingsContainer or Python
+ module.
+
+ This method is recursive.
+ """
+
+ S = {}
+ if type(container) == ModuleType:
+ attrs = vars(container)
+ else:
+ attrs = self._getAllAttrsFromContainer(container)
+
+ for k, v in attrs.items():
+ if (ignoreUnderscored and k.startswith('_')) or v is SettingsContainer:
+ continue
+ if self._isContainer(v):
+ S[k] = self.readSettingsFromContainer(v)
+ else:
+ S[k] = v
+ return S
+
+ # provide an alias
+ readSettingsFromModule = readSettingsFromContainer
+
+ def _isContainer(self, thing):
+
+ """Check if 'thing' is a Python module or a subclass of
+ SettingsContainer."""
+
+ return type(thing) == ModuleType or (
+ type(thing) == ClassType and issubclass(thing, SettingsContainer)
+ )
+
+ def _getAllAttrsFromContainer(self, container):
+ """Extract all the attributes of a SettingsContainer subclass.
+
+ The 'container' is a class, so extracting all attributes from it, an
+ instance of it, and all its base classes.
+
+ This method is not recursive.
+ """
+
+ attrs = container.__dict__.copy()
+ # init an instance of the container and get all attributes
+ attrs.update( container().__dict__ )
+
+ for base in container.__bases__:
+ for k, v in base.__dict__.items():
+ if not attrs.has_key(k):
+ attrs[k] = v
+ return attrs
+
+ def readSettingsFromPySrcFile(self, path):
+
+ """Return new settings dict from variables in a Python source file.
+
+ This method will temporarily add the directory of src file to sys.path so
+ that import statements relative to that dir will work properly."""
+
+ path = self.normalizePath(path)
+ dirName = os.path.dirname(path)
+ tmpPath = tempfile.mkstemp('webware_temp')
+
+ pySrc = translateClassBasedConfigSyntax(open(path).read())
+ modName = path.replace('.','_').replace('/','_').replace('\\','_')
+ open(tmpPath, 'w').write(pySrc)
+ try:
+ fp = open(tmpPath)
+ self._sysPathLock.acquire()
+ sys.path.insert(0, dirName)
+ module = imp.load_source(modName, path, fp)
+ newSettings = self.readSettingsFromModule(module)
+ del sys.path[0]
+ self._sysPathLock.release()
+ return newSettings
+ finally:
+ fp.close()
+ try:
+ os.remove(tmpPath)
+ except:
+ pass
+ if os.path.exists(tmpPath + 'c'):
+ try:
+ os.remove(tmpPath + 'c')
+ except:
+ pass
+ if os.path.exists(path + 'c'):
+ try:
+ os.remove(path + 'c')
+ except:
+ pass
+
+
+ def readSettingsFromPySrcStr(self, theString):
+
+ """Return a dictionary of the settings in a Python src string."""
+
+ globalsDict = {'True':1,
+ 'False':0,
+ 'SettingsContainer':SettingsContainer,
+ }
+ newSettings = {'self':self}
+ exec theString in globalsDict, newSettings
+ del newSettings['self'], newSettings['True'], newSettings['False']
+ module = new.module('temp_settings_module')
+ module.__dict__.update(newSettings)
+ return self.readSettingsFromModule(module)
+
+ def readSettingsFromConfigFile(self, path, convert=True):
+ path = self.normalizePath(path)
+ fp = open(path)
+ settings = self.readSettingsFromConfigFileObj(fp, convert=convert)
+ fp.close()
+ return settings
+
+ def readSettingsFromConfigFileObj(self, inFile, convert=True):
+
+ """Return the settings from a config file that uses the syntax accepted by
+ Python's standard ConfigParser module (like Windows .ini files).
+
+ NOTE:
+ this method maintains case unlike the ConfigParser module, unless this
+ class was initialized with the 'caseSensitive' keyword set to False.
+
+ All setting values are initially parsed as strings. However, If the
+ 'convert' arg is True this method will do the following value
+ conversions:
+
+ * all Python numeric literals will be coverted from string to number
+
+ * The string 'None' will be converted to the Python value None
+
+ * The string 'True' will be converted to a Python truth value
+
+ * The string 'False' will be converted to a Python false value
+
+ * Any string starting with 'python:' will be treated as a Python literal
+ or expression that needs to be eval'd. This approach is useful for
+ declaring lists and dictionaries.
+
+ If a config section titled 'Globals' is present the options defined
+ under it will be treated as top-level settings.
+ """
+
+ p = self._ConfigParserClass()
+ p.readfp(inFile)
+ sects = p.sections()
+ newSettings = {}
+
+ sects = p.sections()
+ newSettings = {}
+
+ for s in sects:
+ newSettings[s] = {}
+ for o in p.options(s):
+ if o != '__name__':
+ newSettings[s][o] = p.get(s,o)
+
+ ## loop through new settings -> deal with global settings, numbers,
+ ## booleans and None ++ also deal with 'importSettings' commands
+
+ for sect, subDict in newSettings.items():
+ for key, val in subDict.items():
+ if convert:
+ if val.lower().startswith('python:'):
+ subDict[key] = eval(val[7:],{},{})
+ if val.lower() == 'none':
+ subDict[key] = None
+ if val.lower() == 'true':
+ subDict[key] = True
+ if val.lower() == 'false':
+ subDict[key] = False
+ if stringIsNumber(val):
+ subDict[key] = convStringToNum(val)
+
+ ## now deal with any 'importSettings' commands
+ if key.lower() == 'importsettings':
+ if val.find(';') < 0:
+ importedSettings = self.readSettingsFromPySrcFile(val)
+ else:
+ path = val.split(';')[0]
+ rest = ''.join(val.split(';')[1:]).strip()
+ parentDict = self.readSettingsFromPySrcFile(path)
+ importedSettings = eval('parentDict["' + rest + '"]')
+
+ subDict.update(mergeNestedDictionaries(subDict,
+ importedSettings))
+
+ if sect.lower() == 'globals':
+ newSettings.update(newSettings[sect])
+ del newSettings[sect]
+
+ return newSettings
+
+
+class SettingsManager(_SettingsCollector):
+
+ """A mixin class that provides facilities for managing application settings.
+
+ SettingsManager is designed to work well with nested settings dictionaries
+ of any depth.
+ """
+
+ ## init methods
+
+ def __init__(self):
+ """MUST BE CALLED BY SUBCLASSES"""
+ _SettingsCollector.__init__(self)
+ self._settings = {}
+ self._initializeSettings()
+
+ def _defaultSettings(self):
+ return {}
+
+ def _initializeSettings(self):
+
+ """A hook that allows for complex setting initialization sequences that
+ involve references to 'self' or other settings. For example:
+ self._settings['myCalcVal'] = self._settings['someVal'] * 15
+ This method should be called by the class' __init__() method when needed.
+ The dummy implementation should be reimplemented by subclasses.
+ """
+
+ pass
+
+ ## core post startup methods
+
+ def setting(self, name, default=NoDefault):
+
+ """Get a setting from self._settings, with or without a default value."""
+
+ if default is NoDefault:
+ return self._settings[name]
+ else:
+ return self._settings.get(name, default)
+
+
+ def hasSetting(self, key):
+ """True/False"""
+ return self._settings.has_key(key)
+
+ def setSetting(self, name, value):
+ """Set a setting in self._settings."""
+ self._settings[name] = value
+
+ def settings(self):
+ """Return a reference to the settings dictionary"""
+ return self._settings
+
+ def copySettings(self):
+ """Returns a shallow copy of the settings dictionary"""
+ return copy(self._settings)
+
+ def deepcopySettings(self):
+ """Returns a deep copy of the settings dictionary"""
+ return deepcopy(self._settings)
+
+ def updateSettings(self, newSettings, merge=True):
+
+ """Update the settings with a selective merge or a complete overwrite."""
+
+ if merge:
+ mergeNestedDictionaries(self._settings, newSettings)
+ else:
+ self._settings.update(newSettings)
+
+
+
+
+ ## source specific update methods
+
+ def updateSettingsFromPySrcStr(self, theString, merge=True):
+
+ """Update the settings from a code in a Python src string."""
+
+ newSettings = self.readSettingsFromPySrcStr(theString)
+ self.updateSettings(newSettings,
+ merge=newSettings.get('mergeSettings',merge) )
+
+ def updateSettingsFromPySrcFile(self, path, merge=True):
+
+ """Update the settings from variables in a Python source file.
+
+ This method will temporarily add the directory of src file to sys.path so
+ that import statements relative to that dir will work properly."""
+
+ newSettings = self.readSettingsFromPySrcFile(path)
+ self.updateSettings(newSettings,
+ merge=newSettings.get('mergeSettings',merge) )
+
+
+ def updateSettingsFromConfigFile(self, path, **kw):
+
+ """Update the settings from a text file using the syntax accepted by
+ Python's standard ConfigParser module (like Windows .ini files).
+ """
+
+ path = self.normalizePath(path)
+ fp = open(path)
+ self.updateSettingsFromConfigFileObj(fp, **kw)
+ fp.close()
+
+
+ def updateSettingsFromConfigFileObj(self, inFile, convert=True, merge=True):
+
+ """See the docstring for .updateSettingsFromConfigFile()
+
+ The caller of this method is responsible for closing the inFile file
+ object."""
+
+ newSettings = self.readSettingsFromConfigFileObj(inFile, convert=convert)
+ self.updateSettings(newSettings,
+ merge=newSettings.get('mergeSettings',merge))
+
+ def updateSettingsFromConfigStr(self, configStr, convert=True, merge=True):
+
+ """See the docstring for .updateSettingsFromConfigFile()
+ """
+
+ configStr = '[globals]\n' + configStr
+ inFile = StringIO(configStr)
+ newSettings = self.readSettingsFromConfigFileObj(inFile, convert=convert)
+ self.updateSettings(newSettings,
+ merge=newSettings.get('mergeSettings',merge))
+
+
+ ## methods for output representations of the settings
+
+ def _createConfigFile(self, outFile=None):
+
+ """
+ Write all the settings that can be represented as strings to an .ini
+ style config string.
+
+ This method can only handle one level of nesting and will only work with
+ numbers, strings, and None.
+ """
+
+ if outFile is None:
+ outFile = StringIO()
+ iniSettings = {'Globals':{}}
+ globals = iniSettings['Globals']
+
+ for key, theSetting in self.settings().items():
+ if type(theSetting) in convertableToStrTypes:
+ globals[key] = theSetting
+ if type(theSetting) is DictType:
+ iniSettings[key] = {}
+ for subKey, subSetting in theSetting.items():
+ if type(subSetting) in convertableToStrTypes:
+ iniSettings[key][subKey] = subSetting
+
+ sections = iniSettings.keys()
+ sections.sort()
+ outFileWrite = outFile.write # short-cut namebinding for efficiency
+ for section in sections:
+ outFileWrite("[" + section + "]\n")
+ sectDict = iniSettings[section]
+
+ keys = sectDict.keys()
+ keys.sort()
+ for key in keys:
+ if key == "__name__":
+ continue
+ outFileWrite("%s = %s\n" % (key, sectDict[key]))
+ outFileWrite("\n")
+
+ return outFile
+
+ def writeConfigFile(self, path):
+
+ """Write all the settings that can be represented as strings to an .ini
+ style config file."""
+
+ path = self.normalizePath(path)
+ fp = open(path,'w')
+ self._createConfigFile(fp)
+ fp.close()
+
+ def getConfigString(self):
+ """Return a string with the settings in .ini file format."""
+
+ return self._createConfigFile().getvalue()
+
+# vim: shiftwidth=4 tabstop=4 expandtab
diff --git a/cobbler/Cheetah/SourceReader.py b/cobbler/Cheetah/SourceReader.py
new file mode 100644
index 0000000..e91c200
--- /dev/null
+++ b/cobbler/Cheetah/SourceReader.py
@@ -0,0 +1,305 @@
+#!/usr/bin/env python
+# $Id: SourceReader.py,v 1.14 2006/01/18 03:16:59 tavis_rudd Exp $
+"""SourceReader class for Cheetah's Parser and CodeGenerator
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com>
+License: This software is released for unlimited distribution under the
+ terms of the MIT license. See the LICENSE file.
+Version: $Revision: 1.14 $
+Start Date: 2001/09/19
+Last Revision Date: $Date: 2006/01/18 03:16:59 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.14 $"[11:-2]
+
+import re
+import sys
+
+EOLre = re.compile(r'[ \f\t]*(?:\r\n|\r|\n)')
+EOLZre = re.compile(r'(?:\r\n|\r|\n|\Z)')
+ENCODINGsearch = re.compile("coding[=:]\s*([-\w.]+)").search
+
+class Error(Exception):
+ pass
+
+class SourceReader:
+ def __init__(self, src, filename=None, breakPoint=None, encoding=None):
+
+ ## @@TR 2005-01-17: the following comes from a patch Terrel Shumway
+ ## contributed to add unicode support to the reading of Cheetah source
+ ## files with dynamically compiled templates. All the existing unit
+ ## tests pass but, it needs more testing and some test cases of its
+ ## own. My instinct is to move this up into the code that passes in the
+ ## src string rather than leaving it here. As implemented here it
+ ## forces all src strings to unicode, which IMO is not what we want.
+ # if encoding is None:
+ # # peek at the encoding in the first two lines
+ # m = EOLZre.search(src)
+ # pos = m.end()
+ # if pos<len(src):
+ # m = EOLZre.search(src,pos)
+ # pos = m.end()
+ # m = ENCODINGsearch(src,0,pos)
+ # if m:
+ # encoding = m.group(1)
+ # else:
+ # encoding = sys.getfilesystemencoding()
+ # self._encoding = encoding
+ # if type(src) is not unicode:
+ # src = src.decode(encoding)
+ ## end of Terrel's patch
+
+ self._src = src
+ self._filename = filename
+
+ self._srcLen = len(src)
+ if breakPoint == None:
+ self._breakPoint = self._srcLen
+ else:
+ self.setBreakPoint(breakPoint)
+ self._pos = 0
+ self._bookmarks = {}
+ self._posTobookmarkMap = {}
+
+ ## collect some meta-information
+ self._EOLs = []
+ pos = 0
+ while pos < len(self):
+ EOLmatch = EOLZre.search(src, pos)
+ self._EOLs.append(EOLmatch.start())
+ pos = EOLmatch.end()
+
+ self._BOLs = []
+ for pos in self._EOLs:
+ BOLpos = self.findBOL(pos)
+ self._BOLs.append(BOLpos)
+
+ def src(self):
+ return self._src
+
+ def filename(self):
+ return self._filename
+
+ def __len__(self):
+ return self._breakPoint
+
+ def __getitem__(self, i):
+ self.checkPos(i)
+ return self._src[i]
+
+ def __getslice__(self, i, j):
+ i = max(i, 0); j = max(j, 0)
+ return self._src[i:j]
+
+ def splitlines(self):
+ if not hasattr(self, '_srcLines'):
+ self._srcLines = self._src.splitlines()
+ return self._srcLines
+
+ def lineNum(self, pos=None):
+ if pos == None:
+ pos = self._pos
+
+ for i in range(len(self._BOLs)):
+ if pos >= self._BOLs[i] and pos <= self._EOLs[i]:
+ return i
+
+ def getRowCol(self, pos=None):
+ if pos == None:
+ pos = self._pos
+ lineNum = self.lineNum(pos)
+ BOL, EOL = self._BOLs[lineNum], self._EOLs[lineNum]
+ return lineNum+1, pos-BOL+1
+
+ def getRowColLine(self, pos=None):
+ if pos == None:
+ pos = self._pos
+ row, col = self.getRowCol(pos)
+ return row, col, self.splitlines()[row-1]
+
+ def getLine(self, pos):
+ if pos == None:
+ pos = self._pos
+ lineNum = self.lineNum(pos)
+ return self.splitlines()[lineNum]
+
+ def pos(self):
+ return self._pos
+
+ def setPos(self, pos):
+ self.checkPos(pos)
+ self._pos = pos
+
+
+ def validPos(self, pos):
+ return pos <= self._breakPoint and pos >=0
+
+ def checkPos(self, pos):
+ if not pos <= self._breakPoint:
+ raise Error("pos (" + str(pos) + ") is invalid: beyond the stream's end (" +
+ str(self._breakPoint-1) + ")" )
+ elif not pos >=0:
+ raise Error("pos (" + str(pos) + ") is invalid: less than 0" )
+
+ def breakPoint(self):
+ return self._breakPoint
+
+ def setBreakPoint(self, pos):
+ if pos > self._srcLen:
+ raise Error("New breakpoint (" + str(pos) +
+ ") is invalid: beyond the end of stream's source string (" +
+ str(self._srcLen) + ")" )
+ elif not pos >= 0:
+ raise Error("New breakpoint (" + str(pos) + ") is invalid: less than 0" )
+
+ self._breakPoint = pos
+
+ def setBookmark(self, name):
+ self._bookmarks[name] = self._pos
+ self._posTobookmarkMap[self._pos] = name
+
+ def hasBookmark(self, name):
+ return self._bookmarks.has_key(name)
+
+ def gotoBookmark(self, name):
+ if not self.hasBookmark(name):
+ raise Error("Invalid bookmark (" + name + ', '+
+ str(pos) + ") is invalid: does not exist" )
+ pos = self._bookmarks[name]
+ if not self.validPos(pos):
+ raise Error("Invalid bookmark (" + name + ', '+
+ str(pos) + ") is invalid: pos is out of range" )
+ self._pos = pos
+
+ def atEnd(self):
+ return self._pos >= self._breakPoint
+
+ def atStart(self):
+ return self._pos == 0
+
+ def peek(self, offset=0):
+ self.checkPos(self._pos+offset)
+ pos = self._pos + offset
+ return self._src[pos]
+
+ def getc(self):
+ pos = self._pos
+ if self.validPos(pos+1):
+ self._pos += 1
+ return self._src[pos]
+
+ def ungetc(self, c=None):
+ if not self.atStart():
+ raise Error('Already at beginning of stream')
+
+ self._pos -= 1
+ if not c==None:
+ self._src[self._pos] = c
+
+ def advance(self, offset=1):
+ self.checkPos(self._pos + offset)
+ self._pos += offset
+
+ def rev(self, offset=1):
+ self.checkPos(self._pos - offset)
+ self._pos -= offset
+
+ def read(self, offset):
+ self.checkPos(self._pos + offset)
+ start = self._pos
+ self._pos += offset
+ return self._src[start:self._pos]
+
+ def readTo(self, to, start=None):
+ self.checkPos(to)
+ if start == None:
+ start = self._pos
+ self._pos = to
+ return self._src[start:to]
+
+
+ def readToEOL(self, start=None, gobble=True):
+ EOLmatch = EOLZre.search(self.src(), self.pos())
+ if gobble:
+ pos = EOLmatch.end()
+ else:
+ pos = EOLmatch.start()
+ return self.readTo(to=pos, start=start)
+
+
+ def find(self, it, pos=None):
+ if pos == None:
+ pos = self._pos
+ return self._src.find(it, pos )
+
+ def startswith(self, it, pos=None):
+ if self.find(it, pos) == self.pos():
+ return True
+ else:
+ return False
+
+ def rfind(self, it, pos):
+ if pos == None:
+ pos = self._pos
+ return self._src.rfind(it, pos)
+
+ def findBOL(self, pos=None):
+ if pos == None:
+ pos = self._pos
+ src = self.src()
+ return max(src.rfind('\n',0,pos)+1, src.rfind('\r',0,pos)+1, 0)
+
+ def findEOL(self, pos=None, gobble=False):
+ if pos == None:
+ pos = self._pos
+
+ match = EOLZre.search(self.src(), pos)
+ if gobble:
+ return match.end()
+ else:
+ return match.start()
+
+ def isLineClearToPos(self, pos=None):
+ if pos == None:
+ pos = self.pos()
+ self.checkPos(pos)
+ src = self.src()
+ BOL = self.findBOL()
+ return BOL == pos or src[BOL:pos].isspace()
+
+ def matches(self, strOrRE):
+ if isinstance(strOrRE, (str, unicode)):
+ return self.startswith(strOrRE, pos=self.pos())
+ else: # assume an re object
+ return strOrRE.match(self.src(), self.pos())
+
+ def matchWhiteSpace(self, WSchars=' \f\t'):
+ return (not self.atEnd()) and self.peek() in WSchars
+
+ def getWhiteSpace(self, max=None, WSchars=' \f\t'):
+ if not self.matchWhiteSpace(WSchars):
+ return ''
+ start = self.pos()
+ breakPoint = self.breakPoint()
+ if max is not None:
+ breakPoint = min(breakPoint, self.pos()+max)
+ while self.pos() < breakPoint:
+ self.advance()
+ if not self.matchWhiteSpace(WSchars):
+ break
+ return self.src()[start:self.pos()]
+
+ def matchNonWhiteSpace(self, WSchars=' \f\t\n\r'):
+ return self.atEnd() or not self.peek() in WSchars
+
+ def getNonWhiteSpace(self, WSchars=' \f\t\n\r'):
+ if not self.matchNonWhiteSpace(WSchars):
+ return ''
+ start = self.pos()
+ while self.pos() < self.breakPoint():
+ self.advance()
+ if not self.matchNonWhiteSpace(WSchars):
+ break
+ return self.src()[start:self.pos()]
diff --git a/cobbler/Cheetah/Template.py b/cobbler/Cheetah/Template.py
new file mode 100644
index 0000000..0185b50
--- /dev/null
+++ b/cobbler/Cheetah/Template.py
@@ -0,0 +1,1858 @@
+#!/usr/bin/env python
+# $Id: Template.py,v 1.181 2006/06/22 20:25:16 hierro Exp $
+"""Provides the core API for Cheetah.
+
+See the docstring in the Template class and the Users' Guide for more information
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com>
+License: This software is released for unlimited distribution under the
+ terms of the MIT license. See the LICENSE file.
+Version: $Revision: 1.181 $
+Start Date: 2001/03/30
+Last Revision Date: $Date: 2006/06/22 20:25:16 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.181 $"[11:-2]
+
+################################################################################
+## DEPENDENCIES
+import sys # used in the error handling code
+import re # used to define the internal delims regex
+import new # used to bind methods and create dummy modules
+import string
+import os.path
+import time # used in the cache refresh code
+from random import randrange
+import imp
+import inspect
+import StringIO
+import traceback
+import pprint
+import cgi # Used by .webInput() if the template is a CGI script.
+import types
+from types import StringType, ClassType
+try:
+ from types import StringTypes
+except ImportError:
+ StringTypes = (types.StringType,types.UnicodeType)
+try:
+ from types import BooleanType
+ boolTypeAvailable = True
+except ImportError:
+ boolTypeAvailable = False
+
+try:
+ from threading import Lock
+except ImportError:
+ class Lock:
+ def acquire(self): pass
+ def release(self): pass
+
+from Cheetah.Version import convertVersionStringToTuple, MinCompatibleVersionTuple
+from Cheetah.Version import MinCompatibleVersion
+# Base classes for Template
+from Cheetah.Servlet import Servlet
+# More intra-package imports ...
+from Cheetah.Parser import ParseError, SourceReader
+from Cheetah.Compiler import Compiler, DEFAULT_COMPILER_SETTINGS
+from Cheetah import ErrorCatchers # for placeholder tags
+from Cheetah import Filters # the output filters
+from Cheetah.convertTmplPathToModuleName import convertTmplPathToModuleName
+from Cheetah.Utils import VerifyType # Used in Template.__init__
+from Cheetah.Utils.Misc import checkKeywords # Used in Template.__init__
+from Cheetah.Utils.Indenter import Indenter # Used in Template.__init__ and for
+ # placeholders
+from Cheetah.NameMapper import NotFound, valueFromSearchList
+from Cheetah.CacheStore import MemoryCacheStore, MemcachedCacheStore
+from Cheetah.CacheRegion import CacheRegion
+from Cheetah.Utils.WebInputMixin import _Converter, _lookup, NonNumericInputError
+
+from Cheetah.Unspecified import Unspecified
+
+class Error(Exception): pass
+class PreprocessError(Error): pass
+
+def hashList(l):
+ hashedList = []
+ for v in l:
+ if isinstance(v, dict):
+ v = hashDict(v)
+ elif isinstance(v, list):
+ v = hashList(v)
+ hashedList.append(v)
+ return hash(tuple(hashedList))
+
+def hashDict(d):
+ items = d.items()
+ items.sort()
+ hashedList = []
+ for k, v in items:
+ if isinstance(v, dict):
+ v = hashDict(v)
+ elif isinstance(v, list):
+ v = hashList(v)
+ hashedList.append((k,v))
+ return hash(tuple(hashedList))
+
+################################################################################
+## MODULE GLOBALS AND CONSTANTS
+
+def _genUniqueModuleName(baseModuleName):
+ """The calling code is responsible for concurrency locking.
+ """
+ if baseModuleName not in sys.modules:
+ finalName = baseModuleName
+ else:
+ finalName = ('cheetah_'+baseModuleName
+ +'_'
+ +''.join(map(lambda x: '%02d' % x, time.localtime(time.time())[:6]))
+ + str(randrange(10000, 99999)))
+ return finalName
+
+# Cache of a cgi.FieldStorage() instance, maintained by .webInput().
+# This is only relavent to templates used as CGI scripts.
+_formUsedByWebInput = None
+
+# used in Template.compile()
+def valOrDefault(val, default):
+ if val is not Unspecified: return val
+ else: return default
+
+
+class CompileCacheItem:
+ pass
+
+class TemplatePreprocessor:
+ """This is used with the preprocessors argument to Template.compile().
+
+ See the docstring for Template.compile
+
+ ** Preprocessors are an advanced topic **
+ """
+ def __init__(self, settings):
+ self._settings = settings
+
+ def preprocess(self, source, file):
+ """Create an intermediate template and return the source code
+ it outputs
+ """
+ settings = self._settings
+ if not source: # @@TR: this needs improving
+ if isinstance(file, (str, unicode)): # it's a filename.
+ f = open(file)
+ source = f.read()
+ f.close()
+ elif hasattr(file, 'read'):
+ source = file.read()
+ file = None
+
+ templateAPIClass = settings.templateAPIClass
+ possibleKwArgs = [
+ arg for arg in
+ inspect.getargs(templateAPIClass.compile.im_func.func_code)[0]
+ if arg not in ('klass', 'source', 'file',)]
+
+ compileKwArgs = {}
+ for arg in possibleKwArgs:
+ if hasattr(settings, arg):
+ compileKwArgs[arg] = getattr(settings, arg)
+
+ tmplClass = templateAPIClass.compile(source=source, file=file, **compileKwArgs)
+ tmplInstance = tmplClass(**settings.templateInitArgs)
+ outputSource = settings.outputTransformer(tmplInstance)
+ outputFile = None
+ return outputSource, outputFile
+
+class Template(Servlet):
+ """This class provides a) methods used by templates at runtime and b)
+ methods for compiling Cheetah source code into template classes.
+
+ This documentation assumes you already know Python and the basics of object
+ oriented programming. If you don't know Python, see the sections of the
+ Cheetah Users' Guide for non-programmers. It also assumes you have read
+ about Cheetah's syntax in the Users' Guide.
+
+ The following explains how to use Cheetah from within Python programs or via
+ the interpreter. If you statically compile your templates on the command
+ line using the 'cheetah' script, this is not relevant to you. Statically
+ compiled Cheetah template modules/classes (e.g. myTemplate.py:
+ MyTemplateClasss) are just like any other Python module or class. Also note,
+ most Python web frameworks (Webware, Aquarium, mod_python, Turbogears,
+ CherryPy, Quixote, etc.) provide plugins that handle Cheetah compilation for
+ you.
+
+ There are several possible usage patterns:
+ 1) tclass = Template.compile(src)
+ t1 = tclass() # or tclass(namespaces=[namespace,...])
+ t2 = tclass() # or tclass(namespaces=[namespace2,...])
+ outputStr = str(t1) # or outputStr = t1.aMethodYouDefined()
+
+ Template.compile provides a rich and very flexible API via its
+ optional arguments so there are many possible variations of this
+ pattern. One example is:
+ tclass = Template.compile('hello $name from $caller', baseclass=dict)
+ print tclass(name='world', caller='me')
+ See the Template.compile() docstring for more details.
+
+ 2) tmplInstance = Template(src)
+ # or Template(src, namespaces=[namespace,...])
+ outputStr = str(tmplInstance) # or outputStr = tmplInstance.aMethodYouDefined(...args...)
+
+ Notes on the usage patterns:
+
+ usage pattern 1)
+ This is the most flexible, but it is slightly more verbose unless you
+ write a wrapper function to hide the plumbing. Under the hood, all
+ other usage patterns are based on this approach. Templates compiled
+ this way can #extend (subclass) any Python baseclass: old-style or
+ new-style (based on object or a builtin type).
+
+ usage pattern 2)
+ This was Cheetah's original usage pattern. It returns an instance,
+ but you can still access the generated class via
+ tmplInstance.__class__. If you want to use several different
+ namespace 'searchLists' with a single template source definition,
+ you're better off with Template.compile (1).
+
+ Limitations (use pattern 1 instead):
+ - Templates compiled this way can only #extend subclasses of the
+ new-style 'object' baseclass. Cheetah.Template is a subclass of
+ 'object'. You also can not #extend dict, list, or other builtin
+ types.
+ - If your template baseclass' __init__ constructor expects args there
+ is currently no way to pass them in.
+
+ If you need to subclass a dynamically compiled Cheetah class, do something like this:
+ from Cheetah.Template import Template
+ T1 = Template.compile('$meth1 #def meth1: this is meth1 in T1')
+ T2 = Template.compile('#implements meth1\nthis is meth1 redefined in T2', baseclass=T1)
+ print T1, T1()
+ print T2, T2()
+
+
+ Note about class and instance attribute names:
+ Attributes used by Cheetah have a special prefix to avoid confusion with
+ the attributes of the templates themselves or those of template
+ baseclasses.
+
+ Class attributes which are used in class methods look like this:
+ klass._CHEETAH_useCompilationCache (_CHEETAH_xxx)
+
+ Instance attributes look like this:
+ klass._CHEETAH__globalSetVars (_CHEETAH__xxx with 2 underscores)
+ """
+
+ # this is used by ._addCheetahPlumbingCodeToClass()
+ _CHEETAH_requiredCheetahMethods = (
+ '_initCheetahInstance',
+ 'searchList',
+ 'errorCatcher',
+ 'getVar',
+ 'varExists',
+ 'getFileContents',
+ 'i18n',
+ 'runAsMainProgram',
+ 'respond',
+ 'shutdown',
+ 'webInput',
+ 'serverSidePath',
+ 'generatedClassCode',
+ 'generatedModuleCode',
+
+ '_getCacheStore',
+ '_getCacheStoreIdPrefix',
+ '_createCacheRegion',
+ 'getCacheRegion',
+ 'getCacheRegions',
+ 'refreshCache',
+
+ '_handleCheetahInclude',
+ '_getTemplateAPIClassForIncludeDirectiveCompilation',
+ )
+ _CHEETAH_requiredCheetahClassMethods = ('subclass',)
+ _CHEETAH_requiredCheetahClassAttributes = ('cacheRegionClass','cacheStore',
+ 'cacheStoreIdPrefix','cacheStoreClass')
+
+ ## the following are used by .compile(). Most are documented in its docstring.
+ _CHEETAH_cacheModuleFilesForTracebacks = False
+ _CHEETAH_cacheDirForModuleFiles = None # change to a dirname
+
+ _CHEETAH_compileCache = dict() # cache store for compiled code and classes
+ # To do something other than simple in-memory caching you can create an
+ # alternative cache store. It just needs to support the basics of Python's
+ # mapping/dict protocol. E.g.:
+ # class AdvCachingTemplate(Template):
+ # _CHEETAH_compileCache = MemoryOrFileCache()
+ _CHEETAH_compileLock = Lock() # used to prevent race conditions
+ _CHEETAH_defaultMainMethodName = None
+ _CHEETAH_compilerSettings = None
+ _CHEETAH_compilerClass = Compiler
+ _CHEETAH_cacheCompilationResults = True
+ _CHEETAH_useCompilationCache = True
+ _CHEETAH_keepRefToGeneratedCode = True
+ _CHEETAH_defaultBaseclassForTemplates = None
+ _CHEETAH_defaultClassNameForTemplates = None
+ # defaults to DEFAULT_COMPILER_SETTINGS['mainMethodName']:
+ _CHEETAH_defaultMainMethodNameForTemplates = None
+ _CHEETAH_defaultModuleNameForTemplates = 'DynamicallyCompiledCheetahTemplate'
+ _CHEETAH_defaultModuleGlobalsForTemplates = None
+ _CHEETAH_preprocessors = None
+ _CHEETAH_defaultPreprocessorClass = TemplatePreprocessor
+
+ ## The following attributes are used by instance methods:
+ _CHEETAH_generatedModuleCode = None
+ NonNumericInputError = NonNumericInputError
+ _CHEETAH_cacheRegionClass = CacheRegion
+ _CHEETAH_cacheStoreClass = MemoryCacheStore
+ #_CHEETAH_cacheStoreClass = MemcachedCacheStore
+ _CHEETAH_cacheStore = None
+ _CHEETAH_cacheStoreIdPrefix = None
+
+ def _getCompilerClass(klass, source=None, file=None):
+ return klass._CHEETAH_compilerClass
+ _getCompilerClass = classmethod(_getCompilerClass)
+
+ def _getCompilerSettings(klass, source=None, file=None):
+ return klass._CHEETAH_compilerSettings
+ _getCompilerSettings = classmethod(_getCompilerSettings)
+
+ def compile(klass, source=None, file=None,
+ returnAClass=True,
+
+ compilerSettings=Unspecified,
+ compilerClass=Unspecified,
+ moduleName=None,
+ className=Unspecified,
+ mainMethodName=Unspecified,
+ baseclass=Unspecified,
+ moduleGlobals=Unspecified,
+ cacheCompilationResults=Unspecified,
+ useCache=Unspecified,
+ preprocessors=Unspecified,
+ cacheModuleFilesForTracebacks=Unspecified,
+ cacheDirForModuleFiles=Unspecified,
+
+ keepRefToGeneratedCode=Unspecified,
+ ):
+
+ """
+ The core API for compiling Cheetah source code into template classes.
+
+ This class method compiles Cheetah source code and returns a python
+ class. You then create template instances using that class. All
+ Cheetah's other compilation API's use this method under the hood.
+
+ Internally, this method a) parses the Cheetah source code and generates
+ Python code defining a module with a single class in it, b) dynamically
+ creates a module object with a unique name, c) execs the generated code
+ in that module's namespace then inserts the module into sys.modules, and
+ d) returns a reference to the generated class. If you want to get the
+ generated python source code instead, pass the argument
+ returnAClass=False.
+
+ It caches generated code and classes. See the descriptions of the
+ arguments'cacheCompilationResults' and 'useCache' for details. This
+ doesn't mean that templates will automatically recompile themselves when
+ the source file changes. Rather, if you call Template.compile(src) or
+ Template.compile(file=path) repeatedly it will attempt to return a
+ cached class definition instead of recompiling.
+
+ Hooks are provided template source preprocessing. See the notes on the
+ 'preprocessors' arg.
+
+ If you are an advanced user and need to customize the way Cheetah parses
+ source code or outputs Python code, you should check out the
+ compilerSettings argument.
+
+ Arguments:
+ You must provide either a 'source' or 'file' arg, but not both:
+ - source (string or None)
+ - file (string path, file-like object, or None)
+
+ The rest of the arguments are strictly optional. All but the first
+ have defaults in attributes of the Template class which can be
+ overridden in subclasses of this class. Working with most of these is
+ an advanced topic.
+
+ - returnAClass=True
+ If false, return the generated module code rather than a class.
+
+ - compilerSettings (a dict)
+ Default: Template._CHEETAH_compilerSettings=None
+
+ a dictionary of settings to override those defined in
+ DEFAULT_COMPILER_SETTINGS. These can also be overridden in your
+ template source code with the #compiler or #compiler-settings
+ directives.
+
+ - compilerClass (a class)
+ Default: Template._CHEETAH_compilerClass=Cheetah.Compiler.Compiler
+
+ a subclass of Cheetah.Compiler.Compiler. Mucking with this is a
+ very advanced topic.
+
+ - moduleName (a string)
+ Default:
+ Template._CHEETAH_defaultModuleNameForTemplates
+ ='DynamicallyCompiledCheetahTemplate'
+
+ What to name the generated Python module. If the provided value is
+ None and a file arg was given, the moduleName is created from the
+ file path. In all cases if the moduleName provided is already in
+ sys.modules it is passed through a filter that generates a unique
+ variant of the name.
+
+
+ - className (a string)
+ Default: Template._CHEETAH_defaultClassNameForTemplates=None
+
+ What to name the generated Python class. If the provided value is
+ None, the moduleName is use as the class name.
+
+ - mainMethodName (a string)
+ Default:
+ Template._CHEETAH_defaultMainMethodNameForTemplates
+ =None (and thus DEFAULT_COMPILER_SETTINGS['mainMethodName'])
+
+ What to name the main output generating method in the compiled
+ template class.
+
+ - baseclass (a string or a class)
+ Default: Template._CHEETAH_defaultBaseclassForTemplates=None
+
+ Specifies the baseclass for the template without manually
+ including an #extends directive in the source. The #extends
+ directive trumps this arg.
+
+ If the provided value is a string you must make sure that a class
+ reference by that name is available to your template, either by
+ using an #import directive or by providing it in the arg
+ 'moduleGlobals'.
+
+ If the provided value is a class, Cheetah will handle all the
+ details for you.
+
+ - moduleGlobals (a dict)
+ Default: Template._CHEETAH_defaultModuleGlobalsForTemplates=None
+
+ A dict of vars that will be added to the global namespace of the
+ module the generated code is executed in, prior to the execution
+ of that code. This should be Python values, not code strings!
+
+ - cacheCompilationResults (True/False)
+ Default: Template._CHEETAH_cacheCompilationResults=True
+
+ Tells Cheetah to cache the generated code and classes so that they
+ can be reused if Template.compile() is called multiple times with
+ the same source and options.
+
+ - useCache (True/False)
+ Default: Template._CHEETAH_useCompilationCache=True
+
+ Should the compilation cache be used? If True and a previous
+ compilation created a cached template class with the same source
+ code, compiler settings and other options, the cached template
+ class will be returned.
+
+ - cacheModuleFilesForTracebacks (True/False)
+ Default: Template._CHEETAH_cacheModuleFilesForTracebacks=False
+
+ In earlier versions of Cheetah tracebacks from exceptions that
+ were raised inside dynamically compiled Cheetah templates were
+ opaque because Python didn't have access to a python source file
+ to use in the traceback:
+
+ File "xxxx.py", line 192, in getTextiledContent
+ content = str(template(searchList=searchList))
+ File "cheetah_yyyy.py", line 202, in __str__
+ File "cheetah_yyyy.py", line 187, in respond
+ File "cheetah_yyyy.py", line 139, in writeBody
+ ZeroDivisionError: integer division or modulo by zero
+
+ It is now possible to keep those files in a cache dir and allow
+ Python to include the actual source lines in tracebacks and makes
+ them much easier to understand:
+
+ File "xxxx.py", line 192, in getTextiledContent
+ content = str(template(searchList=searchList))
+ File "/tmp/CheetahCacheDir/cheetah_yyyy.py", line 202, in __str__
+ def __str__(self): return self.respond()
+ File "/tmp/CheetahCacheDir/cheetah_yyyy.py", line 187, in respond
+ self.writeBody(trans=trans)
+ File "/tmp/CheetahCacheDir/cheetah_yyyy.py", line 139, in writeBody
+ __v = 0/0 # $(0/0)
+ ZeroDivisionError: integer division or modulo by zero
+
+ - cacheDirForModuleFiles (a string representing a dir path)
+ Default: Template._CHEETAH_cacheDirForModuleFiles=None
+
+ See notes on cacheModuleFilesForTracebacks.
+
+ - preprocessors
+ Default: Template._CHEETAH_preprocessors=None
+
+ ** THIS IS A VERY ADVANCED TOPIC **
+
+ These are used to transform the source code prior to compilation.
+ They provide a way to use Cheetah as a code generator for Cheetah
+ code. In other words, you use one Cheetah template to output the
+ source code for another Cheetah template.
+
+ The major expected use cases are:
+
+ a) 'compile-time caching' aka 'partial template binding',
+ wherein an intermediate Cheetah template is used to output
+ the source for the final Cheetah template. The intermediate
+ template is a mix of a modified Cheetah syntax (the
+ 'preprocess syntax') and standard Cheetah syntax. The
+ preprocessor syntax is executed at compile time and outputs
+ Cheetah code which is then compiled in turn. This approach
+ allows one to completely soft-code all the elements in the
+ template which are subject to change yet have it compile to
+ extremely efficient Python code with everything but the
+ elements that must be variable at runtime (per browser
+ request, etc.) compiled as static strings. Examples of this
+ usage pattern will be added to the Cheetah Users' Guide.
+
+ The'preprocess syntax' is just Cheetah's standard one with
+ alternatives for the $ and # tokens:
+
+ e.g. '@' and '%' for code like this
+ @aPreprocessVar $aRuntimeVar
+ %if aCompileTimeCondition then yyy else zzz
+ %% preprocessor comment
+
+ #if aRunTimeCondition then aaa else bbb
+ ## normal comment
+ $aRuntimeVar
+
+ b) adding #import and #extends directives dynamically based on
+ the source
+
+ If preprocessors are provided, Cheetah pipes the source code
+ through each one in the order provided. Each preprocessor should
+ accept the args (source, file) and should return a tuple (source,
+ file).
+
+ The argument value should be a list, but a single non-list value
+ is acceptable and will automatically be converted into a list.
+ Each item in the list will be passed through
+ Template._normalizePreprocessor(). The items should either match
+ one of the following forms:
+
+ - an object with a .preprocess(source, file) method
+ - a callable with the following signature:
+ source, file = f(source, file)
+
+ or one of the forms below:
+
+ - a single string denoting the 2 'tokens' for the preprocess
+ syntax. The tokens should be in the order (placeholderToken,
+ directiveToken) and should separated with a space:
+ e.g. '@ %'
+ klass = Template.compile(src, preprocessors='@ %')
+ # or
+ klass = Template.compile(src, preprocessors=['@ %'])
+
+ - a dict with the following keys or an object with the
+ following attributes (all are optional, but nothing will
+ happen if you don't provide at least one):
+ - tokens: same as the single string described above. You can
+ also provide a tuple of 2 strings.
+ - searchList: the searchList used for preprocess $placeholders
+ - compilerSettings: used in the compilation of the intermediate
+ template
+ - templateAPIClass: an optional subclass of `Template`
+ - outputTransformer: a simple hook for passing in a callable
+ which can do further transformations of the preprocessor
+ output, or do something else like debug logging. The
+ default is str().
+ + any keyword arguments to Template.compile which you want to
+ provide for the compilation of the intermediate template.
+
+ klass = Template.compile(src,
+ preprocessors=[ dict(tokens='@ %', searchList=[...]) ] )
+
+ """
+ ##################################################
+ ## normalize and validate args
+ try:
+ vt = VerifyType.VerifyType
+ vtc = VerifyType.VerifyTypeClass
+ N = types.NoneType; S = types.StringType; U = types.UnicodeType
+ D = types.DictType; F = types.FileType
+ C = types.ClassType; M = types.ModuleType
+ I = types.IntType
+
+ if boolTypeAvailable:
+ B = types.BooleanType
+
+ vt(source, 'source', [N,S,U], 'string or None')
+ vt(file, 'file',[N,S,U,F], 'string, file-like object, or None')
+
+ baseclass = valOrDefault(baseclass, klass._CHEETAH_defaultBaseclassForTemplates)
+ if isinstance(baseclass, Template):
+ baseclass = baseclass.__class__
+ vt(baseclass, 'baseclass', [N,S,C,type], 'string, class or None')
+
+ cacheCompilationResults = valOrDefault(
+ cacheCompilationResults, klass._CHEETAH_cacheCompilationResults)
+ if boolTypeAvailable:
+ vt(cacheCompilationResults, 'cacheCompilationResults', [I,B], 'boolean')
+
+ useCache = valOrDefault(useCache, klass._CHEETAH_useCompilationCache)
+ if boolTypeAvailable:
+ vt(cacheCompilationResults, 'cacheCompilationResults', [I,B], 'boolean')
+
+ compilerSettings = valOrDefault(
+ compilerSettings, klass._getCompilerSettings(source, file) or {})
+ vt(compilerSettings, 'compilerSettings', [D], 'dictionary')
+
+ compilerClass = valOrDefault(compilerClass, klass._getCompilerClass(source, file))
+
+ preprocessors = valOrDefault(preprocessors, klass._CHEETAH_preprocessors)
+
+ keepRefToGeneratedCode = valOrDefault(
+ keepRefToGeneratedCode, klass._CHEETAH_keepRefToGeneratedCode)
+ if boolTypeAvailable:
+ vt(cacheCompilationResults, 'cacheCompilationResults', [I,B], 'boolean')
+
+ vt(moduleName, 'moduleName', [N,S], 'string or None')
+ __orig_file__ = None
+ if not moduleName:
+ if file and type(file) in StringTypes:
+ moduleName = convertTmplPathToModuleName(file)
+ __orig_file__ = file
+ else:
+ moduleName = klass._CHEETAH_defaultModuleNameForTemplates
+
+ className = valOrDefault(
+ className, klass._CHEETAH_defaultClassNameForTemplates)
+ vt(className, 'className', [N,S], 'string or None')
+ className = className or moduleName
+
+ mainMethodName = valOrDefault(
+ mainMethodName, klass._CHEETAH_defaultMainMethodNameForTemplates)
+ vt(mainMethodName, 'mainMethodName', [N,S], 'string or None')
+
+ moduleGlobals = valOrDefault(
+ moduleGlobals, klass._CHEETAH_defaultModuleGlobalsForTemplates)
+
+ cacheModuleFilesForTracebacks = valOrDefault(
+ cacheModuleFilesForTracebacks, klass._CHEETAH_cacheModuleFilesForTracebacks)
+ if boolTypeAvailable:
+ vt(cacheModuleFilesForTracebacks, 'cacheModuleFilesForTracebacks', [I,B], 'boolean')
+
+ cacheDirForModuleFiles = valOrDefault(
+ cacheDirForModuleFiles, klass._CHEETAH_cacheDirForModuleFiles)
+ vt(cacheDirForModuleFiles, 'cacheDirForModuleFiles', [N,S], 'string or None')
+
+ except TypeError, reason:
+ raise TypeError(reason)
+
+ ##################################################
+ ## handle any preprocessors
+ if preprocessors:
+ origSrc = source
+ source, file = klass._preprocessSource(source, file, preprocessors)
+
+ ##################################################
+ ## compilation, using cache if requested/possible
+ baseclassValue = None
+ baseclassName = None
+ if baseclass:
+ if type(baseclass) in StringTypes:
+ baseclassName = baseclass
+ elif type(baseclass) in (ClassType, type):
+ # @@TR: should soft-code this
+ baseclassName = 'CHEETAH_dynamicallyAssignedBaseClass_'+baseclass.__name__
+ baseclassValue = baseclass
+
+
+ cacheHash = None
+ cacheItem = None
+ if source or isinstance(file, (str, unicode)):
+ compilerSettingsHash = None
+ if compilerSettings:
+ compilerSettingsHash = hashDict(compilerSettings)
+
+ moduleGlobalsHash = None
+ if moduleGlobals:
+ moduleGlobalsHash = hashDict(moduleGlobals)
+
+ fileHash = None
+ if file:
+ fileHash = str(hash(file))+str(os.path.getmtime(file))
+
+ try:
+ cacheHash = ''.join([str(v) for v in
+ [hash(source),
+ fileHash,
+ className,
+ moduleName,
+ mainMethodName,
+ hash(compilerClass),
+ hash(baseclass),
+ compilerSettingsHash,
+ moduleGlobalsHash,
+ hash(cacheDirForModuleFiles),
+ ]])
+ except:
+ #@@TR: should add some logging to this
+ pass
+ if useCache and cacheHash and klass._CHEETAH_compileCache.has_key(cacheHash):
+ cacheItem = klass._CHEETAH_compileCache[cacheHash]
+ generatedModuleCode = cacheItem.code
+ #print 'DEBUG: found cached copy'
+ else:
+ compiler = compilerClass(source, file,
+ moduleName=moduleName,
+ mainClassName=className,
+ baseclassName=baseclassName,
+ mainMethodName=mainMethodName,
+ settings=(compilerSettings or {}))
+ compiler.compile()
+ generatedModuleCode = compiler.getModuleCode()
+
+ if not returnAClass:
+ return generatedModuleCode
+ else:
+ if cacheItem:
+ cacheItem.lastCheckoutTime = time.time()
+ return cacheItem.klass
+
+ def updateLinecache(filename, src):
+ import linecache
+ size = len(src)
+ mtime = time.time()
+ lines = src.splitlines()
+ fullname = filename
+ linecache.cache[filename] = size, mtime, lines, fullname
+
+
+ try:
+ klass._CHEETAH_compileLock.acquire()
+ uniqueModuleName = _genUniqueModuleName(moduleName)
+ __file__ = uniqueModuleName+'.py' # relative file path with no dir part
+ mod = new.module(uniqueModuleName)
+ sys.modules[uniqueModuleName] = mod
+ finally:
+ klass._CHEETAH_compileLock.release()
+
+ try:
+ if cacheModuleFilesForTracebacks:
+ if not os.path.exists(cacheDirForModuleFiles):
+ raise Exception('%s does not exist'%
+ cacheDirForModuleFiles)
+
+ __file__ = os.path.join(cacheDirForModuleFiles, __file__)
+ try:
+ klass._CHEETAH_compileLock.acquire()
+ # @@TR: might want to assert that it doesn't already exist
+ try:
+ open(__file__, 'w').write(generatedModuleCode)
+ # @@TR: should probably restrict the perms, etc.
+ except OSError:
+ # @@ TR: should this optionally raise?
+ traceback.print_exc(file=sys.stderr)
+ finally:
+ klass._CHEETAH_compileLock.release()
+
+ if moduleGlobals:
+ for k, v in moduleGlobals.items():
+ setattr(mod, k, v)
+ mod.__file__ = __file__
+ if __orig_file__ and os.path.exists(__orig_file__):
+ # this is used in the WebKit filemonitoring code
+ mod.__orig_file__ = __orig_file__
+
+ if baseclass and baseclassValue:
+ setattr(mod, baseclassName, baseclassValue)
+
+ try:
+ co = compile(generatedModuleCode, __file__, 'exec')
+ exec co in mod.__dict__
+ except SyntaxError, e:
+ try:
+ parseError = genParserErrorFromPythonException(
+ source, file, generatedModuleCode, exception=e)
+ except:
+ traceback.print_exc()
+ updateLinecache(__file__, generatedModuleCode)
+ e.generatedModuleCode = generatedModuleCode
+ raise e
+ else:
+ raise parseError
+ except Exception, e:
+ updateLinecache(__file__, generatedModuleCode)
+ e.generatedModuleCode = generatedModuleCode
+ raise
+
+ except:
+ del sys.modules[uniqueModuleName]
+ raise
+
+ templateClass = getattr(mod, className)
+
+ if (cacheCompilationResults
+ and cacheHash
+ and not klass._CHEETAH_compileCache.has_key(cacheHash)):
+
+ cacheItem = CompileCacheItem()
+ cacheItem.cacheTime = cacheItem.lastCheckoutTime = time.time()
+ cacheItem.code = generatedModuleCode
+ cacheItem.klass = templateClass
+ templateClass._CHEETAH_isInCompilationCache = True
+ klass._CHEETAH_compileCache[cacheHash] = cacheItem
+ else:
+ templateClass._CHEETAH_isInCompilationCache = False
+
+ if keepRefToGeneratedCode or cacheCompilationResults:
+ templateClass._CHEETAH_generatedModuleCode = generatedModuleCode
+
+ return templateClass
+ compile = classmethod(compile)
+
+ def subclass(klass, *args, **kws):
+ """Takes the same args as the .compile() classmethod and returns a
+ template that is a subclass of the template this method is called from.
+
+ T1 = Template.compile(' foo - $meth1 - bar\n#def meth1: this is T1.meth1')
+ T2 = T1.subclass('#implements meth1\n this is T2.meth1')
+ """
+ kws['baseclass'] = klass
+ if isinstance(klass, Template):
+ templateAPIClass = klass
+ else:
+ templateAPIClass = Template
+ return templateAPIClass.compile(*args, **kws)
+ subclass = classmethod(subclass)
+
+ def _preprocessSource(klass, source, file, preprocessors):
+ """Iterates through the .compile() classmethod's preprocessors argument
+ and pipes the source code through each each preprocessor.
+
+ It returns the tuple (source, file) which is then used by
+ Template.compile to finish the compilation.
+ """
+ if not isinstance(preprocessors, (list, tuple)):
+ preprocessors = [preprocessors]
+ for preprocessor in preprocessors:
+ preprocessor = klass._normalizePreprocessorArg(preprocessor)
+ source, file = preprocessor.preprocess(source, file)
+ return source, file
+ _preprocessSource = classmethod(_preprocessSource)
+
+ def _normalizePreprocessorArg(klass, arg):
+ """Used to convert the items in the .compile() classmethod's
+ preprocessors argument into real source preprocessors. This permits the
+ use of several shortcut forms for defining preprocessors.
+ """
+
+ if hasattr(arg, 'preprocess'):
+ return arg
+ elif callable(arg):
+ class WrapperPreprocessor:
+ def preprocess(self, source, file):
+ return arg(source, file)
+ return WrapperPreprocessor()
+ else:
+ class Settings(object):
+ placeholderToken = None
+ directiveToken = None
+ settings = Settings()
+ if isinstance(arg, str) or isinstance(arg, (list, tuple)):
+ settings.tokens = arg
+ elif isinstance(arg, dict):
+ for k, v in arg.items():
+ setattr(settings, k, v)
+ else:
+ settings = arg
+
+ settings = klass._normalizePreprocessorSettings(settings)
+ return klass._CHEETAH_defaultPreprocessorClass(settings)
+
+ _normalizePreprocessorArg = classmethod(_normalizePreprocessorArg)
+
+ def _normalizePreprocessorSettings(klass, settings):
+ settings.keepRefToGeneratedCode = True
+
+ def normalizeSearchList(searchList):
+ if not isinstance(searchList, (list, tuple)):
+ searchList = [searchList]
+ return searchList
+
+ def normalizeTokens(tokens):
+ if isinstance(tokens, str):
+ return tokens.split() # space delimited string e.g.'@ %'
+ elif isinstance(tokens, (list, tuple)):
+ return tokens
+ else:
+ raise PreprocessError('invalid tokens argument: %r'%tokens)
+
+ if hasattr(settings, 'tokens'):
+ (settings.placeholderToken,
+ settings.directiveToken) = normalizeTokens(settings.tokens)
+
+ if (not getattr(settings,'compilerSettings', None)
+ and not getattr(settings, 'placeholderToken', None) ):
+
+ raise TypeError(
+ 'Preprocessor requires either a "tokens" or a "compilerSettings" arg.'
+ ' Neither was provided.')
+
+ if not hasattr(settings, 'templateInitArgs'):
+ settings.templateInitArgs = {}
+ if 'searchList' not in settings.templateInitArgs:
+ if not hasattr(settings, 'searchList') and hasattr(settings, 'namespaces'):
+ settings.searchList = settings.namespaces
+ elif not hasattr(settings, 'searchList'):
+ settings.searchList = []
+ settings.templateInitArgs['searchList'] = settings.searchList
+ settings.templateInitArgs['searchList'] = (
+ normalizeSearchList(settings.templateInitArgs['searchList']))
+
+ if not hasattr(settings, 'outputTransformer'):
+ settings.outputTransformer = unicode
+
+ if not hasattr(settings, 'templateAPIClass'):
+ class PreprocessTemplateAPIClass(klass): pass
+ settings.templateAPIClass = PreprocessTemplateAPIClass
+
+ if not hasattr(settings, 'compilerSettings'):
+ settings.compilerSettings = {}
+
+ klass._updateSettingsWithPreprocessTokens(
+ compilerSettings=settings.compilerSettings,
+ placeholderToken=settings.placeholderToken,
+ directiveToken=settings.directiveToken
+ )
+ return settings
+ _normalizePreprocessorSettings = classmethod(_normalizePreprocessorSettings)
+
+ def _updateSettingsWithPreprocessTokens(
+ klass, compilerSettings, placeholderToken, directiveToken):
+
+ if (placeholderToken and 'cheetahVarStartToken' not in compilerSettings):
+ compilerSettings['cheetahVarStartToken'] = placeholderToken
+ if directiveToken:
+ if 'directiveStartToken' not in compilerSettings:
+ compilerSettings['directiveStartToken'] = directiveToken
+ if 'directiveEndToken' not in compilerSettings:
+ compilerSettings['directiveEndToken'] = directiveToken
+ if 'commentStartToken' not in compilerSettings:
+ compilerSettings['commentStartToken'] = directiveToken*2
+ if 'multiLineCommentStartToken' not in compilerSettings:
+ compilerSettings['multiLineCommentStartToken'] = (
+ directiveToken+'*')
+ if 'multiLineCommentEndToken' not in compilerSettings:
+ compilerSettings['multiLineCommentEndToken'] = (
+ '*'+directiveToken)
+ if 'EOLSlurpToken' not in compilerSettings:
+ compilerSettings['EOLSlurpToken'] = directiveToken
+ _updateSettingsWithPreprocessTokens = classmethod(_updateSettingsWithPreprocessTokens)
+
+ def _addCheetahPlumbingCodeToClass(klass, concreteTemplateClass):
+ """If concreteTemplateClass is not a subclass of Cheetah.Template, add
+ the required cheetah methods and attributes to it.
+
+ This is called on each new template class after it has been compiled.
+ If concreteTemplateClass is not a subclass of Cheetah.Template but
+ already has method with the same name as one of the required cheetah
+ methods, this will skip that method.
+ """
+ for methodname in klass._CHEETAH_requiredCheetahMethods:
+ if not hasattr(concreteTemplateClass, methodname):
+ method = getattr(Template, methodname)
+ newMethod = new.instancemethod(method.im_func, None, concreteTemplateClass)
+ #print methodname, method
+ setattr(concreteTemplateClass, methodname, newMethod)
+
+ for classMethName in klass._CHEETAH_requiredCheetahClassMethods:
+ if not hasattr(concreteTemplateClass, classMethName):
+ meth = getattr(klass, classMethName)
+ setattr(concreteTemplateClass, classMethName, classmethod(meth.im_func))
+
+ for attrname in klass._CHEETAH_requiredCheetahClassAttributes:
+ attrname = '_CHEETAH_'+attrname
+ if not hasattr(concreteTemplateClass, attrname):
+ attrVal = getattr(klass, attrname)
+ setattr(concreteTemplateClass, attrname, attrVal)
+
+ if (not hasattr(concreteTemplateClass, '__str__')
+ or concreteTemplateClass.__str__ is object.__str__):
+
+ mainMethNameAttr = '_mainCheetahMethod_for_'+concreteTemplateClass.__name__
+ mainMethName = getattr(concreteTemplateClass,mainMethNameAttr, None)
+ if mainMethName:
+ def __str__(self): return getattr(self, mainMethName)()
+ elif (hasattr(concreteTemplateClass, 'respond')
+ and concreteTemplateClass.respond!=Servlet.respond):
+ def __str__(self): return self.respond()
+ else:
+ def __str__(self):
+ if hasattr(self, mainMethNameAttr):
+ return getattr(self,mainMethNameAttr)()
+ elif hasattr(self, 'respond'):
+ return self.respond()
+ else:
+ return super(self.__class__, self).__str__()
+
+ __str__ = new.instancemethod(__str__, None, concreteTemplateClass)
+ setattr(concreteTemplateClass, '__str__', __str__)
+
+ _addCheetahPlumbingCodeToClass = classmethod(_addCheetahPlumbingCodeToClass)
+
+ ## end classmethods ##
+
+ def __init__(self, source=None,
+
+ namespaces=None, searchList=None,
+ # use either or. They are aliases for the same thing.
+
+ file=None,
+ filter='RawOrEncodedUnicode', # which filter from Cheetah.Filters
+ filtersLib=Filters,
+ errorCatcher=None,
+
+ compilerSettings=Unspecified, # control the behaviour of the compiler
+ _globalSetVars=None, # used internally for #include'd templates
+ _preBuiltSearchList=None # used internally for #include'd templates
+ ):
+ """a) compiles a new template OR b) instantiates an existing template.
+
+ Read this docstring carefully as there are two distinct usage patterns.
+ You should also read this class' main docstring.
+
+ a) to compile a new template:
+ t = Template(source=aSourceString)
+ # or
+ t = Template(file='some/path')
+ # or
+ t = Template(file=someFileObject)
+ # or
+ namespaces = [{'foo':'bar'}]
+ t = Template(source=aSourceString, namespaces=namespaces)
+ # or
+ t = Template(file='some/path', namespaces=namespaces)
+
+ print t
+
+ b) to create an instance of an existing, precompiled template class:
+ ## i) first you need a reference to a compiled template class:
+ tclass = Template.compile(source=src) # or just Template.compile(src)
+ # or
+ tclass = Template.compile(file='some/path')
+ # or
+ tclass = Template.compile(file=someFileObject)
+ # or
+ # if you used the command line compiler or have Cheetah's ImportHooks
+ # installed your template class is also available via Python's
+ # standard import mechanism:
+ from ACompileTemplate import AcompiledTemplate as tclass
+
+ ## ii) then you create an instance
+ t = tclass(namespaces=namespaces)
+ # or
+ t = tclass(namespaces=namespaces, filter='RawOrEncodedUnicode')
+ print t
+
+ Arguments:
+ for usage pattern a)
+ If you are compiling a new template, you must provide either a
+ 'source' or 'file' arg, but not both:
+ - source (string or None)
+ - file (string path, file-like object, or None)
+
+ Optional args (see below for more) :
+ - compilerSettings
+ Default: Template._CHEETAH_compilerSettings=None
+
+ a dictionary of settings to override those defined in
+ DEFAULT_COMPILER_SETTINGS. See
+ Cheetah.Template.DEFAULT_COMPILER_SETTINGS and the Users' Guide
+ for details.
+
+ You can pass the source arg in as a positional arg with this usage
+ pattern. Use keywords for all other args.
+
+ for usage pattern b)
+ Do not use positional args with this usage pattern, unless your
+ template subclasses something other than Cheetah.Template and you
+ want to pass positional args to that baseclass. E.g.:
+ dictTemplate = Template.compile('hello $name from $caller', baseclass=dict)
+ tmplvars = dict(name='world', caller='me')
+ print dictTemplate(tmplvars)
+ This usage requires all Cheetah args to be passed in as keyword args.
+
+ optional args for both usage patterns:
+
+ - namespaces (aka 'searchList')
+ Default: None
+
+ an optional list of namespaces (dictionaries, objects, modules,
+ etc.) which Cheetah will search through to find the variables
+ referenced in $placeholders.
+
+ If you provide a single namespace instead of a list, Cheetah will
+ automatically convert it into a list.
+
+ NOTE: Cheetah does NOT force you to use the namespaces search list
+ and related features. It's on by default, but you can turn if off
+ using the compiler settings useSearchList=False or
+ useNameMapper=False.
+
+ - filter
+ Default: 'EncodeUnicode'
+
+ Which filter should be used for output filtering. This should
+ either be a string which is the name of a filter in the
+ 'filtersLib' or a subclass of Cheetah.Filters.Filter. . See the
+ Users' Guide for more details.
+
+ - filtersLib
+ Default: Cheetah.Filters
+
+ A module containing subclasses of Cheetah.Filters.Filter. See the
+ Users' Guide for more details.
+
+ - errorCatcher
+ Default: None
+
+ This is a debugging tool. See the Users' Guide for more details.
+ Do not use this or the #errorCatcher diretive with live
+ production systems.
+
+ Do NOT mess with the args _globalSetVars or _preBuiltSearchList!
+
+ """
+
+ ##################################################
+ ## Verify argument keywords and types
+
+ S = types.StringType; U = types.UnicodeType
+ L = types.ListType; T = types.TupleType
+ D = types.DictType; F = types.FileType
+ C = types.ClassType; M = types.ModuleType
+ N = types.NoneType
+ vt = VerifyType.VerifyType
+ vtc = VerifyType.VerifyTypeClass
+ try:
+ vt(source, 'source', [N,S,U], 'string or None')
+ vt(file, 'file', [N,S,U,F], 'string, file open for reading, or None')
+ vtc(filter, 'filter', [S,C,type], 'string or class',
+ Filters.Filter,
+ '(if class, must be subclass of Cheetah.Filters.Filter)')
+ vt(filtersLib, 'filtersLib', [S,M], 'string or module',
+ '(if module, must contain subclasses of Cheetah.Filters.Filter)')
+ vtc(errorCatcher, 'errorCatcher', [N,S,C,type], 'string, class or None',
+ ErrorCatchers.ErrorCatcher,
+ '(if class, must be subclass of Cheetah.ErrorCatchers.ErrorCatcher)')
+ if compilerSettings is not Unspecified:
+ vt(compilerSettings, 'compilerSettings', [D], 'dictionary')
+
+ except TypeError, reason:
+ # Re-raise the exception here so that the traceback will end in
+ # this function rather than in some utility function.
+ raise TypeError(reason)
+
+ if source is not None and file is not None:
+ raise TypeError("you must supply either a source string or the" +
+ " 'file' keyword argument, but not both")
+
+ ##################################################
+ ## Do superclass initialization.
+ Servlet.__init__(self)
+
+ ##################################################
+ ## Do required version check
+ if not hasattr(self, '_CHEETAH_versionTuple'):
+ try:
+ mod = sys.modules[self.__class__.__module__]
+ compiledVersion = mod.__CHEETAH_version__
+ compiledVersionTuple = convertVersionStringToTuple(compiledVersion)
+ if compiledVersionTuple < MinCompatibleVersionTuple:
+ raise AssertionError(
+ 'This template was compiled with Cheetah version'
+ ' %s. Templates compiled before version %s must be recompiled.'%(
+ compiledVersion, MinCompatibleVersion))
+ except AssertionError:
+ raise
+ except:
+ pass
+
+ ##################################################
+ ## Setup instance state attributes used during the life of template
+ ## post-compile
+
+ self._initCheetahInstance(
+ searchList=searchList, namespaces=namespaces,
+ filter=filter, filtersLib=filtersLib,
+ errorCatcher=errorCatcher,
+ _globalSetVars=_globalSetVars,
+ _preBuiltSearchList=_preBuiltSearchList)
+
+ ##################################################
+ ## Now, compile if we're meant to
+ if (source is not None) or (file is not None):
+ self._compile(source, file, compilerSettings=compilerSettings)
+
+ def generatedModuleCode(self):
+ """Return the module code the compiler generated, or None if no
+ compilation took place.
+ """
+
+ return self._CHEETAH_generatedModuleCode
+
+ def generatedClassCode(self):
+ """Return the class code the compiler generated, or None if no
+ compilation took place.
+ """
+
+ return self._CHEETAH_generatedModuleCode[
+ self._CHEETAH_generatedModuleCode.find('\nclass '):
+ self._CHEETAH_generatedModuleCode.find('\n## END CLASS DEFINITION')]
+
+ def searchList(self):
+ """Return a reference to the searchlist
+ """
+ return self._CHEETAH__searchList
+
+ def errorCatcher(self):
+ """Return a reference to the current errorCatcher
+ """
+ return self._CHEETAH__errorCatcher
+
+ ## cache methods ##
+ def _getCacheStore(self):
+ if not self._CHEETAH__cacheStore:
+ if self._CHEETAH_cacheStore is not None:
+ self._CHEETAH__cacheStore = self._CHEETAH_cacheStore
+ else:
+ # @@TR: might want to provide a way to provide init args
+ self._CHEETAH__cacheStore = self._CHEETAH_cacheStoreClass()
+
+ return self._CHEETAH__cacheStore
+
+ def _getCacheStoreIdPrefix(self):
+ if self._CHEETAH_cacheStoreIdPrefix is not None:
+ return self._CHEETAH_cacheStoreIdPrefix
+ else:
+ return str(id(self))
+
+ def _createCacheRegion(self, regionID):
+ return self._CHEETAH_cacheRegionClass(
+ regionID=regionID,
+ templateCacheIdPrefix=self._getCacheStoreIdPrefix(),
+ cacheStore=self._getCacheStore())
+
+ def getCacheRegion(self, regionID, cacheInfo=None, create=True):
+ cacheRegion = self._CHEETAH__cacheRegions.get(regionID)
+ if not cacheRegion and create:
+ cacheRegion = self._createCacheRegion(regionID)
+ self._CHEETAH__cacheRegions[regionID] = cacheRegion
+ return cacheRegion
+
+ def getCacheRegions(self):
+ """Returns a dictionary of the 'cache regions' initialized in a
+ template.
+
+ Each #cache directive block or $*cachedPlaceholder is a separate 'cache
+ region'.
+ """
+ # returns a copy to prevent users mucking it up
+ return self._CHEETAH__cacheRegions.copy()
+
+ def refreshCache(self, cacheRegionId=None, cacheItemId=None):
+ """Refresh a cache region or a specific cache item within a region.
+ """
+
+ if not cacheRegionId:
+ for key, cregion in self.getCacheRegions():
+ cregion.clear()
+ else:
+ cregion = self._CHEETAH__cacheRegions.get(cacheRegionId)
+ if not cregion:
+ return
+ if not cacheItemId: # clear the desired region and all its cacheItems
+ cregion.clear()
+ else: # clear one specific cache of a specific region
+ cache = cregion.getCacheItem(cacheItemId)
+ if cache:
+ cache.clear()
+
+ ## end cache methods ##
+
+ def shutdown(self):
+ """Break reference cycles before discarding a servlet.
+ """
+ try:
+ Servlet.shutdown(self)
+ except:
+ pass
+ self._CHEETAH__searchList = None
+ self.__dict__ = {}
+
+ ## utility functions ##
+
+ def getVar(self, varName, default=Unspecified, autoCall=True):
+ """Get a variable from the searchList. If the variable can't be found
+ in the searchList, it returns the default value if one was given, or
+ raises NameMapper.NotFound.
+ """
+
+ try:
+ return valueFromSearchList(self.searchList(), varName.replace('$',''), autoCall)
+ except NotFound:
+ if default is not Unspecified:
+ return default
+ else:
+ raise
+
+ def varExists(self, varName, autoCall=True):
+ """Test if a variable name exists in the searchList.
+ """
+ try:
+ valueFromSearchList(self.searchList(), varName.replace('$',''), autoCall)
+ return True
+ except NotFound:
+ return False
+
+
+ hasVar = varExists
+
+
+ def i18n(self, message,
+ plural=None,
+ n=None,
+
+ id=None,
+ domain=None,
+ source=None,
+ target=None,
+ comment=None
+ ):
+ """This is just a stub at this time.
+
+ plural = the plural form of the message
+ n = a sized argument to distinguish between single and plural forms
+
+ id = msgid in the translation catalog
+ domain = translation domain
+ source = source lang
+ target = a specific target lang
+ comment = a comment to the translation team
+
+ See the following for some ideas
+ http://www.zope.org/DevHome/Wikis/DevSite/Projects/ComponentArchitecture/ZPTInternationalizationSupport
+
+ Other notes:
+ - There is no need to replicate the i18n:name attribute from plone / PTL,
+ as cheetah placeholders serve the same purpose
+
+
+ """
+
+ return message
+
+ def getFileContents(self, path):
+ """A hook for getting the contents of a file. The default
+ implementation just uses the Python open() function to load local files.
+ This method could be reimplemented to allow reading of remote files via
+ various protocols, as PHP allows with its 'URL fopen wrapper'
+ """
+
+ fp = open(path,'r')
+ output = fp.read()
+ fp.close()
+ return output
+
+ def runAsMainProgram(self):
+ """Allows the Template to function as a standalone command-line program
+ for static page generation.
+
+ Type 'python yourtemplate.py --help to see what it's capabable of.
+ """
+
+ from TemplateCmdLineIface import CmdLineIface
+ CmdLineIface(templateObj=self).run()
+
+ ##################################################
+ ## internal methods -- not to be called by end-users
+
+ def _initCheetahInstance(self,
+ searchList=None,
+ namespaces=None,
+ filter='RawOrEncodedUnicode', # which filter from Cheetah.Filters
+ filtersLib=Filters,
+ errorCatcher=None,
+ _globalSetVars=None,
+ _preBuiltSearchList=None):
+ """Sets up the instance attributes that cheetah templates use at
+ run-time.
+
+ This is automatically called by the __init__ method of compiled
+ templates.
+
+ Note that the names of instance attributes used by Cheetah are prefixed
+ with '_CHEETAH__' (2 underscores), where class attributes are prefixed
+ with '_CHEETAH_' (1 underscore).
+ """
+ if getattr(self, '_CHEETAH__instanceInitialized', False):
+ return
+
+ if namespaces is not None:
+ assert searchList is None, (
+ 'Provide "namespaces" or "searchList", not both!')
+ searchList = namespaces
+ if searchList is not None and not isinstance(searchList, (list, tuple)):
+ searchList = [searchList]
+
+ self._CHEETAH__globalSetVars = {}
+ if _globalSetVars is not None:
+ # this is intended to be used internally by Nested Templates in #include's
+ self._CHEETAH__globalSetVars = _globalSetVars
+
+ if _preBuiltSearchList is not None:
+ # happens with nested Template obj creation from #include's
+ self._CHEETAH__searchList = list(_preBuiltSearchList)
+ self._CHEETAH__searchList.append(self)
+ else:
+ # create our own searchList
+ self._CHEETAH__searchList = [self._CHEETAH__globalSetVars]
+ if searchList is not None:
+ self._CHEETAH__searchList.extend(list(searchList))
+ self._CHEETAH__searchList.append( self )
+ self._CHEETAH__cheetahIncludes = {}
+ self._CHEETAH__cacheRegions = {}
+ self._CHEETAH__indenter = Indenter()
+ self._CHEETAH__filtersLib = filtersLib
+ self._CHEETAH__filters = {}
+ if type(filter) in StringTypes:
+ filterName = filter
+ klass = getattr(self._CHEETAH__filtersLib, filterName)
+ else:
+ klass = filter
+ filterName = klass.__name__
+ self._CHEETAH__currentFilter = self._CHEETAH__filters[filterName] = klass(self).filter
+ self._CHEETAH__initialFilter = self._CHEETAH__currentFilter
+ self._CHEETAH__errorCatchers = {}
+ if errorCatcher:
+ if type(errorCatcher) in StringTypes:
+ errorCatcherClass = getattr(ErrorCatchers, errorCatcher)
+ elif type(errorCatcher) == ClassType:
+ errorCatcherClass = errorCatcher
+
+ self._CHEETAH__errorCatcher = ec = errorCatcherClass(self)
+ self._CHEETAH__errorCatchers[errorCatcher.__class__.__name__] = ec
+
+ else:
+ self._CHEETAH__errorCatcher = None
+ self._CHEETAH__initErrorCatcher = self._CHEETAH__errorCatcher
+
+ if not hasattr(self, 'transaction'):
+ self.transaction = None
+ self._CHEETAH__instanceInitialized = True
+ self._CHEETAH__isBuffering = False
+ self._CHEETAH__isControlledByWebKit = False
+
+ self._CHEETAH__cacheStore = None
+ if self._CHEETAH_cacheStore is not None:
+ self._CHEETAH__cacheStore = self._CHEETAH_cacheStore
+
+ def _compile(self, source=None, file=None, compilerSettings=Unspecified,
+ moduleName=None, mainMethodName=None):
+ """Compile the template. This method is automatically called by
+ Template.__init__ it is provided with 'file' or 'source' args.
+
+ USERS SHOULD *NEVER* CALL THIS METHOD THEMSELVES. Use Template.compile
+ instead.
+ """
+ if compilerSettings is Unspecified:
+ compilerSettings = self._getCompilerSettings(source, file) or {}
+ mainMethodName = mainMethodName or self._CHEETAH_defaultMainMethodName
+ self._fileMtime = None
+ self._fileDirName = None
+ self._fileBaseName = None
+ if file and type(file) in StringTypes:
+ file = self.serverSidePath(file)
+ self._fileMtime = os.path.getmtime(file)
+ self._fileDirName, self._fileBaseName = os.path.split(file)
+ self._filePath = file
+ templateClass = self.compile(source, file,
+ moduleName=moduleName,
+ mainMethodName=mainMethodName,
+ compilerSettings=compilerSettings,
+ keepRefToGeneratedCode=True)
+ self.__class__ = templateClass
+ # must initialize it so instance attributes are accessible
+ templateClass.__init__(self,
+ #_globalSetVars=self._CHEETAH__globalSetVars,
+ #_preBuiltSearchList=self._CHEETAH__searchList
+ )
+ if not hasattr(self, 'transaction'):
+ self.transaction = None
+
+ def _handleCheetahInclude(self, srcArg, trans=None, includeFrom='file', raw=False):
+ """Called at runtime to handle #include directives.
+ """
+ _includeID = srcArg
+ if not self._CHEETAH__cheetahIncludes.has_key(_includeID):
+ if not raw:
+ if includeFrom == 'file':
+ source = None
+ if type(srcArg) in StringTypes:
+ if hasattr(self, 'serverSidePath'):
+ file = path = self.serverSidePath(srcArg)
+ else:
+ file = path = os.path.normpath(srcArg)
+ else:
+ file = srcArg ## a file-like object
+ else:
+ source = srcArg
+ file = None
+ # @@TR: might want to provide some syntax for specifying the
+ # Template class to be used for compilation so compilerSettings
+ # can be changed.
+ compiler = self._getTemplateAPIClassForIncludeDirectiveCompilation(source, file)
+ nestedTemplateClass = compiler.compile(source=source,file=file)
+ nestedTemplate = nestedTemplateClass(_preBuiltSearchList=self.searchList(),
+ _globalSetVars=self._CHEETAH__globalSetVars)
+ self._CHEETAH__cheetahIncludes[_includeID] = nestedTemplate
+ else:
+ if includeFrom == 'file':
+ path = self.serverSidePath(srcArg)
+ self._CHEETAH__cheetahIncludes[_includeID] = self.getFileContents(path)
+ else:
+ self._CHEETAH__cheetahIncludes[_includeID] = srcArg
+ ##
+ if not raw:
+ self._CHEETAH__cheetahIncludes[_includeID].respond(trans)
+ else:
+ trans.response().write(self._CHEETAH__cheetahIncludes[_includeID])
+
+ def _getTemplateAPIClassForIncludeDirectiveCompilation(self, source, file):
+ """Returns the subclass of Template which should be used to compile
+ #include directives.
+
+ This abstraction allows different compiler settings to be used in the
+ included template than were used in the parent.
+ """
+ if issubclass(self.__class__, Template):
+ return self.__class__
+ else:
+ return Template
+
+ ## functions for using templates as CGI scripts
+ def webInput(self, names, namesMulti=(), default='', src='f',
+ defaultInt=0, defaultFloat=0.00, badInt=0, badFloat=0.00, debug=False):
+ """Method for importing web transaction variables in bulk.
+
+ This works for GET/POST fields both in Webware servlets and in CGI
+ scripts, and for cookies and session variables in Webware servlets. If
+ you try to read a cookie or session variable in a CGI script, you'll get
+ a RuntimeError. 'In a CGI script' here means 'not running as a Webware
+ servlet'. If the CGI environment is not properly set up, Cheetah will
+ act like there's no input.
+
+ The public method provided is:
+
+ def webInput(self, names, namesMulti=(), default='', src='f',
+ defaultInt=0, defaultFloat=0.00, badInt=0, badFloat=0.00, debug=False):
+
+ This method places the specified GET/POST fields, cookies or session
+ variables into a dictionary, which is both returned and put at the
+ beginning of the searchList. It handles:
+
+ * single vs multiple values
+ * conversion to integer or float for specified names
+ * default values/exceptions for missing or bad values
+ * printing a snapshot of all values retrieved for debugging
+
+ All the 'default*' and 'bad*' arguments have 'use or raise' behavior,
+ meaning that if they're a subclass of Exception, they're raised. If
+ they're anything else, that value is substituted for the missing/bad
+ value.
+
+
+ The simplest usage is:
+
+ #silent $webInput(['choice'])
+ $choice
+
+ dic = self.webInput(['choice'])
+ write(dic['choice'])
+
+ Both these examples retrieves the GET/POST field 'choice' and print it.
+ If you leave off the'#silent', all the values would be printed too. But
+ a better way to preview the values is
+
+ #silent $webInput(['name'], $debug=1)
+
+ because this pretty-prints all the values inside HTML <PRE> tags.
+
+ ** KLUDGE: 'debug' is supposed to insert into the template output, but it
+ wasn't working so I changed it to a'print' statement. So the debugging
+ output will appear wherever standard output is pointed, whether at the
+ terminal, in a Webware log file, or whatever. ***
+
+ Since we didn't specify any coversions, the value is a string. It's a
+ 'single' value because we specified it in 'names' rather than
+ 'namesMulti'. Single values work like this:
+
+ * If one value is found, take it.
+ * If several values are found, choose one arbitrarily and ignore the rest.
+ * If no values are found, use or raise the appropriate 'default*' value.
+
+ Multi values work like this:
+ * If one value is found, put it in a list.
+ * If several values are found, leave them in a list.
+ * If no values are found, use the empty list ([]). The 'default*'
+ arguments are *not* consulted in this case.
+
+ Example: assume 'days' came from a set of checkboxes or a multiple combo
+ box on a form, and the user chose'Monday', 'Tuesday' and 'Thursday'.
+
+ #silent $webInput([], ['days'])
+ The days you chose are: #slurp
+ #for $day in $days
+ $day #slurp
+ #end for
+
+ dic = self.webInput([], ['days'])
+ write('The days you chose are: ')
+ for day in dic['days']:
+ write(day + ' ')
+
+ Both these examples print: 'The days you chose are: Monday Tuesday Thursday'.
+
+ By default, missing strings are replaced by '' and missing/bad numbers
+ by zero. (A'bad number' means the converter raised an exception for
+ it, usually because of non-numeric characters in the value.) This
+ mimics Perl/PHP behavior, and simplifies coding for many applications
+ where missing/bad values *should* be blank/zero. In those relatively
+ few cases where you must distinguish between empty-string/zero on the
+ one hand and missing/bad on the other, change the appropriate
+ 'default*' and 'bad*' arguments to something like:
+
+ * None
+ * another constant value
+ * $NonNumericInputError/self.NonNumericInputError
+ * $ValueError/ValueError
+
+ (NonNumericInputError is defined in this class and is useful for
+ distinguishing between bad input vs a TypeError/ValueError thrown for
+ some other rason.)
+
+ Here's an example using multiple values to schedule newspaper
+ deliveries. 'checkboxes' comes from a form with checkboxes for all the
+ days of the week. The days the user previously chose are preselected.
+ The user checks/unchecks boxes as desired and presses Submit. The value
+ of 'checkboxes' is a list of checkboxes that were checked when Submit
+ was pressed. Our task now is to turn on the days the user checked, turn
+ off the days he unchecked, and leave on or off the days he didn't
+ change.
+
+ dic = self.webInput([], ['dayCheckboxes'])
+ wantedDays = dic['dayCheckboxes'] # The days the user checked.
+ for day, on in self.getAllValues():
+ if not on and wantedDays.has_key(day):
+ self.TurnOn(day)
+ # ... Set a flag or insert a database record ...
+ elif on and not wantedDays.has_key(day):
+ self.TurnOff(day)
+ # ... Unset a flag or delete a database record ...
+
+ 'source' allows you to look up the variables from a number of different
+ sources:
+ 'f' fields (CGI GET/POST parameters)
+ 'c' cookies
+ 's' session variables
+ 'v' 'values', meaning fields or cookies
+
+ In many forms, you're dealing only with strings, which is why the
+ 'default' argument is third and the numeric arguments are banished to
+ the end. But sometimes you want automatic number conversion, so that
+ you can do numeric comparisions in your templates without having to
+ write a bunch of conversion/exception handling code. Example:
+
+ #silent $webInput(['name', 'height:int'])
+ $name is $height cm tall.
+ #if $height >= 300
+ Wow, you're tall!
+ #else
+ Pshaw, you're short.
+ #end if
+
+ dic = self.webInput(['name', 'height:int'])
+ name = dic[name]
+ height = dic[height]
+ write('%s is %s cm tall.' % (name, height))
+ if height > 300:
+ write('Wow, you're tall!')
+ else:
+ write('Pshaw, you're short.')
+
+ To convert a value to a number, suffix ':int' or ':float' to the name.
+ The method will search first for a 'height:int' variable and then for a
+ 'height' variable. (It will be called 'height' in the final
+ dictionary.) If a numeric conversion fails, use or raise 'badInt' or
+ 'badFloat'. Missing values work the same way as for strings, except the
+ default is 'defaultInt' or 'defaultFloat' instead of 'default'.
+
+ If a name represents an uploaded file, the entire file will be read into
+ memory. For more sophistocated file-upload handling, leave that name
+ out of the list and do your own handling, or wait for
+ Cheetah.Utils.UploadFileMixin.
+
+ This only in a subclass that also inherits from Webware's Servlet or
+ HTTPServlet. Otherwise you'll get an AttributeError on 'self.request'.
+
+ EXCEPTIONS: ValueError if 'source' is not one of the stated characters.
+ TypeError if a conversion suffix is not ':int' or ':float'.
+
+ FUTURE EXPANSION: a future version of this method may allow source
+ cascading; e.g., 'vs' would look first in 'values' and then in session
+ variables.
+
+ Meta-Data
+ ================================================================================
+ Author: Mike Orr <iron@mso.oz.net>
+ License: This software is released for unlimited distribution under the
+ terms of the MIT license. See the LICENSE file.
+ Version: $Revision: 1.181 $
+ Start Date: 2002/03/17
+ Last Revision Date: $Date: 2006/06/22 20:25:16 $
+ """
+ src = src.lower()
+ isCgi = not self._CHEETAH__isControlledByWebKit
+ if isCgi and src in ('f', 'v'):
+ global _formUsedByWebInput
+ if _formUsedByWebInput is None:
+ _formUsedByWebInput = cgi.FieldStorage()
+ source, func = 'field', _formUsedByWebInput.getvalue
+ elif isCgi and src == 'c':
+ raise RuntimeError("can't get cookies from a CGI script")
+ elif isCgi and src == 's':
+ raise RuntimeError("can't get session variables from a CGI script")
+ elif isCgi and src == 'v':
+ source, func = 'value', self.request().value
+ elif isCgi and src == 's':
+ source, func = 'session', self.request().session().value
+ elif src == 'f':
+ source, func = 'field', self.request().field
+ elif src == 'c':
+ source, func = 'cookie', self.request().cookie
+ elif src == 'v':
+ source, func = 'value', self.request().value
+ elif src == 's':
+ source, func = 'session', self.request().session().value
+ else:
+ raise TypeError("arg 'src' invalid")
+ sources = source + 's'
+ converters = {
+ '' : _Converter('string', None, default, default ),
+ 'int' : _Converter('int', int, defaultInt, badInt ),
+ 'float': _Converter('float', float, defaultFloat, badFloat), }
+ #pprint.pprint(locals()); return {}
+ dic = {} # Destination.
+ for name in names:
+ k, v = _lookup(name, func, False, converters)
+ dic[k] = v
+ for name in namesMulti:
+ k, v = _lookup(name, func, True, converters)
+ dic[k] = v
+ # At this point, 'dic' contains all the keys/values we want to keep.
+ # We could split the method into a superclass
+ # method for Webware/WebwareExperimental and a subclass for Cheetah.
+ # The superclass would merely 'return dic'. The subclass would
+ # 'dic = super(ThisClass, self).webInput(names, namesMulti, ...)'
+ # and then the code below.
+ if debug:
+ print "<PRE>\n" + pprint.pformat(dic) + "\n</PRE>\n\n"
+ self.searchList().insert(0, dic)
+ return dic
+
+T = Template # Short and sweet for debugging at the >>> prompt.
+
+
+def genParserErrorFromPythonException(source, file, generatedPyCode, exception):
+
+ #print dir(exception)
+
+ filename = isinstance(file, (str, unicode)) and file or None
+
+ sio = StringIO.StringIO()
+ traceback.print_exc(1, sio)
+ formatedExc = sio.getvalue()
+
+ if hasattr(exception, 'lineno'):
+ pyLineno = exception.lineno
+ else:
+ pyLineno = int(re.search('[ \t]*File.*line (\d+)', formatedExc).group(1))
+
+ lines = generatedPyCode.splitlines()
+
+ prevLines = [] # (i, content)
+ for i in range(1,4):
+ if pyLineno-i <=0:
+ break
+ prevLines.append( (pyLineno+1-i,lines[pyLineno-i]) )
+
+ nextLines = [] # (i, content)
+ for i in range(1,4):
+ if not pyLineno+i < len(lines):
+ break
+ nextLines.append( (pyLineno+i,lines[pyLineno+i]) )
+ nextLines.reverse()
+ report = 'Line|Python Code\n'
+ report += '----|-------------------------------------------------------------\n'
+ while prevLines:
+ lineInfo = prevLines.pop()
+ report += "%(row)-4d|%(line)s\n"% {'row':lineInfo[0], 'line':lineInfo[1]}
+
+ if hasattr(exception, 'offset'):
+ report += ' '*(3+exception.offset) + '^\n'
+
+ while nextLines:
+ lineInfo = nextLines.pop()
+ report += "%(row)-4d|%(line)s\n"% {'row':lineInfo[0], 'line':lineInfo[1]}
+
+
+ message = [
+ "Error in the Python code which Cheetah generated for this template:",
+ '='*80,
+ '',
+ str(exception),
+ '',
+ report,
+ '='*80,
+ ]
+ cheetahPosMatch = re.search('line (\d+), col (\d+)', formatedExc)
+ if cheetahPosMatch:
+ lineno = int(cheetahPosMatch.group(1))
+ col = int(cheetahPosMatch.group(2))
+ #if hasattr(exception, 'offset'):
+ # col = exception.offset
+ message.append('\nHere is the corresponding Cheetah code:\n')
+ else:
+ lineno = None
+ col = None
+ cheetahPosMatch = re.search('line (\d+), col (\d+)',
+ '\n'.join(lines[max(pyLineno-2, 0):]))
+ if cheetahPosMatch:
+ lineno = int(cheetahPosMatch.group(1))
+ col = int(cheetahPosMatch.group(2))
+ message.append('\nHere is the corresponding Cheetah code.')
+ message.append('** I had to guess the line & column numbers,'
+ ' so they are probably incorrect:\n')
+
+
+ message = '\n'.join(message)
+ reader = SourceReader(source, filename=filename)
+ return ParseError(reader, message, lineno=lineno,col=col)
+
+
+# vim: shiftwidth=4 tabstop=4 expandtab
diff --git a/cobbler/Cheetah/TemplateCmdLineIface.py b/cobbler/Cheetah/TemplateCmdLineIface.py
new file mode 100644
index 0000000..abd8ae2
--- /dev/null
+++ b/cobbler/Cheetah/TemplateCmdLineIface.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+# $Id: TemplateCmdLineIface.py,v 1.13 2006/01/10 20:34:35 tavis_rudd Exp $
+
+"""Provides a command line interface to compiled Cheetah template modules.
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com>
+Version: $Revision: 1.13 $
+Start Date: 2001/12/06
+Last Revision Date: $Date: 2006/01/10 20:34:35 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.13 $"[11:-2]
+
+import sys
+import os
+import getopt
+import os.path
+try:
+ from cPickle import load
+except ImportError:
+ from pickle import load
+
+from Cheetah.Version import Version
+
+class Error(Exception):
+ pass
+
+class CmdLineIface:
+ """A command line interface to compiled Cheetah template modules."""
+
+ def __init__(self, templateObj,
+ scriptName=os.path.basename(sys.argv[0]),
+ cmdLineArgs=sys.argv[1:]):
+
+ self._template = templateObj
+ self._scriptName = scriptName
+ self._cmdLineArgs = cmdLineArgs
+
+ def run(self):
+ """The main program controller."""
+
+ self._processCmdLineArgs()
+ print self._template
+
+ def _processCmdLineArgs(self):
+ try:
+ self._opts, self._args = getopt.getopt(
+ self._cmdLineArgs, 'h', ['help',
+ 'env',
+ 'pickle=',
+ ])
+
+ except getopt.GetoptError, v:
+ # print help information and exit:
+ print v
+ print self.usage()
+ sys.exit(2)
+
+ for o, a in self._opts:
+ if o in ('-h','--help'):
+ print self.usage()
+ sys.exit()
+ if o == '--env':
+ self._template.searchList().insert(0, os.environ)
+ if o == '--pickle':
+ if a == '-':
+ unpickled = load(sys.stdin)
+ self._template.searchList().insert(0, unpickled)
+ else:
+ f = open(a)
+ unpickled = load(f)
+ f.close()
+ self._template.searchList().insert(0, unpickled)
+
+ def usage(self):
+ return """Cheetah %(Version)s template module command-line interface
+
+Usage
+-----
+ %(scriptName)s [OPTION]
+
+Options
+-------
+ -h, --help Print this help information
+
+ --env Use shell ENVIRONMENT variables to fill the
+ $placeholders in the template.
+
+ --pickle <file> Use a variables from a dictionary stored in Python
+ pickle file to fill $placeholders in the template.
+ If <file> is - stdin is used:
+ '%(scriptName)s --pickle -'
+
+Description
+-----------
+
+This interface allows you to execute a Cheetah template from the command line
+and collect the output. It can prepend the shell ENVIRONMENT or a pickled
+Python dictionary to the template's $placeholder searchList, overriding the
+defaults for the $placeholders.
+
+""" % {'scriptName':self._scriptName,
+ 'Version':Version,
+ }
+
+# vim: shiftwidth=4 tabstop=4 expandtab
diff --git a/cobbler/Cheetah/Templates/SkeletonPage.py b/cobbler/Cheetah/Templates/SkeletonPage.py
new file mode 100644
index 0000000..0049d17
--- /dev/null
+++ b/cobbler/Cheetah/Templates/SkeletonPage.py
@@ -0,0 +1,273 @@
+#!/usr/bin/env python
+
+
+"""A Skeleton HTML page template, that provides basic structure and utility methods.
+"""
+
+
+##################################################
+## DEPENDENCIES
+import sys
+import os
+import os.path
+from os.path import getmtime, exists
+import time
+import types
+import __builtin__
+from Cheetah.Version import MinCompatibleVersion as RequiredCheetahVersion
+from Cheetah.Version import MinCompatibleVersionTuple as RequiredCheetahVersionTuple
+from Cheetah.Template import Template
+from Cheetah.DummyTransaction import DummyTransaction
+from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList
+from Cheetah.CacheRegion import CacheRegion
+import Cheetah.Filters as Filters
+import Cheetah.ErrorCatchers as ErrorCatchers
+from Cheetah.Templates._SkeletonPage import _SkeletonPage
+
+##################################################
+## MODULE CONSTANTS
+try:
+ True, False
+except NameError:
+ True, False = (1==1), (1==0)
+VFFSL=valueFromFrameOrSearchList
+VFSL=valueFromSearchList
+VFN=valueForName
+currentTime=time.time
+__CHEETAH_version__ = '2.0rc6'
+__CHEETAH_versionTuple__ = (2, 0, 0, 'candidate', 6)
+__CHEETAH_genTime__ = 1139107954.3640411
+__CHEETAH_genTimestamp__ = 'Sat Feb 4 18:52:34 2006'
+__CHEETAH_src__ = 'src/Templates/SkeletonPage.tmpl'
+__CHEETAH_srcLastModified__ = 'Mon Oct 7 11:37:30 2002'
+__CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine'
+
+if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple:
+ raise AssertionError(
+ 'This template was compiled with Cheetah version'
+ ' %s. Templates compiled before version %s must be recompiled.'%(
+ __CHEETAH_version__, RequiredCheetahVersion))
+
+##################################################
+## CLASSES
+
+class SkeletonPage(_SkeletonPage):
+
+ ##################################################
+ ## CHEETAH GENERATED METHODS
+
+
+ def __init__(self, *args, **KWs):
+
+ _SkeletonPage.__init__(self, *args, **KWs)
+ 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)
+
+
+ def writeHeadTag(self, **KWS):
+
+
+
+ ## CHEETAH: generated from #block writeHeadTag at line 22, col 1.
+ trans = KWS.get("trans")
+ if (not trans and not self._CHEETAH__isBuffering and not callable(self.transaction)):
+ trans = self.transaction # is None unless self.awake() was called
+ if not trans:
+ trans = DummyTransaction()
+ _dummyTrans = True
+ else: _dummyTrans = False
+ write = trans.response().write
+ SL = self._CHEETAH__searchList
+ _filter = self._CHEETAH__currentFilter
+
+ ########################################
+ ## START - generated method body
+
+ write('<head>\n<title>')
+ _v = VFFSL(SL,"title",True) # '$title' on line 24, col 8
+ if _v is not None: write(_filter(_v, rawExpr='$title')) # from line 24, col 8.
+ write('</title>\n')
+ _v = VFFSL(SL,"metaTags",True) # '$metaTags' on line 25, col 1
+ if _v is not None: write(_filter(_v, rawExpr='$metaTags')) # from line 25, col 1.
+ write(' \n')
+ _v = VFFSL(SL,"stylesheetTags",True) # '$stylesheetTags' on line 26, col 1
+ if _v is not None: write(_filter(_v, rawExpr='$stylesheetTags')) # from line 26, col 1.
+ write(' \n')
+ _v = VFFSL(SL,"javascriptTags",True) # '$javascriptTags' on line 27, col 1
+ if _v is not None: write(_filter(_v, rawExpr='$javascriptTags')) # from line 27, col 1.
+ write('\n</head>\n')
+
+ ########################################
+ ## END - generated method body
+
+ return _dummyTrans and trans.response().getvalue() or ""
+
+
+ def writeBody(self, **KWS):
+
+
+
+ ## CHEETAH: generated from #block writeBody at line 36, col 1.
+ trans = KWS.get("trans")
+ if (not trans and not self._CHEETAH__isBuffering and not callable(self.transaction)):
+ trans = self.transaction # is None unless self.awake() was called
+ if not trans:
+ trans = DummyTransaction()
+ _dummyTrans = True
+ else: _dummyTrans = False
+ write = trans.response().write
+ SL = self._CHEETAH__searchList
+ _filter = self._CHEETAH__currentFilter
+
+ ########################################
+ ## START - generated method body
+
+ write('This skeleton page has no flesh. Its body needs to be implemented.\n')
+
+ ########################################
+ ## END - generated method body
+
+ return _dummyTrans and trans.response().getvalue() or ""
+
+
+ def respond(self, trans=None):
+
+
+
+ ## CHEETAH: main method generated for this template
+ if (not trans and not self._CHEETAH__isBuffering and not callable(self.transaction)):
+ trans = self.transaction # is None unless self.awake() was called
+ if not trans:
+ trans = DummyTransaction()
+ _dummyTrans = True
+ else: _dummyTrans = False
+ write = trans.response().write
+ SL = self._CHEETAH__searchList
+ _filter = self._CHEETAH__currentFilter
+
+ ########################################
+ ## START - generated method body
+
+
+ ## START CACHE REGION: ID=header. line 6, col 1 in the source.
+ _RECACHE_header = False
+ _cacheRegion_header = self.getCacheRegion(regionID='header', cacheInfo={'type': 2, 'id': 'header'})
+ if _cacheRegion_header.isNew():
+ _RECACHE_header = True
+ _cacheItem_header = _cacheRegion_header.getCacheItem('header')
+ if _cacheItem_header.hasExpired():
+ _RECACHE_header = True
+ if (not _RECACHE_header) and _cacheItem_header.getRefreshTime():
+ try:
+ _output = _cacheItem_header.renderOutput()
+ except KeyError:
+ _RECACHE_header = True
+ else:
+ write(_output)
+ del _output
+ if _RECACHE_header or not _cacheItem_header.getRefreshTime():
+ _orig_transheader = trans
+ trans = _cacheCollector_header = DummyTransaction()
+ write = _cacheCollector_header.response().write
+ _v = VFFSL(SL,"docType",True) # '$docType' on line 7, col 1
+ if _v is not None: write(_filter(_v, rawExpr='$docType')) # from line 7, col 1.
+ write('\n')
+ _v = VFFSL(SL,"htmlTag",True) # '$htmlTag' on line 8, col 1
+ if _v is not None: write(_filter(_v, rawExpr='$htmlTag')) # from line 8, col 1.
+ write('''
+<!-- This document was autogenerated by Cheetah(http://CheetahTemplate.org).
+Do not edit it directly!
+
+Copyright ''')
+ _v = VFFSL(SL,"currentYr",True) # '$currentYr' on line 12, col 11
+ if _v is not None: write(_filter(_v, rawExpr='$currentYr')) # from line 12, col 11.
+ write(' - ')
+ _v = VFFSL(SL,"siteCopyrightName",True) # '$siteCopyrightName' on line 12, col 24
+ if _v is not None: write(_filter(_v, rawExpr='$siteCopyrightName')) # from line 12, col 24.
+ write(' - All Rights Reserved.\nFeel free to copy any javascript or html you like on this site,\nprovided you remove all links and/or references to ')
+ _v = VFFSL(SL,"siteDomainName",True) # '$siteDomainName' on line 14, col 52
+ if _v is not None: write(_filter(_v, rawExpr='$siteDomainName')) # from line 14, col 52.
+ write('''
+However, please do not copy any content or images without permission.
+
+''')
+ _v = VFFSL(SL,"siteCredits",True) # '$siteCredits' on line 17, col 1
+ if _v is not None: write(_filter(_v, rawExpr='$siteCredits')) # from line 17, col 1.
+ write('''
+
+-->
+
+
+''')
+ self.writeHeadTag(trans=trans)
+ write('\n')
+ trans = _orig_transheader
+ write = trans.response().write
+ _cacheData = _cacheCollector_header.response().getvalue()
+ _cacheItem_header.setData(_cacheData)
+ write(_cacheData)
+ del _cacheData
+ del _cacheCollector_header
+ del _orig_transheader
+ ## END CACHE REGION: header
+
+ write('\n')
+ _v = VFFSL(SL,"bodyTag",True) # '$bodyTag' on line 34, col 1
+ if _v is not None: write(_filter(_v, rawExpr='$bodyTag')) # from line 34, col 1.
+ write('\n\n')
+ self.writeBody(trans=trans)
+ write('''
+</body>
+</html>
+
+
+
+''')
+
+ ########################################
+ ## END - generated method body
+
+ return _dummyTrans and trans.response().getvalue() or ""
+
+ ##################################################
+ ## CHEETAH GENERATED ATTRIBUTES
+
+
+ _CHEETAH__instanceInitialized = False
+
+ _CHEETAH_version = __CHEETAH_version__
+
+ _CHEETAH_versionTuple = __CHEETAH_versionTuple__
+
+ _CHEETAH_genTime = __CHEETAH_genTime__
+
+ _CHEETAH_genTimestamp = __CHEETAH_genTimestamp__
+
+ _CHEETAH_src = __CHEETAH_src__
+
+ _CHEETAH_srcLastModified = __CHEETAH_srcLastModified__
+
+ _mainCheetahMethod_for_SkeletonPage= 'respond'
+
+## END CLASS DEFINITION
+
+if not hasattr(SkeletonPage, '_initCheetahAttributes'):
+ templateAPIClass = getattr(SkeletonPage, '_CHEETAH_templateClass', Template)
+ templateAPIClass._addCheetahPlumbingCodeToClass(SkeletonPage)
+
+
+# CHEETAH was developed by Tavis Rudd and Mike Orr
+# with code, advice and input from many other volunteers.
+# For more information visit http://www.CheetahTemplate.org/
+
+##################################################
+## if run from command line:
+if __name__ == '__main__':
+ from Cheetah.TemplateCmdLineIface import CmdLineIface
+ CmdLineIface(templateObj=SkeletonPage()).run()
+
+
diff --git a/cobbler/Cheetah/Templates/SkeletonPage.tmpl b/cobbler/Cheetah/Templates/SkeletonPage.tmpl
new file mode 100644
index 0000000..43c5ecd
--- /dev/null
+++ b/cobbler/Cheetah/Templates/SkeletonPage.tmpl
@@ -0,0 +1,44 @@
+##doc-module: A Skeleton HTML page template, that provides basic structure and utility methods.
+################################################################################
+#extends Cheetah.Templates._SkeletonPage
+#implements respond
+################################################################################
+#cache id='header'
+$docType
+$htmlTag
+<!-- This document was autogenerated by Cheetah(http://CheetahTemplate.org).
+Do not edit it directly!
+
+Copyright $currentYr - $siteCopyrightName - All Rights Reserved.
+Feel free to copy any javascript or html you like on this site,
+provided you remove all links and/or references to $siteDomainName
+However, please do not copy any content or images without permission.
+
+$siteCredits
+
+-->
+
+
+#block writeHeadTag
+<head>
+<title>$title</title>
+$metaTags
+$stylesheetTags
+$javascriptTags
+</head>
+#end block writeHeadTag
+
+#end cache header
+#################
+
+$bodyTag
+
+#block writeBody
+This skeleton page has no flesh. Its body needs to be implemented.
+#end block writeBody
+
+</body>
+</html>
+
+
+
diff --git a/cobbler/Cheetah/Templates/_SkeletonPage.py b/cobbler/Cheetah/Templates/_SkeletonPage.py
new file mode 100644
index 0000000..bf10e30
--- /dev/null
+++ b/cobbler/Cheetah/Templates/_SkeletonPage.py
@@ -0,0 +1,216 @@
+#!/usr/bin/env python
+# $Id: _SkeletonPage.py,v 1.13 2002/10/01 17:52:02 tavis_rudd Exp $
+"""A baseclass for the SkeletonPage template
+
+Meta-Data
+==========
+Author: Tavis Rudd <tavis@damnsimple.com>,
+Version: $Revision: 1.13 $
+Start Date: 2001/04/05
+Last Revision Date: $Date: 2002/10/01 17:52:02 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.13 $"[11:-2]
+
+##################################################
+## DEPENDENCIES ##
+
+import time, types, os, sys
+
+# intra-package imports ...
+from Cheetah.Template import Template
+
+
+##################################################
+## GLOBALS AND CONSTANTS ##
+
+True = (1==1)
+False = (0==1)
+
+##################################################
+## CLASSES ##
+
+class _SkeletonPage(Template):
+ """A baseclass for the SkeletonPage template"""
+
+ docType = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" ' + \
+ '"http://www.w3.org/TR/html4/loose.dtd">'
+
+ # docType = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ' + \
+ #'"http://www.w3.org/TR/xhtml1l/DTD/transitional.dtd">'
+
+ title = ''
+ siteDomainName = 'www.example.com'
+ siteCredits = 'Designed & Implemented by Tavis Rudd'
+ siteCopyrightName = "Tavis Rudd"
+ htmlTag = '<html>'
+
+ def __init__(self, *args, **KWs):
+ Template.__init__(self, *args, **KWs)
+ self._metaTags = {'HTTP-EQUIV':{'keywords':'Cheetah',
+ 'Content-Type':'text/html; charset=iso-8859-1',
+ },
+ 'NAME':{'generator':'Cheetah: The Python-Powered Template Engine'}
+ }
+ # metaTags = {'HTTP_EQUIV':{'test':1234}, 'NAME':{'test':1234,'test2':1234} }
+ self._stylesheets = {}
+ # stylesheets = {'.cssClassName':'stylesheetCode'}
+ self._stylesheetsOrder = []
+ # stylesheetsOrder = ['.cssClassName',]
+ self._stylesheetLibs = {}
+ # stylesheetLibs = {'libName':'libSrcPath'}
+ self._javascriptLibs = {}
+ self._javascriptTags = {}
+ # self._javascriptLibs = {'libName':'libSrcPath'}
+ self._bodyTagAttribs = {}
+
+ def metaTags(self):
+ """Return a formatted vesion of the self._metaTags dictionary, using the
+ formatMetaTags function from Cheetah.Macros.HTML"""
+
+ return self.formatMetaTags(self._metaTags)
+
+ def stylesheetTags(self):
+ """Return a formatted version of the self._stylesheetLibs and
+ self._stylesheets dictionaries. The keys in self._stylesheets must
+ be listed in the order that they should appear in the list
+ self._stylesheetsOrder, to ensure that the style rules are defined in
+ the correct order."""
+
+ stylesheetTagsTxt = ''
+ for title, src in self._stylesheetLibs.items():
+ stylesheetTagsTxt += '<link rel="stylesheet" type="text/css" href="' + str(src) + '" />\n'
+
+ if not self._stylesheetsOrder:
+ return stylesheetTagsTxt
+
+ stylesheetTagsTxt += '<style type="text/css"><!--\n'
+ for identifier in self._stylesheetsOrder:
+ if not self._stylesheets.has_key(identifier):
+ warning = '# the identifier ' + identifier + \
+ 'was in stylesheetsOrder, but not in stylesheets'
+ print warning
+ stylesheetTagsTxt += warning
+ continue
+
+ attribsDict = self._stylesheets[identifier]
+ cssCode = ''
+ attribCode = ''
+ for k, v in attribsDict.items():
+ attribCode += str(k) + ': ' + str(v) + '; '
+ attribCode = attribCode[:-2] # get rid of the last semicolon
+
+ cssCode = '\n' + identifier + ' {' + attribCode + '}'
+ stylesheetTagsTxt += cssCode
+
+ stylesheetTagsTxt += '\n//--></style>\n'
+
+ return stylesheetTagsTxt
+
+ def javascriptTags(self):
+ """Return a formatted version of the javascriptTags and
+ javascriptLibs dictionaries. Each value in javascriptTags
+ should be a either a code string to include, or a list containing the
+ JavaScript version number and the code string. The keys can be anything.
+ The same applies for javascriptLibs, but the string should be the
+ SRC filename rather than a code string."""
+
+ javascriptTagsTxt = []
+ for key, details in self._javascriptTags.items():
+ if type(details) not in (types.ListType, types.TupleType):
+ details = ['',details]
+
+ javascriptTagsTxt += ['<script language="JavaScript', str(details[0]),
+ '" type="text/javascript"><!--\n',
+ str(details[0]), '\n//--></script>\n']
+
+
+ for key, details in self._javascriptLibs.items():
+ if type(details) not in (types.ListType, types.TupleType):
+ details = ['',details]
+
+ javascriptTagsTxt += ['<script language="JavaScript', str(details[0]),
+ '" type="text/javascript" src="',
+ str(details[1]), '" />\n']
+ return ''.join(javascriptTagsTxt)
+
+ def bodyTag(self):
+ """Create a body tag from the entries in the dict bodyTagAttribs."""
+ return self.formHTMLTag('body', self._bodyTagAttribs)
+
+
+ def imgTag(self, src, alt='', width=None, height=None, border=0):
+
+ """Dynamically generate an image tag. Cheetah will try to convert the
+ src argument to a WebKit serverSidePath relative to the servlet's
+ location. If width and height aren't specified they are calculated using
+ PIL or ImageMagick if available."""
+
+ src = self.normalizePath(src)
+
+
+ if not width or not height:
+ try: # see if the dimensions can be calc'd with PIL
+ import Image
+ im = Image.open(src)
+ calcWidth, calcHeight = im.size
+ del im
+ if not width: width = calcWidth
+ if not height: height = calcHeight
+
+ except:
+ try: # try imageMagick instead
+ calcWidth, calcHeight = os.popen(
+ 'identify -format "%w,%h" ' + src).read().split(',')
+ if not width: width = calcWidth
+ if not height: height = calcHeight
+
+ except:
+ pass
+
+ if width and height:
+ return ''.join(['<img src="', src, '" width="', str(width), '" height="', str(height),
+ '" alt="', alt, '" border="', str(border), '" />'])
+ elif width:
+ return ''.join(['<img src="', src, '" width="', str(width),
+ '" alt="', alt, '" border="', str(border), '" />'])
+ elif height:
+ return ''.join(['<img src="', src, '" height="', str(height),
+ '" alt="', alt, '" border="', str(border), '" />'])
+ else:
+ return ''.join(['<img src="', src, '" alt="', alt, '" border="', str(border),'" />'])
+
+
+ def currentYr(self):
+ """Return a string representing the current yr."""
+ return time.strftime("%Y",time.localtime(time.time()))
+
+ def currentDate(self, formatString="%b %d, %Y"):
+ """Return a string representing the current localtime."""
+ return time.strftime(formatString,time.localtime(time.time()))
+
+ def spacer(self, width=1,height=1):
+ return '<img src="spacer.gif" width="%s" height="%s" alt="" />'% (str(width), str(height))
+
+ def formHTMLTag(self, tagName, attributes={}):
+ """returns a string containing an HTML <tag> """
+ tagTxt = ['<', tagName.lower()]
+ for name, val in attributes.items():
+ tagTxt += [' ', name.lower(), '="', str(val),'"']
+ tagTxt.append('>')
+ return ''.join(tagTxt)
+
+ def formatMetaTags(self, metaTags):
+ """format a dict of metaTag definitions into an HTML version"""
+ metaTagsTxt = []
+ if metaTags.has_key('HTTP-EQUIV'):
+ for http_equiv, contents in metaTags['HTTP-EQUIV'].items():
+ metaTagsTxt += ['<meta http-equiv="', str(http_equiv), '" content="',
+ str(contents), '" />\n']
+
+ if metaTags.has_key('NAME'):
+ for name, contents in metaTags['NAME'].items():
+ metaTagsTxt += ['<meta name="', str(name), '" content="', str(contents),
+ '" />\n']
+ return ''.join(metaTagsTxt)
+
diff --git a/cobbler/Cheetah/Templates/__init__.py b/cobbler/Cheetah/Templates/__init__.py
new file mode 100644
index 0000000..4265cc3
--- /dev/null
+++ b/cobbler/Cheetah/Templates/__init__.py
@@ -0,0 +1 @@
+#!/usr/bin/env python
diff --git a/cobbler/Cheetah/Tests/CheetahWrapper.py b/cobbler/Cheetah/Tests/CheetahWrapper.py
new file mode 100644
index 0000000..316c43b
--- /dev/null
+++ b/cobbler/Cheetah/Tests/CheetahWrapper.py
@@ -0,0 +1,596 @@
+#!/usr/bin/env python
+# $Id: CheetahWrapper.py,v 1.5 2006/01/07 07:18:44 tavis_rudd Exp $
+"""Tests for the 'cheetah' command.
+
+Besides unittest usage, recognizes the following command-line options:
+ --list CheetahWrapper.py
+ List all scenarios that are tested. The argument is the path
+ of this script.
+ --nodelete
+ Don't delete scratch directory at end.
+ --output
+ Show the output of each subcommand. (Normally suppressed.)
+
+Meta-Data
+================================================================================
+Author: Mike Orr <iron@mso.oz.net>,
+Version: $Revision: 1.5 $
+Start Date: 2001/10/01
+Last Revision Date: $Date: 2006/01/07 07:18:44 $
+"""
+__author__ = "Mike Orr <iron@mso.oz.net>"
+__revision__ = "$Revision: 1.5 $"[11:-2]
+
+
+##################################################
+## DEPENDENCIES ##
+
+import commands, os, shutil, sys, tempfile
+import unittest_local_copy as unittest
+
+import re # Used by listTests.
+from Cheetah.CheetahWrapper import CheetahWrapper # Used by NoBackup.
+from Cheetah.Utils.optik import OptionParser # Used by main.
+
+##################################################
+## CONSTANTS & GLOBALS ##
+
+try:
+ True,False
+except NameError:
+ True, False = (1==1),(1==0)
+
+DELETE = True # True to clean up after ourselves, False for debugging.
+OUTPUT = False # Normally False, True for debugging.
+
+#DELETE = False # True to clean up after ourselves, False for debugging.
+#OUTPUT = True # Normally False, True for debugging.
+
+BACKUP_SUFFIX = CheetahWrapper.BACKUP_SUFFIX
+
+def warn(msg):
+ sys.stderr.write(msg + '\n')
+
+##################################################
+## TEST BASE CLASSES
+
+class CFBase(unittest.TestCase):
+ """Base class for "cheetah compile" and "cheetah fill" unit tests.
+ """
+ srcDir = '' # Nonblank to create source directory.
+ subdirs = ('child', 'child/grandkid') # Delete in reverse order.
+ srcFiles = ('a.tmpl', 'child/a.tmpl', 'child/grandkid/a.tmpl')
+ expectError = False # Used by --list option.
+
+ def inform(self, message):
+ if self.verbose:
+ print message
+
+ def setUp(self):
+ """Create the top-level directories, subdirectories and .tmpl
+ files.
+ """
+ I = self.inform
+ # Step 1: Create the scratch directory and chdir into it.
+ self.scratchDir = scratchDir = tempfile.mktemp()
+ os.mkdir(scratchDir)
+ self.origCwd = os.getcwd()
+ os.chdir(scratchDir)
+ if self.srcDir:
+ os.mkdir(self.srcDir)
+ # Step 2: Create source subdirectories.
+ for dir in self.subdirs:
+ os.mkdir(dir)
+ # Step 3: Create the .tmpl files, each in its proper directory.
+ for fil in self.srcFiles:
+ f = open(fil, 'w')
+ f.write("Hello, world!\n")
+ f.close()
+
+
+ def tearDown(self):
+ os.chdir(self.origCwd)
+ if DELETE:
+ shutil.rmtree(self.scratchDir, True) # Ignore errors.
+ if os.path.exists(self.scratchDir):
+ warn("Warning: unable to delete scratch directory %s")
+ else:
+ warn("Warning: not deleting scratch directory %s" % self.scratchDir)
+
+
+ def _checkDestFileHelper(self, path, expected,
+ allowSurroundingText, errmsg):
+ """Low-level helper to check a destination file.
+
+ in : path, string, the destination path.
+ expected, string, the expected contents.
+ allowSurroundingtext, bool, allow the result to contain
+ additional text around the 'expected' substring?
+ errmsg, string, the error message. It may contain the
+ following "%"-operator keys: path, expected, result.
+ out: None
+ """
+ path = os.path.abspath(path)
+ exists = os.path.exists(path)
+ msg = "destination file missing: %s" % path
+ self.failUnless(exists, msg)
+ f = open(path, 'r')
+ result = f.read()
+ f.close()
+ if allowSurroundingText:
+ success = result.find(expected) != -1
+ else:
+ success = result == expected
+ msg = errmsg % locals()
+ self.failUnless(success, msg)
+
+
+ def checkCompile(self, path):
+ # Raw string to prevent "\n" from being converted to a newline.
+ #expected = R"write('Hello, world!\n')"
+ expected = R"'Hello, world!\n')" # might output a u'' string
+ errmsg = """\
+destination file %(path)s doesn't contain expected substring:
+%(expected)r"""
+ self._checkDestFileHelper(path, expected, True, errmsg)
+
+
+ def checkFill(self, path):
+ expected = "Hello, world!\n"
+ errmsg = """\
+destination file %(path)s contains wrong result.
+Expected %(expected)r
+Found %(result)r"""
+ self._checkDestFileHelper(path, expected, False, errmsg)
+
+
+ def checkSubdirPyInit(self, path):
+ """Verify a destination subdirectory exists and contains an
+ __init__.py file.
+ """
+ exists = os.path.exists(path)
+ msg = "destination subdirectory %s misssing" % path
+ self.failUnless(exists, msg)
+ initPath = os.path.join(path, "__init__.py")
+ exists = os.path.exists(initPath)
+ msg = "destination init file missing: %s" % initPath
+ self.failUnless(exists, msg)
+
+
+ def checkNoBackup(self, path):
+ """Verify 'path' does not exist. (To check --nobackup.)
+ """
+ exists = os.path.exists(path)
+ msg = "backup file exists in spite of --nobackup: %s" % path
+ self.failIf(exists, msg)
+
+
+ def go(self, cmd, expectedStatus=0, expectedOutputSubstring=None):
+ """Run a "cheetah compile" or "cheetah fill" subcommand.
+
+ in : cmd, string, the command to run.
+ expectedStatus, int, subcommand's expected output status.
+ 0 if the subcommand is expected to succeed, 1-255 otherwise.
+ expectedOutputSubstring, string, substring which much appear
+ in the standard output or standard error. None to skip this
+ test.
+ out: None.
+ """
+ # Use commands.getstatusoutput instead of os.system so
+ # that we can mimic ">/dev/null 2>/dev/null" even on
+ # non-Unix platforms.
+ exit, output = commands.getstatusoutput(cmd)
+ status, signal = divmod(exit, 256)
+ if OUTPUT:
+ if output.endswith("\n"):
+ output = output[:-1]
+ print
+ print "SUBCOMMAND:", cmd
+ print output
+ print
+ msg = "subcommand killed by signal %d: %s" % (signal, cmd)
+ self.failUnlessEqual(signal, 0, msg)
+ msg = "subcommand exit status %d: %s" % (status, cmd)
+ if status!=expectedStatus:
+ print output
+ self.failUnlessEqual(status, expectedStatus, msg)
+ if expectedOutputSubstring is not None:
+ msg = "substring %r not found in subcommand output: %s" % \
+ (expectedOutputSubstring, cmd)
+ substringTest = output.find(expectedOutputSubstring) != -1
+ self.failUnless(substringTest, msg)
+
+
+ def goExpectError(self, cmd):
+ """Run a subcommand and expect it to fail.
+
+ in : cmd, string, the command to run.
+ out: None.
+ """
+ # Use commands.getstatusoutput instead of os.system so
+ # that we can mimic ">/dev/null 2>/dev/null" even on
+ # non-Unix platforms.
+ exit, output = commands.getstatusoutput(cmd)
+ status, signal = divmod(exit, 256)
+ msg = "subcommand killed by signal %s: %s" % (signal, cmd)
+ self.failUnlessEqual(signal, 0, msg) # Signal must be 0.
+ msg = "subcommand exit status %s: %s" % (status, cmd)
+ self.failIfEqual(status, 0, msg) # Status must *not* be 0.
+ if OUTPUT:
+ if output.endswith("\n"):
+ output = output[:-1]
+ print
+ print "SUBCOMMAND:", cmd
+ print output
+ print
+
+
+class CFIdirBase(CFBase):
+ """Subclass for tests with --idir.
+ """
+ srcDir = 'SRC'
+ subdirs = ('SRC/child', 'SRC/child/grandkid') # Delete in reverse order.
+ srcFiles = ('SRC/a.tmpl', 'SRC/child/a.tmpl', 'SRC/child/grandkid/a.tmpl')
+
+
+
+##################################################
+## TEST CASE CLASSES
+
+class OneFile(CFBase):
+ def testCompile(self):
+ self.go("cheetah compile a.tmpl")
+ self.checkCompile("a.py")
+
+ def testFill(self):
+ self.go("cheetah fill a.tmpl")
+ self.checkFill("a.html")
+
+ def testText(self):
+ self.go("cheetah fill --oext txt a.tmpl")
+ self.checkFill("a.txt")
+
+
+class OneFileNoExtension(CFBase):
+ def testCompile(self):
+ self.go("cheetah compile a")
+ self.checkCompile("a.py")
+
+ def testFill(self):
+ self.go("cheetah fill a")
+ self.checkFill("a.html")
+
+ def testText(self):
+ self.go("cheetah fill --oext txt a")
+ self.checkFill("a.txt")
+
+
+class SplatTmpl(CFBase):
+ def testCompile(self):
+ self.go("cheetah compile *.tmpl")
+ self.checkCompile("a.py")
+
+ def testFill(self):
+ self.go("cheetah fill *.tmpl")
+ self.checkFill("a.html")
+
+ def testText(self):
+ self.go("cheetah fill --oext txt *.tmpl")
+ self.checkFill("a.txt")
+
+class ThreeFilesWithSubdirectories(CFBase):
+ def testCompile(self):
+ self.go("cheetah compile a.tmpl child/a.tmpl child/grandkid/a.tmpl")
+ self.checkCompile("a.py")
+ self.checkCompile("child/a.py")
+ self.checkCompile("child/grandkid/a.py")
+
+ def testFill(self):
+ self.go("cheetah fill a.tmpl child/a.tmpl child/grandkid/a.tmpl")
+ self.checkFill("a.html")
+ self.checkFill("child/a.html")
+ self.checkFill("child/grandkid/a.html")
+
+ def testText(self):
+ self.go("cheetah fill --oext txt a.tmpl child/a.tmpl child/grandkid/a.tmpl")
+ self.checkFill("a.txt")
+ self.checkFill("child/a.txt")
+ self.checkFill("child/grandkid/a.txt")
+
+
+class ThreeFilesWithSubdirectoriesNoExtension(CFBase):
+ def testCompile(self):
+ self.go("cheetah compile a child/a child/grandkid/a")
+ self.checkCompile("a.py")
+ self.checkCompile("child/a.py")
+ self.checkCompile("child/grandkid/a.py")
+
+ def testFill(self):
+ self.go("cheetah fill a child/a child/grandkid/a")
+ self.checkFill("a.html")
+ self.checkFill("child/a.html")
+ self.checkFill("child/grandkid/a.html")
+
+ def testText(self):
+ self.go("cheetah fill --oext txt a child/a child/grandkid/a")
+ self.checkFill("a.txt")
+ self.checkFill("child/a.txt")
+ self.checkFill("child/grandkid/a.txt")
+
+
+class SplatTmplWithSubdirectories(CFBase):
+ def testCompile(self):
+ self.go("cheetah compile *.tmpl child/*.tmpl child/grandkid/*.tmpl")
+ self.checkCompile("a.py")
+ self.checkCompile("child/a.py")
+ self.checkCompile("child/grandkid/a.py")
+
+ def testFill(self):
+ self.go("cheetah fill *.tmpl child/*.tmpl child/grandkid/*.tmpl")
+ self.checkFill("a.html")
+ self.checkFill("child/a.html")
+ self.checkFill("child/grandkid/a.html")
+
+ def testText(self):
+ self.go("cheetah fill --oext txt *.tmpl child/*.tmpl child/grandkid/*.tmpl")
+ self.checkFill("a.txt")
+ self.checkFill("child/a.txt")
+ self.checkFill("child/grandkid/a.txt")
+
+
+class OneFileWithOdir(CFBase):
+ def testCompile(self):
+ self.go("cheetah compile --odir DEST a.tmpl")
+ self.checkSubdirPyInit("DEST")
+ self.checkCompile("DEST/a.py")
+
+ def testFill(self):
+ self.go("cheetah fill --odir DEST a.tmpl")
+ self.checkFill("DEST/a.html")
+
+ def testText(self):
+ self.go("cheetah fill --odir DEST --oext txt a.tmpl")
+ self.checkFill("DEST/a.txt")
+
+
+class VarietyWithOdir(CFBase):
+ def testCompile(self):
+ self.go("cheetah compile --odir DEST a.tmpl child/a child/grandkid/*.tmpl")
+ self.checkSubdirPyInit("DEST")
+ self.checkSubdirPyInit("DEST/child")
+ self.checkSubdirPyInit("DEST/child/grandkid")
+ self.checkCompile("DEST/a.py")
+ self.checkCompile("DEST/child/a.py")
+ self.checkCompile("DEST/child/grandkid/a.py")
+
+ def testFill(self):
+ self.go("cheetah fill --odir DEST a.tmpl child/a child/grandkid/*.tmpl")
+ self.checkFill("DEST/a.html")
+ self.checkFill("DEST/child/a.html")
+ self.checkFill("DEST/child/grandkid/a.html")
+
+ def testText(self):
+ self.go("cheetah fill --odir DEST --oext txt a.tmpl child/a child/grandkid/*.tmpl")
+ self.checkFill("DEST/a.txt")
+ self.checkFill("DEST/child/a.txt")
+ self.checkFill("DEST/child/grandkid/a.txt")
+
+
+class RecurseExplicit(CFBase):
+ def testCompile(self):
+ self.go("cheetah compile -R child")
+ self.checkCompile("child/a.py")
+ self.checkCompile("child/grandkid/a.py")
+
+ def testFill(self):
+ self.go("cheetah fill -R child")
+ self.checkFill("child/a.html")
+ self.checkFill("child/grandkid/a.html")
+
+ def testText(self):
+ self.go("cheetah fill -R --oext txt child")
+ self.checkFill("child/a.txt")
+ self.checkFill("child/grandkid/a.txt")
+
+
+class RecurseImplicit(CFBase):
+ def testCompile(self):
+ self.go("cheetah compile -R")
+ self.checkCompile("child/a.py")
+ self.checkCompile("child/grandkid/a.py")
+
+ def testFill(self):
+ self.go("cheetah fill -R")
+ self.checkFill("a.html")
+ self.checkFill("child/a.html")
+ self.checkFill("child/grandkid/a.html")
+
+ def testText(self):
+ self.go("cheetah fill -R --oext txt")
+ self.checkFill("a.txt")
+ self.checkFill("child/a.txt")
+ self.checkFill("child/grandkid/a.txt")
+
+
+class RecurseExplicitWIthOdir(CFBase):
+ def testCompile(self):
+ self.go("cheetah compile -R --odir DEST child")
+ self.checkSubdirPyInit("DEST/child")
+ self.checkSubdirPyInit("DEST/child/grandkid")
+ self.checkCompile("DEST/child/a.py")
+ self.checkCompile("DEST/child/grandkid/a.py")
+
+ def testFill(self):
+ self.go("cheetah fill -R --odir DEST child")
+ self.checkFill("DEST/child/a.html")
+ self.checkFill("DEST/child/grandkid/a.html")
+
+ def testText(self):
+ self.go("cheetah fill -R --odir DEST --oext txt child")
+ self.checkFill("DEST/child/a.txt")
+ self.checkFill("DEST/child/grandkid/a.txt")
+
+
+class Flat(CFBase):
+ def testCompile(self):
+ self.go("cheetah compile --flat child/a.tmpl")
+ self.checkCompile("a.py")
+
+ def testFill(self):
+ self.go("cheetah fill --flat child/a.tmpl")
+ self.checkFill("a.html")
+
+ def testText(self):
+ self.go("cheetah fill --flat --oext txt child/a.tmpl")
+ self.checkFill("a.txt")
+
+
+class FlatRecurseCollision(CFBase):
+ expectError = True
+
+ def testCompile(self):
+ self.goExpectError("cheetah compile -R --flat")
+
+ def testFill(self):
+ self.goExpectError("cheetah fill -R --flat")
+
+ def testText(self):
+ self.goExpectError("cheetah fill -R --flat")
+
+
+class IdirRecurse(CFIdirBase):
+ def testCompile(self):
+ self.go("cheetah compile -R --idir SRC child")
+ self.checkSubdirPyInit("child")
+ self.checkSubdirPyInit("child/grandkid")
+ self.checkCompile("child/a.py")
+ self.checkCompile("child/grandkid/a.py")
+
+ def testFill(self):
+ self.go("cheetah fill -R --idir SRC child")
+ self.checkFill("child/a.html")
+ self.checkFill("child/grandkid/a.html")
+
+ def testText(self):
+ self.go("cheetah fill -R --idir SRC --oext txt child")
+ self.checkFill("child/a.txt")
+ self.checkFill("child/grandkid/a.txt")
+
+
+class IdirOdirRecurse(CFIdirBase):
+ def testCompile(self):
+ self.go("cheetah compile -R --idir SRC --odir DEST child")
+ self.checkSubdirPyInit("DEST/child")
+ self.checkSubdirPyInit("DEST/child/grandkid")
+ self.checkCompile("DEST/child/a.py")
+ self.checkCompile("DEST/child/grandkid/a.py")
+
+ def testFill(self):
+ self.go("cheetah fill -R --idir SRC --odir DEST child")
+ self.checkFill("DEST/child/a.html")
+ self.checkFill("DEST/child/grandkid/a.html")
+
+ def testText(self):
+ self.go("cheetah fill -R --idir SRC --odir DEST --oext txt child")
+ self.checkFill("DEST/child/a.txt")
+ self.checkFill("DEST/child/grandkid/a.txt")
+
+
+class IdirFlatRecurseCollision(CFIdirBase):
+ expectError = True
+
+ def testCompile(self):
+ self.goExpectError("cheetah compile -R --flat --idir SRC")
+
+ def testFill(self):
+ self.goExpectError("cheetah fill -R --flat --idir SRC")
+
+ def testText(self):
+ self.goExpectError("cheetah fill -R --flat --idir SRC --oext txt")
+
+
+class NoBackup(CFBase):
+ """Run the command twice each time and verify a backup file is
+ *not* created.
+ """
+ def testCompile(self):
+ self.go("cheetah compile --nobackup a.tmpl")
+ self.go("cheetah compile --nobackup a.tmpl")
+ self.checkNoBackup("a.py" + BACKUP_SUFFIX)
+
+ def testFill(self):
+ self.go("cheetah fill --nobackup a.tmpl")
+ self.go("cheetah fill --nobackup a.tmpl")
+ self.checkNoBackup("a.html" + BACKUP_SUFFIX)
+
+ def testText(self):
+ self.go("cheetah fill --nobackup --oext txt a.tmpl")
+ self.go("cheetah fill --nobackup --oext txt a.tmpl")
+ self.checkNoBackup("a.txt" + BACKUP_SUFFIX)
+
+
+
+
+
+##################################################
+## LIST TESTS ##
+
+def listTests(cheetahWrapperFile):
+ """cheetahWrapperFile, string, path of this script.
+
+ XXX TODO: don't print test where expectError is true.
+ """
+ rx = re.compile( R'self\.go\("(.*?)"\)' )
+ f = open(cheetahWrapperFile)
+ while 1:
+ lin = f.readline()
+ if not lin:
+ break
+ m = rx.search(lin)
+ if m:
+ print m.group(1)
+ f.close()
+
+##################################################
+## MAIN ROUTINE ##
+
+class MyOptionParser(OptionParser):
+ """Disable the standard --help and --verbose options since
+ --help is used for another purpose.
+ """
+ standard_option_list = []
+
+def main():
+ global DELETE, OUTPUT
+ parser = MyOptionParser()
+ parser.add_option("--list", action="store", dest="listTests")
+ parser.add_option("--nodelete", action="store_true")
+ parser.add_option("--output", action="store_true")
+ # The following options are passed to unittest.
+ parser.add_option("-e", "--explain", action="store_true")
+ parser.add_option("-h", "--help", action="store_true")
+ parser.add_option("-v", "--verbose", action="store_true")
+ parser.add_option("-q", "--quiet", action="store_true")
+ opts, files = parser.parse_args()
+ if opts.nodelete:
+ DELETE = False
+ if opts.output:
+ OUTPUT = True
+ if opts.listTests:
+ listTests(opts.listTests)
+ else:
+ # Eliminate script-specific command-line arguments to prevent
+ # errors in unittest.
+ del sys.argv[1:]
+ for opt in ("explain", "help", "verbose", "quiet"):
+ if getattr(opts, opt):
+ sys.argv.append("--" + opt)
+ sys.argv.extend(files)
+ unittest.main()
+
+##################################################
+## if run from the command line ##
+
+if __name__ == '__main__': main()
+
+# vim: sw=4 ts=4 expandtab
diff --git a/cobbler/Cheetah/Tests/FileRefresh.py b/cobbler/Cheetah/Tests/FileRefresh.py
new file mode 100644
index 0000000..4beb3e7
--- /dev/null
+++ b/cobbler/Cheetah/Tests/FileRefresh.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+# $Id: FileRefresh.py,v 1.6 2002/10/01 17:52:03 tavis_rudd Exp $
+"""Tests to make sure that the file-update-monitoring code is working properly
+
+THIS TEST MODULE IS JUST A SHELL AT THE MOMENT. Feel like filling it in??
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com>,
+Version: $Revision: 1.6 $
+Start Date: 2001/10/01
+Last Revision Date: $Date: 2002/10/01 17:52:03 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.6 $"[11:-2]
+
+
+##################################################
+## DEPENDENCIES ##
+
+import sys
+import types
+import os
+import os.path
+
+
+import unittest_local_copy as unittest
+from Cheetah.Template import Template
+
+##################################################
+## CONSTANTS & GLOBALS ##
+
+try:
+ True,False
+except NameError:
+ True, False = (1==1),(1==0)
+
+##################################################
+## TEST DATA FOR USE IN THE TEMPLATES ##
+
+##################################################
+## TEST BASE CLASSES
+
+class TemplateTest(unittest.TestCase):
+ pass
+
+##################################################
+## TEST CASE CLASSES
+
+
+##################################################
+## if run from the command line ##
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/cobbler/Cheetah/Tests/NameMapper.py b/cobbler/Cheetah/Tests/NameMapper.py
new file mode 100644
index 0000000..2782463
--- /dev/null
+++ b/cobbler/Cheetah/Tests/NameMapper.py
@@ -0,0 +1,539 @@
+#!/usr/bin/env python
+# $Id: NameMapper.py,v 1.11 2006/01/15 20:45:22 tavis_rudd Exp $
+"""NameMapper Tests
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com>,
+Version: $Revision: 1.11 $
+Start Date: 2001/10/01
+Last Revision Date: $Date: 2006/01/15 20:45:22 $
+"""
+from __future__ import generators
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.11 $"[11:-2]
+import sys
+import types
+import os
+import os.path
+
+import unittest_local_copy as unittest
+from Cheetah.NameMapper import NotFound, valueForKey, \
+ valueForName, valueFromSearchList, valueFromFrame, valueFromFrameOrSearchList
+
+
+##################################################
+## TEST DATA FOR USE IN THE TEMPLATES ##
+
+class DummyClass:
+ classVar1 = 123
+
+ def __init__(self):
+ self.instanceVar1 = 123
+
+ def __str__(self):
+ return 'object'
+
+ def meth(self, arg="arff"):
+ return str(arg)
+
+ def meth1(self, arg="doo"):
+ return arg
+
+ def meth2(self, arg1="a1", arg2="a2"):
+ raise ValueError
+
+ def meth3(self):
+ """Tests a bug that Jeff Johnson reported on Oct 1, 2001"""
+
+ x = 'A string'
+ try:
+ for i in [1,2,3,4]:
+ if x == 2:
+ pass
+
+ if x == 'xx':
+ pass
+ return x
+ except:
+ raise
+
+
+def dummyFunc(arg="Scooby"):
+ return arg
+
+def funcThatRaises():
+ raise ValueError
+
+
+testNamespace = {
+ 'aStr':'blarg',
+ 'anInt':1,
+ 'aFloat':1.5,
+ 'aDict': {'one':'item1',
+ 'two':'item2',
+ 'nestedDict':{'one':'nestedItem1',
+ 'two':'nestedItem2',
+ 'funcThatRaises':funcThatRaises,
+ 'aClass': DummyClass,
+ },
+ 'nestedFunc':dummyFunc,
+ },
+ 'aClass': DummyClass,
+ 'aFunc': dummyFunc,
+ 'anObj': DummyClass(),
+ 'aMeth': DummyClass().meth1,
+ 'none' : None,
+ 'emptyString':'',
+ 'funcThatRaises':funcThatRaises,
+ }
+
+autoCallResults = {'aFunc':'Scooby',
+ 'aMeth':'doo',
+ }
+
+results = testNamespace.copy()
+results.update({'anObj.meth1':'doo',
+ 'aDict.one':'item1',
+ 'aDict.nestedDict':testNamespace['aDict']['nestedDict'],
+ 'aDict.nestedDict.one':'nestedItem1',
+ 'aDict.nestedDict.aClass':DummyClass,
+ 'aDict.nestedFunc':'Scooby',
+ 'aClass.classVar1':123,
+ 'anObj.instanceVar1':123,
+ 'anObj.meth3':'A string',
+ })
+
+for k in testNamespace.keys():
+ # put them in the globals for the valueFromFrame tests
+ exec '%s = testNamespace[k]'%k
+
+##################################################
+## TEST BASE CLASSES
+
+class NameMapperTest(unittest.TestCase):
+ failureException = (NotFound,AssertionError)
+ _testNamespace = testNamespace
+ _results = results
+
+ def namespace(self):
+ return self._testNamespace
+
+ def VFN(self, name, autocall=True):
+ return valueForName(self.namespace(), name, autocall)
+
+ def VFS(self, searchList, name, autocall=True):
+ return valueFromSearchList(searchList, name, autocall)
+
+
+ # alias to be overriden later
+ get = VFN
+
+ def check(self, name):
+ got = self.get(name)
+ if autoCallResults.has_key(name):
+ expected = autoCallResults[name]
+ else:
+ expected = self._results[name]
+ assert got == expected
+
+
+##################################################
+## TEST CASE CLASSES
+
+class VFN(NameMapperTest):
+
+ def test1(self):
+ """string in dict lookup"""
+ self.check('aStr')
+
+ def test2(self):
+ """string in dict lookup in a loop"""
+ for i in range(10):
+ self.check('aStr')
+
+ def test3(self):
+ """int in dict lookup"""
+ self.check('anInt')
+
+ def test4(self):
+ """int in dict lookup in a loop"""
+ for i in range(10):
+ self.check('anInt')
+
+ def test5(self):
+ """float in dict lookup"""
+ self.check('aFloat')
+
+ def test6(self):
+ """float in dict lookup in a loop"""
+ for i in range(10):
+ self.check('aFloat')
+
+ def test7(self):
+ """class in dict lookup"""
+ self.check('aClass')
+
+ def test8(self):
+ """class in dict lookup in a loop"""
+ for i in range(10):
+ self.check('aClass')
+
+ def test9(self):
+ """aFunc in dict lookup"""
+ self.check('aFunc')
+
+ def test10(self):
+ """aFunc in dict lookup in a loop"""
+ for i in range(10):
+ self.check('aFunc')
+
+ def test11(self):
+ """aMeth in dict lookup"""
+ self.check('aMeth')
+
+ def test12(self):
+ """aMeth in dict lookup in a loop"""
+ for i in range(10):
+ self.check('aMeth')
+
+ def test13(self):
+ """aMeth in dict lookup"""
+ self.check('aMeth')
+
+ def test14(self):
+ """aMeth in dict lookup in a loop"""
+ for i in range(10):
+ self.check('aMeth')
+
+ def test15(self):
+ """anObj in dict lookup"""
+ self.check('anObj')
+
+ def test16(self):
+ """anObj in dict lookup in a loop"""
+ for i in range(10):
+ self.check('anObj')
+
+ def test17(self):
+ """aDict in dict lookup"""
+ self.check('aDict')
+
+ def test18(self):
+ """aDict in dict lookup in a loop"""
+ for i in range(10):
+ self.check('aDict')
+
+ def test17(self):
+ """aDict in dict lookup"""
+ self.check('aDict')
+
+ def test18(self):
+ """aDict in dict lookup in a loop"""
+ for i in range(10):
+ self.check('aDict')
+
+ def test19(self):
+ """aClass.classVar1 in dict lookup"""
+ self.check('aClass.classVar1')
+
+ def test20(self):
+ """aClass.classVar1 in dict lookup in a loop"""
+ for i in range(10):
+ self.check('aClass.classVar1')
+
+
+ def test23(self):
+ """anObj.instanceVar1 in dict lookup"""
+ self.check('anObj.instanceVar1')
+
+ def test24(self):
+ """anObj.instanceVar1 in dict lookup in a loop"""
+ for i in range(10):
+ self.check('anObj.instanceVar1')
+
+ ## tests 22, 25, and 26 removed when the underscored lookup was removed
+
+ def test27(self):
+ """anObj.meth1 in dict lookup"""
+ self.check('anObj.meth1')
+
+ def test28(self):
+ """anObj.meth1 in dict lookup in a loop"""
+ for i in range(10):
+ self.check('anObj.meth1')
+
+ def test29(self):
+ """aDict.one in dict lookup"""
+ self.check('aDict.one')
+
+ def test30(self):
+ """aDict.one in dict lookup in a loop"""
+ for i in range(10):
+ self.check('aDict.one')
+
+ def test31(self):
+ """aDict.nestedDict in dict lookup"""
+ self.check('aDict.nestedDict')
+
+ def test32(self):
+ """aDict.nestedDict in dict lookup in a loop"""
+ for i in range(10):
+ self.check('aDict.nestedDict')
+
+ def test33(self):
+ """aDict.nestedDict.one in dict lookup"""
+ self.check('aDict.nestedDict.one')
+
+ def test34(self):
+ """aDict.nestedDict.one in dict lookup in a loop"""
+ for i in range(10):
+ self.check('aDict.nestedDict.one')
+
+ def test35(self):
+ """aDict.nestedFunc in dict lookup"""
+ self.check('aDict.nestedFunc')
+
+ def test36(self):
+ """aDict.nestedFunc in dict lookup in a loop"""
+ for i in range(10):
+ self.check('aDict.nestedFunc')
+
+ def test37(self):
+ """aDict.nestedFunc in dict lookup - without autocalling"""
+ assert self.get('aDict.nestedFunc', False) == dummyFunc
+
+ def test38(self):
+ """aDict.nestedFunc in dict lookup in a loop - without autocalling"""
+ for i in range(10):
+ assert self.get('aDict.nestedFunc', False) == dummyFunc
+
+ def test39(self):
+ """aMeth in dict lookup - without autocalling"""
+ assert self.get('aMeth', False) == self.namespace()['aMeth']
+
+ def test40(self):
+ """aMeth in dict lookup in a loop - without autocalling"""
+ for i in range(10):
+ assert self.get('aMeth', False) == self.namespace()['aMeth']
+
+ def test41(self):
+ """anObj.meth3 in dict lookup"""
+ self.check('anObj.meth3')
+
+ def test42(self):
+ """aMeth in dict lookup in a loop"""
+ for i in range(10):
+ self.check('anObj.meth3')
+
+ def test43(self):
+ """NotFound test"""
+
+ def test(self=self):
+ self.get('anObj.methX')
+ self.assertRaises(NotFound,test)
+
+ def test44(self):
+ """NotFound test in a loop"""
+ def test(self=self):
+ self.get('anObj.methX')
+
+ for i in range(10):
+ self.assertRaises(NotFound,test)
+
+ def test45(self):
+ """Other exception from meth test"""
+
+ def test(self=self):
+ self.get('anObj.meth2')
+ self.assertRaises(ValueError, test)
+
+ def test46(self):
+ """Other exception from meth test in a loop"""
+ def test(self=self):
+ self.get('anObj.meth2')
+
+ for i in range(10):
+ self.assertRaises(ValueError,test)
+
+ def test47(self):
+ """None in dict lookup"""
+ self.check('none')
+
+ def test48(self):
+ """None in dict lookup in a loop"""
+ for i in range(10):
+ self.check('none')
+
+ def test49(self):
+ """EmptyString in dict lookup"""
+ self.check('emptyString')
+
+ def test50(self):
+ """EmptyString in dict lookup in a loop"""
+ for i in range(10):
+ self.check('emptyString')
+
+ def test51(self):
+ """Other exception from func test"""
+
+ def test(self=self):
+ self.get('funcThatRaises')
+ self.assertRaises(ValueError, test)
+
+ def test52(self):
+ """Other exception from func test in a loop"""
+ def test(self=self):
+ self.get('funcThatRaises')
+
+ for i in range(10):
+ self.assertRaises(ValueError,test)
+
+
+ def test53(self):
+ """Other exception from func test"""
+
+ def test(self=self):
+ self.get('aDict.nestedDict.funcThatRaises')
+ self.assertRaises(ValueError, test)
+
+ def test54(self):
+ """Other exception from func test in a loop"""
+ def test(self=self):
+ self.get('aDict.nestedDict.funcThatRaises')
+
+ for i in range(10):
+ self.assertRaises(ValueError,test)
+
+ def test55(self):
+ """aDict.nestedDict.aClass in dict lookup"""
+ self.check('aDict.nestedDict.aClass')
+
+ def test56(self):
+ """aDict.nestedDict.aClass in dict lookup in a loop"""
+ for i in range(10):
+ self.check('aDict.nestedDict.aClass')
+
+ def test57(self):
+ """aDict.nestedDict.aClass in dict lookup - without autocalling"""
+ assert self.get('aDict.nestedDict.aClass', False) == DummyClass
+
+ def test58(self):
+ """aDict.nestedDict.aClass in dict lookup in a loop - without autocalling"""
+ for i in range(10):
+ assert self.get('aDict.nestedDict.aClass', False) == DummyClass
+
+ def test59(self):
+ """Other exception from func test -- but without autocalling shouldn't raise"""
+
+ self.get('aDict.nestedDict.funcThatRaises', False)
+
+ def test60(self):
+ """Other exception from func test in a loop -- but without autocalling shouldn't raise"""
+
+ for i in range(10):
+ self.get('aDict.nestedDict.funcThatRaises', False)
+
+class VFS(VFN):
+ _searchListLength = 1
+
+ def searchList(self):
+ lng = self._searchListLength
+ if lng == 1:
+ return [self.namespace()]
+ elif lng == 2:
+ return [self.namespace(),{'dummy':1234}]
+ elif lng == 3:
+ # a tuple for kicks
+ return ({'dummy':1234}, self.namespace(),{'dummy':1234})
+ elif lng == 4:
+ # a generator for more kicks
+ return self.searchListGenerator()
+
+ def searchListGenerator(self):
+ class Test:
+ pass
+ for i in [Test(),{'dummy':1234}, self.namespace(),{'dummy':1234}]:
+ yield i
+
+ def get(self, name, autocall=True):
+ return self.VFS(self.searchList(), name, autocall)
+
+class VFS_2namespaces(VFS):
+ _searchListLength = 2
+
+class VFS_3namespaces(VFS):
+ _searchListLength = 3
+
+class VFS_4namespaces(VFS):
+ _searchListLength = 4
+
+class VFF(VFN):
+ def get(self, name, autocall=True):
+ ns = self._testNamespace
+ aStr = ns['aStr']
+ aFloat = ns['aFloat']
+ none = 'some'
+ return valueFromFrame(name, autocall)
+
+ def setUp(self):
+ """Mod some of the data
+ """
+ self._testNamespace = ns = self._testNamespace.copy()
+ self._results = res = self._results.copy()
+ ns['aStr'] = res['aStr'] = 'BLARG'
+ ns['aFloat'] = res['aFloat'] = 0.1234
+ res['none'] = 'some'
+ res['True'] = True
+ res['False'] = False
+ res['None'] = None
+ res['eval'] = eval
+
+ def test_VFF_1(self):
+ """Builtins"""
+ self.check('True')
+ self.check('None')
+ self.check('False')
+ assert self.get('eval', False)==eval
+ assert self.get('range', False)==range
+
+class VFFSL(VFS):
+ _searchListLength = 1
+
+ def setUp(self):
+ """Mod some of the data
+ """
+ self._testNamespace = ns = self._testNamespace.copy()
+ self._results = res = self._results.copy()
+ ns['aStr'] = res['aStr'] = 'BLARG'
+ ns['aFloat'] = res['aFloat'] = 0.1234
+ res['none'] = 'some'
+
+ del ns['anInt'] # will be picked up by globals
+
+ def VFFSL(self, searchList, name, autocall=True):
+ anInt = 1
+ none = 'some'
+ return valueFromFrameOrSearchList(searchList, name, autocall)
+
+ def get(self, name, autocall=True):
+ return self.VFFSL(self.searchList(), name, autocall)
+
+class VFFSL_2(VFFSL):
+ _searchListLength = 2
+
+class VFFSL_3(VFFSL):
+ _searchListLength = 3
+
+class VFFSL_4(VFFSL):
+ _searchListLength = 4
+
+if sys.platform.startswith('java'):
+ del VFF, VFFSL, VFFSL_2, VFFSL_3, VFFSL_4
+
+
+##################################################
+## if run from the command line ##
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/cobbler/Cheetah/Tests/SyntaxAndOutput.py b/cobbler/Cheetah/Tests/SyntaxAndOutput.py
new file mode 100644
index 0000000..09abc4d
--- /dev/null
+++ b/cobbler/Cheetah/Tests/SyntaxAndOutput.py
@@ -0,0 +1,3170 @@
+#!/usr/bin/env python
+# $Id: SyntaxAndOutput.py,v 1.105 2006/06/21 23:48:19 tavis_rudd Exp $
+"""Syntax and Output tests.
+
+TODO
+- #finally
+- #filter
+- #errorCatcher
+- #echo
+- #silent
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com>
+Version: $Revision: 1.105 $
+Start Date: 2001/03/30
+Last Revision Date: $Date: 2006/06/21 23:48:19 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.105 $"[11:-2]
+
+
+##################################################
+## DEPENDENCIES ##
+
+import sys
+import types
+import re
+from copy import deepcopy
+import os
+import os.path
+import new
+import warnings
+
+from Cheetah.NameMapper import NotFound
+from Cheetah.NameMapper import C_VERSION as NameMapper_C_VERSION
+from Cheetah.Template import Template
+from Cheetah.Parser import ParseError
+from Cheetah.Compiler import Compiler, DEFAULT_COMPILER_SETTINGS
+import unittest_local_copy as unittest
+
+class Unspecified: pass
+##################################################
+## CONSTANTS & GLOBALS ##
+
+majorVer, minorVer = sys.version_info[0], sys.version_info[1]
+versionTuple = (majorVer, minorVer)
+
+try:
+ True,False
+except NameError:
+ True, False = (1==1),(1==0)
+
+##################################################
+## TEST DATA FOR USE IN THE TEMPLATES ##
+
+def testdecorator(func):
+ return func
+
+class DummyClass:
+ _called = False
+ def __str__(self):
+ return 'object'
+
+ def meth(self, arg="arff"):
+ return str(arg)
+
+ def meth1(self, arg="doo"):
+ return arg
+
+ def meth2(self, arg1="a1", arg2="a2"):
+ return str(arg1) + str(arg2)
+
+ def methWithPercentSignDefaultArg(self, arg1="110%"):
+ return str(arg1)
+
+ def callIt(self, arg=1234):
+ self._called = True
+ self._callArg = arg
+
+
+def dummyFunc(arg="Scooby"):
+ return arg
+
+defaultTestNameSpace = {
+ 'aStr':'blarg',
+ 'anInt':1,
+ 'aFloat':1.5,
+ 'aList': ['item0','item1','item2'],
+ 'aDict': {'one':'item1',
+ 'two':'item2',
+ 'nestedDict':{1:'nestedItem1',
+ 'two':'nestedItem2'
+ },
+ 'nestedFunc':dummyFunc,
+ },
+ 'aFunc': dummyFunc,
+ 'anObj': DummyClass(),
+ 'aMeth': DummyClass().meth1,
+ 'aStrToBeIncluded': "$aStr $anInt",
+ 'none' : None,
+ 'emptyString':'',
+ 'numOne':1,
+ 'numTwo':2,
+ 'zero':0,
+ 'tenDigits': 1234567890,
+ 'webSafeTest': 'abc <=> &',
+ 'strip1': ' \t strippable whitespace \t\t \n',
+ 'strip2': ' \t strippable whitespace \t\t ',
+ 'strip3': ' \t strippable whitespace \t\t\n1 2 3\n',
+
+ 'blockToBeParsed':"""$numOne $numTwo""",
+ 'includeBlock2':"""$numOne $numTwo $aSetVar""",
+
+ 'includeFileName':'parseTest.txt',
+ 'listOfLambdas':[lambda x: x, lambda x: x, lambda x: x,],
+ 'list': [
+ {'index': 0, 'numOne': 1, 'numTwo': 2},
+ {'index': 1, 'numOne': 1, 'numTwo': 2},
+ ],
+ 'nameList': [('john', 'doe'), ('jane', 'smith')],
+ 'letterList': ['a', 'b', 'c'],
+ '_': lambda x: 'Translated: ' + x,
+ 'unicodeData':u'aoeu12345\u1234',
+ }
+
+
+##################################################
+## TEST BASE CLASSES
+
+class OutputTest(unittest.TestCase):
+ report = '''
+Template output mismatch:
+
+ Input Template =
+%(template)s%(end)s
+
+ Expected Output =
+%(expected)s%(end)s
+
+ Actual Output =
+%(actual)s%(end)s'''
+
+ convertEOLs = True
+ _EOLreplacement = None
+ _debugEOLReplacement = False
+
+ DEBUGLEV = 0
+ _searchList = [defaultTestNameSpace]
+
+ _useNewStyleCompilation = True
+ #_useNewStyleCompilation = False
+
+ _extraCompileKwArgs = None
+
+ def searchList(self):
+ return self._searchList
+
+ def verify(self, input, expectedOutput,
+ inputEncoding=None,
+ outputEncoding=None,
+ convertEOLs=Unspecified):
+ if self._EOLreplacement:
+ if convertEOLs is Unspecified:
+ convertEOLs = self.convertEOLs
+ if convertEOLs:
+ input = input.replace('\n', self._EOLreplacement)
+ expectedOutput = expectedOutput.replace('\n', self._EOLreplacement)
+
+ self._input = input
+ if self._useNewStyleCompilation:
+ extraKwArgs = self._extraCompileKwArgs or {}
+
+ templateClass = Template.compile(
+ source=input,
+ compilerSettings=self._getCompilerSettings(),
+ keepRefToGeneratedCode=True,
+ **extraKwArgs
+ )
+ moduleCode = templateClass._CHEETAH_generatedModuleCode
+ self.template = templateObj = templateClass(searchList=self.searchList())
+ else:
+ self.template = templateObj = Template(
+ input,
+ searchList=self.searchList(),
+ compilerSettings=self._getCompilerSettings(),
+ )
+ moduleCode = templateObj._CHEETAH_generatedModuleCode
+ if self.DEBUGLEV >= 1:
+ print moduleCode
+ try:
+ try:
+ output = templateObj.respond() # rather than __str__, because of unicode
+ if outputEncoding:
+ output = output.decode(outputEncoding)
+ assert output==expectedOutput, self._outputMismatchReport(output, expectedOutput)
+ except:
+ #print >>sys.stderr, moduleCode
+ raise
+ finally:
+ templateObj.shutdown()
+
+ def _getCompilerSettings(self):
+ return {}
+
+ def _outputMismatchReport(self, output, expectedOutput):
+ if self._debugEOLReplacement and self._EOLreplacement:
+ EOLrepl = self._EOLreplacement
+ marker = '*EOL*'
+ return self.report % {'template': self._input.replace(EOLrepl,marker),
+ 'expected': expectedOutput.replace(EOLrepl,marker),
+ 'actual': output.replace(EOLrepl,marker),
+ 'end': '(end)'}
+ else:
+ return self.report % {'template': self._input,
+ 'expected': expectedOutput,
+ 'actual': output,
+ 'end': '(end)'}
+
+ def genClassCode(self):
+ if hasattr(self, 'template'):
+ return self.template.generatedClassCode()
+
+ def genModuleCode(self):
+ if hasattr(self, 'template'):
+ return self.template.generatedModuleCode()
+
+##################################################
+## TEST CASE CLASSES
+
+class EmptyTemplate(OutputTest):
+ convertEOLs = False
+ def test1(self):
+ """an empty string for the template"""
+
+ warnings.filterwarnings('error',
+ 'You supplied an empty string for the source!',
+ UserWarning)
+ try:
+ self.verify("", "")
+ except UserWarning:
+ pass
+ else:
+ self.fail("Should warn about empty source strings.")
+
+ try:
+ self.verify("#implements foo", "")
+ except NotImplementedError:
+ pass
+ else:
+ self.fail("This should barf about respond() not being implemented.")
+
+ self.verify("#implements respond", "")
+
+ self.verify("#implements respond(foo=1234)", "")
+
+
+class Backslashes(OutputTest):
+ convertEOLs = False
+
+ def setUp(self):
+ fp = open('backslashes.txt','w')
+ fp.write(r'\ #LogFormat "%h %l %u %t \"%r\" %>s %b"' + '\n\n\n\n\n\n\n')
+ fp.flush()
+ fp.close
+
+ def tearDown(self):
+ if os.path.exists('backslashes.txt'):
+ os.remove('backslashes.txt')
+
+ def test1(self):
+ """ a single \\ using rawstrings"""
+ self.verify(r"\ ",
+ r"\ ")
+
+ def test2(self):
+ """ a single \\ using rawstrings and lots of lines"""
+ self.verify(r"\ " + "\n\n\n\n\n\n\n\n\n",
+ r"\ " + "\n\n\n\n\n\n\n\n\n")
+
+ def test3(self):
+ """ a single \\ without using rawstrings"""
+ self.verify("\ \ ",
+ "\ \ ")
+
+ def test4(self):
+ """ single line from an apache conf file"""
+ self.verify(r'#LogFormat "%h %l %u %t \"%r\" %>s %b"',
+ r'#LogFormat "%h %l %u %t \"%r\" %>s %b"')
+
+ def test5(self):
+ """ single line from an apache conf file with many NEWLINES
+
+ The NEWLINES are used to make sure that MethodCompiler.commitStrConst()
+ is handling long and short strings in the same fashion. It uses
+ triple-quotes for strings with lots of \\n in them and repr(theStr) for
+ shorter strings with only a few newlines."""
+
+ self.verify(r'#LogFormat "%h %l %u %t \"%r\" %>s %b"' + '\n\n\n\n\n\n\n',
+ r'#LogFormat "%h %l %u %t \"%r\" %>s %b"' + '\n\n\n\n\n\n\n')
+
+ def test6(self):
+ """ test backslash handling in an included file"""
+ self.verify(r'#include "backslashes.txt"',
+ r'\ #LogFormat "%h %l %u %t \"%r\" %>s %b"' + '\n\n\n\n\n\n\n')
+
+ def test7(self):
+ """ a single \\ without using rawstrings plus many NEWLINES"""
+ self.verify("\ \ " + "\n\n\n\n\n\n\n\n\n",
+ "\ \ " + "\n\n\n\n\n\n\n\n\n")
+
+ def test8(self):
+ """ single line from an apache conf file with single quotes and many NEWLINES
+ """
+
+ self.verify(r"""#LogFormat '%h %l %u %t \"%r\" %>s %b'""" + '\n\n\n\n\n\n\n',
+ r"""#LogFormat '%h %l %u %t \"%r\" %>s %b'""" + '\n\n\n\n\n\n\n')
+
+class NonTokens(OutputTest):
+ def test1(self):
+ """dollar signs not in Cheetah $vars"""
+ self.verify("$ $$ $5 $. $ test",
+ "$ $$ $5 $. $ test")
+
+ def test2(self):
+ """hash not in #directives"""
+ self.verify("# \# #5 ",
+ "# # #5 ")
+
+ def test3(self):
+ """escapted comments"""
+ self.verify(" \##escaped comment ",
+ " ##escaped comment ")
+
+ def test4(self):
+ """escapted multi-line comments"""
+ self.verify(" \#*escaped comment \n*# ",
+ " #*escaped comment \n*# ")
+
+ def test5(self):
+ """1 dollar sign"""
+ self.verify("$",
+ "$")
+ def _X_test6(self):
+ """1 dollar sign followed by hash"""
+ self.verify("\n$#\n",
+ "\n$#\n")
+
+ def test6(self):
+ """1 dollar sign followed by EOL Slurp Token"""
+ if DEFAULT_COMPILER_SETTINGS['EOLSlurpToken']:
+ self.verify("\n$%s\n"%DEFAULT_COMPILER_SETTINGS['EOLSlurpToken'],
+ "\n$")
+ else:
+ self.verify("\n$#\n",
+ "\n$#\n")
+
+class Comments_SingleLine(OutputTest):
+ def test1(self):
+ """## followed by WS"""
+ self.verify("## ",
+ "")
+
+ def test2(self):
+ """## followed by NEWLINE"""
+ self.verify("##\n",
+ "")
+
+ def test3(self):
+ """## followed by text then NEWLINE"""
+ self.verify("## oeuao aoe uaoe \n",
+ "")
+ def test4(self):
+ """## gobbles leading WS"""
+ self.verify(" ## oeuao aoe uaoe \n",
+ "")
+
+ def test5(self):
+ """## followed by text then NEWLINE, + leading WS"""
+ self.verify(" ## oeuao aoe uaoe \n",
+ "")
+
+ def test6(self):
+ """## followed by EOF"""
+ self.verify("##",
+ "")
+
+ def test7(self):
+ """## followed by EOF with leading WS"""
+ self.verify(" ##",
+ "")
+
+ def test8(self):
+ """## gobble line
+ with text on previous and following lines"""
+ self.verify("line1\n ## aoeu 1234 \nline2",
+ "line1\nline2")
+
+ def test9(self):
+ """## don't gobble line
+ with text on previous and following lines"""
+ self.verify("line1\n 12 ## aoeu 1234 \nline2",
+ "line1\n 12 \nline2")
+
+ def test10(self):
+ """## containing $placeholders
+ """
+ self.verify("##$a$b $c($d)",
+ "")
+
+ def test11(self):
+ """## containing #for directive
+ """
+ self.verify("##for $i in range(15)",
+ "")
+
+
+class Comments_MultiLine_NoGobble(OutputTest):
+ """
+ Multiline comments used to not gobble whitespace. They do now, but this can
+ be turned off with a compilerSetting
+ """
+
+ def _getCompilerSettings(self):
+ return {'gobbleWhitespaceAroundMultiLineComments':False}
+
+ def test1(self):
+ """#* *# followed by WS
+ Shouldn't gobble WS
+ """
+ self.verify("#* blarg *# ",
+ " ")
+
+ def test2(self):
+ """#* *# preceded and followed by WS
+ Shouldn't gobble WS
+ """
+ self.verify(" #* blarg *# ",
+ " ")
+
+ def test3(self):
+ """#* *# followed by WS, with NEWLINE
+ Shouldn't gobble WS
+ """
+ self.verify("#* \nblarg\n *# ",
+ " ")
+
+ def test4(self):
+ """#* *# preceded and followed by WS, with NEWLINE
+ Shouldn't gobble WS
+ """
+ self.verify(" #* \nblarg\n *# ",
+ " ")
+
+class Comments_MultiLine(OutputTest):
+ """
+ Note: Multiline comments don't gobble whitespace!
+ """
+
+ def test1(self):
+ """#* *# followed by WS
+ Should gobble WS
+ """
+ self.verify("#* blarg *# ",
+ "")
+
+ def test2(self):
+ """#* *# preceded and followed by WS
+ Should gobble WS
+ """
+ self.verify(" #* blarg *# ",
+ "")
+
+ def test3(self):
+ """#* *# followed by WS, with NEWLINE
+ Shouldn't gobble WS
+ """
+ self.verify("#* \nblarg\n *# ",
+ "")
+
+ def test4(self):
+ """#* *# preceded and followed by WS, with NEWLINE
+ Shouldn't gobble WS
+ """
+ self.verify(" #* \nblarg\n *# ",
+ "")
+
+ def test5(self):
+ """#* *# containing nothing
+ """
+ self.verify("#**#",
+ "")
+
+ def test6(self):
+ """#* *# containing only NEWLINES
+ """
+ self.verify(" #*\n\n\n\n\n\n\n\n*# ",
+ "")
+
+ def test7(self):
+ """#* *# containing $placeholders
+ """
+ self.verify("#* $var $var(1234*$c) *#",
+ "")
+
+ def test8(self):
+ """#* *# containing #for directive
+ """
+ self.verify("#* #for $i in range(15) *#",
+ "")
+
+ def test9(self):
+ """ text around #* *# containing #for directive
+ """
+ self.verify("foo\nfoo bar #* #for $i in range(15) *# foo\n",
+ "foo\nfoo bar foo\n")
+
+ def test9(self):
+ """ text around #* *# containing #for directive and trailing whitespace
+ which should be gobbled
+ """
+ self.verify("foo\nfoo bar #* #for $i in range(15) *# \ntest",
+ "foo\nfoo bar \ntest")
+
+ def test10(self):
+ """ text around #* *# containing #for directive and newlines: trailing whitespace
+ which should be gobbled.
+ """
+ self.verify("foo\nfoo bar #* \n\n#for $i in range(15) \n\n*# \ntest",
+ "foo\nfoo bar \ntest")
+
+class Placeholders(OutputTest):
+ def test1(self):
+ """1 placeholder"""
+ self.verify("$aStr", "blarg")
+
+ def test2(self):
+ """2 placeholders"""
+ self.verify("$aStr $anInt", "blarg 1")
+
+ def test3(self):
+ """2 placeholders, back-to-back"""
+ self.verify("$aStr$anInt", "blarg1")
+
+ def test4(self):
+ """1 placeholder enclosed in ()"""
+ self.verify("$(aStr)", "blarg")
+
+ def test5(self):
+ """1 placeholder enclosed in {}"""
+ self.verify("${aStr}", "blarg")
+
+ def test6(self):
+ """1 placeholder enclosed in []"""
+ self.verify("$[aStr]", "blarg")
+
+ def test7(self):
+ """1 placeholder enclosed in () + WS
+
+ Test to make sure that $(<WS><identifier>.. matches
+ """
+ self.verify("$( aStr )", "blarg")
+
+ def test8(self):
+ """1 placeholder enclosed in {} + WS"""
+ self.verify("${ aStr }", "blarg")
+
+ def test9(self):
+ """1 placeholder enclosed in [] + WS"""
+ self.verify("$[ aStr ]", "blarg")
+
+ def test10(self):
+ """1 placeholder enclosed in () + WS + * cache
+
+ Test to make sure that $*(<WS><identifier>.. matches
+ """
+ self.verify("$*( aStr )", "blarg")
+
+ def test11(self):
+ """1 placeholder enclosed in {} + WS + *cache"""
+ self.verify("$*{ aStr }", "blarg")
+
+ def test12(self):
+ """1 placeholder enclosed in [] + WS + *cache"""
+ self.verify("$*[ aStr ]", "blarg")
+
+ def test13(self):
+ """1 placeholder enclosed in {} + WS + *<int>*cache"""
+ self.verify("$*5*{ aStr }", "blarg")
+
+ def test14(self):
+ """1 placeholder enclosed in [] + WS + *<int>*cache"""
+ self.verify("$*5*[ aStr ]", "blarg")
+
+ def test15(self):
+ """1 placeholder enclosed in {} + WS + *<float>*cache"""
+ self.verify("$*0.5d*{ aStr }", "blarg")
+
+ def test16(self):
+ """1 placeholder enclosed in [] + WS + *<float>*cache"""
+ self.verify("$*.5*[ aStr ]", "blarg")
+
+ def test17(self):
+ """1 placeholder + *<int>*cache"""
+ self.verify("$*5*aStr", "blarg")
+
+ def test18(self):
+ """1 placeholder *<float>*cache"""
+ self.verify("$*0.5h*aStr", "blarg")
+
+ def test19(self):
+ """1 placeholder surrounded by single quotes and multiple newlines"""
+ self.verify("""'\n\n\n\n'$aStr'\n\n\n\n'""",
+ """'\n\n\n\n'blarg'\n\n\n\n'""")
+
+ def test20(self):
+ """silent mode $!placeholders """
+ self.verify("$!aStr$!nonExistant$!*nonExistant$!{nonExistant}", "blarg")
+
+ try:
+ self.verify("$!aStr$nonExistant",
+ "blarg")
+ except NotFound:
+ pass
+ else:
+ self.fail('should raise NotFound exception')
+
+ def test21(self):
+ """Make sure that $*caching is actually working"""
+ namesStr = 'You Me Them Everyone'
+ names = namesStr.split()
+
+ tmpl = Template.compile('#for name in $names: $name ', baseclass=dict)
+ assert str(tmpl({'names':names})).strip()==namesStr
+
+ tmpl = tmpl.subclass('#for name in $names: $*name ')
+ assert str(tmpl({'names':names}))=='You '*len(names)
+
+ tmpl = tmpl.subclass('#for name in $names: $*1*name ')
+ assert str(tmpl({'names':names}))=='You '*len(names)
+
+ tmpl = tmpl.subclass('#for name in $names: $*1*(name) ')
+ assert str(tmpl({'names':names}))=='You '*len(names)
+
+ if versionTuple > (2,2):
+ tmpl = tmpl.subclass('#for name in $names: $*1*(name) ')
+ assert str(tmpl(names=names))=='You '*len(names)
+
+class Placeholders_Vals(OutputTest):
+ convertEOLs = False
+ def test1(self):
+ """string"""
+ self.verify("$aStr", "blarg")
+
+ def test2(self):
+ """string - with whitespace"""
+ self.verify(" $aStr ", " blarg ")
+
+ def test3(self):
+ """empty string - with whitespace"""
+ self.verify("$emptyString", "")
+
+ def test4(self):
+ """int"""
+ self.verify("$anInt", "1")
+
+ def test5(self):
+ """float"""
+ self.verify("$aFloat", "1.5")
+
+ def test6(self):
+ """list"""
+ self.verify("$aList", "['item0', 'item1', 'item2']")
+
+ def test7(self):
+ """None
+
+ The default output filter is ReplaceNone.
+ """
+ self.verify("$none", "")
+
+ def test8(self):
+ """True, False
+ """
+ self.verify("$True $False", "%s %s"%(repr(True), repr(False)))
+
+ def test9(self):
+ """$_
+ """
+ self.verify("$_('foo')", "Translated: foo")
+
+class PlaceholderStrings(OutputTest):
+ def test1(self):
+ """some c'text $placeholder text' strings"""
+ self.verify("$str(c'$aStr')", "blarg")
+
+ def test2(self):
+ """some c'text $placeholder text' strings"""
+ self.verify("$str(c'$aStr.upper')", "BLARG")
+
+ def test3(self):
+ """some c'text $placeholder text' strings"""
+ self.verify("$str(c'$(aStr.upper.replace(c\"A$str()\",\"\"))')", "BLRG")
+
+ def test4(self):
+ """some c'text $placeholder text' strings"""
+ self.verify("#echo $str(c'$(aStr.upper)')", "BLARG")
+
+ def test5(self):
+ """some c'text $placeholder text' strings"""
+ self.verify("#if 1 then $str(c'$(aStr.upper)') else 0", "BLARG")
+
+ def test6(self):
+ """some c'text $placeholder text' strings"""
+ self.verify("#if 1\n$str(c'$(aStr.upper)')#slurp\n#else\n0#end if", "BLARG")
+
+ def test7(self):
+ """some c'text $placeholder text' strings"""
+ self.verify("#def foo(arg=c'$(\"BLARG\")')\n"
+ "$arg#slurp\n"
+ "#end def\n"
+ "$foo()$foo(c'$anInt')#slurp",
+
+ "BLARG1")
+
+
+
+class UnicodeStrings(OutputTest):
+ def test1(self):
+ """unicode data in placeholder
+ """
+ #self.verify(u"$unicodeData", defaultTestNameSpace['unicodeData'], outputEncoding='utf8')
+ self.verify(u"$unicodeData", defaultTestNameSpace['unicodeData'])
+
+ def test2(self):
+ """unicode data in body
+ """
+ self.verify(u"aoeu12345\u1234", u"aoeu12345\u1234")
+ #self.verify(u"#encoding utf8#aoeu12345\u1234", u"aoeu12345\u1234")
+
+class EncodingDirective(OutputTest):
+ def test1(self):
+ """basic #encoding """
+ self.verify("#encoding utf-8\n1234",
+ "1234")
+
+ def test2(self):
+ """basic #encoding """
+ self.verify("#encoding ascii\n1234",
+ "1234")
+
+ def test3(self):
+ """basic #encoding """
+ self.verify("#encoding utf-8\n\xe1\x88\xb4",
+ u'\u1234', outputEncoding='utf8')
+
+ def test4(self):
+ """basic #encoding """
+ self.verify("#encoding ascii\n\xe1\x88\xb4",
+ "\xe1\x88\xb4")
+
+ def test5(self):
+ """basic #encoding """
+ self.verify("#encoding latin-1\nAndr\202",
+ u'Andr\202', outputEncoding='latin-1')
+
+class Placeholders_Esc(OutputTest):
+ convertEOLs = False
+ def test1(self):
+ """1 escaped placeholder"""
+ self.verify("\$var",
+ "$var")
+
+ def test2(self):
+ """2 escaped placeholders"""
+ self.verify("\$var \$_",
+ "$var $_")
+
+ def test3(self):
+ """2 escaped placeholders - back to back"""
+ self.verify("\$var\$_",
+ "$var$_")
+
+ def test4(self):
+ """2 escaped placeholders - nested"""
+ self.verify("\$var(\$_)",
+ "$var($_)")
+
+ def test5(self):
+ """2 escaped placeholders - nested and enclosed"""
+ self.verify("\$(var(\$_)",
+ "$(var($_)")
+
+
+class Placeholders_Calls(OutputTest):
+ def test1(self):
+ """func placeholder - no ()"""
+ self.verify("$aFunc",
+ "Scooby")
+
+ def test2(self):
+ """func placeholder - with ()"""
+ self.verify("$aFunc()",
+ "Scooby")
+
+ def test3(self):
+ r"""func placeholder - with (\n\n)"""
+ self.verify("$aFunc(\n\n)",
+ "Scooby", convertEOLs=False)
+
+ def test4(self):
+ r"""func placeholder - with (\n\n) and $() enclosure"""
+ self.verify("$(aFunc(\n\n))",
+ "Scooby", convertEOLs=False)
+
+ def test5(self):
+ r"""func placeholder - with (\n\n) and ${} enclosure"""
+ self.verify("${aFunc(\n\n)}",
+ "Scooby", convertEOLs=False)
+
+ def test6(self):
+ """func placeholder - with (int)"""
+ self.verify("$aFunc(1234)",
+ "1234")
+
+ def test7(self):
+ r"""func placeholder - with (\nint\n)"""
+ self.verify("$aFunc(\n1234\n)",
+ "1234", convertEOLs=False)
+ def test8(self):
+ """func placeholder - with (string)"""
+ self.verify("$aFunc('aoeu')",
+ "aoeu")
+
+ def test9(self):
+ """func placeholder - with ('''string''')"""
+ self.verify("$aFunc('''aoeu''')",
+ "aoeu")
+ def test10(self):
+ r"""func placeholder - with ('''\nstring\n''')"""
+ self.verify("$aFunc('''\naoeu\n''')",
+ "\naoeu\n")
+
+ def test11(self):
+ r"""func placeholder - with ('''\nstring'\n''')"""
+ self.verify("$aFunc('''\naoeu'\n''')",
+ "\naoeu'\n")
+
+ def test12(self):
+ r'''func placeholder - with ("""\nstring\n""")'''
+ self.verify('$aFunc("""\naoeu\n""")',
+ "\naoeu\n")
+
+ def test13(self):
+ """func placeholder - with (string*int)"""
+ self.verify("$aFunc('aoeu'*2)",
+ "aoeuaoeu")
+
+ def test14(self):
+ """func placeholder - with (int*int)"""
+ self.verify("$aFunc(2*2)",
+ "4")
+
+ def test15(self):
+ """func placeholder - with (int*float)"""
+ self.verify("$aFunc(2*2.0)",
+ "4.0")
+
+ def test16(self):
+ r"""func placeholder - with (int\n*\nfloat)"""
+ self.verify("$aFunc(2\n*\n2.0)",
+ "4.0", convertEOLs=False)
+
+ def test17(self):
+ """func placeholder - with ($arg=float)"""
+ self.verify("$aFunc($arg=4.0)",
+ "4.0")
+
+ def test18(self):
+ """func placeholder - with (arg=float)"""
+ self.verify("$aFunc(arg=4.0)",
+ "4.0")
+
+ def test19(self):
+ """deeply nested argstring, no enclosure"""
+ self.verify("$aFunc($arg=$aMeth($arg=$aFunc(1)))",
+ "1")
+
+ def test20(self):
+ """deeply nested argstring, no enclosure + with WS"""
+ self.verify("$aFunc( $arg = $aMeth( $arg = $aFunc( 1 ) ) )",
+ "1")
+ def test21(self):
+ """deeply nested argstring, () enclosure + with WS"""
+ self.verify("$(aFunc( $arg = $aMeth( $arg = $aFunc( 1 ) ) ) )",
+ "1")
+
+ def test22(self):
+ """deeply nested argstring, {} enclosure + with WS"""
+ self.verify("${aFunc( $arg = $aMeth( $arg = $aFunc( 1 ) ) ) }",
+ "1")
+
+ def test23(self):
+ """deeply nested argstring, [] enclosure + with WS"""
+ self.verify("$[aFunc( $arg = $aMeth( $arg = $aFunc( 1 ) ) ) ]",
+ "1")
+
+ def test24(self):
+ """deeply nested argstring, () enclosure + *cache"""
+ self.verify("$*(aFunc( $arg = $aMeth( $arg = $aFunc( 1 ) ) ) )",
+ "1")
+ def test25(self):
+ """deeply nested argstring, () enclosure + *15*cache"""
+ self.verify("$*15*(aFunc( $arg = $aMeth( $arg = $aFunc( 1 ) ) ) )",
+ "1")
+
+ def test26(self):
+ """a function call with the Python None kw."""
+ self.verify("$aFunc(None)",
+ "")
+
+class NameMapper(OutputTest):
+ def test1(self):
+ """autocalling"""
+ self.verify("$aFunc! $aFunc().",
+ "Scooby! Scooby.")
+
+ def test2(self):
+ """nested autocalling"""
+ self.verify("$aFunc($aFunc).",
+ "Scooby.")
+
+ def test3(self):
+ """list subscription"""
+ self.verify("$aList[0]",
+ "item0")
+
+ def test4(self):
+ """list slicing"""
+ self.verify("$aList[:2]",
+ "['item0', 'item1']")
+
+ def test5(self):
+ """list slicing and subcription combined"""
+ self.verify("$aList[:2][0]",
+ "item0")
+
+ def test6(self):
+ """dictionary access - NameMapper style"""
+ self.verify("$aDict.one",
+ "item1")
+
+ def test7(self):
+ """dictionary access - Python style"""
+ self.verify("$aDict['one']",
+ "item1")
+
+ def test8(self):
+ """dictionary access combined with autocalled string method"""
+ self.verify("$aDict.one.upper",
+ "ITEM1")
+
+ def test9(self):
+ """dictionary access combined with string method"""
+ self.verify("$aDict.one.upper()",
+ "ITEM1")
+
+ def test10(self):
+ """nested dictionary access - NameMapper style"""
+ self.verify("$aDict.nestedDict.two",
+ "nestedItem2")
+
+ def test11(self):
+ """nested dictionary access - Python style"""
+ self.verify("$aDict['nestedDict']['two']",
+ "nestedItem2")
+
+ def test12(self):
+ """nested dictionary access - alternating style"""
+ self.verify("$aDict['nestedDict'].two",
+ "nestedItem2")
+
+ def test13(self):
+ """nested dictionary access using method - alternating style"""
+ self.verify("$aDict.get('nestedDict').two",
+ "nestedItem2")
+
+ def test14(self):
+ """nested dictionary access - NameMapper style - followed by method"""
+ self.verify("$aDict.nestedDict.two.upper",
+ "NESTEDITEM2")
+
+ def test15(self):
+ """nested dictionary access - alternating style - followed by method"""
+ self.verify("$aDict['nestedDict'].two.upper",
+ "NESTEDITEM2")
+
+ def test16(self):
+ """nested dictionary access - NameMapper style - followed by method, then slice"""
+ self.verify("$aDict.nestedDict.two.upper[:4]",
+ "NEST")
+
+ def test17(self):
+ """nested dictionary access - Python style using a soft-coded key"""
+ self.verify("$aDict[$anObj.meth('nestedDict')].two",
+ "nestedItem2")
+
+ def test18(self):
+ """object method access"""
+ self.verify("$anObj.meth1",
+ "doo")
+
+ def test19(self):
+ """object method access, followed by complex slice"""
+ self.verify("$anObj.meth1[0: ((4/4*2)*2)/$anObj.meth1(2) ]",
+ "do")
+
+ def test20(self):
+ """object method access, followed by a very complex slice
+ If it can pass this one, it's safe to say it works!!"""
+ self.verify("$( anObj.meth1[0:\n (\n(4/4*2)*2)/$anObj.meth1(2)\n ] )",
+ "do")
+
+ def test21(self):
+ """object method access with % in the default arg for the meth.
+
+ This tests a bug that Jeff Johnson found and submitted a patch to SF
+ for."""
+
+ self.verify("$anObj.methWithPercentSignDefaultArg",
+ "110%")
+
+
+#class NameMapperDict(OutputTest):
+#
+# _searchList = [{"update": "Yabba dabba doo!"}]
+#
+# def test1(self):
+# if NameMapper_C_VERSION:
+# return # This feature is not in the C version yet.
+# self.verify("$update", "Yabba dabba doo!")
+#
+
+class CacheDirective(OutputTest):
+
+ def test1(self):
+ r"""simple #cache """
+ self.verify("#cache:$anInt",
+ "1")
+
+ def test2(self):
+ r"""simple #cache + WS"""
+ self.verify(" #cache \n$anInt#end cache",
+ "1")
+
+ def test3(self):
+ r"""simple #cache ... #end cache"""
+ self.verify("""#cache id='cache1', timer=150m
+$anInt
+#end cache
+$aStr""",
+ "1\nblarg")
+
+ def test4(self):
+ r"""2 #cache ... #end cache blocks"""
+ self.verify("""#slurp
+#def foo
+#cache ID='cache1', timer=150m
+$anInt
+#end cache
+#cache id='cache2', timer=15s
+ #for $i in range(5)
+$i#slurp
+ #end for
+#end cache
+$aStr#slurp
+#end def
+$foo$foo$foo$foo$foo""",
+ "1\n01234blarg"*5)
+
+
+ def test5(self):
+ r"""nested #cache blocks"""
+ self.verify("""#slurp
+#def foo
+#cache ID='cache1', timer=150m
+$anInt
+#cache id='cache2', timer=15s
+ #for $i in range(5)
+$i#slurp
+ #end for
+$*(6)#slurp
+#end cache
+#end cache
+$aStr#slurp
+#end def
+$foo$foo$foo$foo$foo""",
+ "1\n012346blarg"*5)
+
+
+class CallDirective(OutputTest):
+
+ def test1(self):
+ r"""simple #call """
+ self.verify("#call int\n$anInt#end call",
+ "1")
+ # single line version
+ self.verify("#call int: $anInt",
+ "1")
+ self.verify("#call int: 10\n$aStr",
+ "10\nblarg")
+
+ def test2(self):
+ r"""simple #call + WS"""
+ self.verify("#call int\n$anInt #end call",
+ "1")
+
+ def test3(self):
+ r"""a longer #call"""
+ self.verify('''\
+#def meth(arg)
+$arg.upper()#slurp
+#end def
+#call $meth
+$(1234+1) foo#slurp
+#end call''',
+ "1235 FOO")
+
+ def test4(self):
+ r"""#call with keyword #args"""
+ self.verify('''\
+#def meth(arg1, arg2)
+$arg1.upper() - $arg2.lower()#slurp
+#end def
+#call self.meth
+#arg arg1
+$(1234+1) foo#slurp
+#arg arg2
+UPPER#slurp
+#end call''',
+ "1235 FOO - upper")
+
+ def test5(self):
+ r"""#call with single-line keyword #args """
+ self.verify('''\
+#def meth(arg1, arg2)
+$arg1.upper() - $arg2.lower()#slurp
+#end def
+#call self.meth
+#arg arg1:$(1234+1) foo#slurp
+#arg arg2:UPPER#slurp
+#end call''',
+ "1235 FOO - upper")
+
+ def test6(self):
+ """#call with python kwargs and cheetah output for the 1s positional
+ arg"""
+
+ self.verify('''\
+#def meth(arg1, arg2)
+$arg1.upper() - $arg2.lower()#slurp
+#end def
+#call self.meth arg2="UPPER"
+$(1234+1) foo#slurp
+#end call''',
+ "1235 FOO - upper")
+
+ def test7(self):
+ """#call with python kwargs and #args"""
+ self.verify('''\
+#def meth(arg1, arg2, arg3)
+$arg1.upper() - $arg2.lower() - $arg3#slurp
+#end def
+#call self.meth arg2="UPPER", arg3=999
+#arg arg1:$(1234+1) foo#slurp
+#end call''',
+ "1235 FOO - upper - 999")
+
+ def test8(self):
+ """#call with python kwargs and #args, and using a function to get the
+ function that will be called"""
+ self.verify('''\
+#def meth(arg1, arg2, arg3)
+$arg1.upper() - $arg2.lower() - $arg3#slurp
+#end def
+#call getattr(self, "meth") arg2="UPPER", arg3=999
+#arg arg1:$(1234+1) foo#slurp
+#end call''',
+ "1235 FOO - upper - 999")
+
+ def test9(self):
+ """nested #call directives"""
+ self.verify('''\
+#def meth(arg1)
+$arg1#slurp
+#end def
+#def meth2(x,y)
+$x$y#slurp
+#end def
+##
+#call self.meth
+1#slurp
+#call self.meth
+2#slurp
+#call self.meth
+3#slurp
+#end call 3
+#set two = 2
+#call self.meth2 y=c"$(10/$two)"
+#arg x
+4#slurp
+#end call 4
+#end call 2
+#end call 1''',
+ "12345")
+
+
+
+class I18nDirective(OutputTest):
+ def test1(self):
+ r"""simple #call """
+ self.verify("#i18n \n$anInt#end i18n",
+ "1")
+
+ # single line version
+ self.verify("#i18n: $anInt",
+ "1")
+ self.verify("#i18n: 10\n$aStr",
+ "10\nblarg")
+
+
+class CaptureDirective(OutputTest):
+ def test1(self):
+ r"""simple #capture"""
+ self.verify('''\
+#capture cap1
+$(1234+1) foo#slurp
+#end capture
+$cap1#slurp
+''',
+ "1235 foo")
+
+
+ def test2(self):
+ r"""slightly more complex #capture"""
+ self.verify('''\
+#def meth(arg)
+$arg.upper()#slurp
+#end def
+#capture cap1
+$(1234+1) $anInt $meth("foo")#slurp
+#end capture
+$cap1#slurp
+''',
+ "1235 1 FOO")
+
+
+class SlurpDirective(OutputTest):
+ def test1(self):
+ r"""#slurp with 1 \n """
+ self.verify("#slurp\n",
+ "")
+
+ def test2(self):
+ r"""#slurp with 1 \n, leading whitespace
+ Should gobble"""
+ self.verify(" #slurp\n",
+ "")
+
+ def test3(self):
+ r"""#slurp with 1 \n, leading content
+ Shouldn't gobble"""
+ self.verify(" 1234 #slurp\n",
+ " 1234 ")
+
+ def test4(self):
+ r"""#slurp with WS then \n, leading content
+ Shouldn't gobble"""
+ self.verify(" 1234 #slurp \n",
+ " 1234 ")
+
+ def test5(self):
+ r"""#slurp with garbage chars then \n, leading content
+ Should eat the garbage"""
+ self.verify(" 1234 #slurp garbage \n",
+ " 1234 ")
+
+
+
+class EOLSlurpToken(OutputTest):
+ _EOLSlurpToken = DEFAULT_COMPILER_SETTINGS['EOLSlurpToken']
+ def test1(self):
+ r"""#slurp with 1 \n """
+ self.verify("%s\n"%self._EOLSlurpToken,
+ "")
+
+ def test2(self):
+ r"""#slurp with 1 \n, leading whitespace
+ Should gobble"""
+ self.verify(" %s\n"%self._EOLSlurpToken,
+ "")
+ def test3(self):
+ r"""#slurp with 1 \n, leading content
+ Shouldn't gobble"""
+ self.verify(" 1234 %s\n"%self._EOLSlurpToken,
+ " 1234 ")
+
+ def test4(self):
+ r"""#slurp with WS then \n, leading content
+ Shouldn't gobble"""
+ self.verify(" 1234 %s \n"%self._EOLSlurpToken,
+ " 1234 ")
+
+ def test5(self):
+ r"""#slurp with garbage chars then \n, leading content
+ Should NOT eat the garbage"""
+ self.verify(" 1234 %s garbage \n"%self._EOLSlurpToken,
+ " 1234 %s garbage \n"%self._EOLSlurpToken)
+
+if not DEFAULT_COMPILER_SETTINGS['EOLSlurpToken']:
+ del EOLSlurpToken
+
+class RawDirective(OutputTest):
+ def test1(self):
+ """#raw till EOF"""
+ self.verify("#raw\n$aFunc().\n\n",
+ "$aFunc().\n\n")
+
+ def test2(self):
+ """#raw till #end raw"""
+ self.verify("#raw\n$aFunc().\n#end raw\n$anInt",
+ "$aFunc().\n1")
+
+ def test3(self):
+ """#raw till #end raw gobble WS"""
+ self.verify(" #raw \n$aFunc().\n #end raw \n$anInt",
+ "$aFunc().\n1")
+
+ def test4(self):
+ """#raw till #end raw using explicit directive closure
+ Shouldn't gobble"""
+ self.verify(" #raw #\n$aFunc().\n #end raw #\n$anInt",
+ " \n$aFunc().\n\n1")
+
+ def test5(self):
+ """single-line short form #raw: """
+ self.verify("#raw: $aFunc().\n\n",
+ "$aFunc().\n\n")
+
+ self.verify("#raw: $aFunc().\n$anInt",
+ "$aFunc().\n1")
+
+class BreakpointDirective(OutputTest):
+ def test1(self):
+ """#breakpoint part way through source code"""
+ self.verify("$aFunc(2).\n#breakpoint\n$anInt",
+ "2.\n")
+
+ def test2(self):
+ """#breakpoint at BOF"""
+ self.verify("#breakpoint\n$anInt",
+ "")
+
+ def test3(self):
+ """#breakpoint at EOF"""
+ self.verify("$anInt\n#breakpoint",
+ "1\n")
+
+
+class StopDirective(OutputTest):
+ def test1(self):
+ """#stop part way through source code"""
+ self.verify("$aFunc(2).\n#stop\n$anInt",
+ "2.\n")
+
+ def test2(self):
+ """#stop at BOF"""
+ self.verify("#stop\n$anInt",
+ "")
+
+ def test3(self):
+ """#stop at EOF"""
+ self.verify("$anInt\n#stop",
+ "1\n")
+
+ def test4(self):
+ """#stop in pos test block"""
+ self.verify("""$anInt
+#if 1
+inside the if block
+#stop
+#end if
+blarg""",
+ "1\ninside the if block\n")
+
+ def test5(self):
+ """#stop in neg test block"""
+ self.verify("""$anInt
+#if 0
+inside the if block
+#stop
+#end if
+blarg""",
+ "1\nblarg")
+
+
+class ReturnDirective(OutputTest):
+
+ def test1(self):
+ """#return'ing an int """
+ self.verify("""1
+$str($test-6)
+3
+#def test
+#if 1
+#return (3 *2) \
+ + 2
+#else
+aoeuoaeu
+#end if
+#end def
+""",
+ "1\n2\n3\n")
+
+ def test2(self):
+ """#return'ing an string """
+ self.verify("""1
+$str($test[1])
+3
+#def test
+#if 1
+#return '123'
+#else
+aoeuoaeu
+#end if
+#end def
+""",
+ "1\n2\n3\n")
+
+ def test3(self):
+ """#return'ing an string AND streaming other output via the transaction"""
+ self.verify("""1
+$str($test(trans=trans)[1])
+3
+#def test
+1.5
+#if 1
+#return '123'
+#else
+aoeuoaeu
+#end if
+#end def
+""",
+ "1\n1.5\n2\n3\n")
+
+
+class YieldDirective(OutputTest):
+ convertEOLs = False
+ def test1(self):
+ """simple #yield """
+
+ src1 = """#for i in range(10)\n#yield i\n#end for"""
+ src2 = """#for i in range(10)\n$i#slurp\n#yield\n#end for"""
+ src3 = ("#def iterator\n"
+ "#for i in range(10)\n#yield i\n#end for\n"
+ "#end def\n"
+ "#for i in $iterator\n$i#end for"
+ )
+
+
+ for src in (src1,src2,src3):
+ klass = Template.compile(src, keepRefToGeneratedCode=True)
+ #print klass._CHEETAH_generatedModuleCode
+ iter = klass().respond()
+ output = [str(i) for i in iter]
+ assert ''.join(output)=='0123456789'
+ #print ''.join(output)
+
+ # @@TR: need to expand this to cover error conditions etc.
+
+if versionTuple < (2,3):
+ del YieldDirective
+
+class ForDirective(OutputTest):
+
+ def test1(self):
+ """#for loop with one local var"""
+ self.verify("#for $i in range(5)\n$i\n#end for",
+ "0\n1\n2\n3\n4\n")
+
+ self.verify("#for $i in range(5):\n$i\n#end for",
+ "0\n1\n2\n3\n4\n")
+
+ self.verify("#for $i in range(5): ##comment\n$i\n#end for",
+ "0\n1\n2\n3\n4\n")
+
+ self.verify("#for $i in range(5) ##comment\n$i\n#end for",
+ "0\n1\n2\n3\n4\n")
+
+
+ def test2(self):
+ """#for loop with WS in loop"""
+ self.verify("#for $i in range(5)\n$i \n#end for",
+ "0 \n1 \n2 \n3 \n4 \n")
+
+ def test3(self):
+ """#for loop gobble WS"""
+ self.verify(" #for $i in range(5) \n$i \n #end for ",
+ "0 \n1 \n2 \n3 \n4 \n")
+
+ def test4(self):
+ """#for loop over list"""
+ self.verify("#for $i, $j in [(0,1),(2,3)]\n$i,$j\n#end for",
+ "0,1\n2,3\n")
+
+ def test5(self):
+ """#for loop over list, with #slurp"""
+ self.verify("#for $i, $j in [(0,1),(2,3)]\n$i,$j#slurp\n#end for",
+ "0,12,3")
+
+ def test6(self):
+ """#for loop with explicit closures"""
+ self.verify("#for $i in range(5)#$i#end for#",
+ "01234")
+
+ def test7(self):
+ """#for loop with explicit closures and WS"""
+ self.verify(" #for $i in range(5)#$i#end for# ",
+ " 01234 ")
+
+ def test8(self):
+ """#for loop using another $var"""
+ self.verify(" #for $i in range($aFunc(5))#$i#end for# ",
+ " 01234 ")
+
+ def test9(self):
+ """test methods in for loops"""
+ self.verify("#for $func in $listOfLambdas\n$func($anInt)\n#end for",
+ "1\n1\n1\n")
+
+
+ def test10(self):
+ """#for loop over list, using methods of the items"""
+ self.verify("#for i, j in [('aa','bb'),('cc','dd')]\n$i.upper,$j.upper\n#end for",
+ "AA,BB\nCC,DD\n")
+ self.verify("#for $i, $j in [('aa','bb'),('cc','dd')]\n$i.upper,$j.upper\n#end for",
+ "AA,BB\nCC,DD\n")
+
+ def test11(self):
+ """#for loop over list, using ($i,$j) style target list"""
+ self.verify("#for (i, j) in [('aa','bb'),('cc','dd')]\n$i.upper,$j.upper\n#end for",
+ "AA,BB\nCC,DD\n")
+ self.verify("#for ($i, $j) in [('aa','bb'),('cc','dd')]\n$i.upper,$j.upper\n#end for",
+ "AA,BB\nCC,DD\n")
+
+ def test12(self):
+ """#for loop over list, using i, (j,k) style target list"""
+ self.verify("#for i, (j, k) in enumerate([('aa','bb'),('cc','dd')])\n$j.upper,$k.upper\n#end for",
+ "AA,BB\nCC,DD\n")
+ self.verify("#for $i, ($j, $k) in enumerate([('aa','bb'),('cc','dd')])\n$j.upper,$k.upper\n#end for",
+ "AA,BB\nCC,DD\n")
+
+ def test13(self):
+ """single line #for"""
+ self.verify("#for $i in range($aFunc(5)): $i",
+ "01234")
+
+ def test14(self):
+ """single line #for with 1 extra leading space"""
+ self.verify("#for $i in range($aFunc(5)): $i",
+ " 0 1 2 3 4")
+
+ def test15(self):
+ """2 times single line #for"""
+ self.verify("#for $i in range($aFunc(5)): $i#slurp\n"*2,
+ "01234"*2)
+
+ def test16(self):
+ """false single line #for """
+ self.verify("#for $i in range(5): \n$i\n#end for",
+ "0\n1\n2\n3\n4\n")
+
+if versionTuple < (2,3):
+ del ForDirective.test12
+
+class RepeatDirective(OutputTest):
+
+ def test1(self):
+ """basic #repeat"""
+ self.verify("#repeat 3\n1\n#end repeat",
+ "1\n1\n1\n")
+ self.verify("#repeat 3: \n1\n#end repeat",
+ "1\n1\n1\n")
+
+ self.verify("#repeat 3 ##comment\n1\n#end repeat",
+ "1\n1\n1\n")
+
+ self.verify("#repeat 3: ##comment\n1\n#end repeat",
+ "1\n1\n1\n")
+
+ def test2(self):
+ """#repeat with numeric expression"""
+ self.verify("#repeat 3*3/3\n1\n#end repeat",
+ "1\n1\n1\n")
+
+ def test3(self):
+ """#repeat with placeholder"""
+ self.verify("#repeat $numTwo\n1\n#end repeat",
+ "1\n1\n")
+
+ def test4(self):
+ """#repeat with placeholder * num"""
+ self.verify("#repeat $numTwo*1\n1\n#end repeat",
+ "1\n1\n")
+
+ def test5(self):
+ """#repeat with placeholder and WS"""
+ self.verify(" #repeat $numTwo \n1\n #end repeat ",
+ "1\n1\n")
+
+ def test6(self):
+ """single-line #repeat"""
+ self.verify("#repeat $numTwo: 1",
+ "11")
+ self.verify("#repeat $numTwo: 1\n"*2,
+ "1\n1\n"*2)
+
+ #false single-line
+ self.verify("#repeat 3: \n1\n#end repeat",
+ "1\n1\n1\n")
+
+
+class AttrDirective(OutputTest):
+
+ def test1(self):
+ """#attr with int"""
+ self.verify("#attr $test = 1234\n$test",
+ "1234")
+
+ def test2(self):
+ """#attr with string"""
+ self.verify("#attr $test = 'blarg'\n$test",
+ "blarg")
+
+ def test3(self):
+ """#attr with expression"""
+ self.verify("#attr $test = 'blarg'.upper()*2\n$test",
+ "BLARGBLARG")
+
+ def test4(self):
+ """#attr with string + WS
+ Should gobble"""
+ self.verify(" #attr $test = 'blarg' \n$test",
+ "blarg")
+
+ def test5(self):
+ """#attr with string + WS + leading text
+ Shouldn't gobble"""
+ self.verify(" -- #attr $test = 'blarg' \n$test",
+ " -- \nblarg")
+
+
+class DefDirective(OutputTest):
+
+ def test1(self):
+ """#def without argstring"""
+ self.verify("#def testMeth\n1234\n#end def\n$testMeth",
+ "1234\n")
+
+ self.verify("#def testMeth ## comment\n1234\n#end def\n$testMeth",
+ "1234\n")
+
+ self.verify("#def testMeth: ## comment\n1234\n#end def\n$testMeth",
+ "1234\n")
+
+ def test2(self):
+ """#def without argstring, gobble WS"""
+ self.verify(" #def testMeth \n1234\n #end def \n$testMeth",
+ "1234\n")
+
+ def test3(self):
+ """#def with argstring, gobble WS"""
+ self.verify(" #def testMeth($a=999) \n1234-$a\n #end def\n$testMeth",
+ "1234-999\n")
+
+ def test4(self):
+ """#def with argstring, gobble WS, string used in call"""
+ self.verify(" #def testMeth($a=999) \n1234-$a\n #end def\n$testMeth('ABC')",
+ "1234-ABC\n")
+
+ def test5(self):
+ """#def with argstring, gobble WS, list used in call"""
+ self.verify(" #def testMeth($a=999) \n1234-$a\n #end def\n$testMeth([1,2,3])",
+ "1234-[1, 2, 3]\n")
+
+ def test6(self):
+ """#def with 2 args, gobble WS, list used in call"""
+ self.verify(" #def testMeth($a, $b='default') \n1234-$a$b\n #end def\n$testMeth([1,2,3])",
+ "1234-[1, 2, 3]default\n")
+
+ def test7(self):
+ """#def with *args, gobble WS"""
+ self.verify(" #def testMeth($*args) \n1234-$args\n #end def\n$testMeth",
+ "1234-()\n")
+
+ def test8(self):
+ """#def with **KWs, gobble WS"""
+ self.verify(" #def testMeth($**KWs) \n1234-$KWs\n #end def\n$testMeth",
+ "1234-{}\n")
+
+ def test9(self):
+ """#def with *args + **KWs, gobble WS"""
+ self.verify(" #def testMeth($*args, $**KWs) \n1234-$args-$KWs\n #end def\n$testMeth",
+ "1234-()-{}\n")
+
+ def test10(self):
+ """#def with *args + **KWs, gobble WS"""
+ self.verify(
+ " #def testMeth($*args, $**KWs) \n1234-$args-$KWs.a\n #end def\n$testMeth(1,2, a=1)",
+ "1234-(1, 2)-1\n")
+
+
+ def test11(self):
+ """single line #def with extra WS"""
+ self.verify(
+ "#def testMeth: aoeuaoeu\n- $testMeth -",
+ "- aoeuaoeu -")
+
+ def test12(self):
+ """single line #def with extra WS and nested $placeholders"""
+ self.verify(
+ "#def testMeth: $anInt $aFunc(1234)\n- $testMeth -",
+ "- 1 1234 -")
+
+ def test13(self):
+ """single line #def escaped $placeholders"""
+ self.verify(
+ "#def testMeth: \$aFunc(\$anInt)\n- $testMeth -",
+ "- $aFunc($anInt) -")
+
+ def test14(self):
+ """single line #def 1 escaped $placeholders"""
+ self.verify(
+ "#def testMeth: \$aFunc($anInt)\n- $testMeth -",
+ "- $aFunc(1) -")
+
+ def test15(self):
+ """single line #def 1 escaped $placeholders + more WS"""
+ self.verify(
+ "#def testMeth : \$aFunc($anInt)\n- $testMeth -",
+ "- $aFunc(1) -")
+
+ def test16(self):
+ """multiline #def with $ on methodName"""
+ self.verify("#def $testMeth\n1234\n#end def\n$testMeth",
+ "1234\n")
+
+ def test17(self):
+ """single line #def with $ on methodName"""
+ self.verify("#def $testMeth:1234\n$testMeth",
+ "1234")
+
+ def test18(self):
+ """single line #def with an argument"""
+ self.verify("#def $testMeth($arg=1234):$arg\n$testMeth",
+ "1234")
+
+
+class DecoratorDirective(OutputTest):
+ def test1(self):
+ """single line #def with decorator"""
+ self.verify("#from Cheetah.Tests.SyntaxAndOutput import testdecorator\n"
+ +"#@testdecorator"
+ +"\n#def $testMeth():1234\n$testMeth",
+
+ "1234")
+
+ self.verify("#from Cheetah.Tests.SyntaxAndOutput import testdecorator\n"
+ +"#@testdecorator"
+ +"\n#block $testMeth():1234",
+
+ "1234")
+
+ try:
+ self.verify(
+ "#from Cheetah.Tests.SyntaxAndOutput import testdecorator\n"
+ +"#@testdecorator\n sdf"
+ +"\n#def $testMeth():1234\n$testMeth",
+
+ "1234")
+ except ParseError:
+ pass
+ else:
+ self.fail('should raise a ParseError')
+
+if versionTuple < (2,4):
+ del DecoratorDirective
+
+class BlockDirective(OutputTest):
+
+ def test1(self):
+ """#block without argstring"""
+ self.verify("#block testBlock\n1234\n#end block",
+ "1234\n")
+
+ self.verify("#block testBlock ##comment\n1234\n#end block",
+ "1234\n")
+
+ def test2(self):
+ """#block without argstring, gobble WS"""
+ self.verify(" #block testBlock \n1234\n #end block ",
+ "1234\n")
+
+ def test3(self):
+ """#block with argstring, gobble WS
+
+ Because blocks can be reused in multiple parts of the template arguments
+ (!!with defaults!!) can be given."""
+
+ self.verify(" #block testBlock($a=999) \n1234-$a\n #end block ",
+ "1234-999\n")
+
+ def test4(self):
+ """#block with 2 args, gobble WS"""
+ self.verify(" #block testBlock($a=999, $b=444) \n1234-$a$b\n #end block ",
+ "1234-999444\n")
+
+
+ def test5(self):
+ """#block with 2 nested blocks
+
+ Blocks can be nested to any depth and the name of the block is optional
+ for the #end block part: #end block OR #end block [name] """
+
+ self.verify("""#block testBlock
+this is a test block
+#block outerNest
+outer
+#block innerNest
+inner
+#end block innerNest
+#end block outerNest
+---
+#end block testBlock
+""",
+ "this is a test block\nouter\ninner\n---\n")
+
+
+ def test6(self):
+ """single line #block """
+ self.verify(
+ "#block testMeth: This is my block",
+ "This is my block")
+
+ def test7(self):
+ """single line #block with WS"""
+ self.verify(
+ "#block testMeth: This is my block",
+ "This is my block")
+
+ def test8(self):
+ """single line #block 1 escaped $placeholders"""
+ self.verify(
+ "#block testMeth: \$aFunc($anInt)",
+ "$aFunc(1)")
+
+ def test9(self):
+ """single line #block 1 escaped $placeholders + WS"""
+ self.verify(
+ "#block testMeth: \$aFunc( $anInt )",
+ "$aFunc( 1 )")
+
+ def test10(self):
+ """single line #block 1 escaped $placeholders + more WS"""
+ self.verify(
+ "#block testMeth : \$aFunc( $anInt )",
+ "$aFunc( 1 )")
+
+ def test11(self):
+ """multiline #block $ on argstring"""
+ self.verify("#block $testBlock\n1234\n#end block",
+ "1234\n")
+
+ def test12(self):
+ """single line #block with $ on methodName """
+ self.verify(
+ "#block $testMeth: This is my block",
+ "This is my block")
+
+ def test13(self):
+ """single line #block with an arg """
+ self.verify(
+ "#block $testMeth($arg='This is my block'): $arg",
+ "This is my block")
+
+ def test14(self):
+ """single line #block with None for content"""
+ self.verify(
+ """#block $testMeth: $None\ntest $testMeth-""",
+ "test -")
+
+ def test15(self):
+ """single line #block with nothing for content"""
+ self.verify(
+ """#block $testMeth: \nfoo\n#end block\ntest $testMeth-""",
+ "foo\ntest foo\n-")
+
+class IncludeDirective(OutputTest):
+
+ def setUp(self):
+ fp = open('parseTest.txt','w')
+ fp.write("$numOne $numTwo")
+ fp.flush()
+ fp.close
+
+ def tearDown(self):
+ if os.path.exists('parseTest.txt'):
+ os.remove('parseTest.txt')
+
+ def test1(self):
+ """#include raw of source $emptyString"""
+ self.verify("#include raw source=$emptyString",
+ "")
+
+ def test2(self):
+ """#include raw of source $blockToBeParsed"""
+ self.verify("#include raw source=$blockToBeParsed",
+ "$numOne $numTwo")
+
+ def test3(self):
+ """#include raw of 'parseTest.txt'"""
+ self.verify("#include raw 'parseTest.txt'",
+ "$numOne $numTwo")
+
+ def test4(self):
+ """#include raw of $includeFileName"""
+ self.verify("#include raw $includeFileName",
+ "$numOne $numTwo")
+
+ def test5(self):
+ """#include raw of $includeFileName, with WS"""
+ self.verify(" #include raw $includeFileName ",
+ "$numOne $numTwo")
+
+ def test6(self):
+ """#include raw of source= , with WS"""
+ self.verify(" #include raw source='This is my $Source '*2 ",
+ "This is my $Source This is my $Source ")
+
+ def test7(self):
+ """#include of $blockToBeParsed"""
+ self.verify("#include source=$blockToBeParsed",
+ "1 2")
+
+ def test8(self):
+ """#include of $blockToBeParsed, with WS"""
+ self.verify(" #include source=$blockToBeParsed ",
+ "1 2")
+
+ def test9(self):
+ """#include of 'parseTest.txt', with WS"""
+ self.verify(" #include source=$blockToBeParsed ",
+ "1 2")
+
+ def test10(self):
+ """#include of "parseTest.txt", with WS"""
+ self.verify(" #include source=$blockToBeParsed ",
+ "1 2")
+
+ def test11(self):
+ """#include of 'parseTest.txt', with WS and surrounding text"""
+ self.verify("aoeu\n #include source=$blockToBeParsed \naoeu",
+ "aoeu\n1 2aoeu")
+
+ def test12(self):
+ """#include of 'parseTest.txt', with WS and explicit closure"""
+ self.verify(" #include source=$blockToBeParsed# ",
+ " 1 2 ")
+
+
+class SilentDirective(OutputTest):
+
+ def test1(self):
+ """simple #silent"""
+ self.verify("#silent $aFunc",
+ "")
+
+ def test2(self):
+ """simple #silent"""
+ self.verify("#silent $anObj.callIt\n$anObj.callArg",
+ "1234")
+
+ self.verify("#silent $anObj.callIt ##comment\n$anObj.callArg",
+ "1234")
+
+ def test3(self):
+ """simple #silent"""
+ self.verify("#silent $anObj.callIt(99)\n$anObj.callArg",
+ "99")
+
+class SetDirective(OutputTest):
+
+ def test1(self):
+ """simple #set"""
+ self.verify("#set $testVar = 'blarg'\n$testVar",
+ "blarg")
+ self.verify("#set testVar = 'blarg'\n$testVar",
+ "blarg")
+
+
+ self.verify("#set testVar = 'blarg'##comment\n$testVar",
+ "blarg")
+
+ def test2(self):
+ """simple #set with no WS between operands"""
+ self.verify("#set $testVar='blarg'",
+ "")
+ def test3(self):
+ """#set + use of var"""
+ self.verify("#set $testVar = 'blarg'\n$testVar",
+ "blarg")
+
+ def test4(self):
+ """#set + use in an #include"""
+ self.verify("#set global $aSetVar = 1234\n#include source=$includeBlock2",
+ "1 2 1234")
+
+ def test5(self):
+ """#set with a dictionary"""
+ self.verify( """#set $testDict = {'one':'one1','two':'two2','three':'three3'}
+$testDict.one
+$testDict.two""",
+ "one1\ntwo2")
+
+ def test6(self):
+ """#set with string, then used in #if block"""
+
+ self.verify("""#set $test='a string'\n#if $test#blarg#end if""",
+ "blarg")
+
+ def test7(self):
+ """simple #set, gobble WS"""
+ self.verify(" #set $testVar = 'blarg' ",
+ "")
+
+ def test8(self):
+ """simple #set, don't gobble WS"""
+ self.verify(" #set $testVar = 'blarg'#---",
+ " ---")
+
+ def test9(self):
+ """simple #set with a list"""
+ self.verify(" #set $testVar = [1, 2, 3] \n$testVar",
+ "[1, 2, 3]")
+
+ def test10(self):
+ """simple #set global with a list"""
+ self.verify(" #set global $testVar = [1, 2, 3] \n$testVar",
+ "[1, 2, 3]")
+
+ def test11(self):
+ """simple #set global with a list and *cache
+
+ Caching only works with global #set vars. Local vars are not accesible
+ to the cache namespace.
+ """
+
+ self.verify(" #set global $testVar = [1, 2, 3] \n$*testVar",
+ "[1, 2, 3]")
+
+ def test12(self):
+ """simple #set global with a list and *<int>*cache"""
+ self.verify(" #set global $testVar = [1, 2, 3] \n$*5*testVar",
+ "[1, 2, 3]")
+
+ def test13(self):
+ """simple #set with a list and *<float>*cache"""
+ self.verify(" #set global $testVar = [1, 2, 3] \n$*.5*testVar",
+ "[1, 2, 3]")
+
+ def test14(self):
+ """simple #set without NameMapper on"""
+ self.verify("""#compiler useNameMapper = 0\n#set $testVar = 1 \n$testVar""",
+ "1")
+
+ def test15(self):
+ """simple #set without $"""
+ self.verify("""#set testVar = 1 \n$testVar""",
+ "1")
+
+ def test16(self):
+ """simple #set global without $"""
+ self.verify("""#set global testVar = 1 \n$testVar""",
+ "1")
+
+ def test17(self):
+ """simple #set module without $"""
+ self.verify("""#set module __foo__ = 'bar'\n$__foo__""",
+ "bar")
+
+ def test18(self):
+ """#set with i,j=list style assignment"""
+ self.verify("""#set i,j = [1,2]\n$i$j""",
+ "12")
+ self.verify("""#set $i,$j = [1,2]\n$i$j""",
+ "12")
+
+ def test19(self):
+ """#set with (i,j)=list style assignment"""
+ self.verify("""#set (i,j) = [1,2]\n$i$j""",
+ "12")
+ self.verify("""#set ($i,$j) = [1,2]\n$i$j""",
+ "12")
+
+ def test20(self):
+ """#set with i, (j,k)=list style assignment"""
+ self.verify("""#set i, (j,k) = [1,(2,3)]\n$i$j$k""",
+ "123")
+ self.verify("""#set $i, ($j,$k) = [1,(2,3)]\n$i$j$k""",
+ "123")
+
+
+class IfDirective(OutputTest):
+
+ def test1(self):
+ """simple #if block"""
+ self.verify("#if 1\n$aStr\n#end if\n",
+ "blarg\n")
+
+ self.verify("#if 1:\n$aStr\n#end if\n",
+ "blarg\n")
+
+ self.verify("#if 1: \n$aStr\n#end if\n",
+ "blarg\n")
+
+ self.verify("#if 1: ##comment \n$aStr\n#end if\n",
+ "blarg\n")
+
+ self.verify("#if 1 ##comment \n$aStr\n#end if\n",
+ "blarg\n")
+
+ self.verify("#if 1##for i in range(10)#$i#end for##end if",
+ '0123456789')
+
+ self.verify("#if 1: #for i in range(10)#$i#end for",
+ '0123456789')
+
+ self.verify("#if 1: #for i in range(10):$i",
+ '0123456789')
+
+ def test2(self):
+ """simple #if block, with WS"""
+ self.verify(" #if 1\n$aStr\n #end if \n",
+ "blarg\n")
+ def test3(self):
+ """simple #if block, with WS and explicit closures"""
+ self.verify(" #if 1#\n$aStr\n #end if #--\n",
+ " \nblarg\n --\n")
+
+ def test4(self):
+ """#if block using $numOne"""
+ self.verify("#if $numOne\n$aStr\n#end if\n",
+ "blarg\n")
+
+ def test5(self):
+ """#if block using $zero"""
+ self.verify("#if $zero\n$aStr\n#end if\n",
+ "")
+ def test6(self):
+ """#if block using $emptyString"""
+ self.verify("#if $emptyString\n$aStr\n#end if\n",
+ "")
+ def test7(self):
+ """#if ... #else ... block using a $emptyString"""
+ self.verify("#if $emptyString\n$anInt\n#else\n$anInt - $anInt\n#end if",
+ "1 - 1\n")
+
+ def test8(self):
+ """#if ... #elif ... #else ... block using a $emptyString"""
+ self.verify("#if $emptyString\n$c\n#elif $numOne\n$numOne\n#else\n$c - $c\n#end if",
+ "1\n")
+
+ def test9(self):
+ """#if 'not' test, with #slurp"""
+ self.verify("#if not $emptyString\n$aStr#slurp\n#end if\n",
+ "blarg")
+
+ def test10(self):
+ """#if block using $*emptyString
+
+ This should barf
+ """
+ try:
+ self.verify("#if $*emptyString\n$aStr\n#end if\n",
+ "")
+ except ParseError:
+ pass
+ else:
+ self.fail('This should barf')
+
+ def test11(self):
+ """#if block using invalid top-level $(placeholder) syntax - should barf"""
+
+ for badSyntax in ("#if $*5*emptyString\n$aStr\n#end if\n",
+ "#if ${emptyString}\n$aStr\n#end if\n",
+ "#if $(emptyString)\n$aStr\n#end if\n",
+ "#if $[emptyString]\n$aStr\n#end if\n",
+ "#if $!emptyString\n$aStr\n#end if\n",
+ ):
+ try:
+ self.verify(badSyntax, "")
+ except ParseError:
+ pass
+ else:
+ self.fail('This should barf')
+
+ def test12(self):
+ """#if ... #else if ... #else ... block using a $emptyString
+ Same as test 8 but using else if instead of elif"""
+ self.verify("#if $emptyString\n$c\n#else if $numOne\n$numOne\n#else\n$c - $c\n#end if",
+ "1\n")
+
+
+ def test13(self):
+ """#if# ... #else # ... block using a $emptyString with """
+ self.verify("#if $emptyString# $anInt#else#$anInt - $anInt#end if",
+ "1 - 1")
+
+ def test14(self):
+ """single-line #if: simple"""
+ self.verify("#if $emptyString then 'true' else 'false'",
+ "false")
+
+ def test15(self):
+ """single-line #if: more complex"""
+ self.verify("#if $anInt then 'true' else 'false'",
+ "true")
+
+ def test16(self):
+ """single-line #if: with the words 'else' and 'then' in the output """
+ self.verify("#if ($anInt and not $emptyString==''' else ''') then $str('then') else 'else'",
+ "then")
+
+ def test17(self):
+ """single-line #if: """
+ self.verify("#if 1: foo\n#if 0: bar\n#if 1: foo",
+ "foo\nfoo")
+
+
+ self.verify("#if 1: foo\n#if 0: bar\n#if 1: foo",
+ "foo\nfoo")
+
+ def test18(self):
+ """single-line #if: \n#else: """
+ self.verify("#if 1: foo\n#elif 0: bar",
+ "foo\n")
+
+ self.verify("#if 1: foo\n#elif 0: bar\n#else: blarg\n",
+ "foo\n")
+
+ self.verify("#if 0: foo\n#elif 0: bar\n#else: blarg\n",
+ "blarg\n")
+
+class UnlessDirective(OutputTest):
+
+ def test1(self):
+ """#unless 1"""
+ self.verify("#unless 1\n 1234 \n#end unless",
+ "")
+
+ self.verify("#unless 1:\n 1234 \n#end unless",
+ "")
+
+ self.verify("#unless 1: ##comment\n 1234 \n#end unless",
+ "")
+
+ self.verify("#unless 1 ##comment\n 1234 \n#end unless",
+ "")
+
+
+ def test2(self):
+ """#unless 0"""
+ self.verify("#unless 0\n 1234 \n#end unless",
+ " 1234 \n")
+
+ def test3(self):
+ """#unless $none"""
+ self.verify("#unless $none\n 1234 \n#end unless",
+ " 1234 \n")
+
+ def test4(self):
+ """#unless $numTwo"""
+ self.verify("#unless $numTwo\n 1234 \n#end unless",
+ "")
+
+ def test5(self):
+ """#unless $numTwo with WS"""
+ self.verify(" #unless $numTwo \n 1234 \n #end unless ",
+ "")
+
+ def test6(self):
+ """single-line #unless"""
+ self.verify("#unless 1: 1234", "")
+ self.verify("#unless 0: 1234", "1234")
+ self.verify("#unless 0: 1234\n"*2, "1234\n"*2)
+
+class PSP(OutputTest):
+
+ def test1(self):
+ """simple <%= [int] %>"""
+ self.verify("<%= 1234 %>", "1234")
+
+ def test2(self):
+ """simple <%= [string] %>"""
+ self.verify("<%= 'blarg' %>", "blarg")
+
+ def test3(self):
+ """simple <%= None %>"""
+ self.verify("<%= None %>", "")
+ def test4(self):
+ """simple <%= [string] %> + $anInt"""
+ self.verify("<%= 'blarg' %>$anInt", "blarg1")
+
+ def test5(self):
+ """simple <%= [EXPR] %> + $anInt"""
+ self.verify("<%= ('blarg'*2).upper() %>$anInt", "BLARGBLARG1")
+
+ def test6(self):
+ """for loop in <%%>"""
+ self.verify("<% for i in range(5):%>1<%end%>", "11111")
+
+ def test7(self):
+ """for loop in <%%> and using <%=i%>"""
+ self.verify("<% for i in range(5):%><%=i%><%end%>", "01234")
+
+ def test8(self):
+ """for loop in <% $%> and using <%=i%>"""
+ self.verify("""<% for i in range(5):
+ i=i*2$%><%=i%><%end%>""", "02468")
+
+ def test9(self):
+ """for loop in <% $%> and using <%=i%> plus extra text"""
+ self.verify("""<% for i in range(5):
+ i=i*2$%><%=i%>-<%end%>""", "0-2-4-6-8-")
+
+
+class WhileDirective(OutputTest):
+ def test1(self):
+ """simple #while with a counter"""
+ self.verify("#set $i = 0\n#while $i < 5\n$i#slurp\n#set $i += 1\n#end while",
+ "01234")
+
+class ContinueDirective(OutputTest):
+ def test1(self):
+ """#continue with a #while"""
+ self.verify("""#set $i = 0
+#while $i < 5
+#if $i == 3
+ #set $i += 1
+ #continue
+#end if
+$i#slurp
+#set $i += 1
+#end while""",
+ "0124")
+
+ def test2(self):
+ """#continue with a #for"""
+ self.verify("""#for $i in range(5)
+#if $i == 3
+ #continue
+#end if
+$i#slurp
+#end for""",
+ "0124")
+
+class BreakDirective(OutputTest):
+ def test1(self):
+ """#break with a #while"""
+ self.verify("""#set $i = 0
+#while $i < 5
+#if $i == 3
+ #break
+#end if
+$i#slurp
+#set $i += 1
+#end while""",
+ "012")
+
+ def test2(self):
+ """#break with a #for"""
+ self.verify("""#for $i in range(5)
+#if $i == 3
+ #break
+#end if
+$i#slurp
+#end for""",
+ "012")
+
+
+class TryDirective(OutputTest):
+
+ def test1(self):
+ """simple #try
+ """
+ self.verify("#try\n1234\n#except\nblarg\n#end try",
+ "1234\n")
+
+ def test2(self):
+ """#try / #except with #raise
+ """
+ self.verify("#try\n#raise ValueError\n#except\nblarg\n#end try",
+ "blarg\n")
+
+ def test3(self):
+ """#try / #except with #raise + WS
+
+ Should gobble
+ """
+ self.verify(" #try \n #raise ValueError \n #except \nblarg\n #end try",
+ "blarg\n")
+
+
+ def test4(self):
+ """#try / #except with #raise + WS and leading text
+
+ Shouldn't gobble
+ """
+ self.verify("--#try \n #raise ValueError \n #except \nblarg\n #end try#--",
+ "--\nblarg\n --")
+
+ def test5(self):
+ """nested #try / #except with #raise
+ """
+ self.verify(
+"""#try
+ #raise ValueError
+#except
+ #try
+ #raise ValueError
+ #except
+blarg
+ #end try
+#end try""",
+ "blarg\n")
+
+class PassDirective(OutputTest):
+ def test1(self):
+ """#pass in a #try / #except block
+ """
+ self.verify("#try\n#raise ValueError\n#except\n#pass\n#end try",
+ "")
+
+ def test2(self):
+ """#pass in a #try / #except block + WS
+ """
+ self.verify(" #try \n #raise ValueError \n #except \n #pass \n #end try",
+ "")
+
+
+class AssertDirective(OutputTest):
+ def test1(self):
+ """simple #assert
+ """
+ self.verify("#set $x = 1234\n#assert $x == 1234",
+ "")
+
+ def test2(self):
+ """simple #assert that fails
+ """
+ def test(self=self):
+ self.verify("#set $x = 1234\n#assert $x == 999",
+ ""),
+ self.failUnlessRaises(AssertionError, test)
+
+ def test3(self):
+ """simple #assert with WS
+ """
+ self.verify("#set $x = 1234\n #assert $x == 1234 ",
+ "")
+
+
+class RaiseDirective(OutputTest):
+ def test1(self):
+ """simple #raise ValueError
+
+ Should raise ValueError
+ """
+ def test(self=self):
+ self.verify("#raise ValueError",
+ ""),
+ self.failUnlessRaises(ValueError, test)
+
+ def test2(self):
+ """#raise ValueError in #if block
+
+ Should raise ValueError
+ """
+ def test(self=self):
+ self.verify("#if 1\n#raise ValueError\n#end if\n",
+ "")
+ self.failUnlessRaises(ValueError, test)
+
+
+ def test3(self):
+ """#raise ValueError in #if block
+
+ Shouldn't raise ValueError
+ """
+ self.verify("#if 0\n#raise ValueError\n#else\nblarg#end if\n",
+ "blarg\n")
+
+
+
+class ImportDirective(OutputTest):
+ def test1(self):
+ """#import math
+ """
+ self.verify("#import math",
+ "")
+
+ def test2(self):
+ """#import math + WS
+
+ Should gobble
+ """
+ self.verify(" #import math ",
+ "")
+
+ def test3(self):
+ """#import math + WS + leading text
+
+ Shouldn't gobble
+ """
+ self.verify(" -- #import math ",
+ " -- ")
+
+ def test4(self):
+ """#from math import syn
+ """
+ self.verify("#from math import cos",
+ "")
+
+ def test5(self):
+ """#from math import cos + WS
+ Should gobble
+ """
+ self.verify(" #from math import cos ",
+ "")
+
+ def test6(self):
+ """#from math import cos + WS + leading text
+ Shouldn't gobble
+ """
+ self.verify(" -- #from math import cos ",
+ " -- ")
+
+ def test7(self):
+ """#from math import cos -- use it
+ """
+ self.verify("#from math import cos\n$cos(0)",
+ "1.0")
+
+ def test8(self):
+ """#from math import cos,tan,sin -- and use them
+ """
+ self.verify("#from math import cos, tan, sin\n$cos(0)-$tan(0)-$sin(0)",
+ "1.0-0.0-0.0")
+
+ def test9(self):
+ """#import os.path -- use it
+ """
+
+ self.verify("#import os.path\n$os.path.exists('.')",
+ repr(True))
+
+ def test10(self):
+ """#import os.path -- use it with NameMapper turned off
+ """
+ self.verify("""##
+#compiler-settings
+useNameMapper=False
+#end compiler-settings
+#import os.path
+$os.path.exists('.')""",
+ repr(True))
+
+ def test11(self):
+ """#from math import *
+ """
+
+ self.verify("#from math import *\n$pow(1,2) $log10(10)",
+ "1.0 1.0")
+
+class CompilerDirective(OutputTest):
+ def test1(self):
+ """overriding the commentStartToken
+ """
+ self.verify("""$anInt##comment
+#compiler commentStartToken = '//'
+$anInt//comment
+""",
+ "1\n1\n")
+
+ def test2(self):
+ """overriding and resetting the commentStartToken
+ """
+ self.verify("""$anInt##comment
+#compiler commentStartToken = '//'
+$anInt//comment
+#compiler reset
+$anInt//comment
+""",
+ "1\n1\n1//comment\n")
+
+
+class CompilerSettingsDirective(OutputTest):
+
+ def test1(self):
+ """overriding the cheetahVarStartToken
+ """
+ self.verify("""$anInt
+#compiler-settings
+cheetahVarStartToken = @
+#end compiler-settings
+@anInt
+#compiler-settings reset
+$anInt
+""",
+ "1\n1\n1\n")
+
+ def test2(self):
+ """overriding the directiveStartToken
+ """
+ self.verify("""#set $x = 1234
+$x
+#compiler-settings
+directiveStartToken = @
+#end compiler-settings
+@set $x = 1234
+$x
+""",
+ "1234\n1234\n")
+
+ def test3(self):
+ """overriding the commentStartToken
+ """
+ self.verify("""$anInt##comment
+#compiler-settings
+commentStartToken = //
+#end compiler-settings
+$anInt//comment
+""",
+ "1\n1\n")
+
+if sys.platform.startswith('java'):
+ del CompilerDirective
+ del CompilerSettingsDirective
+
+class ExtendsDirective(OutputTest):
+
+ def test1(self):
+ """#extends Cheetah.Templates._SkeletonPage"""
+ self.verify("""#from Cheetah.Templates._SkeletonPage import _SkeletonPage
+#extends _SkeletonPage
+#implements respond
+$spacer()
+""",
+ '<img src="spacer.gif" width="1" height="1" alt="" />\n')
+
+
+ self.verify("""#from Cheetah.Templates._SkeletonPage import _SkeletonPage
+#extends _SkeletonPage
+#implements respond(foo=1234)
+$spacer()$foo
+""",
+ '<img src="spacer.gif" width="1" height="1" alt="" />1234\n')
+
+ def test2(self):
+ """#extends Cheetah.Templates.SkeletonPage without #import"""
+ self.verify("""#extends Cheetah.Templates.SkeletonPage
+#implements respond
+$spacer()
+""",
+ '<img src="spacer.gif" width="1" height="1" alt="" />\n')
+
+ def test3(self):
+ """#extends Cheetah.Templates.SkeletonPage.SkeletonPage without #import"""
+ self.verify("""#extends Cheetah.Templates.SkeletonPage.SkeletonPage
+#implements respond
+$spacer()
+""",
+ '<img src="spacer.gif" width="1" height="1" alt="" />\n')
+
+ def test4(self):
+ """#extends with globals and searchList test"""
+ self.verify("""#extends Cheetah.Templates.SkeletonPage
+#set global g="Hello"
+#implements respond
+$g $numOne
+""",
+ 'Hello 1\n')
+
+class ImportantExampleCases(OutputTest):
+ def test1(self):
+ """how to make a comma-delimited list"""
+ self.verify("""#set $sep = ''
+#for $letter in $letterList
+$sep$letter#slurp
+#set $sep = ', '
+#end for
+""",
+ "a, b, c")
+
+class FilterDirective(OutputTest):
+ convertEOLs=False
+
+ def _getCompilerSettings(self):
+ return {'useFilterArgsInPlaceholders':True}
+
+ def test1(self):
+ """#filter ReplaceNone
+ """
+ self.verify("#filter ReplaceNone\n$none#end filter",
+ "")
+
+ self.verify("#filter ReplaceNone: $none",
+ "")
+
+ def test2(self):
+ """#filter ReplaceNone with WS
+ """
+ self.verify("#filter ReplaceNone \n$none#end filter",
+ "")
+
+ def test3(self):
+ """#filter MaxLen -- maxlen of 5"""
+
+ self.verify("#filter MaxLen \n${tenDigits, $maxlen=5}#end filter",
+ "12345")
+
+ def test4(self):
+ """#filter MaxLen -- no maxlen
+ """
+ self.verify("#filter MaxLen \n${tenDigits}#end filter",
+ "1234567890")
+
+ def test5(self):
+ """#filter WebSafe -- basic usage
+ """
+ self.verify("#filter WebSafe \n$webSafeTest#end filter",
+ "abc &lt;=&gt; &amp;")
+
+ def test6(self):
+ """#filter WebSafe -- also space
+ """
+ self.verify("#filter WebSafe \n${webSafeTest, $also=' '}#end filter",
+ "abc&nbsp;&lt;=&gt;&nbsp;&amp;")
+
+ def test7(self):
+ """#filter WebSafe -- also space, without $ on the args
+ """
+ self.verify("#filter WebSafe \n${webSafeTest, also=' '}#end filter",
+ "abc&nbsp;&lt;=&gt;&nbsp;&amp;")
+
+ def test8(self):
+ """#filter Strip -- trailing newline
+ """
+ self.verify("#filter Strip\n$strip1#end filter",
+ "strippable whitespace\n")
+
+ def test9(self):
+ """#filter Strip -- no trailing newine
+ """
+ self.verify("#filter Strip\n$strip2#end filter",
+ "strippable whitespace")
+
+ def test10(self):
+ """#filter Strip -- multi-line
+ """
+ self.verify("#filter Strip\n$strip3#end filter",
+ "strippable whitespace\n1 2 3\n")
+
+ def test11(self):
+ """#filter StripSqueeze -- canonicalize all whitespace to ' '
+ """
+ self.verify("#filter StripSqueeze\n$strip3#end filter",
+ "strippable whitespace 1 2 3")
+
+
+class EchoDirective(OutputTest):
+ def test1(self):
+ """#echo 1234
+ """
+ self.verify("#echo 1234",
+ "1234")
+
+class SilentDirective(OutputTest):
+ def test1(self):
+ """#silent 1234
+ """
+ self.verify("#silent 1234",
+ "")
+
+class ErrorCatcherDirective(OutputTest):
+ pass
+
+
+class VarExists(OutputTest): # Template.varExists()
+
+ def test1(self):
+ """$varExists('$anInt')
+ """
+ self.verify("$varExists('$anInt')",
+ repr(True))
+
+ def test2(self):
+ """$varExists('anInt')
+ """
+ self.verify("$varExists('anInt')",
+ repr(True))
+
+ def test3(self):
+ """$varExists('$anInt')
+ """
+ self.verify("$varExists('$bogus')",
+ repr(False))
+
+ def test4(self):
+ """$varExists('$anInt') combined with #if false
+ """
+ self.verify("#if $varExists('$bogus')\n1234\n#else\n999\n#end if",
+ "999\n")
+
+ def test5(self):
+ """$varExists('$anInt') combined with #if true
+ """
+ self.verify("#if $varExists('$anInt')\n1234\n#else\n999#end if",
+ "1234\n")
+
+class GetVar(OutputTest): # Template.getVar()
+ def test1(self):
+ """$getVar('$anInt')
+ """
+ self.verify("$getVar('$anInt')",
+ "1")
+
+ def test2(self):
+ """$getVar('anInt')
+ """
+ self.verify("$getVar('anInt')",
+ "1")
+
+ def test3(self):
+ """$self.getVar('anInt')
+ """
+ self.verify("$self.getVar('anInt')",
+ "1")
+
+ def test4(self):
+ """$getVar('bogus', 1234)
+ """
+ self.verify("$getVar('bogus', 1234)",
+ "1234")
+
+ def test5(self):
+ """$getVar('$bogus', 1234)
+ """
+ self.verify("$getVar('$bogus', 1234)",
+ "1234")
+
+
+class MiscComplexSyntax(OutputTest):
+ def test1(self):
+ """Complex use of {},[] and () in a #set expression
+ ----
+ #set $c = {'A':0}[{}.get('a', {'a' : 'A'}['a'])]
+ $c
+ """
+ self.verify("#set $c = {'A':0}[{}.get('a', {'a' : 'A'}['a'])]\n$c",
+ "0")
+
+
+class CGI(OutputTest):
+ """CGI scripts with(out) the CGI environment and with(out) GET variables.
+ """
+ convertEOLs=False
+
+ def _beginCGI(self):
+ os.environ['REQUEST_METHOD'] = "GET"
+ def _endCGI(self):
+ try:
+ del os.environ['REQUEST_METHOD']
+ except KeyError:
+ pass
+ _guaranteeNoCGI = _endCGI
+
+
+ def test1(self):
+ """A regular template."""
+ self._guaranteeNoCGI()
+ source = "#extends Cheetah.Tools.CGITemplate\n" + \
+ "#implements respond\n" + \
+ "$cgiHeaders#slurp\n" + \
+ "Hello, world!"
+ self.verify(source, "Hello, world!")
+
+
+ def test2(self):
+ """A CGI script."""
+ self._beginCGI()
+ source = "#extends Cheetah.Tools.CGITemplate\n" + \
+ "#implements respond\n" + \
+ "$cgiHeaders#slurp\n" + \
+ "Hello, world!"
+ self.verify(source, "Content-type: text/html\n\nHello, world!")
+ self._endCGI()
+
+
+ def test3(self):
+ """A (pseudo) Webware servlet.
+
+ This uses the Python syntax escape to set
+ self._CHEETAH__isControlledByWebKit.
+ We could instead do '#silent self._CHEETAH__isControlledByWebKit = True',
+ taking advantage of the fact that it will compile unchanged as long
+ as there's no '$' in the statement. (It won't compile with an '$'
+ because that would convert to a function call, and you can't assign
+ to a function call.) Because this isn't really being called from
+ Webware, we'd better not use any Webware services! Likewise, we'd
+ better not call $cgiImport() because it would be misled.
+ """
+ self._beginCGI()
+ source = "#extends Cheetah.Tools.CGITemplate\n" + \
+ "#implements respond\n" + \
+ "<% self._CHEETAH__isControlledByWebKit = True %>#slurp\n" + \
+ "$cgiHeaders#slurp\n" + \
+ "Hello, world!"
+ self.verify(source, "Hello, world!")
+ self._endCGI()
+
+
+ def test4(self):
+ """A CGI script with a GET variable."""
+ self._beginCGI()
+ os.environ['QUERY_STRING'] = "cgiWhat=world"
+ source = "#extends Cheetah.Tools.CGITemplate\n" + \
+ "#implements respond\n" + \
+ "$cgiHeaders#slurp\n" + \
+ "#silent $webInput(['cgiWhat'])##slurp\n" + \
+ "Hello, $cgiWhat!"
+ self.verify(source,
+ "Content-type: text/html\n\nHello, world!")
+ del os.environ['QUERY_STRING']
+ self._endCGI()
+
+
+
+class WhitespaceAfterDirectiveTokens(OutputTest):
+ def _getCompilerSettings(self):
+ return {'allowWhitespaceAfterDirectiveStartToken':True}
+
+ def test1(self):
+ self.verify("# for i in range(10): $i",
+ "0123456789")
+ self.verify("# for i in range(10)\n$i# end for",
+ "0123456789")
+ self.verify("# for i in range(10)#$i#end for",
+ "0123456789")
+
+
+
+class DefmacroDirective(OutputTest):
+ def _getCompilerSettings(self):
+ def aMacro(src):
+ return '$aStr'
+
+ return {'macroDirectives':{'aMacro':aMacro
+ }}
+
+ def test1(self):
+ self.verify("""\
+#defmacro inc: #set @src +=1
+#set i = 1
+#inc: $i
+$i""",
+ "2")
+
+
+
+ self.verify("""\
+#defmacro test
+#for i in range(10): @src
+#end defmacro
+#test: $i-foo#slurp
+#for i in range(3): $i""",
+ "0-foo1-foo2-foo3-foo4-foo5-foo6-foo7-foo8-foo9-foo012")
+
+ self.verify("""\
+#defmacro test
+#for i in range(10): @src
+#end defmacro
+#test: $i-foo
+#for i in range(3): $i""",
+ "0-foo\n1-foo\n2-foo\n3-foo\n4-foo\n5-foo\n6-foo\n7-foo\n8-foo\n9-foo\n012")
+
+
+ self.verify("""\
+#defmacro test: #for i in range(10): @src
+#test: $i-foo#slurp
+-#for i in range(3): $i""",
+ "0-foo1-foo2-foo3-foo4-foo5-foo6-foo7-foo8-foo9-foo-012")
+
+ self.verify("""\
+#defmacro test##for i in range(10): @src#end defmacro##slurp
+#test: $i-foo#slurp
+-#for i in range(3): $i""",
+ "0-foo1-foo2-foo3-foo4-foo5-foo6-foo7-foo8-foo9-foo-012")
+
+ self.verify("""\
+#defmacro testFoo: nothing
+#defmacro test(foo=1234): #for i in range(10): @src
+#test foo=234: $i-foo#slurp
+-#for i in range(3): $i""",
+ "0-foo1-foo2-foo3-foo4-foo5-foo6-foo7-foo8-foo9-foo-012")
+
+ self.verify("""\
+#defmacro testFoo: nothing
+#defmacro test(foo=1234): #for i in range(10): @src@foo
+#test foo='-foo'#$i#end test#-#for i in range(3): $i""",
+ "0-foo1-foo2-foo3-foo4-foo5-foo6-foo7-foo8-foo9-foo-012")
+
+ self.verify("""\
+#defmacro testFoo: nothing
+#defmacro test(foo=1234): #for i in range(10): @src.strip()@foo
+#test foo='-foo': $i
+-#for i in range(3): $i""",
+ "0-foo1-foo2-foo3-foo4-foo5-foo6-foo7-foo8-foo9-foo-012")
+
+ def test2(self):
+ self.verify("#aMacro: foo",
+ "blarg")
+ self.verify("#defmacro nested: @macros.aMacro(@src)\n#nested: foo",
+ "blarg")
+
+
+class Indenter(OutputTest):
+ convertEOLs=False
+
+ source = """
+public class X
+{
+ #for $method in $methods
+ $getMethod($method)
+
+ #end for
+}
+//end of class
+
+#def getMethod($method)
+ #indent ++
+ public $getType($method) ${method.Name}($getParams($method.Params));
+ #indent --
+#end def
+
+#def getParams($params)
+ #indent off
+
+ #for $counter in $range($len($params))
+ #if $counter == len($params) - 1
+ $params[$counter]#slurp
+ #else:
+ $params[$counter],
+ #end if
+ #end for
+ #indent on
+#end def
+
+#def getType($method)
+ #indent push
+ #indent=0
+ #if $method.Type == "VT_VOID"
+ void#slurp
+ #elif $method.Type == "VT_INT"
+ int#slurp
+ #elif $method.Type == "VT_VARIANT"
+ Object#slurp
+ #end if
+ #indent pop
+#end def
+"""
+
+ control = """
+public class X
+{
+ public void Foo(
+ _input,
+ _output);
+
+
+ public int Bar(
+ _str1,
+ str2,
+ _str3);
+
+
+ public Object Add(
+ value1,
+ value);
+
+
+}
+//end of class
+
+
+
+"""
+ def _getCompilerSettings(self):
+ return {'useFilterArgsInPlaceholders':True}
+
+ def searchList(self): # Inside Indenter class.
+ class Method:
+ def __init__(self, _name, _type, *_params):
+ self.Name = _name
+ self.Type = _type
+ self.Params = _params
+ methods = [Method("Foo", "VT_VOID", "_input", "_output"),
+ Method("Bar", "VT_INT", "_str1", "str2", "_str3"),
+ Method("Add", "VT_VARIANT", "value1", "value")]
+ return [{"methods": methods}]
+
+ def test1(self): # Inside Indenter class.
+ self.verify(self.source, self.control)
+
+
+##################################################
+## CREATE CONVERTED EOL VERSIONS OF THE TEST CASES
+
+if OutputTest._useNewStyleCompilation and versionTuple >= (2,3):
+ extraCompileKwArgsForDiffBaseclass = {'baseclass':dict}
+else:
+ extraCompileKwArgsForDiffBaseclass = {'baseclass':object}
+
+
+for klass in [var for var in globals().values()
+ if type(var) == types.ClassType and issubclass(var, unittest.TestCase)]:
+ name = klass.__name__
+ if hasattr(klass,'convertEOLs') and klass.convertEOLs:
+ win32Src = r"class %(name)s_Win32EOL(%(name)s): _EOLreplacement = '\r\n'"%locals()
+ macSrc = r"class %(name)s_MacEOL(%(name)s): _EOLreplacement = '\r'"%locals()
+ #print win32Src
+ #print macSrc
+ exec win32Src+'\n'
+ exec macSrc+'\n'
+
+ if versionTuple >= (2,3):
+ src = r"class %(name)s_DiffBaseClass(%(name)s): "%locals()
+ src += " _extraCompileKwArgs = extraCompileKwArgsForDiffBaseclass"
+ exec src+'\n'
+
+ del name
+ del klass
+
+##################################################
+## if run from the command line ##
+
+if __name__ == '__main__':
+ unittest.main()
+
+# vim: shiftwidth=4 tabstop=4 expandtab
diff --git a/cobbler/Cheetah/Tests/Template.py b/cobbler/Cheetah/Tests/Template.py
new file mode 100644
index 0000000..b97cf5d
--- /dev/null
+++ b/cobbler/Cheetah/Tests/Template.py
@@ -0,0 +1,312 @@
+#!/usr/bin/env python
+# $Id: Template.py,v 1.16 2006/02/03 21:05:50 tavis_rudd Exp $
+"""Tests of the Template class API
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com>,
+Version: $Revision: 1.16 $
+Start Date: 2001/10/01
+Last Revision Date: $Date: 2006/02/03 21:05:50 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.16 $"[11:-2]
+
+
+##################################################
+## DEPENDENCIES ##
+
+import sys
+import types
+import os
+import os.path
+import tempfile
+import shutil
+import unittest_local_copy as unittest
+from Cheetah.Template import Template
+
+##################################################
+## CONSTANTS & GLOBALS ##
+
+majorVer, minorVer = sys.version_info[0], sys.version_info[1]
+versionTuple = (majorVer, minorVer)
+
+try:
+ True,False
+except NameError:
+ True, False = (1==1),(1==0)
+
+##################################################
+## TEST DATA FOR USE IN THE TEMPLATES ##
+
+##################################################
+## TEST BASE CLASSES
+
+class TemplateTest(unittest.TestCase):
+ pass
+
+##################################################
+## TEST CASE CLASSES
+
+class ClassMethods_compile(TemplateTest):
+ """I am using the same Cheetah source for each test to root out clashes
+ caused by the compile caching in Template.compile().
+ """
+
+ def test_basicUsage(self):
+ klass = Template.compile(source='$foo')
+ t = klass(namespaces={'foo':1234})
+ assert str(t)=='1234'
+
+ def test_baseclassArg(self):
+ klass = Template.compile(source='$foo', baseclass=dict)
+ t = klass({'foo':1234})
+ assert str(t)=='1234'
+
+ klass2 = Template.compile(source='$foo', baseclass=klass)
+ t = klass2({'foo':1234})
+ assert str(t)=='1234'
+
+ klass3 = Template.compile(source='#implements dummy\n$bar', baseclass=klass2)
+ t = klass3({'foo':1234})
+ assert str(t)=='1234'
+
+ klass4 = Template.compile(source='$foo', baseclass='dict')
+ t = klass4({'foo':1234})
+ assert str(t)=='1234'
+
+ def test_moduleFileCaching(self):
+ if versionTuple < (2,3):
+ return
+ tmpDir = tempfile.mkdtemp()
+ try:
+ #print tmpDir
+ assert os.path.exists(tmpDir)
+ klass = Template.compile(source='$foo',
+ cacheModuleFilesForTracebacks=True,
+ cacheDirForModuleFiles=tmpDir)
+ mod = sys.modules[klass.__module__]
+ #print mod.__file__
+ assert os.path.exists(mod.__file__)
+ assert os.path.dirname(mod.__file__)==tmpDir
+ finally:
+ shutil.rmtree(tmpDir, True)
+
+ def test_classNameArg(self):
+ klass = Template.compile(source='$foo', className='foo123')
+ assert klass.__name__=='foo123'
+ t = klass(namespaces={'foo':1234})
+ assert str(t)=='1234'
+
+ def test_moduleNameArg(self):
+ klass = Template.compile(source='$foo', moduleName='foo99')
+ mod = sys.modules['foo99']
+ assert klass.__name__=='foo99'
+ t = klass(namespaces={'foo':1234})
+ assert str(t)=='1234'
+
+
+ klass = Template.compile(source='$foo',
+ moduleName='foo1',
+ className='foo2')
+ mod = sys.modules['foo1']
+ assert klass.__name__=='foo2'
+ t = klass(namespaces={'foo':1234})
+ assert str(t)=='1234'
+
+
+ def test_mainMethodNameArg(self):
+ klass = Template.compile(source='$foo',
+ className='foo123',
+ mainMethodName='testMeth')
+ assert klass.__name__=='foo123'
+ t = klass(namespaces={'foo':1234})
+ #print t.generatedClassCode()
+ assert str(t)=='1234'
+ assert t.testMeth()=='1234'
+
+ klass = Template.compile(source='$foo',
+ moduleName='fooXXX',
+ className='foo123',
+ mainMethodName='testMeth',
+ baseclass=dict)
+ assert klass.__name__=='foo123'
+ t = klass({'foo':1234})
+ #print t.generatedClassCode()
+ assert str(t)=='1234'
+ assert t.testMeth()=='1234'
+
+
+
+ def test_moduleGlobalsArg(self):
+ klass = Template.compile(source='$foo',
+ moduleGlobals={'foo':1234})
+ t = klass()
+ assert str(t)=='1234'
+
+ klass2 = Template.compile(source='$foo', baseclass='Test1',
+ moduleGlobals={'Test1':dict})
+ t = klass2({'foo':1234})
+ assert str(t)=='1234'
+
+ klass3 = Template.compile(source='$foo', baseclass='Test1',
+ moduleGlobals={'Test1':dict, 'foo':1234})
+ t = klass3()
+ assert str(t)=='1234'
+
+
+ def test_keepRefToGeneratedCodeArg(self):
+ klass = Template.compile(source='$foo',
+ className='unique58',
+ cacheCompilationResults=False,
+ keepRefToGeneratedCode=False)
+ t = klass(namespaces={'foo':1234})
+ assert str(t)=='1234'
+ assert not t.generatedModuleCode()
+
+
+ klass2 = Template.compile(source='$foo',
+ className='unique58',
+ keepRefToGeneratedCode=True)
+ t = klass2(namespaces={'foo':1234})
+ assert str(t)=='1234'
+ assert t.generatedModuleCode()
+
+ klass3 = Template.compile(source='$foo',
+ className='unique58',
+ keepRefToGeneratedCode=False)
+ t = klass3(namespaces={'foo':1234})
+ assert str(t)=='1234'
+ # still there as this class came from the cache
+ assert t.generatedModuleCode()
+
+
+ def test_compilationCache(self):
+ klass = Template.compile(source='$foo',
+ className='unique111',
+ cacheCompilationResults=False)
+ t = klass(namespaces={'foo':1234})
+ assert str(t)=='1234'
+ assert not klass._CHEETAH_isInCompilationCache
+
+
+ # this time it will place it in the cache
+ klass = Template.compile(source='$foo',
+ className='unique111',
+ cacheCompilationResults=True)
+ t = klass(namespaces={'foo':1234})
+ assert str(t)=='1234'
+ assert klass._CHEETAH_isInCompilationCache
+
+ # by default it will be in the cache
+ klass = Template.compile(source='$foo',
+ className='unique999099')
+ t = klass(namespaces={'foo':1234})
+ assert str(t)=='1234'
+ assert klass._CHEETAH_isInCompilationCache
+
+
+class ClassMethods_subclass(TemplateTest):
+
+ def test_basicUsage(self):
+ klass = Template.compile(source='$foo', baseclass=dict)
+ t = klass({'foo':1234})
+ assert str(t)=='1234'
+
+ klass2 = klass.subclass(source='$foo')
+ t = klass2({'foo':1234})
+ assert str(t)=='1234'
+
+ klass3 = klass2.subclass(source='#implements dummy\n$bar')
+ t = klass3({'foo':1234})
+ assert str(t)=='1234'
+
+
+class Preprocessors(TemplateTest):
+
+ def test_basicUsage1(self):
+ src='''\
+ %set foo = @a
+ $(@foo*10)
+ @a'''
+ src = '\n'.join([ln.strip() for ln in src.splitlines()])
+ preprocessors = {'tokens':'@ %',
+ 'namespaces':{'a':99}
+ }
+ klass = Template.compile(src, preprocessors=preprocessors)
+ assert str(klass())=='990\n99'
+
+ def test_normalizePreprocessorArgVariants(self):
+ src='%set foo = 12\n%%comment\n$(@foo*10)'
+
+ class Settings1: tokens = '@ %'
+ Settings1 = Settings1()
+
+ from Cheetah.Template import TemplatePreprocessor
+ settings = Template._normalizePreprocessorSettings(Settings1)
+ preprocObj = TemplatePreprocessor(settings)
+
+ def preprocFunc(source, file):
+ return '$(12*10)', None
+
+ class TemplateSubclass(Template):
+ pass
+
+ compilerSettings = {'cheetahVarStartToken':'@',
+ 'directiveStartToken':'%',
+ 'commentStartToken':'%%',
+ }
+
+ for arg in ['@ %',
+ {'tokens':'@ %'},
+ {'compilerSettings':compilerSettings},
+ {'compilerSettings':compilerSettings,
+ 'templateInitArgs':{}},
+ {'tokens':'@ %',
+ 'templateAPIClass':TemplateSubclass},
+ Settings1,
+ preprocObj,
+ preprocFunc,
+ ]:
+
+ klass = Template.compile(src, preprocessors=arg)
+ assert str(klass())=='120'
+
+
+ def test_complexUsage(self):
+ src='''\
+ %set foo = @a
+ %def func1: #def func(arg): $arg("***")
+ %% comment
+ $(@foo*10)
+ @func1
+ $func(lambda x:c"--$x--@a")'''
+ src = '\n'.join([ln.strip() for ln in src.splitlines()])
+
+
+ for arg in [{'tokens':'@ %', 'namespaces':{'a':99} },
+ {'tokens':'@ %', 'namespaces':{'a':99} },
+ ]:
+ klass = Template.compile(src, preprocessors=arg)
+ t = klass()
+ assert str(t)=='990\n--***--99'
+
+
+
+ def test_i18n(self):
+ src='''\
+ %i18n: This is a $string that needs translation
+ %i18n id="foo", domain="root": This is a $string that needs translation
+ '''
+ src = '\n'.join([ln.strip() for ln in src.splitlines()])
+ klass = Template.compile(src, preprocessors='@ %', baseclass=dict)
+ t = klass({'string':'bit of text'})
+ #print str(t), repr(str(t))
+ assert str(t)==('This is a bit of text that needs translation\n'*2)[:-1]
+
+
+##################################################
+## if run from the command line ##
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/cobbler/Cheetah/Tests/Test.py b/cobbler/Cheetah/Tests/Test.py
new file mode 100644
index 0000000..9e46a5d
--- /dev/null
+++ b/cobbler/Cheetah/Tests/Test.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+# $Id: Test.py,v 1.44 2006/01/15 20:45:10 tavis_rudd Exp $
+"""Core module of Cheetah's Unit-testing framework
+
+TODO
+================================================================================
+# combo tests
+# negative test cases for expected exceptions
+# black-box vs clear-box testing
+# do some tests that run the Template for long enough to check that the refresh code works
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com>,
+License: This software is released for unlimited distribution under the
+ terms of the MIT license. See the LICENSE file.
+Version: $Revision: 1.44 $
+Start Date: 2001/03/30
+Last Revision Date: $Date: 2006/01/15 20:45:10 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.44 $"[11:-2]
+
+
+##################################################
+## DEPENDENCIES ##
+
+import sys
+import unittest_local_copy as unittest
+
+##################################################
+## CONSTANTS & GLOBALS
+
+try:
+ True, False
+except NameError:
+ True, False = (1==1),(1==0)
+
+##################################################
+## TESTS
+
+import SyntaxAndOutput
+import NameMapper
+import Template
+import FileRefresh
+import CheetahWrapper
+
+SyntaxSuite = unittest.findTestCases(SyntaxAndOutput)
+NameMapperSuite = unittest.findTestCases(NameMapper)
+TemplateSuite = unittest.findTestCases(Template)
+FileRefreshSuite = unittest.findTestCases(FileRefresh)
+if not sys.platform.startswith('java'):
+ CheetahWrapperSuite = unittest.findTestCases(CheetahWrapper)
+
+from SyntaxAndOutput import *
+from NameMapper import *
+from Template import *
+from FileRefresh import *
+
+if not sys.platform.startswith('java'):
+ from CheetahWrapper import *
+
+##################################################
+## if run from the command line
+
+if __name__ == '__main__':
+ unittest.main()
+
+
+
diff --git a/cobbler/Cheetah/Tests/__init__.py b/cobbler/Cheetah/Tests/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/cobbler/Cheetah/Tests/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/cobbler/Cheetah/Tests/unittest_local_copy.py b/cobbler/Cheetah/Tests/unittest_local_copy.py
new file mode 100644
index 0000000..54061ae
--- /dev/null
+++ b/cobbler/Cheetah/Tests/unittest_local_copy.py
@@ -0,0 +1,977 @@
+#!/usr/bin/env python
+""" This is a hacked version of PyUnit that extends its reporting capabilities
+with optional meta data on the test cases. It also makes it possible to
+separate the standard and error output streams in TextTestRunner.
+
+It's a hack rather than a set of subclasses because a) Steve had used double
+underscore private attributes for some things I needed access to, and b) the
+changes affected so many classes that it was easier just to hack it.
+
+The changes are in the following places:
+TestCase:
+ - minor refactoring of __init__ and __call__ internals
+ - added some attributes and methods for storing and retrieving meta data
+
+_TextTestResult
+ - refactored the stream handling
+ - incorporated all the output code from TextTestRunner
+ - made the output of FAIL and ERROR information more flexible and
+ incorporated the new meta data from TestCase
+ - added a flag called 'explain' to __init__ that controls whether the new '
+ explanation' meta data from TestCase is printed along with tracebacks
+
+TextTestRunner
+ - delegated all output to _TextTestResult
+ - added 'err' and 'explain' to the __init__ signature to match the changes
+ in _TextTestResult
+
+TestProgram
+ - added -e and --explain as flags on the command line
+
+-- Tavis Rudd <tavis@redonions.net> (Sept 28th, 2001)
+
+- _TestTextResult.printErrorList(): print blank line after each traceback
+
+-- Mike Orr <mso@oz.net> (Nov 11, 2002)
+
+TestCase methods copied from unittest in Python 2.3:
+ - .assertAlmostEqual(first, second, places=7, msg=None): to N decimal places.
+ - .failIfAlmostEqual(first, second, places=7, msg=None)
+
+-- Mike Orr (Jan 5, 2004)
+
+
+Below is the original docstring for unittest.
+---------------------------------------------------------------------------
+Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's
+Smalltalk testing framework.
+
+This module contains the core framework classes that form the basis of
+specific test cases and suites (TestCase, TestSuite etc.), and also a
+text-based utility class for running the tests and reporting the results
+(TextTestRunner).
+
+Simple usage:
+
+ import unittest
+
+ class IntegerArithmenticTestCase(unittest.TestCase):
+ def testAdd(self): ## test method names begin 'test*'
+ self.assertEquals((1 + 2), 3)
+ self.assertEquals(0 + 1, 1)
+ def testMultiply(self);
+ self.assertEquals((0 * 10), 0)
+ self.assertEquals((5 * 8), 40)
+
+ if __name__ == '__main__':
+ unittest.main()
+
+Further information is available in the bundled documentation, and from
+
+ http://pyunit.sourceforge.net/
+
+Copyright (c) 1999, 2000, 2001 Steve Purcell
+This module is free software, and you may redistribute it and/or modify
+it under the same terms as Python itself, so long as this copyright message
+and disclaimer are retained in their original form.
+
+IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
+SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
+THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
+AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
+SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+"""
+
+__author__ = "Steve Purcell"
+__email__ = "stephen_purcell at yahoo dot com"
+__revision__ = "$Revision: 1.11 $"[11:-2]
+
+
+##################################################
+## DEPENDENCIES ##
+
+import os
+import re
+import string
+import sys
+import time
+import traceback
+import types
+import pprint
+
+##################################################
+## CONSTANTS & GLOBALS
+
+try:
+ True,False
+except NameError:
+ True, False = (1==1),(1==0)
+
+##############################################################################
+# Test framework core
+##############################################################################
+
+
+class TestResult:
+ """Holder for test result information.
+
+ Test results are automatically managed by the TestCase and TestSuite
+ classes, and do not need to be explicitly manipulated by writers of tests.
+
+ Each instance holds the total number of tests run, and collections of
+ failures and errors that occurred among those test runs. The collections
+ contain tuples of (testcase, exceptioninfo), where exceptioninfo is a
+ tuple of values as returned by sys.exc_info().
+ """
+ def __init__(self):
+ self.failures = []
+ self.errors = []
+ self.testsRun = 0
+ self.shouldStop = 0
+
+ def startTest(self, test):
+ "Called when the given test is about to be run"
+ self.testsRun = self.testsRun + 1
+
+ def stopTest(self, test):
+ "Called when the given test has been run"
+ pass
+
+ def addError(self, test, err):
+ "Called when an error has occurred"
+ self.errors.append((test, err))
+
+ def addFailure(self, test, err):
+ "Called when a failure has occurred"
+ self.failures.append((test, err))
+
+ def addSuccess(self, test):
+ "Called when a test has completed successfully"
+ pass
+
+ def wasSuccessful(self):
+ "Tells whether or not this result was a success"
+ return len(self.failures) == len(self.errors) == 0
+
+ def stop(self):
+ "Indicates that the tests should be aborted"
+ self.shouldStop = 1
+
+ def __repr__(self):
+ return "<%s run=%i errors=%i failures=%i>" % \
+ (self.__class__, self.testsRun, len(self.errors),
+ len(self.failures))
+
+class TestCase:
+ """A class whose instances are single test cases.
+
+ By default, the test code itself should be placed in a method named
+ 'runTest'.
+
+ If the fixture may be used for many test cases, create as
+ many test methods as are needed. When instantiating such a TestCase
+ subclass, specify in the constructor arguments the name of the test method
+ that the instance is to execute.
+
+ Test authors should subclass TestCase for their own tests. Construction
+ and deconstruction of the test's environment ('fixture') can be
+ implemented by overriding the 'setUp' and 'tearDown' methods respectively.
+
+ If it is necessary to override the __init__ method, the base class
+ __init__ method must always be called. It is important that subclasses
+ should not change the signature of their __init__ method, since instances
+ of the classes are instantiated automatically by parts of the framework
+ in order to be run.
+ """
+
+ # This attribute determines which exception will be raised when
+ # the instance's assertion methods fail; test methods raising this
+ # exception will be deemed to have 'failed' rather than 'errored'
+
+ failureException = AssertionError
+
+ # the name of the fixture. Used for displaying meta data about the test
+ name = None
+
+ def __init__(self, methodName='runTest'):
+ """Create an instance of the class that will use the named test
+ method when executed. Raises a ValueError if the instance does
+ not have a method with the specified name.
+ """
+ self._testMethodName = methodName
+ self._setupTestMethod()
+ self._setupMetaData()
+
+ def _setupTestMethod(self):
+ try:
+ self._testMethod = getattr(self, self._testMethodName)
+ except AttributeError:
+ raise ValueError, "no such test method in %s: %s" % \
+ (self.__class__, self._testMethodName)
+
+ ## meta data methods
+
+ def _setupMetaData(self):
+ """Setup the default meta data for the test case:
+
+ - id: self.__class__.__name__ + testMethodName OR self.name + testMethodName
+ - description: 1st line of Class docstring + 1st line of method docstring
+ - explanation: rest of Class docstring + rest of method docstring
+
+ """
+
+
+ testDoc = self._testMethod.__doc__ or '\n'
+ testDocLines = testDoc.splitlines()
+
+ testDescription = testDocLines[0].strip()
+ if len(testDocLines) > 1:
+ testExplanation = '\n'.join(
+ [ln.strip() for ln in testDocLines[1:]]
+ ).strip()
+ else:
+ testExplanation = ''
+
+ fixtureDoc = self.__doc__ or '\n'
+ fixtureDocLines = fixtureDoc.splitlines()
+ fixtureDescription = fixtureDocLines[0].strip()
+ if len(fixtureDocLines) > 1:
+ fixtureExplanation = '\n'.join(
+ [ln.strip() for ln in fixtureDocLines[1:]]
+ ).strip()
+ else:
+ fixtureExplanation = ''
+
+ if not self.name:
+ self.name = self.__class__
+ self._id = "%s.%s" % (self.name, self._testMethodName)
+
+ if not fixtureDescription:
+ self._description = testDescription
+ else:
+ self._description = fixtureDescription + ', ' + testDescription
+
+ if not fixtureExplanation:
+ self._explanation = testExplanation
+ else:
+ self._explanation = ['Fixture Explanation:',
+ '--------------------',
+ fixtureExplanation,
+ '',
+ 'Test Explanation:',
+ '-----------------',
+ testExplanation
+ ]
+ self._explanation = '\n'.join(self._explanation)
+
+ def id(self):
+ return self._id
+
+ def setId(self, id):
+ self._id = id
+
+ def describe(self):
+ """Returns a one-line description of the test, or None if no
+ description has been provided.
+
+ The default implementation of this method returns the first line of
+ the specified test method's docstring.
+ """
+ return self._description
+
+ shortDescription = describe
+
+ def setDescription(self, descr):
+ self._description = descr
+
+ def explain(self):
+ return self._explanation
+
+ def setExplanation(self, expln):
+ self._explanation = expln
+
+ ## core methods
+
+ def setUp(self):
+ "Hook method for setting up the test fixture before exercising it."
+ pass
+
+ def run(self, result=None):
+ return self(result)
+
+ def tearDown(self):
+ "Hook method for deconstructing the test fixture after testing it."
+ pass
+
+ def debug(self):
+ """Run the test without collecting errors in a TestResult"""
+ self.setUp()
+ self._testMethod()
+ self.tearDown()
+
+ ## internal methods
+
+ def defaultTestResult(self):
+ return TestResult()
+
+ def __call__(self, result=None):
+ if result is None:
+ result = self.defaultTestResult()
+
+ result.startTest(self)
+ try:
+ try:
+ self.setUp()
+ except:
+ result.addError(self, self.__exc_info())
+ return
+
+ ok = 0
+ try:
+ self._testMethod()
+ ok = 1
+ except self.failureException, e:
+ result.addFailure(self, self.__exc_info())
+ except:
+ result.addError(self, self.__exc_info())
+ try:
+ self.tearDown()
+ except:
+ result.addError(self, self.__exc_info())
+ ok = 0
+ if ok:
+ result.addSuccess(self)
+ finally:
+ result.stopTest(self)
+
+ return result
+
+ def countTestCases(self):
+ return 1
+
+ def __str__(self):
+ return "%s (%s)" % (self._testMethodName, self.__class__)
+
+ def __repr__(self):
+ return "<%s testMethod=%s>" % \
+ (self.__class__, self._testMethodName)
+
+ def __exc_info(self):
+ """Return a version of sys.exc_info() with the traceback frame
+ minimised; usually the top level of the traceback frame is not
+ needed.
+ """
+ exctype, excvalue, tb = sys.exc_info()
+ if sys.platform[:4] == 'java': ## tracebacks look different in Jython
+ return (exctype, excvalue, tb)
+ newtb = tb.tb_next
+ if newtb is None:
+ return (exctype, excvalue, tb)
+ return (exctype, excvalue, newtb)
+
+ ## methods for use by the test cases
+
+ def fail(self, msg=None):
+ """Fail immediately, with the given message."""
+ raise self.failureException, msg
+
+ def failIf(self, expr, msg=None):
+ "Fail the test if the expression is true."
+ if expr: raise self.failureException, msg
+
+ def failUnless(self, expr, msg=None):
+ """Fail the test unless the expression is true."""
+ if not expr: raise self.failureException, msg
+
+ def failUnlessRaises(self, excClass, callableObj, *args, **kwargs):
+ """Fail unless an exception of class excClass is thrown
+ by callableObj when invoked with arguments args and keyword
+ arguments kwargs. If a different type of exception is
+ thrown, it will not be caught, and the test case will be
+ deemed to have suffered an error, exactly as for an
+ unexpected exception.
+ """
+ try:
+ apply(callableObj, args, kwargs)
+ except excClass:
+ return
+ else:
+ if hasattr(excClass,'__name__'): excName = excClass.__name__
+ else: excName = str(excClass)
+ raise self.failureException, excName
+
+ def failUnlessEqual(self, first, second, msg=None):
+ """Fail if the two objects are unequal as determined by the '!='
+ operator.
+ """
+ if first != second:
+ raise self.failureException, (msg or '%s != %s' % (first, second))
+
+ def failIfEqual(self, first, second, msg=None):
+ """Fail if the two objects are equal as determined by the '=='
+ operator.
+ """
+ if first == second:
+ raise self.failureException, (msg or '%s == %s' % (first, second))
+
+ def failUnlessAlmostEqual(self, first, second, places=7, msg=None):
+ """Fail if the two objects are unequal as determined by their
+ difference rounded to the given number of decimal places
+ (default 7) and comparing to zero.
+
+ Note that decimal places (from zero) is usually not the same
+ as significant digits (measured from the most signficant digit).
+ """
+ if round(second-first, places) != 0:
+ raise self.failureException, \
+ (msg or '%s != %s within %s places' % (`first`, `second`, `places` ))
+
+ def failIfAlmostEqual(self, first, second, places=7, msg=None):
+ """Fail if the two objects are equal as determined by their
+ difference rounded to the given number of decimal places
+ (default 7) and comparing to zero.
+
+ Note that decimal places (from zero) is usually not the same
+ as significant digits (measured from the most signficant digit).
+ """
+ if round(second-first, places) == 0:
+ raise self.failureException, \
+ (msg or '%s == %s within %s places' % (`first`, `second`, `places`))
+
+ ## aliases
+
+ assertEqual = assertEquals = failUnlessEqual
+
+ assertNotEqual = assertNotEquals = failIfEqual
+
+ assertAlmostEqual = assertAlmostEquals = failUnlessAlmostEqual
+
+ assertNotAlmostEqual = assertNotAlmostEquals = failIfAlmostEqual
+
+ assertRaises = failUnlessRaises
+
+ assert_ = failUnless
+
+
+class FunctionTestCase(TestCase):
+ """A test case that wraps a test function.
+
+ This is useful for slipping pre-existing test functions into the
+ PyUnit framework. Optionally, set-up and tidy-up functions can be
+ supplied. As with TestCase, the tidy-up ('tearDown') function will
+ always be called if the set-up ('setUp') function ran successfully.
+ """
+
+ def __init__(self, testFunc, setUp=None, tearDown=None,
+ description=None):
+ TestCase.__init__(self)
+ self.__setUpFunc = setUp
+ self.__tearDownFunc = tearDown
+ self.__testFunc = testFunc
+ self.__description = description
+
+ def setUp(self):
+ if self.__setUpFunc is not None:
+ self.__setUpFunc()
+
+ def tearDown(self):
+ if self.__tearDownFunc is not None:
+ self.__tearDownFunc()
+
+ def runTest(self):
+ self.__testFunc()
+
+ def id(self):
+ return self.__testFunc.__name__
+
+ def __str__(self):
+ return "%s (%s)" % (self.__class__, self.__testFunc.__name__)
+
+ def __repr__(self):
+ return "<%s testFunc=%s>" % (self.__class__, self.__testFunc)
+
+
+ def describe(self):
+ if self.__description is not None: return self.__description
+ doc = self.__testFunc.__doc__
+ return doc and string.strip(string.split(doc, "\n")[0]) or None
+
+ ## aliases
+ shortDescription = describe
+
+class TestSuite:
+ """A test suite is a composite test consisting of a number of TestCases.
+
+ For use, create an instance of TestSuite, then add test case instances.
+ When all tests have been added, the suite can be passed to a test
+ runner, such as TextTestRunner. It will run the individual test cases
+ in the order in which they were added, aggregating the results. When
+ subclassing, do not forget to call the base class constructor.
+ """
+ def __init__(self, tests=(), suiteName=None):
+ self._tests = []
+ self._testMap = {}
+ self.suiteName = suiteName
+ self.addTests(tests)
+
+ def __repr__(self):
+ return "<%s tests=%s>" % (self.__class__, pprint.pformat(self._tests))
+
+ __str__ = __repr__
+
+ def countTestCases(self):
+ cases = 0
+ for test in self._tests:
+ cases = cases + test.countTestCases()
+ return cases
+
+ def addTest(self, test):
+ self._tests.append(test)
+ if isinstance(test, TestSuite) and test.suiteName:
+ name = test.suiteName
+ elif isinstance(test, TestCase):
+ #print test, test._testMethodName
+ name = test._testMethodName
+ else:
+ name = test.__class__.__name__
+ self._testMap[name] = test
+
+ def addTests(self, tests):
+ for test in tests:
+ self.addTest(test)
+
+ def getTestForName(self, name):
+ return self._testMap[name]
+
+ def run(self, result):
+ return self(result)
+
+ def __call__(self, result):
+ for test in self._tests:
+ if result.shouldStop:
+ break
+ test(result)
+ return result
+
+ def debug(self):
+ """Run the tests without collecting errors in a TestResult"""
+ for test in self._tests: test.debug()
+
+
+##############################################################################
+# Text UI
+##############################################################################
+
+class StreamWrapper:
+ def __init__(self, out=sys.stdout, err=sys.stderr):
+ self._streamOut = out
+ self._streamErr = err
+
+ def write(self, txt):
+ self._streamOut.write(txt)
+ self._streamOut.flush()
+
+ def writeln(self, *lines):
+ for line in lines:
+ self.write(line + '\n')
+ if not lines:
+ self.write('\n')
+
+ def writeErr(self, txt):
+ self._streamErr.write(txt)
+
+ def writelnErr(self, *lines):
+ for line in lines:
+ self.writeErr(line + '\n')
+ if not lines:
+ self.writeErr('\n')
+
+
+class _TextTestResult(TestResult, StreamWrapper):
+ _separatorWidth = 70
+ _sep1 = '='
+ _sep2 = '-'
+ _errorSep1 = '*'
+ _errorSep2 = '-'
+ _errorSep3 = ''
+
+ def __init__(self,
+ stream=sys.stdout,
+ errStream=sys.stderr,
+ verbosity=1,
+ explain=False):
+
+ TestResult.__init__(self)
+ StreamWrapper.__init__(self, out=stream, err=errStream)
+
+ self._verbosity = verbosity
+ self._showAll = verbosity > 1
+ self._dots = (verbosity == 1)
+ self._explain = explain
+
+ ## startup and shutdown methods
+
+ def beginTests(self):
+ self._startTime = time.time()
+
+ def endTests(self):
+ self._stopTime = time.time()
+ self._timeTaken = float(self._stopTime - self._startTime)
+
+ def stop(self):
+ self.shouldStop = 1
+
+ ## methods called for each test
+
+ def startTest(self, test):
+ TestResult.startTest(self, test)
+ if self._showAll:
+ self.write("%s (%s)" %( test.id(), test.describe() ) )
+ self.write(" ... ")
+
+ def addSuccess(self, test):
+ TestResult.addSuccess(self, test)
+ if self._showAll:
+ self.writeln("ok")
+ elif self._dots:
+ self.write('.')
+
+ def addError(self, test, err):
+ TestResult.addError(self, test, err)
+ if self._showAll:
+ self.writeln("ERROR")
+ elif self._dots:
+ self.write('E')
+ if err[0] is KeyboardInterrupt:
+ self.stop()
+
+ def addFailure(self, test, err):
+ TestResult.addFailure(self, test, err)
+ if self._showAll:
+ self.writeln("FAIL")
+ elif self._dots:
+ self.write('F')
+
+ ## display methods
+
+ def summarize(self):
+ self.printErrors()
+ self.writeSep2()
+ run = self.testsRun
+ self.writeln("Ran %d test%s in %.3fs" %
+ (run, run == 1 and "" or "s", self._timeTaken))
+ self.writeln()
+ if not self.wasSuccessful():
+ self.writeErr("FAILED (")
+ failed, errored = map(len, (self.failures, self.errors))
+ if failed:
+ self.writeErr("failures=%d" % failed)
+ if errored:
+ if failed: self.writeErr(", ")
+ self.writeErr("errors=%d" % errored)
+ self.writelnErr(")")
+ else:
+ self.writelnErr("OK")
+
+ def writeSep1(self):
+ self.writeln(self._sep1 * self._separatorWidth)
+
+ def writeSep2(self):
+ self.writeln(self._sep2 * self._separatorWidth)
+
+ def writeErrSep1(self):
+ self.writeln(self._errorSep1 * self._separatorWidth)
+
+ def writeErrSep2(self):
+ self.writeln(self._errorSep2 * self._separatorWidth)
+
+ def printErrors(self):
+ if self._dots or self._showAll:
+ self.writeln()
+ self.printErrorList('ERROR', self.errors)
+ self.printErrorList('FAIL', self.failures)
+
+ def printErrorList(self, flavour, errors):
+ for test, err in errors:
+ self.writeErrSep1()
+ self.writelnErr("%s %s (%s)" % (flavour, test.id(), test.describe() ))
+ if self._explain:
+ expln = test.explain()
+ if expln:
+ self.writeErrSep2()
+ self.writeErr( expln )
+ self.writelnErr()
+
+ self.writeErrSep2()
+ for line in apply(traceback.format_exception, err):
+ for l in line.split("\n")[:-1]:
+ self.writelnErr(l)
+ self.writelnErr("")
+
+class TextTestRunner:
+ def __init__(self,
+ stream=sys.stdout,
+ errStream=sys.stderr,
+ verbosity=1,
+ explain=False):
+
+ self._out = stream
+ self._err = errStream
+ self._verbosity = verbosity
+ self._explain = explain
+
+ ## main methods
+
+ def run(self, test):
+ result = self._makeResult()
+ result.beginTests()
+ test( result )
+ result.endTests()
+ result.summarize()
+
+ return result
+
+ ## internal methods
+
+ def _makeResult(self):
+ return _TextTestResult(stream=self._out,
+ errStream=self._err,
+ verbosity=self._verbosity,
+ explain=self._explain,
+ )
+
+##############################################################################
+# Locating and loading tests
+##############################################################################
+
+class TestLoader:
+ """This class is responsible for loading tests according to various
+ criteria and returning them wrapped in a Test
+ """
+ testMethodPrefix = 'test'
+ sortTestMethodsUsing = cmp
+ suiteClass = TestSuite
+
+ def loadTestsFromTestCase(self, testCaseClass):
+ """Return a suite of all tests cases contained in testCaseClass"""
+ return self.suiteClass(tests=map(testCaseClass,
+ self.getTestCaseNames(testCaseClass)),
+ suiteName=testCaseClass.__name__)
+
+ def loadTestsFromModule(self, module):
+ """Return a suite of all tests cases contained in the given module"""
+ tests = []
+ for name in dir(module):
+ obj = getattr(module, name)
+ if type(obj) == types.ClassType and issubclass(obj, TestCase):
+ tests.append(self.loadTestsFromTestCase(obj))
+ return self.suiteClass(tests)
+
+ def loadTestsFromName(self, name, module=None):
+ """Return a suite of all tests cases given a string specifier.
+
+ The name may resolve either to a module, a test case class, a
+ test method within a test case class, or a callable object which
+ returns a TestCase or TestSuite instance.
+
+ The method optionally resolves the names relative to a given module.
+ """
+ parts = string.split(name, '.')
+ if module is None:
+ if not parts:
+ raise ValueError, "incomplete test name: %s" % name
+ else:
+ parts_copy = parts[:]
+ while parts_copy:
+ try:
+ module = __import__(string.join(parts_copy,'.'))
+ break
+ except ImportError:
+ del parts_copy[-1]
+ if not parts_copy: raise
+ parts = parts[1:]
+ obj = module
+ for part in parts:
+ if isinstance(obj, TestSuite):
+ obj = obj.getTestForName(part)
+ else:
+ obj = getattr(obj, part)
+
+ if type(obj) == types.ModuleType:
+ return self.loadTestsFromModule(obj)
+ elif type(obj) == types.ClassType and issubclass(obj, TestCase):
+ return self.loadTestsFromTestCase(obj)
+ elif type(obj) == types.UnboundMethodType:
+ return obj.im_class(obj.__name__)
+ elif isinstance(obj, TestSuite):
+ return obj
+ elif isinstance(obj, TestCase):
+ return obj
+ elif callable(obj):
+ test = obj()
+ if not isinstance(test, TestCase) and \
+ not isinstance(test, TestSuite):
+ raise ValueError, \
+ "calling %s returned %s, not a test" %(obj,test)
+ return test
+ else:
+ raise ValueError, "don't know how to make test from: %s" % obj
+
+ def loadTestsFromNames(self, names, module=None):
+ """Return a suite of all tests cases found using the given sequence
+ of string specifiers. See 'loadTestsFromName()'.
+ """
+ suites = []
+ for name in names:
+ suites.append(self.loadTestsFromName(name, module))
+ return self.suiteClass(suites)
+
+ def getTestCaseNames(self, testCaseClass):
+ """Return a sorted sequence of method names found within testCaseClass.
+ """
+ testFnNames = filter(lambda n,p=self.testMethodPrefix: n[:len(p)] == p,
+ dir(testCaseClass))
+ for baseclass in testCaseClass.__bases__:
+ for testFnName in self.getTestCaseNames(baseclass):
+ if testFnName not in testFnNames: # handle overridden methods
+ testFnNames.append(testFnName)
+ if self.sortTestMethodsUsing:
+ testFnNames.sort(self.sortTestMethodsUsing)
+ return testFnNames
+
+
+
+defaultTestLoader = TestLoader()
+
+
+##############################################################################
+# Patches for old functions: these functions should be considered obsolete
+##############################################################################
+
+def _makeLoader(prefix, sortUsing, suiteClass=None):
+ loader = TestLoader()
+ loader.sortTestMethodsUsing = sortUsing
+ loader.testMethodPrefix = prefix
+ if suiteClass: loader.suiteClass = suiteClass
+ return loader
+
+def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp):
+ return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass)
+
+def makeSuite(testCaseClass, prefix='test', sortUsing=cmp, suiteClass=TestSuite):
+ return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass)
+
+def findTestCases(module, prefix='test', sortUsing=cmp, suiteClass=TestSuite):
+ return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(module)
+
+##############################################################################
+# Facilities for running tests from the command line
+##############################################################################
+
+class TestProgram:
+ """A command-line program that runs a set of tests; this is primarily
+ for making test modules conveniently executable.
+ """
+ USAGE = """\
+Usage: %(progName)s [options] [test] [...]
+
+Options:
+ -h, --help Show this message
+ -v, --verbose Verbose output
+ -q, --quiet Minimal output
+ -e, --expain Output extra test details if there is a failure or error
+
+Examples:
+ %(progName)s - run default set of tests
+ %(progName)s MyTestSuite - run suite 'MyTestSuite'
+ %(progName)s MyTestSuite.MyTestCase - run suite 'MyTestSuite'
+ %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething
+ %(progName)s MyTestCase - run all 'test*' test methods
+ in MyTestCase
+"""
+ def __init__(self, module='__main__', defaultTest=None,
+ argv=None, testRunner=None, testLoader=defaultTestLoader,
+ testSuite=None):
+ if type(module) == type(''):
+ self.module = __import__(module)
+ for part in string.split(module,'.')[1:]:
+ self.module = getattr(self.module, part)
+ else:
+ self.module = module
+ if argv is None:
+ argv = sys.argv
+ self.test = testSuite
+ self.verbosity = 1
+ self.explain = 0
+ self.defaultTest = defaultTest
+ self.testRunner = testRunner
+ self.testLoader = testLoader
+ self.progName = os.path.basename(argv[0])
+ self.parseArgs(argv)
+ self.runTests()
+
+ def usageExit(self, msg=None):
+ if msg: print msg
+ print self.USAGE % self.__dict__
+ sys.exit(2)
+
+ def parseArgs(self, argv):
+ import getopt
+ try:
+ options, args = getopt.getopt(argv[1:], 'hHvqer',
+ ['help','verbose','quiet','explain', 'raise'])
+ for opt, value in options:
+ if opt in ('-h','-H','--help'):
+ self.usageExit()
+ if opt in ('-q','--quiet'):
+ self.verbosity = 0
+ if opt in ('-v','--verbose'):
+ self.verbosity = 2
+ if opt in ('-e','--explain'):
+ self.explain = True
+ if len(args) == 0 and self.defaultTest is None and self.test is None:
+ self.test = self.testLoader.loadTestsFromModule(self.module)
+ return
+ if len(args) > 0:
+ self.testNames = args
+ else:
+ self.testNames = (self.defaultTest,)
+ self.createTests()
+ except getopt.error, msg:
+ self.usageExit(msg)
+
+ def createTests(self):
+ if self.test == None:
+ self.test = self.testLoader.loadTestsFromNames(self.testNames,
+ self.module)
+
+ def runTests(self):
+ if self.testRunner is None:
+ self.testRunner = TextTestRunner(verbosity=self.verbosity,
+ explain=self.explain)
+ result = self.testRunner.run(self.test)
+ self._cleanupAfterRunningTests()
+ sys.exit(not result.wasSuccessful())
+
+ def _cleanupAfterRunningTests(self):
+ """A hook method that is called immediately prior to calling
+ sys.exit(not result.wasSuccessful()) in self.runTests().
+ """
+ pass
+
+main = TestProgram
+
+
+##############################################################################
+# Executing this module from the command line
+##############################################################################
+
+if __name__ == "__main__":
+ main(module=None)
+
+# vim: shiftwidth=4 tabstop=4 expandtab
diff --git a/cobbler/Cheetah/Tools/CGITemplate.py b/cobbler/Cheetah/Tools/CGITemplate.py
new file mode 100644
index 0000000..b72e62b
--- /dev/null
+++ b/cobbler/Cheetah/Tools/CGITemplate.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+# $Id: CGITemplate.py,v 1.6 2006/01/29 02:09:59 tavis_rudd Exp $
+"""A subclass of Cheetah.Template for use in CGI scripts.
+
+Usage in a template:
+ #extends Cheetah.Tools.CGITemplate
+ #implements respond
+ $cgiHeaders#slurp
+
+Usage in a template inheriting a Python class:
+1. The template
+ #extends MyPythonClass
+ #implements respond
+ $cgiHeaders#slurp
+
+2. The Python class
+ from Cheetah.Tools import CGITemplate
+ class MyPythonClass(CGITemplate):
+ def cgiHeadersHook(self):
+ return "Content-Type: text/html; charset=koi8-r\n\n"
+
+To read GET/POST variables, use the .webInput method defined in
+Cheetah.Utils.WebInputMixin (available in all templates without importing
+anything), use Python's 'cgi' module, or make your own arrangements.
+
+This class inherits from Cheetah.Template to make it usable in Cheetah's
+single-inheritance model.
+
+
+Meta-Data
+================================================================================
+Author: Mike Orr <iron@mso.oz.net>
+License: This software is released for unlimited distribution under the
+ terms of the MIT license. See the LICENSE file.
+Version: $Revision: 1.6 $
+Start Date: 2001/10/03
+Last Revision Date: $Date: 2006/01/29 02:09:59 $
+"""
+__author__ = "Mike Orr <iron@mso.oz.net>"
+__revision__ = "$Revision: 1.6 $"[11:-2]
+
+import os
+from Cheetah.Template import Template
+
+class CGITemplate(Template):
+ """Methods useful in CGI scripts.
+
+ Any class that inherits this mixin must also inherit Cheetah.Servlet.
+ """
+
+
+ def cgiHeaders(self):
+ """Outputs the CGI headers if this is a CGI script.
+
+ Usage: $cgiHeaders#slurp
+ Override .cgiHeadersHook() if you want to customize the headers.
+ """
+ if self.isCgi():
+ return self.cgiHeadersHook()
+
+
+
+ def cgiHeadersHook(self):
+ """Override if you want to customize the CGI headers.
+ """
+ return "Content-type: text/html\n\n"
+
+
+ def isCgi(self):
+ """Is this a CGI script?
+ """
+ env = os.environ.has_key('REQUEST_METHOD')
+ wk = self._CHEETAH__isControlledByWebKit
+ return env and not wk
+
+
+
+# vim: shiftwidth=4 tabstop=4 expandtab
diff --git a/cobbler/Cheetah/Tools/MondoReport.py b/cobbler/Cheetah/Tools/MondoReport.py
new file mode 100644
index 0000000..f73e7fc
--- /dev/null
+++ b/cobbler/Cheetah/Tools/MondoReport.py
@@ -0,0 +1,464 @@
+#!/usr/bin/env python
+"""
+@@TR: This code is pretty much unsupported.
+
+MondoReport.py -- Batching module for Python and Cheetah.
+
+Version 2001-Nov-18. Doesn't do much practical yet, but the companion
+testMondoReport.py passes all its tests.
+-Mike Orr (Iron)
+
+TODO: BatchRecord.prev/next/prev_batches/next_batches/query, prev.query,
+next.query.
+
+How about Report: .page(), .all(), .summary()? Or PageBreaker.
+"""
+import operator, types
+try:
+ from Cheetah.NameMapper import valueForKey as lookup_func
+except ImportError:
+ def lookup_func(obj, name):
+ if hasattr(obj, name):
+ return getattr(obj, name)
+ else:
+ return obj[name] # Raises KeyError.
+
+########## CONSTANTS ##############################
+
+True, False = (1==1), (1==0)
+numericTypes = types.IntType, types.LongType, types.FloatType
+
+########## PUBLIC GENERIC FUNCTIONS ##############################
+
+class NegativeError(ValueError):
+ pass
+
+def isNumeric(v):
+ return type(v) in numericTypes
+
+def isNonNegative(v):
+ ret = isNumeric(v)
+ if ret and v < 0:
+ raise NegativeError(v)
+
+def isNotNone(v):
+ return v is not None
+
+def Roman(n):
+ n = int(n) # Raises TypeError.
+ if n < 1:
+ raise ValueError("roman numeral for zero or negative undefined: " + n)
+ roman = ''
+ while n >= 1000:
+ n = n - 1000
+ roman = roman + 'M'
+ while n >= 500:
+ n = n - 500
+ roman = roman + 'D'
+ while n >= 100:
+ n = n - 100
+ roman = roman + 'C'
+ while n >= 50:
+ n = n - 50
+ roman = roman + 'L'
+ while n >= 10:
+ n = n - 10
+ roman = roman + 'X'
+ while n >= 5:
+ n = n - 5
+ roman = roman + 'V'
+ while n < 5 and n >= 1:
+ n = n - 1
+ roman = roman + 'I'
+ roman = roman.replace('DCCCC', 'CM')
+ roman = roman.replace('CCCC', 'CD')
+ roman = roman.replace('LXXXX', 'XC')
+ roman = roman.replace('XXXX', 'XL')
+ roman = roman.replace('VIIII', 'IX')
+ roman = roman.replace('IIII', 'IV')
+ return roman
+
+
+def sum(lis):
+ return reduce(operator.add, lis, 0)
+
+def mean(lis):
+ """Always returns a floating-point number.
+ """
+ lis_len = len(lis)
+ if lis_len == 0:
+ return 0.00 # Avoid ZeroDivisionError (not raised for floats anyway)
+ total = float( sum(lis) )
+ return total / lis_len
+
+def median(lis):
+ lis = lis[:]
+ lis.sort()
+ return lis[int(len(lis)/2)]
+
+
+def variance(lis):
+ raise NotImplementedError()
+
+def variance_n(lis):
+ raise NotImplementedError()
+
+def standardDeviation(lis):
+ raise NotImplementedError()
+
+def standardDeviation_n(lis):
+ raise NotImplementedError()
+
+
+
+class IndexFormats:
+ """Eight ways to display a subscript index.
+ ("Fifty ways to leave your lover....")
+ """
+ def __init__(self, index, item=None):
+ self._index = index
+ self._number = index + 1
+ self._item = item
+
+ def index(self):
+ return self._index
+
+ __call__ = index
+
+ def number(self):
+ return self._number
+
+ def even(self):
+ return self._number % 2 == 0
+
+ def odd(self):
+ return not self.even()
+
+ def even_i(self):
+ return self._index % 2 == 0
+
+ def odd_i(self):
+ return not self.even_i()
+
+ def letter(self):
+ return self.Letter().lower()
+
+ def Letter(self):
+ n = ord('A') + self._index
+ return chr(n)
+
+ def roman(self):
+ return self.Roman().lower()
+
+ def Roman(self):
+ return Roman(self._number)
+
+ def item(self):
+ return self._item
+
+
+
+########## PRIVATE CLASSES ##############################
+
+class ValuesGetterMixin:
+ def __init__(self, origList):
+ self._origList = origList
+
+ def _getValues(self, field=None, criteria=None):
+ if field:
+ ret = [lookup_func(elm, field) for elm in self._origList]
+ else:
+ ret = self._origList
+ if criteria:
+ ret = filter(criteria, ret)
+ return ret
+
+
+class RecordStats(IndexFormats, ValuesGetterMixin):
+ """The statistics that depend on the current record.
+ """
+ def __init__(self, origList, index):
+ record = origList[index] # Raises IndexError.
+ IndexFormats.__init__(self, index, record)
+ ValuesGetterMixin.__init__(self, origList)
+
+ def length(self):
+ return len(self._origList)
+
+ def first(self):
+ return self._index == 0
+
+ def last(self):
+ return self._index >= len(self._origList) - 1
+
+ def _firstOrLastValue(self, field, currentIndex, otherIndex):
+ currentValue = self._origList[currentIndex] # Raises IndexError.
+ try:
+ otherValue = self._origList[otherIndex]
+ except IndexError:
+ return True
+ if field:
+ currentValue = lookup_func(currentValue, field)
+ otherValue = lookup_func(otherValue, field)
+ return currentValue != otherValue
+
+ def firstValue(self, field=None):
+ return self._firstOrLastValue(field, self._index, self._index - 1)
+
+ def lastValue(self, field=None):
+ return self._firstOrLastValue(field, self._index, self._index + 1)
+
+ # firstPage and lastPage not implemented. Needed?
+
+ def percentOfTotal(self, field=None, suffix='%', default='N/A', decimals=2):
+ rec = self._origList[self._index]
+ if field:
+ val = lookup_func(rec, field)
+ else:
+ val = rec
+ try:
+ lis = self._getValues(field, isNumeric)
+ except NegativeError:
+ return default
+ total = sum(lis)
+ if total == 0.00: # Avoid ZeroDivisionError.
+ return default
+ val = float(val)
+ try:
+ percent = (val / total) * 100
+ except ZeroDivisionError:
+ return default
+ if decimals == 0:
+ percent = int(percent)
+ else:
+ percent = round(percent, decimals)
+ if suffix:
+ return str(percent) + suffix # String.
+ else:
+ return percent # Numeric.
+
+ def __call__(self): # Overrides IndexFormats.__call__
+ """This instance is not callable, so we override the super method.
+ """
+ raise NotImplementedError()
+
+ def prev(self):
+ if self._index == 0:
+ return None
+ else:
+ length = self.length()
+ start = self._index - length
+ return PrevNextPage(self._origList, length, start)
+
+ def next(self):
+ if self._index + self.length() == self.length():
+ return None
+ else:
+ length = self.length()
+ start = self._index + length
+ return PrevNextPage(self._origList, length, start)
+
+ def prevPages(self):
+ raise NotImplementedError()
+
+ def nextPages(self):
+ raise NotImplementedError()
+
+ prev_batches = prevPages
+ next_batches = nextPages
+
+ def summary(self):
+ raise NotImplementedError()
+
+
+
+ def _prevNextHelper(self, start,end,size,orphan,sequence):
+ """Copied from Zope's DT_InSV.py's "opt" function.
+ """
+ if size < 1:
+ if start > 0 and end > 0 and end >= start:
+ size=end+1-start
+ else: size=7
+
+ if start > 0:
+
+ try: sequence[start-1]
+ except: start=len(sequence)
+ # if start > l: start=l
+
+ if end > 0:
+ if end < start: end=start
+ else:
+ end=start+size-1
+ try: sequence[end+orphan-1]
+ except: end=len(sequence)
+ # if l - end < orphan: end=l
+ elif end > 0:
+ try: sequence[end-1]
+ except: end=len(sequence)
+ # if end > l: end=l
+ start=end+1-size
+ if start - 1 < orphan: start=1
+ else:
+ start=1
+ end=start+size-1
+ try: sequence[end+orphan-1]
+ except: end=len(sequence)
+ # if l - end < orphan: end=l
+ return start,end,size
+
+
+
+class Summary(ValuesGetterMixin):
+ """The summary statistics, that don't depend on the current record.
+ """
+ def __init__(self, origList):
+ ValuesGetterMixin.__init__(self, origList)
+
+ def sum(self, field=None):
+ lis = self._getValues(field, isNumeric)
+ return sum(lis)
+
+ total = sum
+
+ def count(self, field=None):
+ lis = self._getValues(field, isNotNone)
+ return len(lis)
+
+ def min(self, field=None):
+ lis = self._getValues(field, isNotNone)
+ return min(lis) # Python builtin function min.
+
+ def max(self, field=None):
+ lis = self._getValues(field, isNotNone)
+ return max(lis) # Python builtin function max.
+
+ def mean(self, field=None):
+ """Always returns a floating point number.
+ """
+ lis = self._getValues(field, isNumeric)
+ return mean(lis)
+
+ average = mean
+
+ def median(self, field=None):
+ lis = self._getValues(field, isNumeric)
+ return median(lis)
+
+ def variance(self, field=None):
+ raiseNotImplementedError()
+
+ def variance_n(self, field=None):
+ raiseNotImplementedError()
+
+ def standardDeviation(self, field=None):
+ raiseNotImplementedError()
+
+ def standardDeviation_n(self, field=None):
+ raiseNotImplementedError()
+
+
+class PrevNextPage:
+ def __init__(self, origList, size, start):
+ end = start + size
+ self.start = IndexFormats(start, origList[start])
+ self.end = IndexFormats(end, origList[end])
+ self.length = size
+
+
+########## MAIN PUBLIC CLASS ##############################
+class MondoReport:
+ _RecordStatsClass = RecordStats
+ _SummaryClass = Summary
+
+ def __init__(self, origlist):
+ self._origList = origlist
+
+ def page(self, size, start, overlap=0, orphan=0):
+ """Returns list of ($r, $a, $b)
+ """
+ if overlap != 0:
+ raise NotImplementedError("non-zero overlap")
+ if orphan != 0:
+ raise NotImplementedError("non-zero orphan")
+ origList = self._origList
+ origList_len = len(origList)
+ start = max(0, start)
+ end = min( start + size, len(self._origList) )
+ mySlice = origList[start:end]
+ ret = []
+ for rel in range(size):
+ abs_ = start + rel
+ r = mySlice[rel]
+ a = self._RecordStatsClass(origList, abs_)
+ b = self._RecordStatsClass(mySlice, rel)
+ tup = r, a, b
+ ret.append(tup)
+ return ret
+
+
+ batch = page
+
+ def all(self):
+ origList_len = len(self._origList)
+ return self.page(origList_len, 0, 0, 0)
+
+
+ def summary(self):
+ return self._SummaryClass(self._origList)
+
+"""
+**********************************
+ Return a pageful of records from a sequence, with statistics.
+
+ in : origlist, list or tuple. The entire set of records. This is
+ usually a list of objects or a list of dictionaries.
+ page, int >= 0. Which page to display.
+ size, int >= 1. How many records per page.
+ widow, int >=0. Not implemented.
+ orphan, int >=0. Not implemented.
+ base, int >=0. Number of first page (usually 0 or 1).
+
+ out: list of (o, b) pairs. The records for the current page. 'o' is
+ the original element from 'origlist' unchanged. 'b' is a Batch
+ object containing meta-info about 'o'.
+ exc: IndexError if 'page' or 'size' is < 1. If 'origlist' is empty or
+ 'page' is too high, it returns an empty list rather than raising
+ an error.
+
+ origlist_len = len(origlist)
+ start = (page + base) * size
+ end = min(start + size, origlist_len)
+ ret = []
+ # widow, orphan calculation: adjust 'start' and 'end' up and down,
+ # Set 'widow', 'orphan', 'first_nonwidow', 'first_nonorphan' attributes.
+ for i in range(start, end):
+ o = origlist[i]
+ b = Batch(origlist, size, i)
+ tup = o, b
+ ret.append(tup)
+ return ret
+
+ def prev(self):
+ # return a PrevNextPage or None
+
+ def next(self):
+ # return a PrevNextPage or None
+
+ def prev_batches(self):
+ # return a list of SimpleBatch for the previous batches
+
+ def next_batches(self):
+ # return a list of SimpleBatch for the next batches
+
+########## PUBLIC MIXIN CLASS FOR CHEETAH TEMPLATES ##############
+class MondoReportMixin:
+ def batch(self, origList, size=None, start=0, overlap=0, orphan=0):
+ bat = MondoReport(origList)
+ return bat.batch(size, start, overlap, orphan)
+ def batchstats(self, origList):
+ bat = MondoReport(origList)
+ return bat.stats()
+"""
+
+# vim: shiftwidth=4 tabstop=4 expandtab textwidth=79
diff --git a/cobbler/Cheetah/Tools/MondoReportDoc.txt b/cobbler/Cheetah/Tools/MondoReportDoc.txt
new file mode 100644
index 0000000..29a026d
--- /dev/null
+++ b/cobbler/Cheetah/Tools/MondoReportDoc.txt
@@ -0,0 +1,391 @@
+MondoReport Documentation
+Version 0.01 alpha 24-Nov-2001. iron@mso.oz.net or mso@oz.net.
+Copyright (c) 2001 Mike Orr. License: same as Python or Cheetah.
+
+* * * * *
+STATUS: previous/next batches and query string are not implemented yet.
+Sorting not designed yet. Considering "click on this column header to sort by
+this field" and multiple ascending/descending sort fields for a future version.
+
+Tested with Python 2.2b1. May work with Python 2.1 or 2.0.
+
+* * * * *
+OVERVIEW
+
+MondoReport -- provide information about a list that is useful in generating
+any kind of report. The module consists of one main public class, and some
+generic functions you may find useful in other programs. This file contains an
+overview, syntax reference and examples. The module is designed both for
+standalone use and for integration with the Cheetah template system
+(http://www.cheetahtemplate.org/), so the examples are in both Python and
+Cheetah. The main uses of MondoReport are:
+
+(A) to iterate through a list. In this sense MR is a for-loop enhancer,
+providing information that would be verbose to calculate otherwise.
+
+(B) to separate a list into equal-size "pages" (or "batches"--the two terms are
+interchangeable) and only display the current page, plus limited information
+about the previous and next pages.
+
+(C) to extract summary statistics about a certain column ("field") in the list.
+
+* * * * *
+MAIN PUBLIC CLASS
+
+To create a MondoReport instance, supply a list to operate on.
+
+ mr = MondoReport(origList)
+
+The list may be a list of anything, but if you use the 'field' argument in any
+of the methods below, the elements must be instances or dictionaries.
+
+MondoReport assumes it's operating on an unchanging list. Do not modify the
+list or any of its elements until you are completely finished with the
+ModoReport object and its sub-objects. Otherwise, you may get an exception or
+incorrect results.
+
+MondoReport instances have three methods:
+
+ .page(size, start, overlap=0, orphan=0
+ sort=None, reverse=False) => list of (r, a, b).
+
+'size' is an integer >= 1. 'start', 'overlap' and 'orphan' are integers >= 0.
+The list returned contains one triple for each record in the current page. 'r'
+is the original record. 'a' is a BatchRecord instance for the current record
+in relation to all records in the origList. 'b' is a BatchRecord instance for
+the current record in relation to all the records in that batch/page. (There
+is a .batch method that's identical to .page.)
+
+The other options aren't implemented yet, but 'overlap' duplicates this many
+records on adjacent batches. 'orphan' moves this many records or fewer, if
+they are on a page alone, onto the neighboring page. 'sort' (string) specifies
+a field to sort the records by. It may be suffixed by ":desc" to sort in
+descending order. 'reverse' (boolean) reverses the sort order. If both
+":desc" and 'reverse' are specified, they will cancel each other out. This
+sorting/reversal happens on a copy of the origList, and all objects returned
+by this method use the sorted list, except when resorting the next time.
+To do more complicated sorting, such as a hierarchy of columns, do it to the
+original list before creating the ModoReport object.
+
+ .all(sort=None, reverse=False) => list of (r, a).
+
+Same, but the current page spans the entire origList.
+
+ .summary() => Summary instance.
+
+Summary statistics for the entire origList.
+
+In Python, use .page or .all in a for loop:
+
+ from Cheetah.Tools.MondoReport import MondoReport
+ mr = MondoReport(myList)
+ for r, a, b in mr.page(20, 40):
+ # Do something with r, a and b. The current page is the third page,
+ # with twenty records corresponding to origList[40:60].
+ if not myList:
+ # Warn the user there are no records in the list.
+
+It works the same way in Cheetah, just convert to Cheetah syntax. This example
+assumes the template doubles as a Webware servlet, so we use the servlet's
+'$request' method to look up the CGI parameter 'start'. The default value is 0
+for the first page.
+
+ #from Cheetah.Tools.MondoReport import MondoReport
+ #set $mr = $MondoReport($bigList)
+ #set $start = $request.field("start", 0)
+ #for $o, $a, $b in $mr.page(20, $start)
+ ... do something with $o, $a and $b ...
+ #end for
+ #unless $bigList
+ This is displayed if the original list has no elements.
+ It's equivalent to the "else" part Zope DTML's <dtml-in>.
+ #end unless
+
+* * * * *
+USING 'r' RECORDS
+
+Use 'r' just as you would the original element. For instance:
+
+ print r.attribute # If r is an instance.
+ print r['key'] # If r is a dictionary.
+ print r # If r is numeric or a string.
+
+In Cheetah, you can take advantage of Universal Dotted Notation and autocalling:
+
+ $r.name ## 'name' may be an attribute or key of 'r'. If 'r' and/or
+ ## 'name' is a function or method, it will be called without
+ ## arguments.
+ $r.attribute
+ $r['key']
+ $r
+ $r().attribute()['key']()
+
+If origList is a list of name/value pairs (2-tuples or 2-lists), you may
+prefer to do this:
+
+ for (key, value), a, b in mr.page(20, 40):
+ print key, "=>", value
+
+ #for ($key, $value), $a, $b in $mr.page(20, $start)
+ $key =&gt; $value
+ #end for
+
+* * * * *
+STATISTICS METHODS AND FIELD VALUES
+
+Certain methods below have an optional argument 'field'. If specified,
+MondoReport will look up that field in each affected record and use its value
+in the calculation. MondoReport uses Cheetah's NameMapper if available,
+otherwise it uses a minimal NameMapper substitute that looks for an attribute
+or dictionary key called "field". You'll get an exception if any record is a
+type without attributes or keys, or if one or more records is missing that
+attribute/key.
+
+If 'field' is None, MondoReport will use the entire record in its
+calculation. This makes sense mainly if the records are a numeric type.
+
+All statistics methods filter out None values from their calculations, and
+reduce the number of records accordingly. Most filter out non-numeric fields
+(or records). Some raise NegativeError if a numeric field (or record) is
+negative.
+
+
+* * * * *
+BatchRecord METHODS
+
+The 'a' and 'b' objects of MondoReport.page() and MondoReport.all() provide
+these methods.
+
+ .index()
+
+The current subscript. For 'a', this is the true subscript into origList.
+For 'b', this is relative to the current page, so the first record will be 0.
+Hint: In Cheetah, use autocalling to skip the parentheses: '$b.index'.
+
+ .number()
+
+The record's position starting from 1. This is always '.index() + 1'.
+
+ .Letter()
+
+The letter ("A", "B", "C") corresponding to .number(). Undefined if .number()
+> 26. The current implementation just adds the offset to 'a' and returns
+whatever character it happens to be.
+
+To make a less dumb implementation (e.g., "Z, AA, BB" or "Z, A1, B1"):
+1) Subclass BatchRecord and override the .Letter method.
+2) Subclass MondoReport and set the class variable .BatchRecordClass to your
+new improved class.
+
+ .letter()
+
+Same but lower case.
+
+ .Roman()
+
+The Roman numeral corresponding to .number().
+
+ .roman()
+
+Same but lower case.
+
+ .even()
+
+True if .number() is even.
+
+ .odd()
+
+True if .number() is odd.
+
+ .even_i()
+
+True if .index() is even.
+
+ .odd_i()
+
+True if .index() is odd.
+
+ .length()
+
+For 'a', number of records in origList. For 'b', number of records on this
+page.
+
+ .item()
+
+The record itself. You don't need this in the normal case since it's the same
+as 'r', but it's useful for previous/next batches.
+
+ .size()
+
+The 'size' argument used when this BatchRecord was created.
+'a.size() == b.size()'.
+
+ .first()
+
+True if this is the first record.
+
+ .last()
+
+True if this is the last record.
+
+ .firstValue(field=None)
+
+True if there is no previous record, or if the previous field/record has a
+different value. Used for to print section headers. For instance, if you
+are printing addresses by country, this will be true at the first occurrance
+of each country. Or for indexes, you can have a non-printing field showing
+which letter of the alphablet this entry starts with, and then print a "B"
+header before printing the first record starting with "B".
+
+ .lastValue(field=None)
+
+True if this is the last record containing the current value in the
+field/record.
+
+ .percentOfTotal(field=None, suffix="%", default="N/A", decimals=2)
+
+Returns the percent that the current field/record is of all fields/records.
+If 'suffix' is None, returns a number; otherwise it returns a string with
+'suffix' suffixed. If the current value is non-numeric, returns 'default'
+instead (without 'suffix'). 'decimals' tells the number of decimal places to
+return; if 0, there will be no decimal point.
+
+ .prev()
+
+Returns a PrevNextBatch instance for the previous page. If there is no
+previous page, returns None. [Not implemented yet.]
+
+ .next()
+
+Returns a PrevNextBatch instance for the next page. If there is no next page,
+returns None. [Not implemented yet.]
+
+ .prevPages()
+
+Returns a list of PrevNextPage instances for every previous page, or [] if no
+previous pages. [Not implemented yet.]
+
+ .nextPages()
+
+Returns a list of PrevNextPage instances for every next page, or [] if no next
+pages. [Not implemented yet.]
+
+ .query(start=None, label=None, attribName="start", attribs=[])
+
+[Not implemented yet.]
+
+With no arguments, returns the HTML query string with start value removed (so
+you can append a new start value in your hyperlink). The query string is taken
+from the 'QUERY_STRING' environmental variable, or "" if missing. (This is
+Webware compatible.)
+
+With 'start' (an integer >= 0), returns the query string with an updated start
+value, normally for the next or previous batch.
+
+With 'label' (a string), returns a complete HTML hyperlink:
+'<A HREF="?new_query_string">label</A>'. You'll get a TypeError if you specify
+'label' but not 'start'.
+
+With 'attribName' (a string), uses this attribute name rather than "start".
+Useful if you have another CGI parameter "start" that's used for something
+else.
+
+With 'attribs' (a dictionary), adds these attributes to the hyperlink.
+For instance, 'attribs={"target": "_blank"}'. Ignored unless 'label' is
+specified too.
+
+This method assumes the start parameter is a GET variable, not a POST variable.
+
+ .summary()
+
+Returns a Summary instance. 'a.summary()' refers to all records in the
+origList, so it's the same as MondoReport.summary(). 'b.summary()' refers only
+to the records on the current page. [Not implemented yet.]
+
+* * * * *
+PrevNextPage INSTANCES
+
+[Not implemented yet.]
+
+PrevNextPage instances have the following methods:
+
+ .start()
+
+The index (true index of origList) that that page starts at. You may also use
+'.start().index()', '.start().number()', etc. Also
+'.start().item(field=None)'. (Oh, so *that*'s what .item is for!)
+
+ .end()
+
+The index (true index of origList) that that page ends at. You may also use
+'.end().index()', '.end().number()', etc. Also
+'.end().item(field=None)'.
+
+ .length()
+
+Number of records on that page.
+
+ .query(label=None, attribName="start", attribs={}, before="", after="")
+
+[Not implemented yet.]
+
+Similar to 'a.query()' and 'b.query()', but automatically calculates the start
+value for the appropriate page.
+
+For fancy HTML formatting, 'before' is prepended to the returned text and
+'after' is appended. (There was an argument 'else_' for if there is no such
+batch, but it was removed because you can't even get to this method at all in
+that case.)
+
+* * * * * *
+SUMMARY STATISTICS
+
+These methods are supported by the Summary instances returned by
+MondoReport.Summary():
+
+ .sum(field=None)
+
+Sum of all numeric values in a field, or sum of all records.
+
+ .total(field=None)
+
+Same.
+
+ .count(field=None)
+
+Number of fields/records with non-None values.
+
+ .min(field=None)
+
+Minimum value in that field/record. Ignores None values.
+
+ .max(field=None)
+
+Maximum value in that field/record. Ignores None values.
+
+ .mean(field=None)
+
+The mean (=average) of all numeric values in that field/record.
+
+ .average(field=None)
+
+Same.
+
+ .median(field=None)
+
+The median of all numeric values in that field/record. This is done by sorting
+the values and taking the middle value.
+
+ .variance(field=None), .variance_n(field=None)
+ .standardDeviation(field=None), .standardDeviation_n(field=None)
+
+[Not implemented yet.]
+
+
+* * * * *
+To run the regression tests (requires unittest.py, which is standard with
+Python 2.2), run MondoReportTest.py from the command line. The regression test
+double as usage examples.
+
+
+# vim: shiftwidth=4 tabstop=4 expandtab textwidth=79
diff --git a/cobbler/Cheetah/Tools/RecursiveNull.py b/cobbler/Cheetah/Tools/RecursiveNull.py
new file mode 100644
index 0000000..4897d80
--- /dev/null
+++ b/cobbler/Cheetah/Tools/RecursiveNull.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+"""Nothing, but in a friendly way. Good for filling in for objects you want to
+hide. If $form.f1 is a RecursiveNull object, then
+$form.f1.anything["you"].might("use") will resolve to the empty string.
+
+This module was contributed by Ian Bicking.
+"""
+
+class RecursiveNull:
+ __doc__ = __doc__ # Use the module's docstring for the class's docstring.
+ def __getattr__(self, attr):
+ return self
+ def __getitem__(self, item):
+ return self
+ def __call__(self, *vars, **kw):
+ return self
+ def __str__(self):
+ return ''
+ def __repr__(self):
+ return ''
+ def __nonzero__(self):
+ return 0
+
diff --git a/cobbler/Cheetah/Tools/SiteHierarchy.py b/cobbler/Cheetah/Tools/SiteHierarchy.py
new file mode 100644
index 0000000..d4a92e1
--- /dev/null
+++ b/cobbler/Cheetah/Tools/SiteHierarchy.py
@@ -0,0 +1,183 @@
+#!/usr/bin/env python
+# $Id: SiteHierarchy.py,v 1.1 2001/10/11 03:25:54 tavis_rudd Exp $
+"""Create menus and crumbs from a site hierarchy.
+
+You define the site hierarchy as lists/tuples. Each location in the hierarchy
+is a (url, description) tuple. Each list has the base URL/text in the 0
+position, and all the children coming after it. Any child can be a list,
+representing further depth to the hierarchy. See the end of the file for an
+example hierarchy.
+
+Use Hierarchy(contents, currentURL), where contents is this hierarchy, and
+currentURL is the position you are currently in. The menubar and crumbs methods
+give you the HTML output.
+
+There are methods you can override to customize the HTML output.
+
+Meta-Data
+================================================================================
+Author: Ian Bicking <ianb@colorstudy.com>
+Version: $Revision: 1.1 $
+Start Date: 2001/07/23
+Last Revision Date: $Date: 2001/10/11 03:25:54 $
+"""
+__author__ = "Ian Bicking <ianb@colorstudy.com>"
+__version__ = "$Revision: 1.1 $"[11:-2]
+
+##################################################
+## DEPENDENCIES
+import string
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+
+##################################################
+## GLOBALS & CONSTANTS
+
+True, False = (1==1), (0==1)
+
+##################################################
+## CLASSES
+
+class Hierarchy:
+ def __init__(self, hierarchy, currentURL, prefix='', menuCSSClass=None,
+ crumbCSSClass=None):
+ """
+ hierarchy is described above, currentURL should be somewhere in
+ the hierarchy. prefix will be added before all of the URLs (to
+ help mitigate the problems with absolute URLs), and if given,
+ cssClass will be used for both links *and* nonlinks.
+ """
+
+ self._contents = hierarchy
+ self._currentURL = currentURL
+ if menuCSSClass:
+ self._menuCSSClass = ' class="%s"' % menuCSSClass
+ else:
+ self._menuCSSClass = ''
+ if crumbCSSClass:
+ self._crumbCSSClass = ' class="%s"' % crumbCSSClass
+ else:
+ self._crumbCSSClass = ''
+ self._prefix=prefix
+
+
+ ## Main output methods
+
+ def menuList(self, menuCSSClass=None):
+ """An indented menu list"""
+ if menuCSSClass:
+ self._menuCSSClass = ' class="%s"' % menuCSSClass
+
+ stream = StringIO()
+ for item in self._contents[1:]:
+ self._menubarRecurse(item, 0, stream)
+ return stream.getvalue()
+
+ def crumbs(self, crumbCSSClass=None):
+ """The home>where>you>are crumbs"""
+ if crumbCSSClass:
+ self._crumbCSSClass = ' class="%s"' % crumbCSSClass
+
+ path = []
+ pos = self._contents
+ while 1:
+ ## This is not the fastest algorithm, I'm afraid.
+ ## But it probably won't be for a huge hierarchy anyway.
+ foundAny = False
+ path.append(pos[0])
+ for item in pos[1:]:
+ if self._inContents(item):
+ if type(item) is type(()):
+ path.append(item)
+ break
+ else:
+ pos = item
+ foundAny = True
+ break
+ if not foundAny:
+ break
+ if len(path) == 1:
+ return self.emptyCrumb()
+ return string.join(map(lambda x, self=self: self.crumbLink(x[0], x[1]),
+ path), self.crumbSeperator()) + \
+ self.crumbTerminator()
+
+ ## Methods to control the Aesthetics
+ # - override these methods for your own look
+
+ def menuLink(self, url, text, indent):
+ if url == self._currentURL or self._prefix + url == self._currentURL:
+ return '%s<B%s>%s</B> <BR>\n' % ('&nbsp;'*2*indent,
+ self._menuCSSClass, text)
+ else:
+ return '%s<A HREF="%s%s"%s>%s</A> <BR>\n' % \
+ ('&nbsp;'*2*indent, self._prefix, url,
+ self._menuCSSClass, text)
+
+ def crumbLink(self, url, text):
+ if url == self._currentURL or self._prefix + url == self._currentURL:
+ return '<B%s>%s</B>' % (text, self._crumbCSSClass)
+ else:
+ return '<A HREF="%s%s"%s>%s</A>' % \
+ (self._prefix, url, self._crumbCSSClass, text)
+
+ def crumbSeperator(self):
+ return '&nbsp;&gt;&nbsp;'
+
+ def crumbTerminator(self):
+ return ''
+
+ def emptyCrumb(self):
+ """When you are at the homepage"""
+ return ''
+
+ ## internal methods
+
+ def _menubarRecurse(self, contents, indent, stream):
+ if type(contents) is type(()):
+ url, text = contents
+ rest = []
+ else:
+ url, text = contents[0]
+ rest = contents[1:]
+ stream.write(self.menuLink(url, text, indent))
+ if self._inContents(contents):
+ for item in rest:
+ self._menubarRecurse(item, indent+1, stream)
+
+ def _inContents(self, contents):
+ if type(contents) is type(()):
+ return self._currentURL == contents[0]
+ for item in contents:
+ if self._inContents(item):
+ return True
+ return False
+
+##################################################
+## from the command line
+
+if __name__ == '__main__':
+ hierarchy = [('/', 'home'),
+ ('/about', 'About Us'),
+ [('/services', 'Services'),
+ [('/services/products', 'Products'),
+ ('/services/products/widget', 'The Widget'),
+ ('/services/products/wedge', 'The Wedge'),
+ ('/services/products/thimble', 'The Thimble'),
+ ],
+ ('/services/prices', 'Prices'),
+ ],
+ ('/contact', 'Contact Us'),
+ ]
+
+ for url in ['/', '/services', '/services/products/widget', '/contact']:
+ print '<p>', '='*50
+ print '<br> %s: <br>\n' % url
+ n = Hierarchy(hierarchy, url, menuCSSClass='menu', crumbCSSClass='crumb',
+ prefix='/here')
+ print n.menuList()
+ print '<p>', '-'*50
+ print n.crumbs()
diff --git a/cobbler/Cheetah/Tools/__init__.py b/cobbler/Cheetah/Tools/__init__.py
new file mode 100644
index 0000000..506503b
--- /dev/null
+++ b/cobbler/Cheetah/Tools/__init__.py
@@ -0,0 +1,8 @@
+"""This package contains classes, functions, objects and packages contributed
+ by Cheetah users. They are not used by Cheetah itself. There is no
+ guarantee that this directory will be included in Cheetah releases, that
+ these objects will remain here forever, or that they will remain
+ backward-compatible.
+"""
+
+# vim: shiftwidth=5 tabstop=5 expandtab
diff --git a/cobbler/Cheetah/Unspecified.py b/cobbler/Cheetah/Unspecified.py
new file mode 100644
index 0000000..89c5176
--- /dev/null
+++ b/cobbler/Cheetah/Unspecified.py
@@ -0,0 +1,9 @@
+try:
+ from ds.sys.Unspecified import Unspecified
+except ImportError:
+ class _Unspecified:
+ def __repr__(self):
+ return 'Unspecified'
+ def __str__(self):
+ return 'Unspecified'
+ Unspecified = _Unspecified()
diff --git a/cobbler/Cheetah/Utils/Indenter.py b/cobbler/Cheetah/Utils/Indenter.py
new file mode 100644
index 0000000..abdb0dd
--- /dev/null
+++ b/cobbler/Cheetah/Utils/Indenter.py
@@ -0,0 +1,135 @@
+#!/usr/bin/env python
+# $Id: Indenter.py,v 1.7 2006/01/08 01:09:30 tavis_rudd Exp $
+"""Indentation maker.
+@@TR: this code is unsupported and largely undocumented ...
+
+This version is based directly on code by Robert Kuzelj
+<robert_kuzelj@yahoo.com> and uses his directive syntax. Some classes and
+attributes have been renamed. Indentation is output via
+$self._CHEETAH__indenter.indent() to prevent '_indenter' being looked up on the
+searchList and another one being found. The directive syntax will
+soon be changed somewhat.
+
+Meta-Data
+================================================================================
+Author: Mike Orr <iron@mso.oz.net>
+License: This software is released for unlimited distribution under the
+ terms of the MIT license. See the LICENSE file.
+Version: $Revision: 1.7 $
+Start Date: 2001/11/07
+Last Revision Date: $Date: 2006/01/08 01:09:30 $
+"""
+__author__ = "Mike Orr <iron@mso.oz.net>"
+__revision__ = "$Revision: 1.7 $"[11:-2]
+
+import re
+import sys
+
+def indentize(source):
+ return IndentProcessor().process(source)
+
+class IndentProcessor:
+ """Preprocess #indent tags."""
+ LINE_SEP = '\n'
+ ARGS = "args"
+ INDENT_DIR = re.compile(r'[ \t]*#indent[ \t]*(?P<args>.*)')
+ DIRECTIVE = re.compile(r"[ \t]*#")
+ WS = "ws"
+ WHITESPACES = re.compile(r"(?P<ws>[ \t]*)")
+
+ INC = "++"
+ DEC = "--"
+
+ SET = "="
+ CHAR = "char"
+
+ ON = "on"
+ OFF = "off"
+
+ PUSH = "push"
+ POP = "pop"
+
+ def process(self, _txt):
+ result = []
+
+ for line in _txt.splitlines():
+ match = self.INDENT_DIR.match(line)
+ if match:
+ #is indention directive
+ args = match.group(self.ARGS).strip()
+ if args == self.ON:
+ line = "#silent $self._CHEETAH__indenter.on()"
+ elif args == self.OFF:
+ line = "#silent $self._CHEETAH__indenter.off()"
+ elif args == self.INC:
+ line = "#silent $self._CHEETAH__indenter.inc()"
+ elif args == self.DEC:
+ line = "#silent $self._CHEETAH__indenter.dec()"
+ elif args.startswith(self.SET):
+ level = int(args[1:])
+ line = "#silent $self._CHEETAH__indenter.setLevel(%(level)d)" % {"level":level}
+ elif args.startswith('chars'):
+ self.indentChars = eval(args.split('=')[1])
+ line = "#silent $self._CHEETAH__indenter.setChars(%(level)d)" % {"level":level}
+ elif args.startswith(self.PUSH):
+ line = "#silent $self._CHEETAH__indenter.push()"
+ elif args.startswith(self.POP):
+ line = "#silent $self._CHEETAH__indenter.pop()"
+ else:
+ match = self.DIRECTIVE.match(line)
+ if not match:
+ #is not another directive
+ match = self.WHITESPACES.match(line)
+ if match:
+ size = len(match.group("ws").expandtabs(4))
+ line = ("${self._CHEETAH__indenter.indent(%(size)d)}" % {"size":size}) + line.lstrip()
+ else:
+ line = "${self._CHEETAH__indenter.indent(0)}" + line
+ result.append(line)
+
+ return self.LINE_SEP.join(result)
+
+class Indenter:
+ """A class that keeps track of the current indentation level.
+ .indent() returns the appropriate amount of indentation.
+ """
+ def __init__(self):
+ self.On = 1
+ self.Level = 0
+ self.Chars = " "*4
+ self.LevelStack = []
+ def on(self):
+ self.On = 1
+ def off(self):
+ self.On = 0
+ def inc(self):
+ self.Level += 1
+ def dec(self):
+ """decrement can only be applied to values greater zero
+ values below zero don't make any sense at all!"""
+ if self.Level > 0:
+ self.Level -= 1
+ def push(self):
+ self.LevelStack.append(self.Level)
+ def pop(self):
+ """the levestack can not become -1. any attempt to do so
+ sets the level to 0!"""
+ if len(self.LevelStack) > 0:
+ self.Level = self.LevelStack.pop()
+ else:
+ self.Level = 0
+ def setLevel(self, _level):
+ """the leve can't be less than zero. any attempt to do so
+ sets the level automatically to zero!"""
+ if _level < 0:
+ self.Level = 0
+ else:
+ self.Level = _level
+ def setChar(self, _chars):
+ self.Chars = _chars
+ def indent(self, _default=0):
+ if self.On:
+ return self.Chars * self.Level
+ else:
+ return " " * _default
+
diff --git a/cobbler/Cheetah/Utils/Misc.py b/cobbler/Cheetah/Utils/Misc.py
new file mode 100644
index 0000000..aa5cc6d
--- /dev/null
+++ b/cobbler/Cheetah/Utils/Misc.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+# $Id: Misc.py,v 1.8 2005/11/02 22:26:08 tavis_rudd Exp $
+"""Miscellaneous functions/objects used by Cheetah but also useful standalone.
+
+Meta-Data
+================================================================================
+Author: Mike Orr <iron@mso.oz.net>
+License: This software is released for unlimited distribution under the
+ terms of the MIT license. See the LICENSE file.
+Version: $Revision: 1.8 $
+Start Date: 2001/11/07
+Last Revision Date: $Date: 2005/11/02 22:26:08 $
+"""
+__author__ = "Mike Orr <iron@mso.oz.net>"
+__revision__ = "$Revision: 1.8 $"[11:-2]
+
+import os # Used in mkdirsWithPyInitFile.
+import types # Used in useOrRaise.
+import sys # Used in die.
+
+##################################################
+## MISCELLANEOUS FUNCTIONS
+
+def die(reason):
+ sys.stderr.write(reason + '\n')
+ sys.exit(1)
+
+def useOrRaise(thing, errmsg=''):
+ """Raise 'thing' if it's a subclass of Exception. Otherwise return it.
+
+ Called by: Cheetah.Servlet.cgiImport()
+ """
+ if type(thing) == types.ClassType and issubclass(thing, Exception):
+ raise thing(errmsg)
+ return thing
+
+
+def checkKeywords(dic, legalKeywords, what='argument'):
+ """Verify no illegal keyword arguments were passed to a function.
+
+ in : dic, dictionary (**kw in the calling routine).
+ legalKeywords, list of strings, the keywords that are allowed.
+ what, string, suffix for error message (see function source).
+ out: None.
+ exc: TypeError if 'dic' contains a key not in 'legalKeywords'.
+ called by: Cheetah.Template.__init__()
+ """
+ # XXX legalKeywords could be a set when sets get added to Python.
+ for k in dic.keys(): # Can be dic.iterkeys() if Python >= 2.2.
+ if k not in legalKeywords:
+ raise TypeError("'%s' is not a valid %s" % (k, what))
+
+
+def removeFromList(list_, *elements):
+ """Save as list_.remove(each element) but don't raise an error if
+ element is missing. Modifies 'list_' in place! Returns None.
+ """
+ for elm in elements:
+ try:
+ list_.remove(elm)
+ except ValueError:
+ pass
+
+
+def mkdirsWithPyInitFiles(path):
+ """Same as os.makedirs (mkdir 'path' and all missing parent directories)
+ but also puts a Python '__init__.py' file in every directory it
+ creates. Does nothing (without creating an '__init__.py' file) if the
+ directory already exists.
+ """
+ dir, fil = os.path.split(path)
+ if dir and not os.path.exists(dir):
+ mkdirsWithPyInitFiles(dir)
+ if not os.path.exists(path):
+ os.mkdir(path)
+ init = os.path.join(path, "__init__.py")
+ f = open(init, 'w') # Open and close to produce empty file.
+ f.close()
+
+
+
+# vim: shiftwidth=4 tabstop=4 expandtab
diff --git a/cobbler/Cheetah/Utils/VerifyType.py b/cobbler/Cheetah/Utils/VerifyType.py
new file mode 100644
index 0000000..6c52e09
--- /dev/null
+++ b/cobbler/Cheetah/Utils/VerifyType.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+# $Id: VerifyType.py,v 1.4 2005/11/02 22:26:08 tavis_rudd Exp $
+"""Functions to verify an argument's type
+
+Meta-Data
+================================================================================
+Author: Mike Orr <iron@mso.oz.net>
+License: This software is released for unlimited distribution under the
+ terms of the MIT license. See the LICENSE file.
+Version: $Revision: 1.4 $
+Start Date: 2001/11/07
+Last Revision Date: $Date: 2005/11/02 22:26:08 $
+"""
+__author__ = "Mike Orr <iron@mso.oz.net>"
+__revision__ = "$Revision: 1.4 $"[11:-2]
+
+##################################################
+## DEPENDENCIES
+
+import types # Used in VerifyTypeClass.
+
+##################################################
+## PRIVATE FUNCTIONS
+
+def _errmsg(argname, ltd, errmsgExtra=''):
+ """Construct an error message.
+
+ argname, string, the argument name.
+ ltd, string, description of the legal types.
+ errmsgExtra, string, text to append to error mssage.
+ Returns: string, the error message.
+ """
+ if errmsgExtra:
+ errmsgExtra = '\n' + errmsgExtra
+ return "arg '%s' must be %s%s" % (argname, ltd, errmsgExtra)
+
+
+##################################################
+## TYPE VERIFICATION FUNCTIONS
+
+def VerifyType(arg, argname, legalTypes, ltd, errmsgExtra=''):
+ """Verify the type of an argument.
+
+ arg, any, the argument.
+ argname, string, name of the argument.
+ legalTypes, list of type objects, the allowed types.
+ ltd, string, description of legal types (for error message).
+ errmsgExtra, string, text to append to error message.
+ Returns: None.
+ Exceptions: TypeError if 'arg' is the wrong type.
+ """
+ if type(arg) not in legalTypes:
+ m = _errmsg(argname, ltd, errmsgExtra)
+ raise TypeError(m)
+
+
+def VerifyTypeClass(arg, argname, legalTypes, ltd, klass, errmsgExtra=''):
+ """Same, but if it's a class, verify it's a subclass of the right class.
+
+ arg, any, the argument.
+ argname, string, name of the argument.
+ legalTypes, list of type objects, the allowed types.
+ ltd, string, description of legal types (for error message).
+ klass, class, the parent class.
+ errmsgExtra, string, text to append to the error message.
+ Returns: None.
+ Exceptions: TypeError if 'arg' is the wrong type.
+ """
+ VerifyType(arg, argname, legalTypes, ltd, errmsgExtra)
+ # If no exception, the arg is a legal type.
+ if type(arg) == types.ClassType and not issubclass(arg, klass):
+ # Must test for "is class type" to avoid TypeError from issubclass().
+ m = _errmsg(argname, ltd, errmsgExtra)
+ raise TypeError(m)
+
+# @@MO: Commented until we determine whether it's useful.
+#def VerifyClass(arg, argname, klass, ltd):
+# """Same, but allow *only* a subclass of the right class.
+# """
+# VerifyTypeClass(arg, argname, [types.ClassType], ltd, klass)
+
+# vim: shiftwidth=4 tabstop=4 expandtab
diff --git a/cobbler/Cheetah/Utils/WebInputMixin.py b/cobbler/Cheetah/Utils/WebInputMixin.py
new file mode 100644
index 0000000..930c13e
--- /dev/null
+++ b/cobbler/Cheetah/Utils/WebInputMixin.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+# $Id: WebInputMixin.py,v 1.10 2006/01/06 21:56:54 tavis_rudd Exp $
+"""Provides helpers for Template.webInput(), a method for importing web
+transaction variables in bulk. See the docstring of webInput for full details.
+
+Meta-Data
+================================================================================
+Author: Mike Orr <iron@mso.oz.net>
+License: This software is released for unlimited distribution under the
+ terms of the MIT license. See the LICENSE file.
+Version: $Revision: 1.10 $
+Start Date: 2002/03/17
+Last Revision Date: $Date: 2006/01/06 21:56:54 $
+"""
+__author__ = "Mike Orr <iron@mso.oz.net>"
+__revision__ = "$Revision: 1.10 $"[11:-2]
+
+from Cheetah.Utils.Misc import useOrRaise
+
+class NonNumericInputError(ValueError): pass
+
+##################################################
+## PRIVATE FUNCTIONS AND CLASSES
+
+class _Converter:
+ """A container object for info about type converters.
+ .name, string, name of this converter (for error messages).
+ .func, function, factory function.
+ .default, value to use or raise if the real value is missing.
+ .error, value to use or raise if .func() raises an exception.
+ """
+ def __init__(self, name, func, default, error):
+ self.name = name
+ self.func = func
+ self.default = default
+ self.error = error
+
+
+def _lookup(name, func, multi, converters):
+ """Look up a Webware field/cookie/value/session value. Return
+ '(realName, value)' where 'realName' is like 'name' but with any
+ conversion suffix strips off. Applies numeric conversion and
+ single vs multi values according to the comments in the source.
+ """
+ # Step 1 -- split off the conversion suffix from 'name'; e.g. "height:int".
+ # If there's no colon, the suffix is "". 'longName' is the name with the
+ # suffix, 'shortName' is without.
+ # XXX This implementation assumes "height:" means "height".
+ colon = name.find(':')
+ if colon != -1:
+ longName = name
+ shortName, ext = name[:colon], name[colon+1:]
+ else:
+ longName = shortName = name
+ ext = ''
+
+ # Step 2 -- look up the values by calling 'func'.
+ if longName != shortName:
+ values = func(longName, None) or func(shortName, None)
+ else:
+ values = func(shortName, None)
+ # 'values' is a list of strings, a string or None.
+
+ # Step 3 -- Coerce 'values' to a list of zero, one or more strings.
+ if values is None:
+ values = []
+ elif isinstance(values, str):
+ values = [values]
+
+ # Step 4 -- Find a _Converter object or raise TypeError.
+ try:
+ converter = converters[ext]
+ except KeyError:
+ fmt = "'%s' is not a valid converter name in '%s'"
+ tup = (ext, longName)
+ raise TypeError(fmt % tup)
+
+ # Step 5 -- if there's a converter func, run it on each element.
+ # If the converter raises an exception, use or raise 'converter.error'.
+ if converter.func is not None:
+ tmp = values[:]
+ values = []
+ for elm in tmp:
+ try:
+ elm = converter.func(elm)
+ except (TypeError, ValueError):
+ tup = converter.name, elm
+ errmsg = "%s '%s' contains invalid characters" % tup
+ elm = useOrRaise(converter.error, errmsg)
+ values.append(elm)
+ # 'values' is now a list of strings, ints or floats.
+
+ # Step 6 -- If we're supposed to return a multi value, return the list
+ # as is. If we're supposed to return a single value and the list is
+ # empty, return or raise 'converter.default'. Otherwise, return the
+ # first element in the list and ignore any additional values.
+ if multi:
+ return shortName, values
+ if len(values) == 0:
+ return shortName, useOrRaise(converter.default)
+ return shortName, values[0]
+
+# vim: sw=4 ts=4 expandtab
diff --git a/cobbler/Cheetah/Utils/__init__.py b/cobbler/Cheetah/Utils/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/cobbler/Cheetah/Utils/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/cobbler/Cheetah/Utils/htmlDecode.py b/cobbler/Cheetah/Utils/htmlDecode.py
new file mode 100644
index 0000000..2832a74
--- /dev/null
+++ b/cobbler/Cheetah/Utils/htmlDecode.py
@@ -0,0 +1,14 @@
+"""This is a copy of the htmlDecode function in Webware.
+
+@@TR: It implemented more efficiently.
+
+"""
+
+from Cheetah.Utils.htmlEncode import htmlCodesReversed
+
+def htmlDecode(s, codes=htmlCodesReversed):
+ """ Returns the ASCII decoded version of the given HTML string. This does
+ NOT remove normal HTML tags like <p>. It is the inverse of htmlEncode()."""
+ for code in codes:
+ s = s.replace(code[1], code[0])
+ return s
diff --git a/cobbler/Cheetah/Utils/htmlEncode.py b/cobbler/Cheetah/Utils/htmlEncode.py
new file mode 100644
index 0000000..f76c77e
--- /dev/null
+++ b/cobbler/Cheetah/Utils/htmlEncode.py
@@ -0,0 +1,21 @@
+"""This is a copy of the htmlEncode function in Webware.
+
+
+@@TR: It implemented more efficiently.
+
+"""
+htmlCodes = [
+ ['&', '&amp;'],
+ ['<', '&lt;'],
+ ['>', '&gt;'],
+ ['"', '&quot;'],
+]
+htmlCodesReversed = htmlCodes[:]
+htmlCodesReversed.reverse()
+
+def htmlEncode(s, codes=htmlCodes):
+ """ Returns the HTML encoded version of the given string. This is useful to
+ display a plain ASCII text string on a web page."""
+ for code in codes:
+ s = s.replace(code[0], code[1])
+ return s
diff --git a/cobbler/Cheetah/Utils/memcache.py b/cobbler/Cheetah/Utils/memcache.py
new file mode 100644
index 0000000..03ba032
--- /dev/null
+++ b/cobbler/Cheetah/Utils/memcache.py
@@ -0,0 +1,625 @@
+#!/usr/bin/env python
+
+"""
+client module for memcached (memory cache daemon)
+
+Overview
+========
+
+See U{the MemCached homepage<http://www.danga.com/memcached>} for more about memcached.
+
+Usage summary
+=============
+
+This should give you a feel for how this module operates::
+
+ import memcache
+ mc = memcache.Client(['127.0.0.1:11211'], debug=0)
+
+ mc.set("some_key", "Some value")
+ value = mc.get("some_key")
+
+ mc.set("another_key", 3)
+ mc.delete("another_key")
+
+ mc.set("key", "1") # note that the key used for incr/decr must be a string.
+ mc.incr("key")
+ mc.decr("key")
+
+The standard way to use memcache with a database is like this::
+
+ key = derive_key(obj)
+ obj = mc.get(key)
+ if not obj:
+ obj = backend_api.get(...)
+ mc.set(key, obj)
+
+ # we now have obj, and future passes through this code
+ # will use the object from the cache.
+
+Detailed Documentation
+======================
+
+More detailed documentation is available in the L{Client} class.
+"""
+
+import sys
+import socket
+import time
+import types
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
+
+__author__ = "Evan Martin <martine@danga.com>"
+__version__ = "1.2_tummy5"
+__copyright__ = "Copyright (C) 2003 Danga Interactive"
+__license__ = "Python"
+
+class _Error(Exception):
+ pass
+
+class Client:
+ """
+ Object representing a pool of memcache servers.
+
+ See L{memcache} for an overview.
+
+ In all cases where a key is used, the key can be either:
+ 1. A simple hashable type (string, integer, etc.).
+ 2. A tuple of C{(hashvalue, key)}. This is useful if you want to avoid
+ making this module calculate a hash value. You may prefer, for
+ example, to keep all of a given user's objects on the same memcache
+ server, so you could use the user's unique id as the hash value.
+
+ @group Setup: __init__, set_servers, forget_dead_hosts, disconnect_all, debuglog
+ @group Insertion: set, add, replace
+ @group Retrieval: get, get_multi
+ @group Integers: incr, decr
+ @group Removal: delete
+ @sort: __init__, set_servers, forget_dead_hosts, disconnect_all, debuglog,\
+ set, add, replace, get, get_multi, incr, decr, delete
+ """
+
+ _usePickle = False
+ _FLAG_PICKLE = 1<<0
+ _FLAG_INTEGER = 1<<1
+ _FLAG_LONG = 1<<2
+
+ _SERVER_RETRIES = 10 # how many times to try finding a free server.
+
+ def __init__(self, servers, debug=0):
+ """
+ Create a new Client object with the given list of servers.
+
+ @param servers: C{servers} is passed to L{set_servers}.
+ @param debug: whether to display error messages when a server can't be
+ contacted.
+ """
+ self.set_servers(servers)
+ self.debug = debug
+ self.stats = {}
+
+ def set_servers(self, servers):
+ """
+ Set the pool of servers used by this client.
+
+ @param servers: an array of servers.
+ Servers can be passed in two forms:
+ 1. Strings of the form C{"host:port"}, which implies a default weight of 1.
+ 2. Tuples of the form C{("host:port", weight)}, where C{weight} is
+ an integer weight value.
+ """
+ self.servers = [_Host(s, self.debuglog) for s in servers]
+ self._init_buckets()
+
+ def get_stats(self):
+ '''Get statistics from each of the servers.
+
+ @return: A list of tuples ( server_identifier, stats_dictionary ).
+ The dictionary contains a number of name/value pairs specifying
+ the name of the status field and the string value associated with
+ it. The values are not converted from strings.
+ '''
+ data = []
+ for s in self.servers:
+ if not s.connect(): continue
+ name = '%s:%s (%s)' % ( s.ip, s.port, s.weight )
+ s.send_cmd('stats')
+ serverData = {}
+ data.append(( name, serverData ))
+ readline = s.readline
+ while 1:
+ line = readline()
+ if not line or line.strip() == 'END': break
+ stats = line.split(' ', 2)
+ serverData[stats[1]] = stats[2]
+
+ return(data)
+
+ def flush_all(self):
+ 'Expire all data currently in the memcache servers.'
+ for s in self.servers:
+ if not s.connect(): continue
+ s.send_cmd('flush_all')
+ s.expect("OK")
+
+ def debuglog(self, str):
+ if self.debug:
+ sys.stderr.write("MemCached: %s\n" % str)
+
+ def _statlog(self, func):
+ if not self.stats.has_key(func):
+ self.stats[func] = 1
+ else:
+ self.stats[func] += 1
+
+ def forget_dead_hosts(self):
+ """
+ Reset every host in the pool to an "alive" state.
+ """
+ for s in self.servers:
+ s.dead_until = 0
+
+ def _init_buckets(self):
+ self.buckets = []
+ for server in self.servers:
+ for i in range(server.weight):
+ self.buckets.append(server)
+
+ def _get_server(self, key):
+ if type(key) == types.TupleType:
+ serverhash = key[0]
+ key = key[1]
+ else:
+ serverhash = hash(key)
+
+ for i in range(Client._SERVER_RETRIES):
+ server = self.buckets[serverhash % len(self.buckets)]
+ if server.connect():
+ #print "(using server %s)" % server,
+ return server, key
+ serverhash = hash(str(serverhash) + str(i))
+ return None, None
+
+ def disconnect_all(self):
+ for s in self.servers:
+ s.close_socket()
+
+ def delete(self, key, time=0):
+ '''Deletes a key from the memcache.
+
+ @return: Nonzero on success.
+ @rtype: int
+ '''
+ server, key = self._get_server(key)
+ if not server:
+ return 0
+ self._statlog('delete')
+ if time != None:
+ cmd = "delete %s %d" % (key, time)
+ else:
+ cmd = "delete %s" % key
+
+ try:
+ server.send_cmd(cmd)
+ server.expect("DELETED")
+ except socket.error, msg:
+ server.mark_dead(msg[1])
+ return 0
+ return 1
+
+ def incr(self, key, delta=1):
+ """
+ Sends a command to the server to atomically increment the value for C{key} by
+ C{delta}, or by 1 if C{delta} is unspecified. Returns None if C{key} doesn't
+ exist on server, otherwise it returns the new value after incrementing.
+
+ Note that the value for C{key} must already exist in the memcache, and it
+ must be the string representation of an integer.
+
+ >>> mc.set("counter", "20") # returns 1, indicating success
+ 1
+ >>> mc.incr("counter")
+ 21
+ >>> mc.incr("counter")
+ 22
+
+ Overflow on server is not checked. Be aware of values approaching
+ 2**32. See L{decr}.
+
+ @param delta: Integer amount to increment by (should be zero or greater).
+ @return: New value after incrementing.
+ @rtype: int
+ """
+ return self._incrdecr("incr", key, delta)
+
+ def decr(self, key, delta=1):
+ """
+ Like L{incr}, but decrements. Unlike L{incr}, underflow is checked and
+ new values are capped at 0. If server value is 1, a decrement of 2
+ returns 0, not -1.
+
+ @param delta: Integer amount to decrement by (should be zero or greater).
+ @return: New value after decrementing.
+ @rtype: int
+ """
+ return self._incrdecr("decr", key, delta)
+
+ def _incrdecr(self, cmd, key, delta):
+ server, key = self._get_server(key)
+ if not server:
+ return 0
+ self._statlog(cmd)
+ cmd = "%s %s %d" % (cmd, key, delta)
+ try:
+ server.send_cmd(cmd)
+ line = server.readline()
+ return int(line)
+ except socket.error, msg:
+ server.mark_dead(msg[1])
+ return None
+
+ def add(self, key, val, time=0):
+ '''
+ Add new key with value.
+
+ Like L{set}, but only stores in memcache if the key doesn\'t already exist.
+
+ @return: Nonzero on success.
+ @rtype: int
+ '''
+ return self._set("add", key, val, time)
+ def replace(self, key, val, time=0):
+ '''Replace existing key with value.
+
+ Like L{set}, but only stores in memcache if the key already exists.
+ The opposite of L{add}.
+
+ @return: Nonzero on success.
+ @rtype: int
+ '''
+ return self._set("replace", key, val, time)
+ def set(self, key, val, time=0):
+ '''Unconditionally sets a key to a given value in the memcache.
+
+ The C{key} can optionally be an tuple, with the first element being the
+ hash value, if you want to avoid making this module calculate a hash value.
+ You may prefer, for example, to keep all of a given user's objects on the
+ same memcache server, so you could use the user's unique id as the hash
+ value.
+
+ @return: Nonzero on success.
+ @rtype: int
+ '''
+ return self._set("set", key, val, time)
+
+ def _set(self, cmd, key, val, time):
+ server, key = self._get_server(key)
+ if not server:
+ return 0
+
+ self._statlog(cmd)
+
+ flags = 0
+ if isinstance(val, types.StringTypes):
+ pass
+ elif isinstance(val, int):
+ flags |= Client._FLAG_INTEGER
+ val = "%d" % val
+ elif isinstance(val, long):
+ flags |= Client._FLAG_LONG
+ val = "%d" % val
+ elif self._usePickle:
+ flags |= Client._FLAG_PICKLE
+ val = pickle.dumps(val, 2)
+ else:
+ pass
+
+ fullcmd = "%s %s %d %d %d\r\n%s" % (cmd, key, flags, time, len(val), val)
+ try:
+ server.send_cmd(fullcmd)
+ server.expect("STORED")
+ except socket.error, msg:
+ server.mark_dead(msg[1])
+ return 0
+ return 1
+
+ def get(self, key):
+ '''Retrieves a key from the memcache.
+
+ @return: The value or None.
+ '''
+ server, key = self._get_server(key)
+ if not server:
+ return None
+
+ self._statlog('get')
+
+ try:
+ server.send_cmd("get %s" % key)
+ rkey, flags, rlen, = self._expectvalue(server)
+ if not rkey:
+ return None
+ value = self._recv_value(server, flags, rlen)
+ server.expect("END")
+ except (_Error, socket.error), msg:
+ if type(msg) is types.TupleType:
+ msg = msg[1]
+ server.mark_dead(msg)
+ return None
+ return value
+
+ def get_multi(self, keys):
+ '''
+ Retrieves multiple keys from the memcache doing just one query.
+
+ >>> success = mc.set("foo", "bar")
+ >>> success = mc.set("baz", 42)
+ >>> mc.get_multi(["foo", "baz", "foobar"]) == {"foo": "bar", "baz": 42}
+ 1
+
+ This method is recommended over regular L{get} as it lowers the number of
+ total packets flying around your network, reducing total latency, since
+ your app doesn\'t have to wait for each round-trip of L{get} before sending
+ the next one.
+
+ @param keys: An array of keys.
+ @return: A dictionary of key/value pairs that were available.
+
+ '''
+
+ self._statlog('get_multi')
+
+ server_keys = {}
+
+ # build up a list for each server of all the keys we want.
+ for key in keys:
+ server, key = self._get_server(key)
+ if not server:
+ continue
+ if not server_keys.has_key(server):
+ server_keys[server] = []
+ server_keys[server].append(key)
+
+ # send out all requests on each server before reading anything
+ dead_servers = []
+ for server in server_keys.keys():
+ try:
+ server.send_cmd("get %s" % " ".join(server_keys[server]))
+ except socket.error, msg:
+ server.mark_dead(msg[1])
+ dead_servers.append(server)
+
+ # if any servers died on the way, don't expect them to respond.
+ for server in dead_servers:
+ del server_keys[server]
+
+ retvals = {}
+ for server in server_keys.keys():
+ try:
+ line = server.readline()
+ while line and line != 'END':
+ rkey, flags, rlen = self._expectvalue(server, line)
+ # Bo Yang reports that this can sometimes be None
+ if rkey is not None:
+ val = self._recv_value(server, flags, rlen)
+ retvals[rkey] = val
+ line = server.readline()
+ except (_Error, socket.error), msg:
+ server.mark_dead(msg)
+ return retvals
+
+ def _expectvalue(self, server, line=None):
+ if not line:
+ line = server.readline()
+
+ if line[:5] == 'VALUE':
+ resp, rkey, flags, len = line.split()
+ flags = int(flags)
+ rlen = int(len)
+ return (rkey, flags, rlen)
+ else:
+ return (None, None, None)
+
+ def _recv_value(self, server, flags, rlen):
+ rlen += 2 # include \r\n
+ buf = server.recv(rlen)
+ if len(buf) != rlen:
+ raise _Error("received %d bytes when expecting %d" % (len(buf), rlen))
+
+ if len(buf) == rlen:
+ buf = buf[:-2] # strip \r\n
+
+ if flags == 0:
+ val = buf
+ elif flags & Client._FLAG_INTEGER:
+ val = int(buf)
+ elif flags & Client._FLAG_LONG:
+ val = long(buf)
+ elif self._usePickle and flags & Client._FLAG_PICKLE:
+ try:
+ val = pickle.loads(buf)
+ except:
+ self.debuglog('Pickle error...\n')
+ val = None
+ else:
+ self.debuglog("unknown flags on get: %x\n" % flags)
+
+ return val
+
+class _Host:
+ _DEAD_RETRY = 30 # number of seconds before retrying a dead server.
+
+ def __init__(self, host, debugfunc=None):
+ if isinstance(host, types.TupleType):
+ host = host[0]
+ self.weight = host[1]
+ else:
+ self.weight = 1
+
+ if host.find(":") > 0:
+ self.ip, self.port = host.split(":")
+ self.port = int(self.port)
+ else:
+ self.ip, self.port = host, 11211
+
+ if not debugfunc:
+ debugfunc = lambda x: x
+ self.debuglog = debugfunc
+
+ self.deaduntil = 0
+ self.socket = None
+
+ def _check_dead(self):
+ if self.deaduntil and self.deaduntil > time.time():
+ return 1
+ self.deaduntil = 0
+ return 0
+
+ def connect(self):
+ if self._get_socket():
+ return 1
+ return 0
+
+ def mark_dead(self, reason):
+ self.debuglog("MemCache: %s: %s. Marking dead." % (self, reason))
+ self.deaduntil = time.time() + _Host._DEAD_RETRY
+ self.close_socket()
+
+ def _get_socket(self):
+ if self._check_dead():
+ return None
+ if self.socket:
+ return self.socket
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ # Python 2.3-ism: s.settimeout(1)
+ try:
+ s.connect((self.ip, self.port))
+ except socket.error, msg:
+ self.mark_dead("connect: %s" % msg[1])
+ return None
+ self.socket = s
+ return s
+
+ def close_socket(self):
+ if self.socket:
+ self.socket.close()
+ self.socket = None
+
+ def send_cmd(self, cmd):
+ if len(cmd) > 100:
+ self.socket.sendall(cmd)
+ self.socket.sendall('\r\n')
+ else:
+ self.socket.sendall(cmd + '\r\n')
+
+ def readline(self):
+ buffers = ''
+ recv = self.socket.recv
+ while 1:
+ data = recv(1)
+ if not data:
+ self.mark_dead('Connection closed while reading from %s'
+ % repr(self))
+ break
+ if data == '\n' and buffers and buffers[-1] == '\r':
+ return(buffers[:-1])
+ buffers = buffers + data
+ return(buffers)
+
+ def expect(self, text):
+ line = self.readline()
+ if line != text:
+ self.debuglog("while expecting '%s', got unexpected response '%s'" % (text, line))
+ return line
+
+ def recv(self, rlen):
+ buf = ''
+ recv = self.socket.recv
+ while len(buf) < rlen:
+ buf = buf + recv(rlen - len(buf))
+ return buf
+
+ def __str__(self):
+ d = ''
+ if self.deaduntil:
+ d = " (dead until %d)" % self.deaduntil
+ return "%s:%d%s" % (self.ip, self.port, d)
+
+def _doctest():
+ import doctest, memcache
+ servers = ["127.0.0.1:11211"]
+ mc = Client(servers, debug=1)
+ globs = {"mc": mc}
+ return doctest.testmod(memcache, globs=globs)
+
+if __name__ == "__main__":
+ print "Testing docstrings..."
+ _doctest()
+ print "Running tests:"
+ print
+ #servers = ["127.0.0.1:11211", "127.0.0.1:11212"]
+ servers = ["127.0.0.1:11211"]
+ mc = Client(servers, debug=1)
+
+ def to_s(val):
+ if not isinstance(val, types.StringTypes):
+ return "%s (%s)" % (val, type(val))
+ return "%s" % val
+ def test_setget(key, val):
+ print "Testing set/get {'%s': %s} ..." % (to_s(key), to_s(val)),
+ mc.set(key, val)
+ newval = mc.get(key)
+ if newval == val:
+ print "OK"
+ return 1
+ else:
+ print "FAIL"
+ return 0
+
+ class FooStruct:
+ def __init__(self):
+ self.bar = "baz"
+ def __str__(self):
+ return "A FooStruct"
+ def __eq__(self, other):
+ if isinstance(other, FooStruct):
+ return self.bar == other.bar
+ return 0
+
+ test_setget("a_string", "some random string")
+ test_setget("an_integer", 42)
+ if test_setget("long", long(1<<30)):
+ print "Testing delete ...",
+ if mc.delete("long"):
+ print "OK"
+ else:
+ print "FAIL"
+ print "Testing get_multi ...",
+ print mc.get_multi(["a_string", "an_integer"])
+
+ print "Testing get(unknown value) ...",
+ print to_s(mc.get("unknown_value"))
+
+ f = FooStruct()
+ test_setget("foostruct", f)
+
+ print "Testing incr ...",
+ x = mc.incr("an_integer", 1)
+ if x == 43:
+ print "OK"
+ else:
+ print "FAIL"
+
+ print "Testing decr ...",
+ x = mc.decr("an_integer", 1)
+ if x == 42:
+ print "OK"
+ else:
+ print "FAIL"
+
+
+
+# vim: ts=4 sw=4 et :
diff --git a/cobbler/Cheetah/Utils/optik/__init__.py b/cobbler/Cheetah/Utils/optik/__init__.py
new file mode 100644
index 0000000..75a30ba
--- /dev/null
+++ b/cobbler/Cheetah/Utils/optik/__init__.py
@@ -0,0 +1,32 @@
+"""optik
+
+A powerful, extensible, and easy-to-use command-line parser for Python.
+
+By Greg Ward <gward@python.net>
+
+See http://optik.sourceforge.net/
+
+Cheetah modifications: added "Cheetah.Utils.optik." prefix to
+ all intra-Optik imports.
+"""
+
+# Copyright (c) 2001 Gregory P. Ward. All rights reserved.
+# See the README.txt distributed with Optik for licensing terms.
+
+__revision__ = "$Id: __init__.py,v 1.2 2002/09/12 06:56:51 hierro Exp $"
+
+__version__ = "1.3"
+
+
+# Re-import these for convenience
+from Cheetah.Utils.optik.option import Option
+from Cheetah.Utils.optik.option_parser import \
+ OptionParser, SUPPRESS_HELP, SUPPRESS_USAGE, STD_HELP_OPTION
+from Cheetah.Utils.optik.errors import OptionValueError
+
+
+# Some day, there might be many Option classes. As of Optik 1.3, the
+# preferred way to instantiate Options is indirectly, via make_option(),
+# which will become a factory function when there are many Option
+# classes.
+make_option = Option
diff --git a/cobbler/Cheetah/Utils/optik/errors.py b/cobbler/Cheetah/Utils/optik/errors.py
new file mode 100644
index 0000000..2ed75e6
--- /dev/null
+++ b/cobbler/Cheetah/Utils/optik/errors.py
@@ -0,0 +1,52 @@
+"""optik.errors
+
+Exception classes used by Optik.
+"""
+
+__revision__ = "$Id: errors.py,v 1.1 2002/08/24 17:10:06 hierro Exp $"
+
+# Copyright (c) 2001 Gregory P. Ward. All rights reserved.
+# See the README.txt distributed with Optik for licensing terms.
+
+# created 2001/10/17 GPW (from optik.py)
+
+
+class OptikError (Exception):
+ def __init__ (self, msg):
+ self.msg = msg
+
+ def __str__ (self):
+ return self.msg
+
+
+class OptionError (OptikError):
+ """
+ Raised if an Option instance is created with invalid or
+ inconsistent arguments.
+ """
+
+ def __init__ (self, msg, option):
+ self.msg = msg
+ self.option_id = str(option)
+
+ def __str__ (self):
+ if self.option_id:
+ return "option %s: %s" % (self.option_id, self.msg)
+ else:
+ return self.msg
+
+class OptionConflictError (OptionError):
+ """
+ Raised if conflicting options are added to an OptionParser.
+ """
+
+class OptionValueError (OptikError):
+ """
+ Raised if an invalid option value is encountered on the command
+ line.
+ """
+
+class BadOptionError (OptikError):
+ """
+ Raised if an invalid or ambiguous option is seen on the command-line.
+ """
diff --git a/cobbler/Cheetah/Utils/optik/option.py b/cobbler/Cheetah/Utils/optik/option.py
new file mode 100644
index 0000000..ac85c3d
--- /dev/null
+++ b/cobbler/Cheetah/Utils/optik/option.py
@@ -0,0 +1,354 @@
+"""optik.option
+
+Defines the Option class and some standard value-checking functions.
+
+Cheetah modifications: added "Cheetah.Utils.optik." prefix to
+ all intra-Optik imports.
+"""
+
+__revision__ = "$Id: option.py,v 1.2 2002/09/12 06:56:51 hierro Exp $"
+
+# Copyright (c) 2001 Gregory P. Ward. All rights reserved.
+# See the README.txt distributed with Optik for licensing terms.
+
+# created 2001/10/17, GPW (from optik.py)
+
+import sys
+from types import TupleType, DictType
+from Cheetah.Utils.optik.errors import OptionError, OptionValueError
+
+_builtin_cvt = { "int" : (int, "integer"),
+ "long" : (long, "long integer"),
+ "float" : (float, "floating-point"),
+ "complex" : (complex, "complex") }
+
+def check_builtin (option, opt, value):
+ (cvt, what) = _builtin_cvt[option.type]
+ try:
+ return cvt(value)
+ except ValueError:
+ raise OptionValueError(
+ #"%s: invalid %s argument %r" % (opt, what, value))
+ "option %s: invalid %s value: %r" % (opt, what, value))
+
+# Not supplying a default is different from a default of None,
+# so we need an explicit "not supplied" value.
+NO_DEFAULT = "NO"+"DEFAULT"
+
+
+class Option:
+ """
+ Instance attributes:
+ _short_opts : [string]
+ _long_opts : [string]
+
+ action : string
+ type : string
+ dest : string
+ default : any
+ nargs : int
+ const : any
+ callback : function
+ callback_args : (any*)
+ callback_kwargs : { string : any }
+ help : string
+ metavar : string
+ """
+
+ # The list of instance attributes that may be set through
+ # keyword args to the constructor.
+ ATTRS = ['action',
+ 'type',
+ 'dest',
+ 'default',
+ 'nargs',
+ 'const',
+ 'callback',
+ 'callback_args',
+ 'callback_kwargs',
+ 'help',
+ 'metavar']
+
+ # The set of actions allowed by option parsers. Explicitly listed
+ # here so the constructor can validate its arguments.
+ ACTIONS = ("store",
+ "store_const",
+ "store_true",
+ "store_false",
+ "append",
+ "count",
+ "callback",
+ "help",
+ "version")
+
+ # The set of actions that involve storing a value somewhere;
+ # also listed just for constructor argument validation. (If
+ # the action is one of these, there must be a destination.)
+ STORE_ACTIONS = ("store",
+ "store_const",
+ "store_true",
+ "store_false",
+ "append",
+ "count")
+
+ # The set of actions for which it makes sense to supply a value
+ # type, ie. where we expect an argument to this option.
+ TYPED_ACTIONS = ("store",
+ "append",
+ "callback")
+
+ # The set of known types for option parsers. Again, listed here for
+ # constructor argument validation.
+ TYPES = ("string", "int", "long", "float", "complex")
+
+ # Dictionary of argument checking functions, which convert and
+ # validate option arguments according to the option type.
+ #
+ # Signature of checking functions is:
+ # check(option : Option, opt : string, value : string) -> any
+ # where
+ # option is the Option instance calling the checker
+ # opt is the actual option seen on the command-line
+ # (eg. "-a", "--file")
+ # value is the option argument seen on the command-line
+ #
+ # The return value should be in the appropriate Python type
+ # for option.type -- eg. an integer if option.type == "int".
+ #
+ # If no checker is defined for a type, arguments will be
+ # unchecked and remain strings.
+ TYPE_CHECKER = { "int" : check_builtin,
+ "long" : check_builtin,
+ "float" : check_builtin,
+ "complex" : check_builtin,
+ }
+
+
+ # CHECK_METHODS is a list of unbound method objects; they are called
+ # by the constructor, in order, after all attributes are
+ # initialized. The list is created and filled in later, after all
+ # the methods are actually defined. (I just put it here because I
+ # like to define and document all class attributes in the same
+ # place.) Subclasses that add another _check_*() method should
+ # define their own CHECK_METHODS list that adds their check method
+ # to those from this class.
+ CHECK_METHODS = None
+
+
+ # -- Constructor/initialization methods ----------------------------
+
+ def __init__ (self, *opts, **attrs):
+ # Set _short_opts, _long_opts attrs from 'opts' tuple
+ opts = self._check_opt_strings(opts)
+ self._set_opt_strings(opts)
+
+ # Set all other attrs (action, type, etc.) from 'attrs' dict
+ self._set_attrs(attrs)
+
+ # Check all the attributes we just set. There are lots of
+ # complicated interdependencies, but luckily they can be farmed
+ # out to the _check_*() methods listed in CHECK_METHODS -- which
+ # could be handy for subclasses! The one thing these all share
+ # is that they raise OptionError if they discover a problem.
+ for checker in self.CHECK_METHODS:
+ checker(self)
+
+ def _check_opt_strings (self, opts):
+ # Filter out None because early versions of Optik had exactly
+ # one short option and one long option, either of which
+ # could be None.
+ opts = filter(None, opts)
+ if not opts:
+ raise OptionError("at least one option string must be supplied",
+ self)
+ return opts
+
+ def _set_opt_strings (self, opts):
+ self._short_opts = []
+ self._long_opts = []
+ for opt in opts:
+ if len(opt) < 2:
+ raise OptionError(
+ "invalid option string %r: "
+ "must be at least two characters long" % opt, self)
+ elif len(opt) == 2:
+ if not (opt[0] == "-" and opt[1] != "-"):
+ raise OptionError(
+ "invalid short option string %r: "
+ "must be of the form -x, (x any non-dash char)" % opt,
+ self)
+ self._short_opts.append(opt)
+ else:
+ if not (opt[0:2] == "--" and opt[2] != "-"):
+ raise OptionError(
+ "invalid long option string %r: "
+ "must start with --, followed by non-dash" % opt,
+ self)
+ self._long_opts.append(opt)
+
+ def _set_attrs (self, attrs):
+ for attr in self.ATTRS:
+ if attrs.has_key(attr):
+ setattr(self, attr, attrs[attr])
+ del attrs[attr]
+ else:
+ if attr == 'default':
+ setattr(self, attr, NO_DEFAULT)
+ else:
+ setattr(self, attr, None)
+ if attrs:
+ raise OptionError(
+ "invalid keyword arguments: %s" % ", ".join(attrs.keys()),
+ self)
+
+
+ # -- Constructor validation methods --------------------------------
+
+ def _check_action (self):
+ if self.action is None:
+ self.action = "store"
+ elif self.action not in self.ACTIONS:
+ raise OptionError("invalid action: %r" % self.action, self)
+
+ def _check_type (self):
+ if self.type is None:
+ # XXX should factor out another class attr here: list of
+ # actions that *require* a type
+ if self.action in ("store", "append"):
+ # No type given? "string" is the most sensible default.
+ self.type = "string"
+ else:
+ if self.type not in self.TYPES:
+ raise OptionError("invalid option type: %r" % self.type, self)
+ if self.action not in self.TYPED_ACTIONS:
+ raise OptionError(
+ "must not supply a type for action %r" % self.action, self)
+
+ def _check_dest (self):
+ if self.action in self.STORE_ACTIONS and self.dest is None:
+ # No destination given, and we need one for this action.
+ # Glean a destination from the first long option string,
+ # or from the first short option string if no long options.
+ if self._long_opts:
+ # eg. "--foo-bar" -> "foo_bar"
+ self.dest = self._long_opts[0][2:].replace('-', '_')
+ else:
+ self.dest = self._short_opts[0][1]
+
+ def _check_const (self):
+ if self.action != "store_const" and self.const is not None:
+ raise OptionError(
+ "'const' must not be supplied for action %r" % self.action,
+ self)
+
+ def _check_nargs (self):
+ if self.action in self.TYPED_ACTIONS:
+ if self.nargs is None:
+ self.nargs = 1
+ elif self.nargs is not None:
+ raise OptionError(
+ "'nargs' must not be supplied for action %r" % self.action,
+ self)
+
+ def _check_callback (self):
+ if self.action == "callback":
+ if not callable(self.callback):
+ raise OptionError(
+ "callback not callable: %r" % self.callback, self)
+ if (self.callback_args is not None and
+ type(self.callback_args) is not TupleType):
+ raise OptionError(
+ "callback_args, if supplied, must be a tuple: not %r"
+ % self.callback_args, self)
+ if (self.callback_kwargs is not None and
+ type(self.callback_kwargs) is not DictType):
+ raise OptionError(
+ "callback_kwargs, if supplied, must be a dict: not %r"
+ % self.callback_kwargs, self)
+ else:
+ if self.callback is not None:
+ raise OptionError(
+ "callback supplied (%r) for non-callback option"
+ % self.callback, self)
+ if self.callback_args is not None:
+ raise OptionError(
+ "callback_args supplied for non-callback option", self)
+ if self.callback_kwargs is not None:
+ raise OptionError(
+ "callback_kwargs supplied for non-callback option", self)
+
+
+ CHECK_METHODS = [_check_action,
+ _check_type,
+ _check_dest,
+ _check_const,
+ _check_nargs,
+ _check_callback]
+
+
+ # -- Miscellaneous methods -----------------------------------------
+
+ def __str__ (self):
+ if self._short_opts or self._long_opts:
+ return "/".join(self._short_opts + self._long_opts)
+ else:
+ raise RuntimeError, "short_opts and long_opts both empty!"
+
+ def takes_value (self):
+ return self.type is not None
+
+
+ # -- Processing methods --------------------------------------------
+
+ def check_value (self, opt, value):
+ checker = self.TYPE_CHECKER.get(self.type)
+ if checker is None:
+ return value
+ else:
+ return checker(self, opt, value)
+
+ def process (self, opt, value, values, parser):
+
+ # First, convert the value(s) to the right type. Howl if any
+ # value(s) are bogus.
+ if value is not None:
+ if self.nargs == 1:
+ value = self.check_value(opt, value)
+ else:
+ value = tuple([self.check_value(opt, v) for v in value])
+
+ # And then take whatever action is expected of us.
+ # This is a separate method to make life easier for
+ # subclasses to add new actions.
+ return self.take_action(
+ self.action, self.dest, opt, value, values, parser)
+
+ def take_action (self, action, dest, opt, value, values, parser):
+ if action == "store":
+ setattr(values, dest, value)
+ elif action == "store_const":
+ setattr(values, dest, self.const)
+ elif action == "store_true":
+ setattr(values, dest, 1)
+ elif action == "store_false":
+ setattr(values, dest, 0)
+ elif action == "append":
+ values.ensure_value(dest, []).append(value)
+ elif action == "count":
+ setattr(values, dest, values.ensure_value(dest, 0) + 1)
+ elif action == "callback":
+ args = self.callback_args or ()
+ kwargs = self.callback_kwargs or {}
+ self.callback(self, opt, value, parser, *args, **kwargs)
+ elif action == "help":
+ parser.print_help()
+ sys.exit(0)
+ elif action == "version":
+ parser.print_version()
+ sys.exit(0)
+ else:
+ raise RuntimeError, "unknown action %r" % self.action
+
+ return 1
+
+# class Option
diff --git a/cobbler/Cheetah/Utils/optik/option_parser.py b/cobbler/Cheetah/Utils/optik/option_parser.py
new file mode 100644
index 0000000..1b4e632
--- /dev/null
+++ b/cobbler/Cheetah/Utils/optik/option_parser.py
@@ -0,0 +1,667 @@
+"""optik.option_parser
+
+Provides the OptionParser and Values classes.
+
+Cheetah modifications: added "Cheetah.Utils.optik." prefix to
+ all intra-Optik imports.
+"""
+
+__revision__ = "$Id: option_parser.py,v 1.2 2002/09/12 06:56:51 hierro Exp $"
+
+# Copyright (c) 2001 Gregory P. Ward. All rights reserved.
+# See the README.txt distributed with Optik for licensing terms.
+
+# created 2001/10/17, GPW (from optik.py)
+
+import sys, os
+import types
+from Cheetah.Utils.optik.option import Option, NO_DEFAULT
+from Cheetah.Utils.optik.errors import OptionConflictError, OptionValueError, BadOptionError
+
+def get_prog_name ():
+ return os.path.basename(sys.argv[0])
+
+
+SUPPRESS_HELP = "SUPPRESS"+"HELP"
+SUPPRESS_USAGE = "SUPPRESS"+"USAGE"
+
+STD_HELP_OPTION = Option("-h", "--help",
+ action="help",
+ help="show this help message and exit")
+STD_VERSION_OPTION = Option("--version",
+ action="version",
+ help="show program's version number and exit")
+
+
+class Values:
+
+ def __init__ (self, defaults=None):
+ if defaults:
+ for (attr, val) in defaults.items():
+ setattr(self, attr, val)
+
+
+ def _update_careful (self, dict):
+ """
+ Update the option values from an arbitrary dictionary, but only
+ use keys from dict that already have a corresponding attribute
+ in self. Any keys in dict without a corresponding attribute
+ are silently ignored.
+ """
+ for attr in dir(self):
+ if dict.has_key(attr):
+ dval = dict[attr]
+ if dval is not None:
+ setattr(self, attr, dval)
+
+ def _update_loose (self, dict):
+ """
+ Update the option values from an arbitrary dictionary,
+ using all keys from the dictionary regardless of whether
+ they have a corresponding attribute in self or not.
+ """
+ self.__dict__.update(dict)
+
+ def _update (self, dict, mode):
+ if mode == "careful":
+ self._update_careful(dict)
+ elif mode == "loose":
+ self._update_loose(dict)
+ else:
+ raise ValueError, "invalid update mode: %r" % mode
+
+ def read_module (self, modname, mode="careful"):
+ __import__(modname)
+ mod = sys.modules[modname]
+ self._update(vars(mod), mode)
+
+ def read_file (self, filename, mode="careful"):
+ vars = {}
+ execfile(filename, vars)
+ self._update(vars, mode)
+
+ def ensure_value (self, attr, value):
+ if not hasattr(self, attr) or getattr(self, attr) is None:
+ setattr(self, attr, value)
+ return getattr(self, attr)
+
+
+class OptionParser:
+ """
+ Class attributes:
+ standard_option_list : [Option]
+ list of standard options that will be accepted by all instances
+ of this parser class (intended to be overridden by subclasses).
+
+ Instance attributes:
+ usage : string
+ a usage string for your program. Before it is displayed
+ to the user, "%prog" will be expanded to the name of
+ your program (os.path.basename(sys.argv[0])).
+ option_list : [Option]
+ the list of all options accepted on the command-line of
+ this program
+ _short_opt : { string : Option }
+ dictionary mapping short option strings, eg. "-f" or "-X",
+ to the Option instances that implement them. If an Option
+ has multiple short option strings, it will appears in this
+ dictionary multiple times.
+ _long_opt : { string : Option }
+ dictionary mapping long option strings, eg. "--file" or
+ "--exclude", to the Option instances that implement them.
+ Again, a given Option can occur multiple times in this
+ dictionary.
+ _long_opts : [string]
+ list of long option strings recognized by this option
+ parser. Should be equal to _long_opt.values().
+ defaults : { string : any }
+ dictionary mapping option destination names to default
+ values for each destination.
+
+ allow_interspersed_args : boolean = true
+ if true, positional arguments may be interspersed with options.
+ Assuming -a and -b each take a single argument, the command-line
+ -ablah foo bar -bboo baz
+ will be interpreted the same as
+ -ablah -bboo -- foo bar baz
+ If this flag were false, that command line would be interpreted as
+ -ablah -- foo bar -bboo baz
+ -- ie. we stop processing options as soon as we see the first
+ non-option argument. (This is the tradition followed by
+ Python's getopt module, Perl's Getopt::Std, and other argument-
+ parsing libraries, but it is generally annoying to users.)
+
+ rargs : [string]
+ the argument list currently being parsed. Only set when
+ parse_args() is active, and continually trimmed down as
+ we consume arguments. Mainly there for the benefit of
+ callback options.
+ largs : [string]
+ the list of leftover arguments that we have skipped while
+ parsing options. If allow_interspersed_args is false, this
+ list is always empty.
+ values : Values
+ the set of option values currently being accumulated. Only
+ set when parse_args() is active. Also mainly for callbacks.
+
+ Because of the 'rargs', 'largs', and 'values' attributes,
+ OptionParser is not thread-safe. If, for some perverse reason, you
+ need to parse command-line arguments simultaneously in different
+ threads, use different OptionParser instances.
+
+ """
+
+ standard_option_list = [STD_HELP_OPTION]
+
+
+ def __init__ (self,
+ usage=None,
+ option_list=None,
+ option_class=Option,
+ version=None,
+ conflict_handler="error"):
+ self.set_usage(usage)
+ self.option_class = option_class
+ self.version = version
+ self.set_conflict_handler(conflict_handler)
+ self.allow_interspersed_args = 1
+
+ # Create the various lists and dicts that constitute the
+ # "option list". See class docstring for details about
+ # each attribute.
+ self._create_option_list()
+
+ # Populate the option list; initial sources are the
+ # standard_option_list class attribute, the 'option_list'
+ # argument, and the STD_VERSION_OPTION global (if 'version'
+ # supplied).
+ self._populate_option_list(option_list)
+
+ self._init_parsing_state()
+
+ # -- Private methods -----------------------------------------------
+ # (used by the constructor)
+
+ def _create_option_list (self):
+ self.option_list = []
+ self._short_opt = {} # single letter -> Option instance
+ self._long_opt = {} # long option -> Option instance
+ self._long_opts = [] # list of long options
+ self.defaults = {} # maps option dest -> default value
+
+ def _populate_option_list (self, option_list):
+ if self.standard_option_list:
+ self.add_options(self.standard_option_list)
+ if self.version:
+ self.add_option(STD_VERSION_OPTION)
+ if option_list:
+ self.add_options(option_list)
+
+ def _init_parsing_state (self):
+ # These are set in parse_args() for the convenience of callbacks.
+ self.rargs = None
+ self.largs = None
+ self.values = None
+
+
+ # -- Simple modifier methods ---------------------------------------
+
+ def set_usage (self, usage):
+ if usage is None:
+ self.usage = "usage: %prog [options]"
+ elif usage is SUPPRESS_USAGE:
+ self.usage = None
+ else:
+ self.usage = usage
+
+ def enable_interspersed_args (self):
+ self.allow_interspersed_args = 1
+
+ def disable_interspersed_args (self):
+ self.allow_interspersed_args = 0
+
+ def set_conflict_handler (self, handler):
+ if handler not in ("ignore", "error", "resolve"):
+ raise ValueError, "invalid conflict_resolution value %r" % handler
+ self.conflict_handler = handler
+
+ def set_default (self, dest, value):
+ self.defaults[dest] = value
+
+ def set_defaults (self, **kwargs):
+ self.defaults.update(kwargs)
+
+
+ # -- Option-adding methods -----------------------------------------
+
+ def _check_conflict (self, option):
+ conflict_opts = []
+ for opt in option._short_opts:
+ if self._short_opt.has_key(opt):
+ conflict_opts.append((opt, self._short_opt[opt]))
+ for opt in option._long_opts:
+ if self._long_opt.has_key(opt):
+ conflict_opts.append((opt, self._long_opt[opt]))
+
+ if conflict_opts:
+ handler = self.conflict_handler
+ if handler == "ignore": # behaviour for Optik 1.0, 1.1
+ pass
+ elif handler == "error": # new in 1.2
+ raise OptionConflictError(
+ "conflicting option string(s): %s"
+ % ", ".join([co[0] for co in conflict_opts]),
+ option)
+ elif handler == "resolve": # new in 1.2
+ for (opt, c_option) in conflict_opts:
+ if opt.startswith("--"):
+ c_option._long_opts.remove(opt)
+ del self._long_opt[opt]
+ else:
+ c_option._short_opts.remove(opt)
+ del self._short_opt[opt]
+ if not (c_option._short_opts or c_option._long_opts):
+ self.option_list.remove(c_option)
+
+
+ def add_option (self, *args, **kwargs):
+ """add_option(Option)
+ add_option(opt_str, ..., kwarg=val, ...)
+ """
+ if type(args[0]) is types.StringType:
+ option = self.option_class(*args, **kwargs)
+ elif len(args) == 1 and not kwargs:
+ option = args[0]
+ if not isinstance(option, Option):
+ raise TypeError, "not an Option instance: %r" % option
+ else:
+ raise TypeError, "invalid arguments"
+
+ self._check_conflict(option)
+
+ self.option_list.append(option)
+ for opt in option._short_opts:
+ self._short_opt[opt] = option
+ for opt in option._long_opts:
+ self._long_opt[opt] = option
+ self._long_opts.append(opt)
+
+ if option.dest is not None: # option has a dest, we need a default
+ if option.default is not NO_DEFAULT:
+ self.defaults[option.dest] = option.default
+ elif not self.defaults.has_key(option.dest):
+ self.defaults[option.dest] = None
+
+ def add_options (self, option_list):
+ for option in option_list:
+ self.add_option(option)
+
+
+ # -- Option query/removal methods ----------------------------------
+
+ def get_option (self, opt_str):
+ return (self._short_opt.get(opt_str) or
+ self._long_opt.get(opt_str))
+
+ def has_option (self, opt_str):
+ return (self._short_opt.has_key(opt_str) or
+ self._long_opt.has_key(opt_str))
+
+
+ def remove_option (self, opt_str):
+ option = self._short_opt.get(opt_str)
+ if option is None:
+ option = self._long_opt.get(opt_str)
+ if option is None:
+ raise ValueError("no such option %r" % opt_str)
+
+ for opt in option._short_opts:
+ del self._short_opt[opt]
+ for opt in option._long_opts:
+ del self._long_opt[opt]
+ self._long_opts.remove(opt)
+ self.option_list.remove(option)
+
+
+ # -- Option-parsing methods ----------------------------------------
+
+ def _get_args (self, args):
+ if args is None:
+ return sys.argv[1:]
+ else:
+ return args[:] # don't modify caller's list
+
+ def parse_args (self, args=None, values=None):
+ """
+ parse_args(args : [string] = sys.argv[1:],
+ values : Values = None)
+ -> (values : Values, args : [string])
+
+ Parse the command-line options found in 'args' (default:
+ sys.argv[1:]). Any errors result in a call to 'error()', which
+ by default prints the usage message to stderr and calls
+ sys.exit() with an error message. On success returns a pair
+ (values, args) where 'values' is an Values instance (with all
+ your option values) and 'args' is the list of arguments left
+ over after parsing options.
+ """
+ rargs = self._get_args(args)
+ if values is None:
+ values = Values(self.defaults)
+
+ # Store the halves of the argument list as attributes for the
+ # convenience of callbacks:
+ # rargs
+ # the rest of the command-line (the "r" stands for
+ # "remaining" or "right-hand")
+ # largs
+ # the leftover arguments -- ie. what's left after removing
+ # options and their arguments (the "l" stands for "leftover"
+ # or "left-hand")
+
+ # Say this is the original argument list:
+ # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]
+ # ^
+ # (we are about to process arg(i)).
+ #
+ # Then rargs is [arg(i), ..., arg(N-1)]
+ # and largs is a *subset* of [arg0, ..., arg(i-1)]
+ # (any options and their arguments will have been removed
+ # from largs).
+ #
+ # _process_arg() will always consume 1 or more arguments.
+ # If it consumes 1 (eg. arg is an option that takes no arguments),
+ # then after _process_arg() is done the situation is:
+ # largs = subset of [arg0, ..., arg(i)]
+ # rargs = [arg(i+1), ..., arg(N-1)]
+ #
+ # If allow_interspersed_args is false, largs will always be
+ # *empty* -- still a subset of [arg0, ..., arg(i-1)], but
+ # not a very interesting subset!
+
+ self.rargs = rargs
+ self.largs = largs = []
+ self.values = values
+
+ stop = 0
+ while rargs and not stop:
+ try:
+ stop = self._process_arg(largs, rargs, values)
+ except (BadOptionError, OptionValueError), err:
+ self.error(err.msg)
+
+ args = largs + rargs
+ return self.check_values(values, args)
+
+ def check_values (self, values, args):
+ """
+ check_values(values : Values, args : [string])
+ -> (values : Values, args : [string])
+
+ Check that the supplied option values and leftover arguments are
+ valid. Returns the option values and leftover arguments
+ (possibly adjusted, possibly completely new -- whatever you
+ like). Default implementation just returns the passed-in
+ values; subclasses may override as desired.
+ """
+ return (values, args)
+
+ def _process_arg (self, largs, rargs, values):
+ """_process_args(largs : [string],
+ rargs : [string],
+ values : Values)
+ -> stop : boolean
+
+ Process a single command-line argument, consuming zero or more
+ arguments. The next argument to process is rargs[0], which will
+ almost certainly be consumed from rargs. (It might wind up in
+ largs, or it might affect a value in values, or -- if a callback
+ is involved -- almost anything might happen. It will not be
+ consumed if it is a non-option argument and
+ allow_interspersed_args is false.) More arguments from rargs
+ may also be consumed, depending on circumstances.
+
+ Returns true if option processing should stop after this
+ argument is processed.
+ """
+
+ # We handle bare "--" explicitly, and bare "-" is handled by the
+ # standard arg handler since the short arg case ensures that the len
+ # of the opt string is greater than 1.
+
+ arg = rargs[0]
+ if arg == "--":
+ del rargs[0]
+ return 1
+ elif arg[0:2] == "--":
+ # process a single long option (possibly with value(s))
+ self._process_long_opt(rargs, values)
+ elif arg[:1] == "-" and len(arg) > 1:
+ # process a cluster of short options (possibly with
+ # value(s) for the last one only)
+ self._process_short_opts(rargs, values)
+ else:
+ if self.allow_interspersed_args:
+ largs.append(arg)
+ del rargs[0]
+ else:
+ return 1 # stop now, leave this arg in rargs
+
+ return 0 # keep processing args
+
+ def _match_long_opt (self, opt):
+ """_match_long_opt(opt : string) -> string
+
+ Determine which long option string 'opt' matches, ie. which one
+ it is an unambiguous abbrevation for. Raises BadOptionError if
+ 'opt' doesn't unambiguously match any long option string.
+ """
+ return _match_abbrev(opt, self._long_opts)
+
+ def _process_long_opt (self, rargs, values):
+ arg = rargs.pop(0)
+
+ # Value explicitly attached to arg? Pretend it's the next
+ # argument.
+ if "=" in arg:
+ (opt, next_arg) = arg.split("=", 1)
+ rargs.insert(0, next_arg)
+ had_explicit_value = 1
+ else:
+ opt = arg
+ had_explicit_value = 0
+
+ opt = self._match_long_opt(opt)
+ option = self._long_opt[opt]
+ if option.takes_value():
+ nargs = option.nargs
+ if len(rargs) < nargs:
+ if nargs == 1:
+ self.error("%s option requires a value" % opt)
+ else:
+ self.error("%s option requires %d values"
+ % (opt, nargs))
+ elif nargs == 1:
+ value = rargs.pop(0)
+ else:
+ value = tuple(rargs[0:nargs])
+ del rargs[0:nargs]
+
+ elif had_explicit_value:
+ self.error("%s option does not take a value" % opt)
+
+ else:
+ value = None
+
+ option.process(opt, value, values, self)
+
+ def _process_short_opts (self, rargs, values):
+ arg = rargs.pop(0)
+ stop = 0
+ i = 1
+ for ch in arg[1:]:
+ opt = "-" + ch
+ option = self._short_opt.get(opt)
+ i += 1 # we have consumed a character
+
+ if not option:
+ self.error("no such option: %s" % opt)
+ if option.takes_value():
+ # Any characters left in arg? Pretend they're the
+ # next arg, and stop consuming characters of arg.
+ if i < len(arg):
+ rargs.insert(0, arg[i:])
+ stop = 1
+
+ nargs = option.nargs
+ if len(rargs) < nargs:
+ if nargs == 1:
+ self.error("%s option requires a value" % opt)
+ else:
+ self.error("%s option requires %s values"
+ % (opt, nargs))
+ elif nargs == 1:
+ value = rargs.pop(0)
+ else:
+ value = tuple(rargs[0:nargs])
+ del rargs[0:nargs]
+
+ else: # option doesn't take a value
+ value = None
+
+ option.process(opt, value, values, self)
+
+ if stop:
+ break
+
+
+ # -- Output/error methods ------------------------------------------
+
+ def error (self, msg):
+ self.print_usage(sys.stderr)
+ sys.exit("%s: error: %s" % (get_prog_name(), msg))
+
+ def print_usage (self, file=None):
+ if self.usage:
+ usage = self.usage.replace("%prog", get_prog_name())
+ print >>file, usage
+ print >>file
+
+ def print_version (self, file=None):
+ if self.version:
+ version = self.version.replace("%prog", get_prog_name())
+ print >>file, version
+
+ def print_help (self, file=None):
+ from distutils.fancy_getopt import wrap_text
+
+ if file is None:
+ file = sys.stdout
+
+ self.print_usage(file)
+
+ # The help for each option consists of two parts:
+ # * the opt strings and metavars
+ # eg. ("-x", or "-fFILENAME, --file=FILENAME")
+ # * the user-supplied help string
+ # eg. ("turn on expert mode", "read data from FILENAME")
+ #
+ # If possible, we write both of these on the same line:
+ # -x turn on expert mode
+ #
+ # But if the opt string list is too long, we put the help
+ # string on a second line, indented to the same column it would
+ # start in if it fit on the first line.
+ # -fFILENAME, --file=FILENAME
+ # read data from FILENAME
+
+ print >>file, "options:"
+ width = 78 # assume 80 cols for now
+
+ option_help = [] # list of (string, string) tuples
+ lengths = []
+
+ for option in self.option_list:
+ takes_value = option.takes_value()
+ if takes_value:
+ metavar = option.metavar or option.dest.upper()
+
+ opts = [] # list of "-a" or "--foo=FILE" strings
+ if option.help is SUPPRESS_HELP:
+ continue
+
+ if takes_value:
+ for sopt in option._short_opts:
+ opts.append(sopt + metavar)
+ for lopt in option._long_opts:
+ opts.append(lopt + "=" + metavar)
+ else:
+ for opt in option._short_opts + option._long_opts:
+ opts.append(opt)
+
+ opts = ", ".join(opts)
+ option_help.append((opts, option.help))
+ lengths.append(len(opts))
+
+ max_opts = min(max(lengths), 20)
+
+ for (opts, help) in option_help:
+ # how much to indent lines 2 .. N of help text
+ indent_rest = 2 + max_opts + 2
+ help_width = width - indent_rest
+
+ if len(opts) > max_opts:
+ opts = " " + opts + "\n"
+ indent_first = indent_rest
+
+ else: # start help on same line as opts
+ opts = " %-*s " % (max_opts, opts)
+ indent_first = 0
+
+ file.write(opts)
+
+ if help:
+ help_lines = wrap_text(help, help_width)
+ print >>file, "%*s%s" % (indent_first, "", help_lines[0])
+ for line in help_lines[1:]:
+ print >>file, "%*s%s" % (indent_rest, "", line)
+ elif opts[-1] != "\n":
+ file.write("\n")
+
+# class OptionParser
+
+
+def _match_abbrev (s, words):
+ """_match_abbrev(s : string, words : [string]) -> string
+
+ Returns the string in 'words' for which 's' is an unambiguous
+ abbreviation. If 's' is found to be ambiguous or doesn't match any
+ of 'words', raises BadOptionError.
+ """
+ match = None
+ for word in words:
+ # If s isn't even a prefix for this word, don't waste any
+ # more time on it: skip to the next word and try again.
+ if not word.startswith(s):
+ continue
+
+ # Exact match? Great, return now.
+ if s == word:
+ return word
+
+ # Now comes the tricky business of disambiguation. At this
+ # point, we know s is a proper prefix of word, eg. s='--foo' and
+ # word=='--foobar'. If we have already seen another word where
+ # this was the case, eg. '--foobaz', fail: s is ambiguous.
+ # Otherwise record this match and keep looping; we will return
+ # if we see an exact match, or when we fall out of the loop and
+ # it turns out that the current word is the match.
+ if match:
+ raise BadOptionError("ambiguous option: %s (%s, %s, ...?)"
+ % (s, match, word))
+ match = word
+
+ if match:
+ return match
+ else:
+ raise BadOptionError("no such option: %s" % s)
diff --git a/cobbler/Cheetah/Version.py b/cobbler/Cheetah/Version.py
new file mode 100644
index 0000000..32af08a
--- /dev/null
+++ b/cobbler/Cheetah/Version.py
@@ -0,0 +1,58 @@
+Version = '2.0rc7'
+VersionTuple = (2,0,0,'candidate',7)
+
+MinCompatibleVersion = '2.0rc6'
+MinCompatibleVersionTuple = (2,0,0,'candidate',6)
+
+####
+def convertVersionStringToTuple(s):
+ versionNum = [0,0,0]
+ releaseType = 'final'
+ releaseTypeSubNum = 0
+ if s.find('a')!=-1:
+ num, releaseTypeSubNum = s.split('a')
+ releaseType = 'alpha'
+ elif s.find('b')!=-1:
+ num, releaseTypeSubNum = s.split('b')
+ releaseType = 'beta'
+ elif s.find('rc')!=-1:
+ num, releaseTypeSubNum = s.split('rc')
+ releaseType = 'candidate'
+ else:
+ num = s
+ num = num.split('.')
+ for i in range(len(num)):
+ versionNum[i] = int(num[i])
+ if len(versionNum)<3:
+ versionNum += [0]
+ releaseTypeSubNum = int(releaseTypeSubNum)
+
+ return tuple(versionNum+[releaseType,releaseTypeSubNum])
+
+
+if __name__ == '__main__':
+ c = convertVersionStringToTuple
+ print c('2.0a1')
+ print c('2.0b1')
+ print c('2.0rc1')
+ print c('2.0')
+ print c('2.0.2')
+
+
+ assert c('0.9.19b1') < c('0.9.19')
+ assert c('0.9b1') < c('0.9.19')
+
+ assert c('2.0a2') > c('2.0a1')
+ assert c('2.0b1') > c('2.0a2')
+ assert c('2.0b2') > c('2.0b1')
+ assert c('2.0b2') == c('2.0b2')
+
+ assert c('2.0rc1') > c('2.0b1')
+ assert c('2.0rc2') > c('2.0rc1')
+ assert c('2.0rc2') > c('2.0b1')
+
+ assert c('2.0') > c('2.0a1')
+ assert c('2.0') > c('2.0b1')
+ assert c('2.0') > c('2.0rc1')
+ assert c('2.0.1') > c('2.0')
+ assert c('2.0rc1') > c('2.0b1')
diff --git a/cobbler/Cheetah/__init__.py b/cobbler/Cheetah/__init__.py
new file mode 100644
index 0000000..08c240e
--- /dev/null
+++ b/cobbler/Cheetah/__init__.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+# $Id: __init__.py,v 1.10 2006/01/14 04:44:07 tavis_rudd Exp $
+
+"""Cheetah is an open source template engine and code generation tool.
+
+It can be used standalone or combined with other tools and frameworks. Web
+development is its principle use, but Cheetah is very flexible and is also being
+used to generate C++ game code, Java, sql, form emails and even Python code.
+
+Homepage
+================================================================================
+http://www.CheetahTemplate.org/
+
+Documentation
+================================================================================
+For a high-level introduction to Cheetah please refer to the User's Guide
+at http://cheetahtemplate.org/learn.html
+
+Mailing list
+================================================================================
+cheetahtemplate-discuss@lists.sourceforge.net
+Subscribe at http://lists.sourceforge.net/lists/listinfo/cheetahtemplate-discuss
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.10 $"[11:-2]
+
+from Version import Version
diff --git a/cobbler/Cheetah/_namemapper.so b/cobbler/Cheetah/_namemapper.so
new file mode 100755
index 0000000..8f56724
--- /dev/null
+++ b/cobbler/Cheetah/_namemapper.so
Binary files differ
diff --git a/cobbler/Cheetah/convertTmplPathToModuleName.py b/cobbler/Cheetah/convertTmplPathToModuleName.py
new file mode 100644
index 0000000..4f9d8ea
--- /dev/null
+++ b/cobbler/Cheetah/convertTmplPathToModuleName.py
@@ -0,0 +1,15 @@
+import os.path
+import string
+
+l = ['_'] * 256
+for c in string.digits + string.letters:
+ l[ord(c)] = c
+_pathNameTransChars = string.join(l, '')
+del l, c
+
+def convertTmplPathToModuleName(tmplPath,
+ _pathNameTransChars=_pathNameTransChars,
+ splitdrive=os.path.splitdrive,
+ translate=string.translate,
+ ):
+ return translate(splitdrive(tmplPath)[1], _pathNameTransChars)
diff --git a/cobbler/action_import.py b/cobbler/action_import.py
index aad4756..721bb7b 100644
--- a/cobbler/action_import.py
+++ b/cobbler/action_import.py
@@ -221,7 +221,7 @@ class Importer:
# don't run creatrepo twice -- this can happen easily for Xen and PXE, when
# they'll share same repo files.
if not processed_repos.has_key(comps_path):
- cmd = "createrepo --groupfile %s %s" % (comps_file, comps_path)
+ cmd = "createrepo --basedir / --groupfile %s %s" % (comps_file, comps_path)
print "- %s" % cmd
sub_process.call(cmd,shell=True)
print "- repository updated"
diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py
index 47e61ce..13ddd34 100644
--- a/cobbler/action_sync.py
+++ b/cobbler/action_sync.py
@@ -32,6 +32,14 @@ import item_distro
import item_profile
import item_system
+# add Cheetah to sys.path so Cheetah's internal imports work with our
+# bundled copy of Cheetah (which we bundle to support older distros
+# that don't package it).
+import distutils.sysconfig as distconfig
+site_packages = distconfig.get_python_lib(False, False)
+sys.path.append(os.path.join(site_packages, "cobbler"))
+sys.path.append(os.path.join(site_packages, "cobbler", "Cheetah"))
+
from Cheetah.Template import Template
class BootSync:
@@ -271,10 +279,11 @@ class BootSync:
if not x.endswith(".py"):
self.rmfile(path)
if os.path.isdir(path):
- if not x in ["localmirror","repo_mirror","ks_mirror","kickstarts","kickstarts_sys","distros","images","systems"] :
- # new versions of cobbler use repo_mirror for repos and ks_mirror for
- # basic kickstart tree core data. older versions just used "local_mirror" so we
- # do have to leave the "localmirror" in there to avoid breaking users on upgrades
+ if not x in ["localmirror","repo_mirror","ks_mirror","kickstarts","kickstarts_sys","distros","images","systems","profiles"] :
+ # delete directories that shouldn't exist
+ self.rmtree(path)
+ if x in ["kickstarts","kickstarts_sys","images","systems","distros","profiles"]:
+ # clean out directory contents
self.rmtree_contents(path)
self.rmtree_contents(os.path.join(self.settings.tftpboot, "pxelinux.cfg"))
self.rmtree_contents(os.path.join(self.settings.tftpboot, "images"))
@@ -823,13 +832,14 @@ class BootSync:
if self.verbose:
print "del %s" % (path)
try:
- return shutil.rmtree(path)
+ if os.path.isfile(path):
+ return self.rmfile(path)
+ else:
+ return shutil.rmtree(path,ignore_errors=False)
except OSError, ioe:
+ traceback.print_exc()
if not ioe.errno == errno.ENOENT: # doesn't exist
- try:
- self.rmfile(path)
- except:
- raise cexceptions.CobblerException("no_delete",path)
+ raise cexceptions.CobblerException("no_delete",path)
return True
def mkdir(self,path,mode=0777):