mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
changed template engine
This commit is contained in:
parent
35a1b04c5a
commit
a28a6f7d5b
1663
src/Cheetah/CHANGES
1663
src/Cheetah/CHANGES
File diff suppressed because it is too large
Load Diff
@ -1,136 +0,0 @@
|
||||
# $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().
|
||||
'''
|
||||
|
||||
try:
|
||||
from hashlib import md5
|
||||
except ImportError:
|
||||
from md5 import md5
|
||||
|
||||
import time
|
||||
import Cheetah.CacheStore
|
||||
|
||||
class CacheItem(object):
|
||||
'''
|
||||
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 time.time() > self._expiryTime)
|
||||
|
||||
def setExpiryTime(self, time):
|
||||
self._expiryTime = time
|
||||
|
||||
def getExpiryTime(self):
|
||||
return self._expiryTime
|
||||
|
||||
def setData(self, data):
|
||||
self._refreshTime = time.time()
|
||||
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(object):
|
||||
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(object):
|
||||
'''
|
||||
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 = Cheetah.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(str(cacheItemID)).hexdigest()
|
||||
|
||||
if cacheItemID not in self._cacheItems:
|
||||
cacheItem = self._cacheItemClass(
|
||||
cacheItemID=cacheItemID, cacheStore=self._wrappedCacheDataStore)
|
||||
self._cacheItems[cacheItemID] = cacheItem
|
||||
self._isNew = False
|
||||
return self._cacheItems[cacheItemID]
|
@ -1,106 +0,0 @@
|
||||
'''
|
||||
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
|
||||
'''
|
||||
import time
|
||||
|
||||
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 key in self._data:
|
||||
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 key in self._data:
|
||||
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 time.time() > 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
|
||||
from memcache import Client as MemcachedClient
|
||||
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()
|
@ -1,632 +0,0 @@
|
||||
# $Id: CheetahWrapper.py,v 1.26 2007/10/02 01:22:04 tavis_rudd Exp $
|
||||
"""Cheetah command-line interface.
|
||||
|
||||
2002-09-03 MSO: Total rewrite.
|
||||
2002-09-04 MSO: Bugfix, compile command was using wrong output ext.
|
||||
2002-11-08 MSO: Another rewrite.
|
||||
|
||||
Meta-Data
|
||||
================================================================================
|
||||
Author: Tavis Rudd <tavis@damnsimple.com> and Mike Orr <sluggoster@gmail.com>>
|
||||
Version: $Revision: 1.26 $
|
||||
Start Date: 2001/03/30
|
||||
Last Revision Date: $Date: 2007/10/02 01:22:04 $
|
||||
"""
|
||||
__author__ = "Tavis Rudd <tavis@damnsimple.com> and Mike Orr <sluggoster@gmail.com>"
|
||||
__revision__ = "$Revision: 1.26 $"[11:-2]
|
||||
|
||||
import getopt, glob, os, pprint, re, shutil, sys
|
||||
import cPickle as pickle
|
||||
from optparse import OptionParser
|
||||
|
||||
from Cheetah.Version import Version
|
||||
from Cheetah.Template import Template, DEFAULT_COMPILER_SETTINGS
|
||||
from Cheetah.Utils.Misc import mkdirsWithPyInitFiles
|
||||
|
||||
optionDashesRE = re.compile( R"^-{1,2}" )
|
||||
moduleNameRE = re.compile( R"^[a-zA-Z_][a-zA-Z_0-9]*$" )
|
||||
|
||||
def fprintfMessage(stream, format, *args):
|
||||
if format[-1:] == '^':
|
||||
format = format[:-1]
|
||||
else:
|
||||
format += '\n'
|
||||
if args:
|
||||
message = format % args
|
||||
else:
|
||||
message = format
|
||||
stream.write(message)
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Bundle:
|
||||
"""Wrap the source, destination and backup paths in one neat little class.
|
||||
Used by CheetahWrapper.getBundles().
|
||||
"""
|
||||
def __init__(self, **kw):
|
||||
self.__dict__.update(kw)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Bundle %r>" % self.__dict__
|
||||
|
||||
|
||||
##################################################
|
||||
## USAGE FUNCTION & MESSAGES
|
||||
|
||||
def usage(usageMessage, errorMessage="", out=sys.stderr):
|
||||
"""Write help text, an optional error message, and abort the program.
|
||||
"""
|
||||
out.write(WRAPPER_TOP)
|
||||
out.write(usageMessage)
|
||||
exitStatus = 0
|
||||
if errorMessage:
|
||||
out.write('\n')
|
||||
out.write("*** USAGE ERROR ***: %s\n" % errorMessage)
|
||||
exitStatus = 1
|
||||
sys.exit(exitStatus)
|
||||
|
||||
|
||||
WRAPPER_TOP = """\
|
||||
__ ____________ __
|
||||
\ \/ \/ /
|
||||
\/ * * \/ CHEETAH %(Version)s Command-Line Tool
|
||||
\ | /
|
||||
\ ==----== / by Tavis Rudd <tavis@damnsimple.com>
|
||||
\__________/ and Mike Orr <sluggoster@gmail.com>
|
||||
|
||||
""" % globals()
|
||||
|
||||
|
||||
HELP_PAGE1 = """\
|
||||
USAGE:
|
||||
------
|
||||
cheetah compile [options] [FILES ...] : Compile template definitions
|
||||
cheetah fill [options] [FILES ...] : Fill template definitions
|
||||
cheetah help : Print this help message
|
||||
cheetah options : Print options help message
|
||||
cheetah test [options] : Run Cheetah's regression tests
|
||||
: (same as for unittest)
|
||||
cheetah version : Print Cheetah version number
|
||||
|
||||
You may abbreviate the command to the first letter; e.g., 'h' == 'help'.
|
||||
If FILES is a single "-", read standard input and write standard output.
|
||||
Run "cheetah options" for the list of valid options.
|
||||
"""
|
||||
|
||||
##################################################
|
||||
## CheetahWrapper CLASS
|
||||
|
||||
class CheetahWrapper(object):
|
||||
MAKE_BACKUPS = True
|
||||
BACKUP_SUFFIX = ".bak"
|
||||
_templateClass = None
|
||||
_compilerSettings = None
|
||||
|
||||
def __init__(self):
|
||||
self.progName = None
|
||||
self.command = None
|
||||
self.opts = None
|
||||
self.pathArgs = None
|
||||
self.sourceFiles = []
|
||||
self.searchList = []
|
||||
self.parser = None
|
||||
|
||||
##################################################
|
||||
## MAIN ROUTINE
|
||||
|
||||
def main(self, argv=None):
|
||||
"""The main program controller."""
|
||||
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
|
||||
# Step 1: Determine the command and arguments.
|
||||
try:
|
||||
self.progName = progName = os.path.basename(argv[0])
|
||||
self.command = command = optionDashesRE.sub("", argv[1])
|
||||
if command == 'test':
|
||||
self.testOpts = argv[2:]
|
||||
else:
|
||||
self.parseOpts(argv[2:])
|
||||
except IndexError:
|
||||
usage(HELP_PAGE1, "not enough command-line arguments")
|
||||
|
||||
# Step 2: Call the command
|
||||
meths = (self.compile, self.fill, self.help, self.options,
|
||||
self.test, self.version)
|
||||
for meth in meths:
|
||||
methName = meth.__name__
|
||||
# Or meth.im_func.func_name
|
||||
# Or meth.func_name (Python >= 2.1 only, sometimes works on 2.0)
|
||||
methInitial = methName[0]
|
||||
if command in (methName, methInitial):
|
||||
sys.argv[0] += (" " + methName)
|
||||
# @@MO: I don't necessarily agree sys.argv[0] should be
|
||||
# modified.
|
||||
meth()
|
||||
return
|
||||
# If none of the commands matched.
|
||||
usage(HELP_PAGE1, "unknown command '%s'" % command)
|
||||
|
||||
def parseOpts(self, args):
|
||||
C, D, W = self.chatter, self.debug, self.warn
|
||||
self.isCompile = isCompile = self.command[0] == 'c'
|
||||
defaultOext = isCompile and ".py" or ".html"
|
||||
self.parser = OptionParser()
|
||||
pao = self.parser.add_option
|
||||
pao("--idir", action="store", dest="idir", default='', help='Input directory (defaults to current directory)')
|
||||
pao("--odir", action="store", dest="odir", default="", help='Output directory (defaults to current directory)')
|
||||
pao("--iext", action="store", dest="iext", default=".tmpl", help='File input extension (defaults: compile: .tmpl, fill: .tmpl)')
|
||||
pao("--oext", action="store", dest="oext", default=defaultOext, help='File output extension (defaults: compile: .py, fill: .html)')
|
||||
pao("-R", action="store_true", dest="recurse", default=False, help='Recurse through subdirectories looking for input files')
|
||||
pao("--stdout", "-p", action="store_true", dest="stdout", default=False, help='Send output to stdout instead of writing to a file')
|
||||
pao("--quiet", action="store_false", dest="verbose", default=True, help='Do not print informational messages to stdout')
|
||||
pao("--debug", action="store_true", dest="debug", default=False, help='Print diagnostic/debug information to stderr')
|
||||
pao("--env", action="store_true", dest="env", default=False, help='Pass the environment into the search list')
|
||||
pao("--pickle", action="store", dest="pickle", default="", help='Unpickle FILE and pass it through in the search list')
|
||||
pao("--flat", action="store_true", dest="flat", default=False, help='Do not build destination subdirectories')
|
||||
pao("--nobackup", action="store_true", dest="nobackup", default=False, help='Do not make backup files when generating new ones')
|
||||
pao("--settings", action="store", dest="compilerSettingsString", default=None, help='String of compiler settings to pass through, e.g. --settings="useNameMapper=False,useFilters=False"')
|
||||
pao('--print-settings', action='store_true', dest='print_settings', help='Print out the list of available compiler settings')
|
||||
pao("--templateAPIClass", action="store", dest="templateClassName", default=None, help='Name of a subclass of Cheetah.Template.Template to use for compilation, e.g. MyTemplateClass')
|
||||
pao("--parallel", action="store", type="int", dest="parallel", default=1, help='Compile/fill templates in parallel, e.g. --parallel=4')
|
||||
pao('--shbang', dest='shbang', default='#!/usr/bin/env python', help='Specify the shbang to place at the top of compiled templates, e.g. --shbang="#!/usr/bin/python2.6"')
|
||||
|
||||
opts, files = self.parser.parse_args(args)
|
||||
self.opts = opts
|
||||
if sys.platform == "win32":
|
||||
new_files = []
|
||||
for spec in files:
|
||||
file_list = glob.glob(spec)
|
||||
if file_list:
|
||||
new_files.extend(file_list)
|
||||
else:
|
||||
new_files.append(spec)
|
||||
files = new_files
|
||||
self.pathArgs = files
|
||||
|
||||
D("""\
|
||||
cheetah compile %s
|
||||
Options are
|
||||
%s
|
||||
Files are %s""", args, pprint.pformat(vars(opts)), files)
|
||||
|
||||
|
||||
if opts.print_settings:
|
||||
print()
|
||||
print('>> Available Cheetah compiler settings:')
|
||||
from Cheetah.Compiler import _DEFAULT_COMPILER_SETTINGS
|
||||
listing = _DEFAULT_COMPILER_SETTINGS
|
||||
listing.sort(key=lambda l: l[0][0].lower())
|
||||
|
||||
for l in listing:
|
||||
print('\t%s (default: "%s")\t%s' % l)
|
||||
sys.exit(0)
|
||||
|
||||
#cleanup trailing path separators
|
||||
seps = [sep for sep in [os.sep, os.altsep] if sep]
|
||||
for attr in ['idir', 'odir']:
|
||||
for sep in seps:
|
||||
path = getattr(opts, attr, None)
|
||||
if path and path.endswith(sep):
|
||||
path = path[:-len(sep)]
|
||||
setattr(opts, attr, path)
|
||||
break
|
||||
|
||||
self._fixExts()
|
||||
if opts.env:
|
||||
self.searchList.insert(0, os.environ)
|
||||
if opts.pickle:
|
||||
f = open(opts.pickle, 'rb')
|
||||
unpickled = pickle.load(f)
|
||||
f.close()
|
||||
self.searchList.insert(0, unpickled)
|
||||
|
||||
##################################################
|
||||
## COMMAND METHODS
|
||||
|
||||
def compile(self):
|
||||
self._compileOrFill()
|
||||
|
||||
def fill(self):
|
||||
from Cheetah.ImportHooks import install
|
||||
install()
|
||||
self._compileOrFill()
|
||||
|
||||
def help(self):
|
||||
usage(HELP_PAGE1, "", sys.stdout)
|
||||
|
||||
def options(self):
|
||||
return self.parser.print_help()
|
||||
|
||||
def test(self):
|
||||
# @@MO: Ugly kludge.
|
||||
TEST_WRITE_FILENAME = 'cheetah_test_file_creation_ability.tmp'
|
||||
try:
|
||||
f = open(TEST_WRITE_FILENAME, 'w')
|
||||
except:
|
||||
sys.exit("""\
|
||||
Cannot run the tests because you don't have write permission in the current
|
||||
directory. The tests need to create temporary files. Change to a directory
|
||||
you do have write permission to and re-run the tests.""")
|
||||
else:
|
||||
f.close()
|
||||
os.remove(TEST_WRITE_FILENAME)
|
||||
# @@MO: End ugly kludge.
|
||||
from Cheetah.Tests import Test
|
||||
import unittest
|
||||
verbosity = 1
|
||||
if '-q' in self.testOpts:
|
||||
verbosity = 0
|
||||
if '-v' in self.testOpts:
|
||||
verbosity = 2
|
||||
runner = unittest.TextTestRunner(verbosity=verbosity)
|
||||
runner.run(unittest.TestSuite(Test.suites))
|
||||
|
||||
def version(self):
|
||||
print(Version)
|
||||
|
||||
# If you add a command, also add it to the 'meths' variable in main().
|
||||
|
||||
##################################################
|
||||
## LOGGING METHODS
|
||||
|
||||
def chatter(self, format, *args):
|
||||
"""Print a verbose message to stdout. But don't if .opts.stdout is
|
||||
true or .opts.verbose is false.
|
||||
"""
|
||||
if self.opts.stdout or not self.opts.verbose:
|
||||
return
|
||||
fprintfMessage(sys.stdout, format, *args)
|
||||
|
||||
|
||||
def debug(self, format, *args):
|
||||
"""Print a debugging message to stderr, but don't if .debug is
|
||||
false.
|
||||
"""
|
||||
if self.opts.debug:
|
||||
fprintfMessage(sys.stderr, format, *args)
|
||||
|
||||
def warn(self, format, *args):
|
||||
"""Always print a warning message to stderr.
|
||||
"""
|
||||
fprintfMessage(sys.stderr, format, *args)
|
||||
|
||||
def error(self, format, *args):
|
||||
"""Always print a warning message to stderr and exit with an error code.
|
||||
"""
|
||||
fprintfMessage(sys.stderr, format, *args)
|
||||
sys.exit(1)
|
||||
|
||||
##################################################
|
||||
## HELPER METHODS
|
||||
|
||||
|
||||
def _fixExts(self):
|
||||
assert self.opts.oext, "oext is empty!"
|
||||
iext, oext = self.opts.iext, self.opts.oext
|
||||
if iext and not iext.startswith("."):
|
||||
self.opts.iext = "." + iext
|
||||
if oext and not oext.startswith("."):
|
||||
self.opts.oext = "." + oext
|
||||
|
||||
|
||||
|
||||
def _compileOrFill(self):
|
||||
C, D, W = self.chatter, self.debug, self.warn
|
||||
opts, files = self.opts, self.pathArgs
|
||||
if files == ["-"]:
|
||||
self._compileOrFillStdin()
|
||||
return
|
||||
elif not files and opts.recurse:
|
||||
which = opts.idir and "idir" or "current"
|
||||
C("Drilling down recursively from %s directory.", which)
|
||||
sourceFiles = []
|
||||
dir = os.path.join(self.opts.idir, os.curdir)
|
||||
os.path.walk(dir, self._expandSourceFilesWalk, sourceFiles)
|
||||
elif not files:
|
||||
usage(HELP_PAGE1, "Neither files nor -R specified!")
|
||||
else:
|
||||
sourceFiles = self._expandSourceFiles(files, opts.recurse, True)
|
||||
sourceFiles = [os.path.normpath(x) for x in sourceFiles]
|
||||
D("All source files found: %s", sourceFiles)
|
||||
bundles = self._getBundles(sourceFiles)
|
||||
D("All bundles: %s", pprint.pformat(bundles))
|
||||
if self.opts.flat:
|
||||
self._checkForCollisions(bundles)
|
||||
|
||||
# In parallel mode a new process is forked for each template
|
||||
# compilation, out of a pool of size self.opts.parallel. This is not
|
||||
# really optimal in all cases (e.g. probably wasteful for small
|
||||
# templates), but seems to work well in real life for me.
|
||||
#
|
||||
# It also won't work for Windows users, but I'm not going to lose any
|
||||
# sleep over that.
|
||||
if self.opts.parallel > 1:
|
||||
bad_child_exit = 0
|
||||
pid_pool = set()
|
||||
|
||||
def child_wait():
|
||||
pid, status = os.wait()
|
||||
pid_pool.remove(pid)
|
||||
return os.WEXITSTATUS(status)
|
||||
|
||||
while bundles:
|
||||
b = bundles.pop()
|
||||
pid = os.fork()
|
||||
if pid:
|
||||
pid_pool.add(pid)
|
||||
else:
|
||||
self._compileOrFillBundle(b)
|
||||
sys.exit(0)
|
||||
|
||||
if len(pid_pool) == self.opts.parallel:
|
||||
bad_child_exit = child_wait()
|
||||
if bad_child_exit:
|
||||
break
|
||||
|
||||
while pid_pool:
|
||||
child_exit = child_wait()
|
||||
if not bad_child_exit:
|
||||
bad_child_exit = child_exit
|
||||
|
||||
if bad_child_exit:
|
||||
sys.exit("Child process failed, exited with code %d" % bad_child_exit)
|
||||
|
||||
else:
|
||||
for b in bundles:
|
||||
self._compileOrFillBundle(b)
|
||||
|
||||
def _checkForCollisions(self, bundles):
|
||||
"""Check for multiple source paths writing to the same destination
|
||||
path.
|
||||
"""
|
||||
C, D, W = self.chatter, self.debug, self.warn
|
||||
isError = False
|
||||
dstSources = {}
|
||||
for b in bundles:
|
||||
if b.dst in dstSources:
|
||||
dstSources[b.dst].append(b.src)
|
||||
else:
|
||||
dstSources[b.dst] = [b.src]
|
||||
keys = sorted(dstSources.keys())
|
||||
for dst in keys:
|
||||
sources = dstSources[dst]
|
||||
if len(sources) > 1:
|
||||
isError = True
|
||||
sources.sort()
|
||||
fmt = "Collision: multiple source files %s map to one destination file %s"
|
||||
W(fmt, sources, dst)
|
||||
if isError:
|
||||
what = self.isCompile and "Compilation" or "Filling"
|
||||
sys.exit("%s aborted due to collisions" % what)
|
||||
|
||||
|
||||
def _expandSourceFilesWalk(self, arg, dir, files):
|
||||
"""Recursion extension for .expandSourceFiles().
|
||||
This method is a callback for os.path.walk().
|
||||
'arg' is a list to which successful paths will be appended.
|
||||
"""
|
||||
iext = self.opts.iext
|
||||
for f in files:
|
||||
path = os.path.join(dir, f)
|
||||
if path.endswith(iext) and os.path.isfile(path):
|
||||
arg.append(path)
|
||||
elif os.path.islink(path) and os.path.isdir(path):
|
||||
os.path.walk(path, self._expandSourceFilesWalk, arg)
|
||||
# If is directory, do nothing; 'walk' will eventually get it.
|
||||
|
||||
|
||||
def _expandSourceFiles(self, files, recurse, addIextIfMissing):
|
||||
"""Calculate source paths from 'files' by applying the
|
||||
command-line options.
|
||||
"""
|
||||
C, D, W = self.chatter, self.debug, self.warn
|
||||
idir = self.opts.idir
|
||||
iext = self.opts.iext
|
||||
files = []
|
||||
for f in self.pathArgs:
|
||||
oldFilesLen = len(files)
|
||||
D("Expanding %s", f)
|
||||
path = os.path.join(idir, f)
|
||||
pathWithExt = path + iext # May or may not be valid.
|
||||
if os.path.isdir(path):
|
||||
if recurse:
|
||||
os.path.walk(path, self._expandSourceFilesWalk, files)
|
||||
else:
|
||||
raise Error("source file '%s' is a directory" % path)
|
||||
elif os.path.isfile(path):
|
||||
files.append(path)
|
||||
elif (addIextIfMissing and not path.endswith(iext) and
|
||||
os.path.isfile(pathWithExt)):
|
||||
files.append(pathWithExt)
|
||||
# Do not recurse directories discovered by iext appending.
|
||||
elif os.path.exists(path):
|
||||
W("Skipping source file '%s', not a plain file.", path)
|
||||
else:
|
||||
W("Skipping source file '%s', not found.", path)
|
||||
if len(files) > oldFilesLen:
|
||||
D(" ... found %s", files[oldFilesLen:])
|
||||
return files
|
||||
|
||||
|
||||
def _getBundles(self, sourceFiles):
|
||||
flat = self.opts.flat
|
||||
idir = self.opts.idir
|
||||
iext = self.opts.iext
|
||||
nobackup = self.opts.nobackup
|
||||
odir = self.opts.odir
|
||||
oext = self.opts.oext
|
||||
idirSlash = idir + os.sep
|
||||
bundles = []
|
||||
for src in sourceFiles:
|
||||
# 'base' is the subdirectory plus basename.
|
||||
base = src
|
||||
if idir and src.startswith(idirSlash):
|
||||
base = src[len(idirSlash):]
|
||||
if iext and base.endswith(iext):
|
||||
base = base[:-len(iext)]
|
||||
basename = os.path.basename(base)
|
||||
if flat:
|
||||
dst = os.path.join(odir, basename + oext)
|
||||
else:
|
||||
dbn = basename
|
||||
if odir and base.startswith(os.sep):
|
||||
odd = odir
|
||||
while odd != '':
|
||||
idx = base.find(odd)
|
||||
if idx == 0:
|
||||
dbn = base[len(odd):]
|
||||
if dbn[0] == '/':
|
||||
dbn = dbn[1:]
|
||||
break
|
||||
odd = os.path.dirname(odd)
|
||||
if odd == '/':
|
||||
break
|
||||
dst = os.path.join(odir, dbn + oext)
|
||||
else:
|
||||
dst = os.path.join(odir, base + oext)
|
||||
bak = dst + self.BACKUP_SUFFIX
|
||||
b = Bundle(src=src, dst=dst, bak=bak, base=base, basename=basename)
|
||||
bundles.append(b)
|
||||
return bundles
|
||||
|
||||
|
||||
def _getTemplateClass(self):
|
||||
C, D, W = self.chatter, self.debug, self.warn
|
||||
modname = None
|
||||
if self._templateClass:
|
||||
return self._templateClass
|
||||
|
||||
modname = self.opts.templateClassName
|
||||
|
||||
if not modname:
|
||||
return Template
|
||||
p = modname.rfind('.')
|
||||
if ':' not in modname:
|
||||
self.error('The value of option --templateAPIClass is invalid\n'
|
||||
'It must be in the form "module:class", '
|
||||
'e.g. "Cheetah.Template:Template"')
|
||||
|
||||
modname, classname = modname.split(':')
|
||||
|
||||
C('using --templateAPIClass=%s:%s'%(modname, classname))
|
||||
|
||||
if p >= 0:
|
||||
mod = getattr(__import__(modname[:p], {}, {}, [modname[p+1:]]), modname[p+1:])
|
||||
else:
|
||||
mod = __import__(modname, {}, {}, [])
|
||||
|
||||
klass = getattr(mod, classname, None)
|
||||
if klass:
|
||||
self._templateClass = klass
|
||||
return klass
|
||||
else:
|
||||
self.error('**Template class specified in option --templateAPIClass not found\n'
|
||||
'**Falling back on Cheetah.Template:Template')
|
||||
|
||||
|
||||
def _getCompilerSettings(self):
|
||||
if self._compilerSettings:
|
||||
return self._compilerSettings
|
||||
|
||||
def getkws(**kws):
|
||||
return kws
|
||||
if self.opts.compilerSettingsString:
|
||||
try:
|
||||
exec('settings = getkws(%s)'%self.opts.compilerSettingsString)
|
||||
except:
|
||||
self.error("There's an error in your --settings option."
|
||||
"It must be valid Python syntax.\n"
|
||||
+" --settings='%s'\n"%self.opts.compilerSettingsString
|
||||
+" %s: %s"%sys.exc_info()[:2]
|
||||
)
|
||||
|
||||
validKeys = DEFAULT_COMPILER_SETTINGS.keys()
|
||||
if [k for k in settings.keys() if k not in validKeys]:
|
||||
self.error(
|
||||
'The --setting "%s" is not a valid compiler setting name.'%k)
|
||||
|
||||
self._compilerSettings = settings
|
||||
return settings
|
||||
else:
|
||||
return {}
|
||||
|
||||
def _compileOrFillStdin(self):
|
||||
TemplateClass = self._getTemplateClass()
|
||||
compilerSettings = self._getCompilerSettings()
|
||||
if self.isCompile:
|
||||
pysrc = TemplateClass.compile(file=sys.stdin,
|
||||
compilerSettings=compilerSettings,
|
||||
returnAClass=False)
|
||||
output = pysrc
|
||||
else:
|
||||
output = str(TemplateClass(file=sys.stdin, compilerSettings=compilerSettings))
|
||||
sys.stdout.write(output)
|
||||
|
||||
def _compileOrFillBundle(self, b):
|
||||
C, D, W = self.chatter, self.debug, self.warn
|
||||
TemplateClass = self._getTemplateClass()
|
||||
compilerSettings = self._getCompilerSettings()
|
||||
src = b.src
|
||||
dst = b.dst
|
||||
base = b.base
|
||||
basename = b.basename
|
||||
dstDir = os.path.dirname(dst)
|
||||
what = self.isCompile and "Compiling" or "Filling"
|
||||
C("%s %s -> %s^", what, src, dst) # No trailing newline.
|
||||
if os.path.exists(dst) and not self.opts.nobackup:
|
||||
bak = b.bak
|
||||
C(" (backup %s)", bak) # On same line as previous message.
|
||||
else:
|
||||
bak = None
|
||||
C("")
|
||||
if self.isCompile:
|
||||
if not moduleNameRE.match(basename):
|
||||
tup = basename, src
|
||||
raise Error("""\
|
||||
%s: base name %s contains invalid characters. It must
|
||||
be named according to the same rules as Python modules.""" % tup)
|
||||
pysrc = TemplateClass.compile(file=src, returnAClass=False,
|
||||
moduleName=basename,
|
||||
className=basename,
|
||||
commandlineopts=self.opts,
|
||||
compilerSettings=compilerSettings)
|
||||
output = pysrc
|
||||
else:
|
||||
#output = str(TemplateClass(file=src, searchList=self.searchList))
|
||||
tclass = TemplateClass.compile(file=src, compilerSettings=compilerSettings)
|
||||
output = str(tclass(searchList=self.searchList))
|
||||
|
||||
if bak:
|
||||
shutil.copyfile(dst, bak)
|
||||
if dstDir and not os.path.exists(dstDir):
|
||||
if self.isCompile:
|
||||
mkdirsWithPyInitFiles(dstDir)
|
||||
else:
|
||||
os.makedirs(dstDir)
|
||||
if self.opts.stdout:
|
||||
sys.stdout.write(output)
|
||||
else:
|
||||
f = open(dst, 'w')
|
||||
f.write(output)
|
||||
f.close()
|
||||
|
||||
|
||||
# Called when invoked as `cheetah`
|
||||
def _cheetah():
|
||||
CheetahWrapper().main()
|
||||
|
||||
# Called when invoked as `cheetah-compile`
|
||||
def _cheetah_compile():
|
||||
sys.argv.insert(1, "compile")
|
||||
CheetahWrapper().main()
|
||||
|
||||
|
||||
##################################################
|
||||
## if run from the command line
|
||||
if __name__ == '__main__': CheetahWrapper().main()
|
||||
|
||||
# vim: shiftwidth=4 tabstop=4 expandtab
|
File diff suppressed because it is too large
Load Diff
@ -1,98 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import pprint
|
||||
|
||||
try:
|
||||
from functools import reduce
|
||||
except ImportError:
|
||||
# Assume we have reduce
|
||||
pass
|
||||
|
||||
from Cheetah import Parser
|
||||
from Cheetah import Compiler
|
||||
from Cheetah import Template
|
||||
|
||||
class Analyzer(Parser.Parser):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.calls = {}
|
||||
super(Analyzer, self).__init__(*args, **kwargs)
|
||||
|
||||
def eatDirective(self):
|
||||
directive = self.matchDirective()
|
||||
try:
|
||||
self.calls[directive] += 1
|
||||
except KeyError:
|
||||
self.calls[directive] = 1
|
||||
super(Analyzer, self).eatDirective()
|
||||
|
||||
class AnalysisCompiler(Compiler.ModuleCompiler):
|
||||
parserClass = Analyzer
|
||||
|
||||
|
||||
def analyze(source):
|
||||
klass = Template.Template.compile(source, compilerClass=AnalysisCompiler)
|
||||
return klass._CHEETAH_compilerInstance._parser.calls
|
||||
|
||||
def main_file(f):
|
||||
fd = open(f, 'r')
|
||||
try:
|
||||
print u'>>> Analyzing %s' % f
|
||||
calls = analyze(fd.read())
|
||||
return calls
|
||||
finally:
|
||||
fd.close()
|
||||
|
||||
|
||||
def _find_templates(directory, suffix):
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for f in files:
|
||||
if not f.endswith(suffix):
|
||||
continue
|
||||
yield root + os.path.sep + f
|
||||
|
||||
def _analyze_templates(iterable):
|
||||
for template in iterable:
|
||||
yield main_file(template)
|
||||
|
||||
def main_dir(opts):
|
||||
results = _analyze_templates(_find_templates(opts.dir, opts.suffix))
|
||||
totals = {}
|
||||
for series in results:
|
||||
if not series:
|
||||
continue
|
||||
for k, v in series.iteritems():
|
||||
try:
|
||||
totals[k] += v
|
||||
except KeyError:
|
||||
totals[k] = v
|
||||
return totals
|
||||
|
||||
|
||||
def main():
|
||||
from optparse import OptionParser
|
||||
op = OptionParser()
|
||||
op.add_option('-f', '--file', dest='file', default=None,
|
||||
help='Specify a single file to analyze')
|
||||
op.add_option('-d', '--dir', dest='dir', default=None,
|
||||
help='Specify a directory of templates to analyze')
|
||||
op.add_option('--suffix', default='tmpl', dest='suffix',
|
||||
help='Specify a custom template file suffix for the -d option (default: "tmpl")')
|
||||
opts, args = op.parse_args()
|
||||
|
||||
if not opts.file and not opts.dir:
|
||||
op.print_help()
|
||||
return
|
||||
|
||||
results = None
|
||||
if opts.file:
|
||||
results = main_file(opts.file)
|
||||
if opts.dir:
|
||||
results = main_dir(opts)
|
||||
|
||||
pprint.pprint(results)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
@ -1,16 +0,0 @@
|
||||
import Cheetah.Template
|
||||
|
||||
def render(template_file, **kwargs):
|
||||
'''
|
||||
Cheetah.Django.render() takes the template filename
|
||||
(the filename should be a file in your Django
|
||||
TEMPLATE_DIRS)
|
||||
|
||||
Any additional keyword arguments are passed into the
|
||||
template are propogated into the template's searchList
|
||||
'''
|
||||
import django.http
|
||||
import django.template.loader
|
||||
source, loader = django.template.loader.find_template_source(template_file)
|
||||
t = Cheetah.Template.Template(source, searchList=[kwargs])
|
||||
return django.http.HttpResponse(t.__str__())
|
@ -1,108 +0,0 @@
|
||||
|
||||
'''
|
||||
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.
|
||||
|
||||
Warning: This may be deprecated in the future, please do not rely on any
|
||||
specific DummyTransaction or DummyResponse behavior
|
||||
'''
|
||||
|
||||
import logging
|
||||
import types
|
||||
|
||||
class DummyResponseFailure(Exception):
|
||||
pass
|
||||
|
||||
class DummyResponse(object):
|
||||
'''
|
||||
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 = []
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def safeConvert(self, chunk):
|
||||
# Exceptionally gross, but the safest way
|
||||
# I've found to ensure I get a legit unicode object
|
||||
if not chunk:
|
||||
return u''
|
||||
if isinstance(chunk, unicode):
|
||||
return chunk
|
||||
try:
|
||||
return chunk.decode('utf-8', 'strict')
|
||||
except UnicodeDecodeError:
|
||||
try:
|
||||
return chunk.decode('latin-1', 'strict')
|
||||
except UnicodeDecodeError:
|
||||
return chunk.decode('ascii', 'ignore')
|
||||
except AttributeError:
|
||||
return unicode(chunk, errors='ignore')
|
||||
return chunk
|
||||
|
||||
def write(self, value):
|
||||
self._outputChunks.append(value)
|
||||
|
||||
def writeln(self, txt):
|
||||
write(txt)
|
||||
write('\n')
|
||||
|
||||
def getvalue(self, outputChunks=None):
|
||||
chunks = outputChunks or self._outputChunks
|
||||
try:
|
||||
return u''.join(chunks)
|
||||
except UnicodeDecodeError, ex:
|
||||
logging.debug('Trying to work around a UnicodeDecodeError in getvalue()')
|
||||
logging.debug('...perhaps you could fix "%s" while you\'re debugging')
|
||||
return ''.join((self.safeConvert(c) for c in chunks))
|
||||
|
||||
def writelines(self, *lines):
|
||||
## not used
|
||||
[self.writeln(ln) for ln in lines]
|
||||
|
||||
|
||||
class DummyTransaction(object):
|
||||
'''
|
||||
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, *args, **kwargs):
|
||||
self._response = None
|
||||
|
||||
def response(self, resp=None):
|
||||
if self._response is None:
|
||||
self._response = resp or DummyResponse()
|
||||
return self._response
|
||||
|
||||
|
||||
class TransformerResponse(DummyResponse):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TransformerResponse, self).__init__(*args, **kwargs)
|
||||
self._filter = None
|
||||
|
||||
def getvalue(self, **kwargs):
|
||||
output = super(TransformerResponse, self).getvalue(**kwargs)
|
||||
if self._filter:
|
||||
_filter = self._filter
|
||||
if isinstance(_filter, type):
|
||||
_filter = _filter()
|
||||
return _filter.filter(output)
|
||||
return output
|
||||
|
||||
|
||||
class TransformerTransaction(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._response = None
|
||||
def response(self):
|
||||
if self._response:
|
||||
return self._response
|
||||
return TransformerResponse()
|
||||
|
@ -1,62 +0,0 @@
|
||||
# $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 + "<" + rawCode + " could not be found>" + "="*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
|
||||
|
||||
|
@ -1,357 +0,0 @@
|
||||
|
||||
from glob import glob
|
||||
import os
|
||||
from os import listdir
|
||||
import os.path
|
||||
import re
|
||||
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 isinstance(patternOrRE, basestring):
|
||||
self._regex = re.compile(patternOrRE)
|
||||
else:
|
||||
self._regex = patternOrRE
|
||||
if isinstance(replacement, basestring):
|
||||
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 'orig' in locals():
|
||||
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 'orig' not in locals():
|
||||
orig = open(file).read()
|
||||
new = regex.sub(subber, orig)
|
||||
open(file, 'w').write(new)
|
||||
|
||||
def _subDispatcher(self, match):
|
||||
if self._recordResults:
|
||||
if self._currFile not in self._results:
|
||||
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
|
@ -1,212 +0,0 @@
|
||||
'''
|
||||
Filters for the #filter directive as well as #transform
|
||||
|
||||
#filter results in output filters Cheetah's $placeholders .
|
||||
#transform results in a filter on the entirety of the output
|
||||
'''
|
||||
import sys
|
||||
|
||||
# Additional entities WebSafe knows how to transform. No need to include
|
||||
# '<', '>' or '&' since those will have been done already.
|
||||
webSafeEntities = {' ': ' ', '"': '"'}
|
||||
|
||||
class Filter(object):
|
||||
"""A baseclass for the Cheetah Filters."""
|
||||
|
||||
def __init__(self, template=None):
|
||||
"""Setup a reference to the template that is using the filter instance.
|
||||
This reference isn't used by any of the standard filters, but is
|
||||
available to Filter subclasses, should they need it.
|
||||
|
||||
Subclasses should call this method.
|
||||
"""
|
||||
self.template = template
|
||||
|
||||
def filter(self, val, encoding=None, str=str, **kw):
|
||||
'''
|
||||
Pass Unicode strings through unmolested, unless an encoding is specified.
|
||||
'''
|
||||
if val is None:
|
||||
return u''
|
||||
if isinstance(val, unicode):
|
||||
# ignore the encoding and return the unicode object
|
||||
return val
|
||||
else:
|
||||
try:
|
||||
return unicode(val)
|
||||
except UnicodeDecodeError:
|
||||
# we could put more fallbacks here, but we'll just pass the str
|
||||
# on and let DummyTransaction worry about it
|
||||
return str(val)
|
||||
|
||||
RawOrEncodedUnicode = Filter
|
||||
|
||||
EncodeUnicode = Filter
|
||||
|
||||
class Markdown(EncodeUnicode):
|
||||
'''
|
||||
Markdown will change regular strings to Markdown
|
||||
(http://daringfireball.net/projects/markdown/)
|
||||
|
||||
Such that:
|
||||
My Header
|
||||
=========
|
||||
Becaomes:
|
||||
<h1>My Header</h1>
|
||||
|
||||
and so on.
|
||||
|
||||
Markdown is meant to be used with the #transform
|
||||
tag, as it's usefulness with #filter is marginal at
|
||||
best
|
||||
'''
|
||||
def filter(self, value, **kwargs):
|
||||
# This is a bit of a hack to allow outright embedding of the markdown module
|
||||
try:
|
||||
import markdown
|
||||
except ImportError:
|
||||
print('>>> Exception raised importing the "markdown" module')
|
||||
print('>>> Are you sure you have the ElementTree module installed?')
|
||||
print(' http://effbot.org/downloads/#elementtree')
|
||||
raise
|
||||
|
||||
encoded = super(Markdown, self).filter(value, **kwargs)
|
||||
return markdown.markdown(encoded)
|
||||
|
||||
class CodeHighlighter(EncodeUnicode):
|
||||
'''
|
||||
The CodeHighlighter filter depends on the "pygments" module which you can
|
||||
download and install from: http://pygments.org
|
||||
|
||||
What the CodeHighlighter assumes the string that it's receiving is source
|
||||
code and uses pygments.lexers.guess_lexer() to try to guess which parser
|
||||
to use when highlighting it.
|
||||
|
||||
CodeHighlighter will return the HTML and CSS to render the code block, syntax
|
||||
highlighted, in a browser
|
||||
|
||||
NOTE: I had an issue installing pygments on Linux/amd64/Python 2.6 dealing with
|
||||
importing of pygments.lexers, I was able to correct the failure by adding:
|
||||
raise ImportError
|
||||
to line 39 of pygments/plugin.py (since importing pkg_resources was causing issues)
|
||||
'''
|
||||
def filter(self, source, **kwargs):
|
||||
encoded = super(CodeHighlighter, self).filter(source, **kwargs)
|
||||
try:
|
||||
from pygments import highlight
|
||||
from pygments import lexers
|
||||
from pygments import formatters
|
||||
except ImportError, ex:
|
||||
print('<%s> - Failed to import pygments! (%s)' % (self.__class__.__name__, ex))
|
||||
print('-- You may need to install it from: http://pygments.org')
|
||||
return encoded
|
||||
|
||||
lexer = None
|
||||
try:
|
||||
lexer = lexers.guess_lexer(source)
|
||||
except lexers.ClassNotFound:
|
||||
lexer = lexers.PythonLexer()
|
||||
|
||||
formatter = formatters.HtmlFormatter(cssclass='code_highlighter')
|
||||
encoded = highlight(encoded, lexer, formatter)
|
||||
css = formatter.get_style_defs('.code_highlighter')
|
||||
return '''<style type="text/css"><!--
|
||||
%(css)s
|
||||
--></style>%(source)s''' % {'css' : css, 'source' : encoded}
|
||||
|
||||
|
||||
|
||||
class MaxLen(Filter):
|
||||
def filter(self, val, **kw):
|
||||
"""Replace None with '' and cut off at maxlen."""
|
||||
|
||||
output = super(MaxLen, self).filter(val, **kw)
|
||||
if 'maxlen' in kw and len(output) > kw['maxlen']:
|
||||
return output[:kw['maxlen']]
|
||||
return output
|
||||
|
||||
class WebSafe(Filter):
|
||||
"""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("&", "&") # Must be done first!
|
||||
s = s.replace("<", "<")
|
||||
s = s.replace(">", ">")
|
||||
# Process the additional transformations if any.
|
||||
if 'also' in kw:
|
||||
also = kw['also']
|
||||
entities = webSafeEntities # Global variable.
|
||||
for k in also:
|
||||
if k in entities:
|
||||
v = entities[k]
|
||||
else:
|
||||
v = "&#%s;" % ord(k)
|
||||
s = s.replace(k, v)
|
||||
return s
|
||||
|
||||
|
||||
class Strip(Filter):
|
||||
"""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 True: # 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(Filter):
|
||||
"""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:", repr(s1))
|
||||
print(" WebSafe:", repr(WebSafe().filter(s1)))
|
||||
|
||||
print()
|
||||
print(" Strip INPUT:", repr(s2))
|
||||
print(" Strip:", repr(Strip().filter(s2)))
|
||||
print("StripSqueeze:", repr(StripSqueeze().filter(s2)))
|
||||
|
||||
print("Unicode:", repr(EncodeUnicode().filter(u'aoeu12345\u1234')))
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
||||
|
||||
# vim: shiftwidth=4 tabstop=4 expandtab
|
@ -1,129 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
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()
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
import types
|
||||
import __builtin__
|
||||
import imp
|
||||
from threading import RLock
|
||||
import string
|
||||
import traceback
|
||||
import types
|
||||
|
||||
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 = RLock()
|
||||
_acquireLock = _lock.acquire
|
||||
_releaseLock = _lock.release
|
||||
|
||||
templateFileExtensions = ('.tmpl',)
|
||||
|
||||
def getmod(self, name):
|
||||
self._acquireLock()
|
||||
try:
|
||||
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 = types.ModuleType(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 isinstance(__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 isinstance(__builtin__.__import__, types.MethodType):
|
||||
__builtin__.__import__ = __oldimport__
|
||||
global _manager
|
||||
del _manager
|
||||
|
||||
if __name__ == '__main__':
|
||||
install()
|
@ -1,541 +0,0 @@
|
||||
"""
|
||||
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
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import imp
|
||||
import marshal
|
||||
|
||||
_installed = False
|
||||
|
||||
# _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
|
||||
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 True:
|
||||
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 isinstance(thing, basestring):
|
||||
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, level=-1):
|
||||
'''
|
||||
NOTE: Currently importHook will accept the keyword-argument "level"
|
||||
but it will *NOT* use it (currently). Details about the "level" keyword
|
||||
argument can be found here: http://www.python.org/doc/2.5.2/lib/built-in-funcs.html
|
||||
'''
|
||||
# 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, 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,
|
||||
]
|
@ -1,16 +0,0 @@
|
||||
Copyright 2001-2005, The Cheetah Development Team: Tavis Rudd, Mike Orr,
|
||||
Chuck Esterbrook, Ian Bicking.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any purpose
|
||||
and without fee is hereby granted, provided that the above copyright notice
|
||||
appear in all copies and that both that copyright notice and this permission
|
||||
notice appear in supporting documentation, and that the names of the authors not
|
||||
be used in advertising or publicity pertaining to distribution of the software
|
||||
without specific, written prior permission.
|
||||
|
||||
THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS
|
||||
BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
|
||||
CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
|
||||
WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
@ -1,67 +0,0 @@
|
||||
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
|
||||
|
@ -1 +0,0 @@
|
||||
#
|
@ -1,366 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""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.
|
||||
"""
|
||||
|
||||
__author__ = "Tavis Rudd <tavis@damnsimple.com>," +\
|
||||
"\nChuck Esterbrook <echuck@mindspring.com>"
|
||||
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',
|
||||
]
|
||||
|
||||
if not hasattr(inspect.imp, 'get_suffixes'):
|
||||
# This is to fix broken behavior of the inspect module under the
|
||||
# Google App Engine, see the following issue:
|
||||
# http://bugs.communitycheetah.org/view.php?id=10
|
||||
setattr(inspect.imp, 'get_suffixes', lambda: [('.py', 'U', 1)])
|
||||
|
||||
## 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:
|
||||
excStr += ' in the namespace %s'%pformat(namespace)
|
||||
exc.args = (excStr,)
|
||||
raise
|
||||
|
||||
def _isInstanceOrClass(obj):
|
||||
if isinstance(obj, type):
|
||||
# oldstyle
|
||||
return True
|
||||
|
||||
if hasattr(obj, "__class__"):
|
||||
# newstyle
|
||||
if hasattr(obj, 'mro'):
|
||||
# type/class
|
||||
return True
|
||||
elif (hasattr(obj, 'im_func') or hasattr(obj, 'func_code') or hasattr(obj, '__self__')):
|
||||
# method, func, or builtin func
|
||||
return False
|
||||
elif hasattr(obj, '__init__'):
|
||||
# instance
|
||||
return True
|
||||
return False
|
||||
|
||||
def hasKey(obj, key):
|
||||
"""Determine if 'obj' has 'key' """
|
||||
if hasattr(obj, 'has_key') and key in obj:
|
||||
return True
|
||||
elif hasattr(obj, key):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def valueForKey(obj, key):
|
||||
if hasattr(obj, 'has_key') and key in obj:
|
||||
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 key in obj:
|
||||
nextObj = obj[key]
|
||||
else:
|
||||
try:
|
||||
nextObj = getattr(obj, key)
|
||||
except AttributeError:
|
||||
_raiseNotFoundException(key, obj)
|
||||
|
||||
if executeCallables and hasattr(nextObj, '__call__') and not _isInstanceOrClass(nextObj):
|
||||
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 Cheetah._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()
|
||||
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,51 +0,0 @@
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
Credits
|
||||
================================================================================
|
||||
http://cheetahtemplate.org/credits.html
|
||||
|
||||
Praise
|
||||
================================================================================
|
||||
"I\'m enamored with Cheetah" - Sam Ruby, senior member of IBM Emerging
|
||||
Technologies Group & director of Apache Software Foundation
|
||||
|
||||
"Give Cheetah a try. You won\'t regret it. ... Cheetah is a truly powerful
|
||||
system. ... Cheetah is a serious contender for the 'best of breed' Python
|
||||
templating." - Alex Martelli
|
||||
|
||||
"People with a strong PHP background absolutely love Cheetah for being Smarty,
|
||||
but much, much better." - Marek Baczynski
|
||||
|
||||
"I am using Smarty and I know it very well, but compiled Cheetah Templates with
|
||||
its inheritance approach is much powerful and easier to use than Smarty." -
|
||||
Jaroslaw Zabiello
|
||||
|
||||
"There is no better solution than Cheetah" - Wilk
|
||||
|
||||
"A cheetah template can inherit from a python class, or a cheetah template, and
|
||||
a Python class can inherit from a cheetah template. This brings the full power
|
||||
of OO programming facilities to the templating system, and simply blows away
|
||||
other templating systems" - Mike Meyer
|
||||
|
||||
"Cheetah has successfully been introduced as a replacement for the overweight
|
||||
XSL Templates for code generation. Despite the power of XSL (and notably XPath
|
||||
expressions), code generation is better suited to Cheetah as templates are much
|
||||
easier to implement and manage." - The FEAR development team
|
||||
(http://fear.sourceforge.net/docs/latest/guide/Build.html#id2550573)
|
||||
|
||||
"I\'ve used Cheetah quite a bit and it\'s a very good package" - Kevin Dangoor,
|
||||
lead developer of TurboGears.
|
@ -1,48 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
Provides an abstract Servlet baseclass for Cheetah's Template class
|
||||
'''
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
|
||||
class Servlet(object):
|
||||
"""
|
||||
This class is an abstract baseclass for Cheetah.Template.Template.
|
||||
"""
|
||||
|
||||
transaction = None
|
||||
application = None
|
||||
request = None
|
||||
session = None
|
||||
|
||||
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):
|
||||
super(Servlet, self).sleep(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 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
|
@ -1,284 +0,0 @@
|
||||
import sys
|
||||
import os.path
|
||||
import copy as copyModule
|
||||
from ConfigParser import ConfigParser
|
||||
import re
|
||||
from tokenize import Intnumber, Floatnumber, Number
|
||||
import types
|
||||
import time
|
||||
from StringIO import StringIO # not cStringIO because of unicode support
|
||||
import imp # used by SettingsManager.updateSettingsFromPySrcFile()
|
||||
|
||||
|
||||
numberRE = re.compile(Number)
|
||||
complexNumberRE = re.compile('[\(]*' +Number + r'[ \t]*\+[ \t]*' + Number + '[\)]*')
|
||||
|
||||
##################################################
|
||||
## 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.iteritems():
|
||||
if key in dict1 and isinstance(val, dict) and isinstance(dict1[key], dict):
|
||||
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, {}, {})
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
class NoDefault(object):
|
||||
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 _SettingsCollector(object):
|
||||
"""An abstract base class that provides the methods SettingsManager uses to
|
||||
collect settings from config files and strings.
|
||||
|
||||
This class only collects settings it doesn't modify the _settings dictionary
|
||||
of SettingsManager instances in any way.
|
||||
"""
|
||||
|
||||
_ConfigParserClass = ConfigParserCaseSensitive
|
||||
|
||||
def readSettingsFromModule(self, mod, ignoreUnderscored=True):
|
||||
"""Returns all settings from a Python module.
|
||||
"""
|
||||
S = {}
|
||||
attrs = vars(mod)
|
||||
for k, v in attrs.iteritems():
|
||||
if (ignoreUnderscored and k.startswith('_')):
|
||||
continue
|
||||
else:
|
||||
S[k] = v
|
||||
return S
|
||||
|
||||
def readSettingsFromPySrcStr(self, theString):
|
||||
"""Return a dictionary of the settings in a Python src string."""
|
||||
|
||||
globalsDict = {'True': (1==1),
|
||||
'False': (0==1),
|
||||
}
|
||||
newSettings = {'self':self}
|
||||
exec((theString+os.linesep), globalsDict, newSettings)
|
||||
del newSettings['self']
|
||||
module = types.ModuleType('temp_settings_module')
|
||||
module.__dict__.update(newSettings)
|
||||
return self.readSettingsFromModule(module)
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(SettingsManager, self).__init__()
|
||||
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 key in self._settings
|
||||
|
||||
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 copyModule.copy(self._settings)
|
||||
|
||||
def deepcopySettings(self):
|
||||
"""Returns a deep copy of the settings dictionary"""
|
||||
return copyModule.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 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))
|
||||
|
@ -1,267 +0,0 @@
|
||||
"""SourceReader class for Cheetah's Parser and CodeGenerator
|
||||
"""
|
||||
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(object):
|
||||
def __init__(self, src, filename=None, breakPoint=None, encoding=None):
|
||||
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):
|
||||
if not isinstance(i, int):
|
||||
self.checkPos(i.stop)
|
||||
else:
|
||||
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 name in self._bookmarks
|
||||
|
||||
def gotoBookmark(self, name):
|
||||
if not self.hasBookmark(name):
|
||||
raise Error("Invalid bookmark (" + name + ") 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()]
|
File diff suppressed because it is too large
Load Diff
@ -1,107 +0,0 @@
|
||||
# $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
|
@ -1,272 +0,0 @@
|
||||
|
||||
|
||||
"""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 hasattr(self.transaction, '__call__')):
|
||||
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 hasattr(self.transaction, '__call__')):
|
||||
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 hasattr(self.transaction, '__call__')):
|
||||
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()
|
||||
|
||||
|
@ -1,44 +0,0 @@
|
||||
##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>
|
||||
|
||||
|
||||
|
@ -1,215 +0,0 @@
|
||||
# $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 identifier not in self._stylesheets:
|
||||
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.iteritems():
|
||||
if not isinstance(details, (list, tuple)):
|
||||
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.iteritems():
|
||||
if not isinstance(details, (list, tuple)):
|
||||
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 'HTTP-EQUIV' in metaTags:
|
||||
for http_equiv, contents in metaTags['HTTP-EQUIV'].items():
|
||||
metaTagsTxt += ['<meta http-equiv="', str(http_equiv), '" content="',
|
||||
str(contents), '" />\n']
|
||||
|
||||
if 'NAME' in metaTags:
|
||||
for name, contents in metaTags['NAME'].items():
|
||||
metaTagsTxt += ['<meta name="', str(name), '" content="', str(contents),
|
||||
'" />\n']
|
||||
return ''.join(metaTagsTxt)
|
||||
|
@ -1 +0,0 @@
|
||||
|
@ -1,29 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import unittest
|
||||
|
||||
from Cheetah import DirectiveAnalyzer
|
||||
|
||||
|
||||
class AnalyzerTests(unittest.TestCase):
|
||||
def test_set(self):
|
||||
template = '''
|
||||
#set $foo = "bar"
|
||||
Hello ${foo}!
|
||||
'''
|
||||
calls = DirectiveAnalyzer.analyze(template)
|
||||
self.assertEquals(1, calls.get('set'))
|
||||
|
||||
def test_compilersettings(self):
|
||||
template = '''
|
||||
#compiler-settings
|
||||
useNameMapper = False
|
||||
#end compiler-settings
|
||||
'''
|
||||
calls = DirectiveAnalyzer.analyze(template)
|
||||
self.assertEquals(1, calls.get('compiler-settings'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -1,579 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
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.)
|
||||
'''
|
||||
import os
|
||||
import os.path
|
||||
import pdb
|
||||
import re # Used by listTests.
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from optparse import OptionParser
|
||||
from Cheetah.CheetahWrapper import CheetahWrapper # Used by NoBackup.
|
||||
|
||||
try:
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
class Popen4(Popen):
|
||||
def __init__(self, cmd, bufsize=-1, shell=True, close_fds=True,
|
||||
stdin=PIPE, stdout=PIPE, stderr=STDOUT, **kwargs):
|
||||
|
||||
super(Popen4, self).__init__(cmd, bufsize=bufsize, shell=shell,
|
||||
close_fds=close_fds, stdin=stdin, stdout=stdout,
|
||||
stderr=stderr, **kwargs)
|
||||
|
||||
self.tochild = self.stdin
|
||||
self.fromchild = self.stdout
|
||||
self.childerr = self.stderr
|
||||
except ImportError:
|
||||
from popen2 import Popen4
|
||||
|
||||
DELETE = True # True to clean up after ourselves, False for debugging.
|
||||
OUTPUT = False # Normally False, True for debugging.
|
||||
|
||||
BACKUP_SUFFIX = CheetahWrapper.BACKUP_SUFFIX
|
||||
|
||||
def warn(msg):
|
||||
sys.stderr.write(msg + '\n')
|
||||
|
||||
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.
|
||||
"""
|
||||
self.cmd = self.locate_cheetah('cheetah')
|
||||
pythonPath = os.getcwd()
|
||||
if not os.environ.get('PYTHONPATH'):
|
||||
os.environ['PYTHONPATH'] = pythonPath
|
||||
else:
|
||||
os.environ['PYTHONPATH'] = '%s:%s' % (os.environ['PYTHONPATH'], pythonPath)
|
||||
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 = "Hello, world!" # 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 locate_cheetah(self, cmd):
|
||||
paths = os.getenv('PATH')
|
||||
if not paths:
|
||||
return cmd
|
||||
parts = cmd.split(' ')
|
||||
paths = paths.split(':')
|
||||
for p in paths:
|
||||
p = os.path.join(p, cmd)
|
||||
p = os.path.abspath(p)
|
||||
if os.path.isfile(p):
|
||||
return p
|
||||
return cmd
|
||||
|
||||
def assertWin32Subprocess(self, cmd):
|
||||
_in, _out = os.popen4(cmd)
|
||||
_in.close()
|
||||
output = _out.read()
|
||||
rc = _out.close()
|
||||
if rc is None:
|
||||
rc = 0
|
||||
return rc, output
|
||||
|
||||
def assertPosixSubprocess(self, cmd):
|
||||
cmd = cmd.replace('cheetah', self.cmd)
|
||||
process = Popen4(cmd, env=os.environ)
|
||||
process.tochild.close()
|
||||
output = process.fromchild.read()
|
||||
status = process.wait()
|
||||
process.fromchild.close()
|
||||
return status, output
|
||||
|
||||
def assertSubprocess(self, cmd, nonzero=False):
|
||||
status, output = None, None
|
||||
if sys.platform == 'win32':
|
||||
status, output = self.assertWin32Subprocess(cmd)
|
||||
else:
|
||||
status, output = self.assertPosixSubprocess(cmd)
|
||||
|
||||
if not nonzero:
|
||||
self.failUnlessEqual(status, 0, '''Subprocess exited with a non-zero status (%d)
|
||||
%s''' % (status, output))
|
||||
else:
|
||||
self.failIfEqual(status, 0, '''Subprocess exited with a zero status (%d)
|
||||
%s''' % (status, output))
|
||||
return output
|
||||
|
||||
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.
|
||||
"""
|
||||
output = self.assertSubprocess(cmd)
|
||||
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)
|
||||
|
||||
|
||||
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.assertSubprocess("cheetah compile -R --flat", nonzero=True)
|
||||
|
||||
def testFill(self):
|
||||
self.assertSubprocess("cheetah fill -R --flat", nonzero=True)
|
||||
|
||||
def testText(self):
|
||||
self.assertSubprocess("cheetah fill -R --flat", nonzero=True)
|
||||
|
||||
|
||||
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.assertSubprocess("cheetah compile -R --flat --idir SRC", nonzero=True)
|
||||
|
||||
def testFill(self):
|
||||
self.assertSubprocess("cheetah fill -R --flat --idir SRC", nonzero=True)
|
||||
|
||||
def testText(self):
|
||||
self.assertSubprocess("cheetah fill -R --flat --idir SRC --oext txt", nonzero=True)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
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 True:
|
||||
lin = f.readline()
|
||||
if not lin:
|
||||
break
|
||||
m = rx.search(lin)
|
||||
if m:
|
||||
print(m.group(1))
|
||||
f.close()
|
||||
|
||||
def main():
|
||||
global DELETE, OUTPUT
|
||||
parser = OptionParser()
|
||||
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("-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", "verbose", "quiet"):
|
||||
if getattr(opts, opt):
|
||||
sys.argv.append("--" + opt)
|
||||
sys.argv.extend(files)
|
||||
unittest.main()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
# vim: sw=4 ts=4 expandtab
|
@ -1,39 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import unittest
|
||||
|
||||
import Cheetah
|
||||
import Cheetah.Parser
|
||||
import Cheetah.Template
|
||||
|
||||
class Chep_2_Conditionalized_Import_Behavior(unittest.TestCase):
|
||||
def test_ModuleLevelImport(self):
|
||||
''' Verify module level (traditional) import behavior '''
|
||||
pass
|
||||
|
||||
def test_InlineImport(self):
|
||||
''' Verify (new) inline import behavior works '''
|
||||
template = '''
|
||||
#def funky($s)
|
||||
#try
|
||||
#import urllib
|
||||
#except ImportError
|
||||
#pass
|
||||
#end try
|
||||
#return urllib.quote($s)
|
||||
#end def
|
||||
'''
|
||||
try:
|
||||
template = Cheetah.Template.Template.compile(template)
|
||||
except Cheetah.Parser.ParseError, ex:
|
||||
self.fail('Failed to properly generate code %s' % ex)
|
||||
template = template()
|
||||
rc = tepmlate.funky('abc def')
|
||||
assert rc == 'abc+def'
|
||||
|
||||
def test_LegacyMode(self):
|
||||
''' Verify disabling of CHEP #2 works '''
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -1,70 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import Cheetah.Template
|
||||
import Cheetah.Filters
|
||||
|
||||
majorVer, minorVer = sys.version_info[0], sys.version_info[1]
|
||||
versionTuple = (majorVer, minorVer)
|
||||
|
||||
class BasicMarkdownFilterTest(unittest.TestCase):
|
||||
'''
|
||||
Test that our markdown filter works
|
||||
'''
|
||||
def test_BasicHeader(self):
|
||||
template = '''
|
||||
#from Cheetah.Filters import Markdown
|
||||
#transform Markdown
|
||||
$foo
|
||||
|
||||
Header
|
||||
======
|
||||
'''
|
||||
expected = '''<p>bar</p>
|
||||
<h1>Header</h1>'''
|
||||
try:
|
||||
template = Cheetah.Template.Template(template, searchList=[{'foo' : 'bar'}])
|
||||
template = str(template)
|
||||
assert template == expected
|
||||
except ImportError, ex:
|
||||
print('>>> We probably failed to import markdown, bummer %s' % ex)
|
||||
return
|
||||
except Exception, ex:
|
||||
if ex.__class__.__name__ == 'MarkdownException' and majorVer == 2 and minorVer < 5:
|
||||
print('>>> NOTE: Support for the Markdown filter will be broken for you. Markdown says: %s' % ex)
|
||||
return
|
||||
raise
|
||||
|
||||
|
||||
class BasicCodeHighlighterFilterTest(unittest.TestCase):
|
||||
'''
|
||||
Test that our code highlighter filter works
|
||||
'''
|
||||
def test_Python(self):
|
||||
template = '''
|
||||
#from Cheetah.Filters import CodeHighlighter
|
||||
#transform CodeHighlighter
|
||||
|
||||
def foo(self):
|
||||
return '$foo'
|
||||
'''
|
||||
template = Cheetah.Template.Template(template, searchList=[{'foo' : 'bar'}])
|
||||
template = str(template)
|
||||
assert template, (template, 'We should have some content here...')
|
||||
|
||||
def test_Html(self):
|
||||
template = '''
|
||||
#from Cheetah.Filters import CodeHighlighter
|
||||
#transform CodeHighlighter
|
||||
|
||||
<html><head></head><body>$foo</body></html>
|
||||
'''
|
||||
template = Cheetah.Template.Template(template, searchList=[{'foo' : 'bar'}])
|
||||
template = str(template)
|
||||
assert template, (template, 'We should have some content here...')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import unittest
|
||||
|
||||
from Cheetah import SettingsManager
|
||||
|
||||
|
||||
class SettingsManagerTests(unittest.TestCase):
|
||||
def test_mergeDictionaries(self):
|
||||
left = {'foo' : 'bar', 'abc' : {'a' : 1, 'b' : 2, 'c' : (3,)}}
|
||||
right = {'xyz' : (10, 9)}
|
||||
expect = {'xyz': (10, 9), 'foo': 'bar', 'abc': {'a': 1, 'c': (3,), 'b': 2}}
|
||||
|
||||
result = SettingsManager.mergeNestedDictionaries(left, right)
|
||||
self.assertEquals(result, expect)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -1,548 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
import types
|
||||
import os
|
||||
import os.path
|
||||
|
||||
import unittest
|
||||
from Cheetah.NameMapper import NotFound, valueForKey, \
|
||||
valueForName, valueFromSearchList, valueFromFrame, valueFromFrameOrSearchList
|
||||
|
||||
|
||||
class DummyClass(object):
|
||||
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
|
||||
|
||||
class DummyClassGetAttrRaises(object):
|
||||
def __getattr__(self, name):
|
||||
raise ValueError
|
||||
|
||||
|
||||
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(),
|
||||
'anObjThatRaises': DummyClassGetAttrRaises(),
|
||||
'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
|
||||
_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 name in autoCallResults:
|
||||
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)
|
||||
|
||||
def test61(self):
|
||||
"""Accessing attribute where __getattr__ raises shouldn't segfault if something follows it"""
|
||||
|
||||
def test(self=self):
|
||||
self.get('anObjThatRaises.willraise.anything')
|
||||
self.assertRaises(ValueError, test)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
class MapBuiltins(unittest.TestCase):
|
||||
def test_int(self):
|
||||
from Cheetah.Template import Template
|
||||
t = Template('''
|
||||
#def intify(val)
|
||||
#return $int(val)
|
||||
#end def''', compilerSettings={'useStackFrames' : False})
|
||||
self.assertEquals(5, t.intify('5'))
|
||||
|
||||
|
||||
|
||||
##################################################
|
||||
## if run from the command line ##
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -1,49 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import unittest
|
||||
|
||||
from Cheetah import Parser
|
||||
|
||||
class ArgListTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(ArgListTest, self).setUp()
|
||||
self.al = Parser.ArgList()
|
||||
|
||||
def test_merge1(self):
|
||||
'''
|
||||
Testing the ArgList case results from Template.Preprocessors.test_complexUsage
|
||||
'''
|
||||
self.al.add_argument('arg')
|
||||
expect = [('arg', None)]
|
||||
|
||||
self.assertEquals(expect, self.al.merge())
|
||||
|
||||
def test_merge2(self):
|
||||
'''
|
||||
Testing the ArgList case results from SyntaxAndOutput.BlockDirective.test4
|
||||
'''
|
||||
self.al.add_argument('a')
|
||||
self.al.add_default('999')
|
||||
self.al.next()
|
||||
self.al.add_argument('b')
|
||||
self.al.add_default('444')
|
||||
|
||||
expect = [(u'a', u'999'), (u'b', u'444')]
|
||||
|
||||
self.assertEquals(expect, self.al.merge())
|
||||
|
||||
|
||||
|
||||
def test_merge3(self):
|
||||
'''
|
||||
Testing the ArgList case results from SyntaxAndOutput.BlockDirective.test13
|
||||
'''
|
||||
self.al.add_argument('arg')
|
||||
self.al.add_default("'This is my block'")
|
||||
expect = [('arg', "'This is my block'")]
|
||||
|
||||
self.assertEquals(expect, self.al.merge())
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -1,243 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import hotshot
|
||||
import hotshot.stats
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from test import pystone
|
||||
import time
|
||||
|
||||
import Cheetah.NameMapper
|
||||
import Cheetah.Template
|
||||
|
||||
# This can be turned on with the `--debug` flag when running the test
|
||||
# and will cause the tests to all just dump out how long they took
|
||||
# insteasd of asserting on duration
|
||||
DEBUG = False
|
||||
|
||||
# TOLERANCE in Pystones
|
||||
kPS = 1000
|
||||
TOLERANCE = 0.5*kPS
|
||||
|
||||
class DurationError(AssertionError):
|
||||
pass
|
||||
|
||||
_pystone_calibration_mark = None
|
||||
def _pystone_calibration():
|
||||
global _pystone_calibration_mark
|
||||
if not _pystone_calibration_mark:
|
||||
_pystone_calibration_mark = pystone.pystones(loops=pystone.LOOPS)
|
||||
return _pystone_calibration_mark
|
||||
|
||||
def perftest(max_num_pystones, current_pystone=None):
|
||||
'''
|
||||
Performance test decorator based off the 'timedtest'
|
||||
decorator found in this Active State recipe:
|
||||
http://code.activestate.com/recipes/440700/
|
||||
'''
|
||||
if not isinstance(max_num_pystones, float):
|
||||
max_num_pystones = float(max_num_pystones)
|
||||
|
||||
if not current_pystone:
|
||||
current_pystone = _pystone_calibration()
|
||||
|
||||
def _test(function):
|
||||
def wrapper(*args, **kw):
|
||||
start_time = time.time()
|
||||
try:
|
||||
return function(*args, **kw)
|
||||
finally:
|
||||
total_time = time.time() - start_time
|
||||
if total_time == 0:
|
||||
pystone_total_time = 0
|
||||
else:
|
||||
pystone_rate = current_pystone[0] / current_pystone[1]
|
||||
pystone_total_time = total_time / pystone_rate
|
||||
global DEBUG
|
||||
if DEBUG:
|
||||
print('The test "%s" took: %s pystones' % (function.func_name,
|
||||
pystone_total_time))
|
||||
else:
|
||||
if pystone_total_time > (max_num_pystones + TOLERANCE):
|
||||
raise DurationError((('Test too long (%.2f Ps, '
|
||||
'need at most %.2f Ps)')
|
||||
% (pystone_total_time,
|
||||
max_num_pystones)))
|
||||
return wrapper
|
||||
return _test
|
||||
|
||||
|
||||
class DynamicTemplatePerformanceTest(unittest.TestCase):
|
||||
loops = 10
|
||||
#@perftest(1200)
|
||||
def test_BasicDynamic(self):
|
||||
template = '''
|
||||
#def foo(arg1, arg2)
|
||||
#pass
|
||||
#end def
|
||||
'''
|
||||
for i in range(self.loops):
|
||||
klass = Cheetah.Template.Template.compile(template)
|
||||
assert klass
|
||||
test_BasicDynamic = perftest(1200)(test_BasicDynamic)
|
||||
|
||||
class PerformanceTest(unittest.TestCase):
|
||||
iterations = 100000
|
||||
display = False
|
||||
save = False
|
||||
|
||||
def runTest(self):
|
||||
self.prof = hotshot.Profile('%s.prof' % self.__class__.__name__)
|
||||
self.prof.start()
|
||||
for i in range(self.iterations):
|
||||
if hasattr(self, 'performanceSample'):
|
||||
self.display = True
|
||||
self.performanceSample()
|
||||
self.prof.stop()
|
||||
self.prof.close()
|
||||
if self.display:
|
||||
print('>>> %s (%d iterations) ' % (self.__class__.__name__,
|
||||
self.iterations))
|
||||
stats = hotshot.stats.load('%s.prof' % self.__class__.__name__)
|
||||
#stats.strip_dirs()
|
||||
stats.sort_stats('time', 'calls')
|
||||
stats.print_stats(50)
|
||||
|
||||
if not self.save:
|
||||
os.unlink('%s.prof' % self.__class__.__name__)
|
||||
|
||||
class DynamicMethodCompilationTest(PerformanceTest):
|
||||
def performanceSample(self):
|
||||
template = '''
|
||||
#import sys
|
||||
#import os
|
||||
#def testMethod()
|
||||
#set foo = [1, 2, 3, 4]
|
||||
#return $foo[0]
|
||||
#end def
|
||||
'''
|
||||
template = Cheetah.Template.Template.compile(template,
|
||||
keepRefToGeneratedCode=False)
|
||||
template = template()
|
||||
value = template.testMethod()
|
||||
|
||||
|
||||
class BunchOfWriteCalls(PerformanceTest):
|
||||
iterations = 1000
|
||||
def performanceSample(self):
|
||||
template = '''
|
||||
#import sys
|
||||
#import os
|
||||
#for i in range(1000)
|
||||
$i
|
||||
#end for
|
||||
'''
|
||||
template = Cheetah.Template.Template.compile(template,
|
||||
keepRefToGeneratedCode=False)
|
||||
template = template()
|
||||
value = template.respond()
|
||||
del value
|
||||
|
||||
class DynamicSimpleCompilationTest(PerformanceTest):
|
||||
def performanceSample(self):
|
||||
template = '''
|
||||
#import sys
|
||||
#import os
|
||||
#set foo = [1,2,3,4]
|
||||
|
||||
Well hello there! This is basic.
|
||||
|
||||
Here's an array too: $foo
|
||||
'''
|
||||
template = Cheetah.Template.Template.compile(template,
|
||||
keepRefToGeneratedCode=False)
|
||||
template = template()
|
||||
template = unicode(template)
|
||||
|
||||
|
||||
class FilterTest(PerformanceTest):
|
||||
template = None
|
||||
def setUp(self):
|
||||
super(FilterTest, self).setUp()
|
||||
template = '''
|
||||
#import sys
|
||||
#import os
|
||||
#set foo = [1, 2, 3, 4]
|
||||
|
||||
$foo, $foo, $foo
|
||||
'''
|
||||
template = Cheetah.Template.Template.compile(template,
|
||||
keepRefToGeneratedCode=False)
|
||||
self.template = template()
|
||||
|
||||
def performanceSample(self):
|
||||
value = unicode(self.template)
|
||||
|
||||
|
||||
class LongCompileTest(PerformanceTest):
|
||||
''' Test the compilation on a sufficiently large template '''
|
||||
def compile(self, template):
|
||||
return Cheetah.Template.Template.compile(template, keepRefToGeneratedCode=False)
|
||||
|
||||
def performanceSample(self):
|
||||
template = '''
|
||||
#import sys
|
||||
#import Cheetah.Template
|
||||
|
||||
#extends Cheetah.Template.Template
|
||||
|
||||
#def header()
|
||||
<center><h2>This is my header</h2></center>
|
||||
#end def
|
||||
|
||||
#def footer()
|
||||
#return "Huzzah"
|
||||
#end def
|
||||
|
||||
#def scripts()
|
||||
#pass
|
||||
#end def
|
||||
|
||||
#def respond()
|
||||
<html>
|
||||
<head>
|
||||
<title>${title}</title>
|
||||
|
||||
$scripts()
|
||||
</head>
|
||||
<body>
|
||||
$header()
|
||||
|
||||
#for $i in $range(10)
|
||||
This is just some stupid page!
|
||||
<br/>
|
||||
#end for
|
||||
|
||||
<br/>
|
||||
$footer()
|
||||
</body>
|
||||
</html>
|
||||
#end def
|
||||
|
||||
'''
|
||||
return self.compile(template)
|
||||
|
||||
class LongCompile_CompilerSettingsTest(LongCompileTest):
|
||||
def compile(self, template):
|
||||
return Cheetah.Template.Template.compile(template, keepRefToGeneratedCode=False,
|
||||
compilerSettings={'useStackFrames' : True, 'useAutocalling' : True})
|
||||
|
||||
class LongCompileAndRun(LongCompileTest):
|
||||
def performanceSample(self):
|
||||
template = super(LongCompileAndRun, self).performanceSample()
|
||||
template = template(searchList=[{'title' : 'foo'}])
|
||||
template = template.respond()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if '--debug' in sys.argv:
|
||||
DEBUG = True
|
||||
sys.argv = [arg for arg in sys.argv if not arg == '--debug']
|
||||
unittest.main()
|
@ -1,247 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import Cheetah.NameMapper
|
||||
import Cheetah.Template
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
majorVer, minorVer = sys.version_info[0], sys.version_info[1]
|
||||
versionTuple = (majorVer, minorVer)
|
||||
|
||||
def isPython23():
|
||||
''' Python 2.3 is still supported by Cheetah, but doesn't support decorators '''
|
||||
return majorVer == 2 and minorVer < 4
|
||||
|
||||
class GetAttrException(Exception):
|
||||
pass
|
||||
|
||||
class CustomGetAttrClass(object):
|
||||
def __getattr__(self, name):
|
||||
raise GetAttrException('FAIL, %s' % name)
|
||||
|
||||
class GetAttrTest(unittest.TestCase):
|
||||
'''
|
||||
Test for an issue occurring when __getatttr__() raises an exception
|
||||
causing NameMapper to raise a NotFound exception
|
||||
'''
|
||||
def test_ValidException(self):
|
||||
o = CustomGetAttrClass()
|
||||
try:
|
||||
print(o.attr)
|
||||
except GetAttrException, e:
|
||||
# expected
|
||||
return
|
||||
except:
|
||||
self.fail('Invalid exception raised: %s' % e)
|
||||
self.fail('Should have had an exception raised')
|
||||
|
||||
def test_NotFoundException(self):
|
||||
template = '''
|
||||
#def raiseme()
|
||||
$obj.attr
|
||||
#end def'''
|
||||
|
||||
template = Cheetah.Template.Template.compile(template, compilerSettings={}, keepRefToGeneratedCode=True)
|
||||
template = template(searchList=[{'obj' : CustomGetAttrClass()}])
|
||||
assert template, 'We should have a valid template object by now'
|
||||
|
||||
self.failUnlessRaises(GetAttrException, template.raiseme)
|
||||
|
||||
|
||||
class InlineImportTest(unittest.TestCase):
|
||||
def test_FromFooImportThing(self):
|
||||
'''
|
||||
Verify that a bug introduced in v2.1.0 where an inline:
|
||||
#from module import class
|
||||
would result in the following code being generated:
|
||||
import class
|
||||
'''
|
||||
template = '''
|
||||
#def myfunction()
|
||||
#if True
|
||||
#from os import path
|
||||
#return 17
|
||||
Hello!
|
||||
#end if
|
||||
#end def
|
||||
'''
|
||||
template = Cheetah.Template.Template.compile(template, compilerSettings={'useLegacyImportMode' : False}, keepRefToGeneratedCode=True)
|
||||
template = template(searchList=[{}])
|
||||
|
||||
assert template, 'We should have a valid template object by now'
|
||||
|
||||
rc = template.myfunction()
|
||||
assert rc == 17, (template, 'Didn\'t get a proper return value')
|
||||
|
||||
def test_ImportFailModule(self):
|
||||
template = '''
|
||||
#try
|
||||
#import invalidmodule
|
||||
#except
|
||||
#set invalidmodule = dict(FOO='BAR!')
|
||||
#end try
|
||||
|
||||
$invalidmodule.FOO
|
||||
'''
|
||||
template = Cheetah.Template.Template.compile(template, compilerSettings={'useLegacyImportMode' : False}, keepRefToGeneratedCode=True)
|
||||
template = template(searchList=[{}])
|
||||
|
||||
assert template, 'We should have a valid template object by now'
|
||||
assert str(template), 'We weren\'t able to properly generate the result from the template'
|
||||
|
||||
def test_ProperImportOfBadModule(self):
|
||||
template = '''
|
||||
#from invalid import fail
|
||||
|
||||
This should totally $fail
|
||||
'''
|
||||
self.failUnlessRaises(ImportError, Cheetah.Template.Template.compile, template, compilerSettings={'useLegacyImportMode' : False}, keepRefToGeneratedCode=True)
|
||||
|
||||
def test_AutoImporting(self):
|
||||
template = '''
|
||||
#extends FakeyTemplate
|
||||
|
||||
Boo!
|
||||
'''
|
||||
self.failUnlessRaises(ImportError, Cheetah.Template.Template.compile, template)
|
||||
|
||||
def test_StuffBeforeImport_Legacy(self):
|
||||
template = '''
|
||||
###
|
||||
### I like comments before import
|
||||
###
|
||||
#extends Foo
|
||||
Bar
|
||||
'''
|
||||
self.failUnlessRaises(ImportError, Cheetah.Template.Template.compile, template, compilerSettings={'useLegacyImportMode' : True}, keepRefToGeneratedCode=True)
|
||||
|
||||
|
||||
class Mantis_Issue_11_Regression_Test(unittest.TestCase):
|
||||
'''
|
||||
Test case for bug outlined in Mantis issue #11:
|
||||
|
||||
Output:
|
||||
Traceback (most recent call last):
|
||||
File "test.py", line 12, in <module>
|
||||
t.respond()
|
||||
File "DynamicallyCompiledCheetahTemplate.py", line 86, in respond
|
||||
File "/usr/lib64/python2.6/cgi.py", line 1035, in escape
|
||||
s = s.replace("&", "&") # Must be done first!
|
||||
'''
|
||||
def test_FailingBehavior(self):
|
||||
import cgi
|
||||
template = Cheetah.Template.Template("$escape($request)", searchList=[{'escape' : cgi.escape, 'request' : 'foobar'}])
|
||||
assert template
|
||||
self.failUnlessRaises(AttributeError, template.respond)
|
||||
|
||||
|
||||
def test_FailingBehaviorWithSetting(self):
|
||||
import cgi
|
||||
template = Cheetah.Template.Template("$escape($request)",
|
||||
searchList=[{'escape' : cgi.escape, 'request' : 'foobar'}],
|
||||
compilerSettings={'prioritizeSearchListOverSelf' : True})
|
||||
assert template
|
||||
assert template.respond()
|
||||
|
||||
class Mantis_Issue_21_Regression_Test(unittest.TestCase):
|
||||
'''
|
||||
Test case for bug outlined in issue #21
|
||||
|
||||
Effectively @staticmethod and @classmethod
|
||||
decorated methods in templates don't
|
||||
properly define the _filter local, which breaks
|
||||
when using the NameMapper
|
||||
'''
|
||||
def runTest(self):
|
||||
if isPython23():
|
||||
return
|
||||
template = '''
|
||||
#@staticmethod
|
||||
#def testMethod()
|
||||
This is my $output
|
||||
#end def
|
||||
'''
|
||||
template = Cheetah.Template.Template.compile(template)
|
||||
assert template
|
||||
assert template.testMethod(output='bug') # raises a NameError: global name '_filter' is not defined
|
||||
|
||||
|
||||
class Mantis_Issue_22_Regression_Test(unittest.TestCase):
|
||||
'''
|
||||
Test case for bug outlined in issue #22
|
||||
|
||||
When using @staticmethod and @classmethod
|
||||
in conjunction with the #filter directive
|
||||
the generated code for the #filter is reliant
|
||||
on the `self` local, breaking the function
|
||||
'''
|
||||
def test_NoneFilter(self):
|
||||
# XXX: Disabling this test for now
|
||||
return
|
||||
if isPython23():
|
||||
return
|
||||
template = '''
|
||||
#@staticmethod
|
||||
#def testMethod()
|
||||
#filter None
|
||||
This is my $output
|
||||
#end filter
|
||||
#end def
|
||||
'''
|
||||
template = Cheetah.Template.Template.compile(template)
|
||||
assert template
|
||||
assert template.testMethod(output='bug')
|
||||
|
||||
def test_DefinedFilter(self):
|
||||
# XXX: Disabling this test for now
|
||||
return
|
||||
if isPython23():
|
||||
return
|
||||
template = '''
|
||||
#@staticmethod
|
||||
#def testMethod()
|
||||
#filter Filter
|
||||
This is my $output
|
||||
#end filter
|
||||
#end def
|
||||
'''
|
||||
# The generated code for the template's testMethod() should look something
|
||||
# like this in the 'error' case:
|
||||
'''
|
||||
@staticmethod
|
||||
def testMethod(**KWS):
|
||||
## CHEETAH: generated from #def testMethod() at line 3, col 13.
|
||||
trans = DummyTransaction()
|
||||
_dummyTrans = True
|
||||
write = trans.response().write
|
||||
SL = [KWS]
|
||||
_filter = lambda x, **kwargs: unicode(x)
|
||||
|
||||
########################################
|
||||
## START - generated method body
|
||||
|
||||
_orig_filter_18517345 = _filter
|
||||
filterName = u'Filter'
|
||||
if self._CHEETAH__filters.has_key("Filter"):
|
||||
_filter = self._CHEETAH__currentFilter = self._CHEETAH__filters[filterName]
|
||||
else:
|
||||
_filter = self._CHEETAH__currentFilter = \
|
||||
self._CHEETAH__filters[filterName] = getattr(self._CHEETAH__filtersLib, filterName)(self).filter
|
||||
write(u' This is my ')
|
||||
_v = VFFSL(SL,"output",True) # u'$output' on line 5, col 32
|
||||
if _v is not None: write(_filter(_v, rawExpr=u'$output')) # from line 5, col 32.
|
||||
|
||||
########################################
|
||||
## END - generated method body
|
||||
|
||||
return _dummyTrans and trans.response().getvalue() or ""
|
||||
'''
|
||||
template = Cheetah.Template.Template.compile(template)
|
||||
assert template
|
||||
assert template.testMethod(output='bug')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
File diff suppressed because it is too large
Load Diff
@ -1,363 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import pdb
|
||||
import sys
|
||||
import types
|
||||
import os
|
||||
import os.path
|
||||
import tempfile
|
||||
import shutil
|
||||
import unittest
|
||||
from Cheetah.Template import Template
|
||||
|
||||
majorVer, minorVer = sys.version_info[0], sys.version_info[1]
|
||||
versionTuple = (majorVer, minorVer)
|
||||
|
||||
class TemplateTest(unittest.TestCase):
|
||||
pass
|
||||
|
||||
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]
|
||||
|
||||
|
||||
class TryExceptImportTest(TemplateTest):
|
||||
def test_FailCase(self):
|
||||
''' Test situation where an inline #import statement will get relocated '''
|
||||
source = '''
|
||||
#def myFunction()
|
||||
Ahoy!
|
||||
#try
|
||||
#import sys
|
||||
#except ImportError
|
||||
$print "This will never happen!"
|
||||
#end try
|
||||
#end def
|
||||
'''
|
||||
# This should raise an IndentationError (if the bug exists)
|
||||
klass = Template.compile(source=source, compilerSettings={'useLegacyImportMode' : False})
|
||||
t = klass(namespaces={'foo' : 1234})
|
||||
|
||||
class ClassMethodSupport(TemplateTest):
|
||||
def test_BasicDecorator(self):
|
||||
if sys.version_info[0] == 2 and sys.version_info[1] == 3:
|
||||
print('This version of Python doesn\'t support decorators, skipping tests')
|
||||
return
|
||||
template = '''
|
||||
#@classmethod
|
||||
#def myClassMethod()
|
||||
#return '$foo = %s' % $foo
|
||||
#end def
|
||||
'''
|
||||
template = Template.compile(source=template)
|
||||
try:
|
||||
rc = template.myClassMethod(foo='bar')
|
||||
assert rc == '$foo = bar', (rc, 'Template class method didn\'t return what I expected')
|
||||
except AttributeError, ex:
|
||||
self.fail(ex)
|
||||
|
||||
class StaticMethodSupport(TemplateTest):
|
||||
def test_BasicDecorator(self):
|
||||
if sys.version_info[0] == 2 and sys.version_info[1] == 3:
|
||||
print('This version of Python doesn\'t support decorators, skipping tests')
|
||||
return
|
||||
template = '''
|
||||
#@staticmethod
|
||||
#def myStaticMethod()
|
||||
#return '$foo = %s' % $foo
|
||||
#end def
|
||||
'''
|
||||
template = Template.compile(source=template)
|
||||
try:
|
||||
rc = template.myStaticMethod(foo='bar')
|
||||
assert rc == '$foo = bar', (rc, 'Template class method didn\'t return what I expected')
|
||||
except AttributeError, ex:
|
||||
self.fail(ex)
|
||||
|
||||
class Useless(object):
|
||||
def boink(self):
|
||||
return [1, 2, 3]
|
||||
|
||||
class MultipleInheritanceSupport(TemplateTest):
|
||||
def runTest(self):
|
||||
template = '''
|
||||
#extends Template, Useless
|
||||
#def foo()
|
||||
#return [4,5] + $boink()
|
||||
#end def
|
||||
'''
|
||||
template = Template.compile(template,
|
||||
moduleGlobals={'Useless' : Useless},
|
||||
compilerSettings={'autoImportForExtendsDirective' : False})
|
||||
template = template()
|
||||
result = template.foo()
|
||||
assert result == [4, 5, 1, 2, 3], (result, 'Unexpected result')
|
||||
|
||||
class SubclassSearchListTest(TemplateTest):
|
||||
'''
|
||||
Verify that if we subclass Template, we can still
|
||||
use attributes on that subclass in the searchList
|
||||
'''
|
||||
def runTest(self):
|
||||
class Sub(Template):
|
||||
greeting = 'Hola'
|
||||
tmpl = Sub('''When we meet, I say "${greeting}"''')
|
||||
self.assertEquals(unicode(tmpl), 'When we meet, I say "Hola"')
|
||||
|
||||
##################################################
|
||||
## if run from the command line ##
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -1,53 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
'''
|
||||
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
|
||||
'''
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from Cheetah.Tests import SyntaxAndOutput
|
||||
from Cheetah.Tests import NameMapper
|
||||
from Cheetah.Tests import Misc
|
||||
from Cheetah.Tests import Filters
|
||||
from Cheetah.Tests import Template
|
||||
from Cheetah.Tests import Cheps
|
||||
from Cheetah.Tests import Parser
|
||||
from Cheetah.Tests import Regressions
|
||||
from Cheetah.Tests import Unicode
|
||||
from Cheetah.Tests import CheetahWrapper
|
||||
from Cheetah.Tests import Analyzer
|
||||
|
||||
SyntaxAndOutput.install_eols()
|
||||
|
||||
suites = [
|
||||
unittest.findTestCases(SyntaxAndOutput),
|
||||
unittest.findTestCases(NameMapper),
|
||||
unittest.findTestCases(Filters),
|
||||
unittest.findTestCases(Template),
|
||||
#unittest.findTestCases(Cheps),
|
||||
unittest.findTestCases(Regressions),
|
||||
unittest.findTestCases(Unicode),
|
||||
unittest.findTestCases(Misc),
|
||||
unittest.findTestCases(Parser),
|
||||
unittest.findTestCases(Analyzer),
|
||||
]
|
||||
|
||||
if not sys.platform.startswith('java'):
|
||||
suites.append(unittest.findTestCases(CheetahWrapper))
|
||||
|
||||
if __name__ == '__main__':
|
||||
runner = unittest.TextTestRunner()
|
||||
if 'xml' in sys.argv:
|
||||
import xmlrunner
|
||||
runner = xmlrunner.XMLTestRunner(filename='Cheetah-Tests.xml')
|
||||
|
||||
results = runner.run(unittest.TestSuite(suites))
|
||||
|
@ -1,237 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf8 -*-
|
||||
|
||||
from Cheetah.Template import Template
|
||||
from Cheetah import CheetahWrapper
|
||||
from Cheetah import DummyTransaction
|
||||
import imp
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
class CommandLineTest(unittest.TestCase):
|
||||
def createAndCompile(self, source):
|
||||
sourcefile = '-'
|
||||
while sourcefile.find('-') != -1:
|
||||
sourcefile = tempfile.mktemp()
|
||||
|
||||
fd = open('%s.tmpl' % sourcefile, 'w')
|
||||
fd.write(source)
|
||||
fd.close()
|
||||
|
||||
wrap = CheetahWrapper.CheetahWrapper()
|
||||
wrap.main(['cheetah', 'compile', '--quiet', '--nobackup', sourcefile])
|
||||
module_path, module_name = os.path.split(sourcefile)
|
||||
module = loadModule(module_name, [module_path])
|
||||
template = getattr(module, module_name)
|
||||
return template
|
||||
|
||||
class JBQ_UTF8_Test1(unittest.TestCase):
|
||||
def runTest(self):
|
||||
t = Template.compile(source="""Main file with |$v|
|
||||
|
||||
$other""")
|
||||
|
||||
otherT = Template.compile(source="Other template with |$v|")
|
||||
other = otherT()
|
||||
t.other = other
|
||||
|
||||
t.v = u'Unicode String'
|
||||
t.other.v = u'Unicode String'
|
||||
|
||||
assert unicode(t())
|
||||
|
||||
class JBQ_UTF8_Test2(unittest.TestCase):
|
||||
def runTest(self):
|
||||
t = Template.compile(source="""Main file with |$v|
|
||||
|
||||
$other""")
|
||||
|
||||
otherT = Template.compile(source="Other template with |$v|")
|
||||
other = otherT()
|
||||
t.other = other
|
||||
|
||||
t.v = u'Unicode String with eacute é'
|
||||
t.other.v = u'Unicode String'
|
||||
|
||||
assert unicode(t())
|
||||
|
||||
|
||||
class JBQ_UTF8_Test3(unittest.TestCase):
|
||||
def runTest(self):
|
||||
t = Template.compile(source="""Main file with |$v|
|
||||
|
||||
$other""")
|
||||
|
||||
otherT = Template.compile(source="Other template with |$v|")
|
||||
other = otherT()
|
||||
t.other = other
|
||||
|
||||
t.v = u'Unicode String with eacute é'
|
||||
t.other.v = u'Unicode String and an eacute é'
|
||||
|
||||
assert unicode(t())
|
||||
|
||||
class JBQ_UTF8_Test4(unittest.TestCase):
|
||||
def runTest(self):
|
||||
t = Template.compile(source="""#encoding utf-8
|
||||
Main file with |$v| and eacute in the template é""")
|
||||
|
||||
t.v = 'Unicode String'
|
||||
|
||||
assert unicode(t())
|
||||
|
||||
class JBQ_UTF8_Test5(unittest.TestCase):
|
||||
def runTest(self):
|
||||
t = Template.compile(source="""#encoding utf-8
|
||||
Main file with |$v| and eacute in the template é""")
|
||||
|
||||
t.v = u'Unicode String'
|
||||
|
||||
assert unicode(t())
|
||||
|
||||
def loadModule(moduleName, path=None):
|
||||
if path:
|
||||
assert isinstance(path, list)
|
||||
try:
|
||||
mod = sys.modules[moduleName]
|
||||
except KeyError:
|
||||
fp = None
|
||||
|
||||
try:
|
||||
fp, pathname, description = imp.find_module(moduleName, path)
|
||||
mod = imp.load_module(moduleName, fp, pathname, description)
|
||||
finally:
|
||||
if fp:
|
||||
fp.close()
|
||||
return mod
|
||||
|
||||
class JBQ_UTF8_Test6(unittest.TestCase):
|
||||
def runTest(self):
|
||||
source = """#encoding utf-8
|
||||
#set $someUnicodeString = u"Bébé"
|
||||
Main file with |$v| and eacute in the template é"""
|
||||
t = Template.compile(source=source)
|
||||
|
||||
t.v = u'Unicode String'
|
||||
|
||||
assert unicode(t())
|
||||
|
||||
class JBQ_UTF8_Test7(CommandLineTest):
|
||||
def runTest(self):
|
||||
source = """#encoding utf-8
|
||||
#set $someUnicodeString = u"Bébé"
|
||||
Main file with |$v| and eacute in the template é"""
|
||||
|
||||
template = self.createAndCompile(source)
|
||||
template.v = u'Unicode String'
|
||||
|
||||
assert unicode(template())
|
||||
|
||||
class JBQ_UTF8_Test8(CommandLineTest):
|
||||
def testStaticCompile(self):
|
||||
source = """#encoding utf-8
|
||||
#set $someUnicodeString = u"Bébé"
|
||||
$someUnicodeString"""
|
||||
|
||||
template = self.createAndCompile(source)()
|
||||
|
||||
a = unicode(template).encode("utf-8")
|
||||
self.assertEquals("Bébé", a)
|
||||
|
||||
def testDynamicCompile(self):
|
||||
source = """#encoding utf-8
|
||||
#set $someUnicodeString = u"Bébé"
|
||||
$someUnicodeString"""
|
||||
|
||||
template = Template(source = source)
|
||||
|
||||
a = unicode(template).encode("utf-8")
|
||||
self.assertEquals("Bébé", a)
|
||||
|
||||
class EncodeUnicodeCompatTest(unittest.TestCase):
|
||||
"""
|
||||
Taken initially from Red Hat's bugzilla #529332
|
||||
https://bugzilla.redhat.com/show_bug.cgi?id=529332
|
||||
"""
|
||||
def runTest(self):
|
||||
t = Template("""Foo ${var}""", filter='EncodeUnicode')
|
||||
t.var = u"Text with some non-ascii characters: åäö"
|
||||
|
||||
rc = t.respond()
|
||||
assert isinstance(rc, unicode), ('Template.respond() should return unicode', rc)
|
||||
|
||||
rc = str(t)
|
||||
assert isinstance(rc, str), ('Template.__str__() should return a UTF-8 encoded string', rc)
|
||||
|
||||
|
||||
class Unicode_in_SearchList_Test(CommandLineTest):
|
||||
def test_BasicASCII(self):
|
||||
source = '''This is $adjective'''
|
||||
|
||||
template = self.createAndCompile(source)
|
||||
assert template and issubclass(template, Template)
|
||||
template = template(searchList=[{'adjective' : u'neat'}])
|
||||
assert template.respond()
|
||||
|
||||
def test_Thai(self):
|
||||
# The string is something in Thai
|
||||
source = '''This is $foo $adjective'''
|
||||
template = self.createAndCompile(source)
|
||||
assert template and issubclass(template, Template)
|
||||
template = template(searchList=[{'foo' : 'bar',
|
||||
'adjective' : u'\u0e22\u0e34\u0e19\u0e14\u0e35\u0e15\u0e49\u0e2d\u0e19\u0e23\u0e31\u0e1a'}])
|
||||
assert template.respond()
|
||||
|
||||
def test_Thai_utf8(self):
|
||||
utf8 = '\xe0\xb8\xa2\xe0\xb8\xb4\xe0\xb8\x99\xe0\xb8\x94\xe0\xb8\xb5\xe0\xb8\x95\xe0\xb9\x89\xe0\xb8\xad\xe0\xb8\x99\xe0\xb8\xa3\xe0\xb8\xb1\xe0\xb8\x9a'
|
||||
|
||||
source = '''This is $adjective'''
|
||||
template = self.createAndCompile(source)
|
||||
assert template and issubclass(template, Template)
|
||||
template = template(searchList=[{'adjective' : utf8}])
|
||||
assert template.respond()
|
||||
|
||||
|
||||
class InlineSpanishTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(InlineSpanishTest, self).setUp()
|
||||
self.template = '''
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Pagina del vendedor</title>
|
||||
</head>
|
||||
<body>
|
||||
$header
|
||||
<h2>Bienvenido $nombre.</h2>
|
||||
<br /><br /><br />
|
||||
<center>
|
||||
Usted tiene $numpedidos_noconf <a href="">pedidós</a> sin confirmar.
|
||||
<br /><br />
|
||||
Bodega tiene fecha para $numpedidos_bodega <a href="">pedidos</a>.
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
def test_failure(self):
|
||||
""" Test a template lacking a proper #encoding tag """
|
||||
self.failUnlessRaises(UnicodeDecodeError, Template, self.template, searchList=[{'header' : '',
|
||||
'nombre' : '', 'numpedidos_bodega' : '',
|
||||
'numpedidos_noconf' : ''}])
|
||||
|
||||
def test_success(self):
|
||||
""" Test a template with a proper #encoding tag """
|
||||
template = '#encoding utf-8\n%s' % self.template
|
||||
template = Template(template, searchList=[{'header' : '',
|
||||
'nombre' : '', 'numpedidos_bodega' : '',
|
||||
'numpedidos_noconf' : ''}])
|
||||
self.assertTrue(unicode(template))
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -1 +0,0 @@
|
||||
#
|
@ -1,381 +0,0 @@
|
||||
"""
|
||||
XML Test Runner for PyUnit
|
||||
"""
|
||||
|
||||
# Written by Sebastian Rittau <srittau@jroger.in-berlin.de> and placed in
|
||||
# the Public Domain. With contributions by Paolo Borelli.
|
||||
|
||||
__revision__ = "$Id: /private/python/stdlib/xmlrunner.py 16654 2007-11-12T12:46:35.368945Z srittau $"
|
||||
|
||||
import os.path
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import unittest
|
||||
from StringIO import StringIO
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
from StringIO import StringIO
|
||||
|
||||
|
||||
|
||||
class _TestInfo(object):
|
||||
|
||||
"""Information about a particular test.
|
||||
|
||||
Used by _XMLTestResult.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, test, time):
|
||||
_pieces = test.id().split('.')
|
||||
(self._class, self._method) = ('.'.join(_pieces[:-1]), _pieces[-1])
|
||||
self._time = time
|
||||
self._error = None
|
||||
self._failure = None
|
||||
|
||||
|
||||
def print_report(self, stream):
|
||||
"""Print information about this test case in XML format to the
|
||||
supplied stream.
|
||||
|
||||
"""
|
||||
stream.write(' <testcase classname="%(class)s" name="%(method)s" time="%(time).4f">' % \
|
||||
{
|
||||
"class": self._class,
|
||||
"method": self._method,
|
||||
"time": self._time,
|
||||
})
|
||||
if self._failure != None:
|
||||
self._print_error(stream, 'failure', self._failure)
|
||||
if self._error != None:
|
||||
self._print_error(stream, 'error', self._error)
|
||||
stream.write('</testcase>\n')
|
||||
|
||||
def _print_error(self, stream, tagname, error):
|
||||
"""Print information from a failure or error to the supplied stream."""
|
||||
text = escape(str(error[1]))
|
||||
stream.write('\n')
|
||||
stream.write(' <%s type="%s">%s\n' \
|
||||
% (tagname, issubclass(error[0], Exception) and error[0].__name__ or str(error[0]), text))
|
||||
tb_stream = StringIO()
|
||||
traceback.print_tb(error[2], None, tb_stream)
|
||||
stream.write(escape(tb_stream.getvalue()))
|
||||
stream.write(' </%s>\n' % tagname)
|
||||
stream.write(' ')
|
||||
|
||||
# Module level functions since Python 2.3 doesn't grok decorators
|
||||
def create_success(test, time):
|
||||
"""Create a _TestInfo instance for a successful test."""
|
||||
return _TestInfo(test, time)
|
||||
|
||||
def create_failure(test, time, failure):
|
||||
"""Create a _TestInfo instance for a failed test."""
|
||||
info = _TestInfo(test, time)
|
||||
info._failure = failure
|
||||
return info
|
||||
|
||||
def create_error(test, time, error):
|
||||
"""Create a _TestInfo instance for an erroneous test."""
|
||||
info = _TestInfo(test, time)
|
||||
info._error = error
|
||||
return info
|
||||
|
||||
class _XMLTestResult(unittest.TestResult):
|
||||
|
||||
"""A test result class that stores result as XML.
|
||||
|
||||
Used by XMLTestRunner.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, classname):
|
||||
unittest.TestResult.__init__(self)
|
||||
self._test_name = classname
|
||||
self._start_time = None
|
||||
self._tests = []
|
||||
self._error = None
|
||||
self._failure = None
|
||||
|
||||
def startTest(self, test):
|
||||
unittest.TestResult.startTest(self, test)
|
||||
self._error = None
|
||||
self._failure = None
|
||||
self._start_time = time.time()
|
||||
|
||||
def stopTest(self, test):
|
||||
time_taken = time.time() - self._start_time
|
||||
unittest.TestResult.stopTest(self, test)
|
||||
if self._error:
|
||||
info = create_error(test, time_taken, self._error)
|
||||
elif self._failure:
|
||||
info = create_failure(test, time_taken, self._failure)
|
||||
else:
|
||||
info = create_success(test, time_taken)
|
||||
self._tests.append(info)
|
||||
|
||||
def addError(self, test, err):
|
||||
unittest.TestResult.addError(self, test, err)
|
||||
self._error = err
|
||||
|
||||
def addFailure(self, test, err):
|
||||
unittest.TestResult.addFailure(self, test, err)
|
||||
self._failure = err
|
||||
|
||||
def print_report(self, stream, time_taken, out, err):
|
||||
"""Prints the XML report to the supplied stream.
|
||||
|
||||
The time the tests took to perform as well as the captured standard
|
||||
output and standard error streams must be passed in.a
|
||||
|
||||
"""
|
||||
stream.write('<testsuite errors="%(e)d" failures="%(f)d" ' % \
|
||||
{ "e": len(self.errors), "f": len(self.failures) })
|
||||
stream.write('name="%(n)s" tests="%(t)d" time="%(time).3f">\n' % \
|
||||
{
|
||||
"n": self._test_name,
|
||||
"t": self.testsRun,
|
||||
"time": time_taken,
|
||||
})
|
||||
for info in self._tests:
|
||||
info.print_report(stream)
|
||||
stream.write(' <system-out><![CDATA[%s]]></system-out>\n' % out)
|
||||
stream.write(' <system-err><![CDATA[%s]]></system-err>\n' % err)
|
||||
stream.write('</testsuite>\n')
|
||||
|
||||
|
||||
class XMLTestRunner(object):
|
||||
|
||||
"""A test runner that stores results in XML format compatible with JUnit.
|
||||
|
||||
XMLTestRunner(stream=None) -> XML test runner
|
||||
|
||||
The XML file is written to the supplied stream. If stream is None, the
|
||||
results are stored in a file called TEST-<module>.<class>.xml in the
|
||||
current working directory (if not overridden with the path property),
|
||||
where <module> and <class> are the module and class name of the test class.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._stream = kwargs.get('stream')
|
||||
self._filename = kwargs.get('filename')
|
||||
self._path = "."
|
||||
|
||||
def run(self, test):
|
||||
"""Run the given test case or test suite."""
|
||||
class_ = test.__class__
|
||||
classname = class_.__module__ + "." + class_.__name__
|
||||
if self._stream == None:
|
||||
filename = "TEST-%s.xml" % classname
|
||||
if self._filename:
|
||||
filename = self._filename
|
||||
stream = file(os.path.join(self._path, filename), "w")
|
||||
stream.write('<?xml version="1.0" encoding="utf-8"?>\n')
|
||||
else:
|
||||
stream = self._stream
|
||||
|
||||
result = _XMLTestResult(classname)
|
||||
start_time = time.time()
|
||||
|
||||
# TODO: Python 2.5: Use the with statement
|
||||
old_stdout = sys.stdout
|
||||
old_stderr = sys.stderr
|
||||
sys.stdout = StringIO()
|
||||
sys.stderr = StringIO()
|
||||
|
||||
try:
|
||||
test(result)
|
||||
try:
|
||||
out_s = sys.stdout.getvalue()
|
||||
except AttributeError:
|
||||
out_s = ""
|
||||
try:
|
||||
err_s = sys.stderr.getvalue()
|
||||
except AttributeError:
|
||||
err_s = ""
|
||||
finally:
|
||||
sys.stdout = old_stdout
|
||||
sys.stderr = old_stderr
|
||||
|
||||
time_taken = time.time() - start_time
|
||||
result.print_report(stream, time_taken, out_s, err_s)
|
||||
if self._stream == None:
|
||||
stream.close()
|
||||
|
||||
return result
|
||||
|
||||
def _set_path(self, path):
|
||||
self._path = path
|
||||
|
||||
path = property(lambda self: self._path, _set_path, None,
|
||||
"""The path where the XML files are stored.
|
||||
|
||||
This property is ignored when the XML file is written to a file
|
||||
stream.""")
|
||||
|
||||
|
||||
class XMLTestRunnerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self._stream = StringIO()
|
||||
|
||||
def _try_test_run(self, test_class, expected):
|
||||
|
||||
"""Run the test suite against the supplied test class and compare the
|
||||
XML result against the expected XML string. Fail if the expected
|
||||
string doesn't match the actual string. All time attribute in the
|
||||
expected string should have the value "0.000". All error and failure
|
||||
messages are reduced to "Foobar".
|
||||
|
||||
"""
|
||||
|
||||
runner = XMLTestRunner(self._stream)
|
||||
runner.run(unittest.makeSuite(test_class))
|
||||
|
||||
got = self._stream.getvalue()
|
||||
# Replace all time="X.YYY" attributes by time="0.000" to enable a
|
||||
# simple string comparison.
|
||||
got = re.sub(r'time="\d+\.\d+"', 'time="0.000"', got)
|
||||
# Likewise, replace all failure and error messages by a simple "Foobar"
|
||||
# string.
|
||||
got = re.sub(r'(?s)<failure (.*?)>.*?</failure>', r'<failure \1>Foobar</failure>', got)
|
||||
got = re.sub(r'(?s)<error (.*?)>.*?</error>', r'<error \1>Foobar</error>', got)
|
||||
|
||||
self.assertEqual(expected, got)
|
||||
|
||||
def test_no_tests(self):
|
||||
"""Regression test: Check whether a test run without any tests
|
||||
matches a previous run.
|
||||
|
||||
"""
|
||||
class TestTest(unittest.TestCase):
|
||||
pass
|
||||
self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="0" time="0.000">
|
||||
<system-out><![CDATA[]]></system-out>
|
||||
<system-err><![CDATA[]]></system-err>
|
||||
</testsuite>
|
||||
""")
|
||||
|
||||
def test_success(self):
|
||||
"""Regression test: Check whether a test run with a successful test
|
||||
matches a previous run.
|
||||
|
||||
"""
|
||||
class TestTest(unittest.TestCase):
|
||||
def test_foo(self):
|
||||
pass
|
||||
self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000">
|
||||
<testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase>
|
||||
<system-out><![CDATA[]]></system-out>
|
||||
<system-err><![CDATA[]]></system-err>
|
||||
</testsuite>
|
||||
""")
|
||||
|
||||
def test_failure(self):
|
||||
"""Regression test: Check whether a test run with a failing test
|
||||
matches a previous run.
|
||||
|
||||
"""
|
||||
class TestTest(unittest.TestCase):
|
||||
def test_foo(self):
|
||||
self.assert_(False)
|
||||
self._try_test_run(TestTest, """<testsuite errors="0" failures="1" name="unittest.TestSuite" tests="1" time="0.000">
|
||||
<testcase classname="__main__.TestTest" name="test_foo" time="0.000">
|
||||
<failure type="exceptions.AssertionError">Foobar</failure>
|
||||
</testcase>
|
||||
<system-out><![CDATA[]]></system-out>
|
||||
<system-err><![CDATA[]]></system-err>
|
||||
</testsuite>
|
||||
""")
|
||||
|
||||
def test_error(self):
|
||||
"""Regression test: Check whether a test run with a erroneous test
|
||||
matches a previous run.
|
||||
|
||||
"""
|
||||
class TestTest(unittest.TestCase):
|
||||
def test_foo(self):
|
||||
raise IndexError()
|
||||
self._try_test_run(TestTest, """<testsuite errors="1" failures="0" name="unittest.TestSuite" tests="1" time="0.000">
|
||||
<testcase classname="__main__.TestTest" name="test_foo" time="0.000">
|
||||
<error type="exceptions.IndexError">Foobar</error>
|
||||
</testcase>
|
||||
<system-out><![CDATA[]]></system-out>
|
||||
<system-err><![CDATA[]]></system-err>
|
||||
</testsuite>
|
||||
""")
|
||||
|
||||
def test_stdout_capture(self):
|
||||
"""Regression test: Check whether a test run with output to stdout
|
||||
matches a previous run.
|
||||
|
||||
"""
|
||||
class TestTest(unittest.TestCase):
|
||||
def test_foo(self):
|
||||
print("Test")
|
||||
self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000">
|
||||
<testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase>
|
||||
<system-out><![CDATA[Test
|
||||
]]></system-out>
|
||||
<system-err><![CDATA[]]></system-err>
|
||||
</testsuite>
|
||||
""")
|
||||
|
||||
def test_stderr_capture(self):
|
||||
"""Regression test: Check whether a test run with output to stderr
|
||||
matches a previous run.
|
||||
|
||||
"""
|
||||
class TestTest(unittest.TestCase):
|
||||
def test_foo(self):
|
||||
sys.stderr.write('Test\n')
|
||||
self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000">
|
||||
<testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase>
|
||||
<system-out><![CDATA[]]></system-out>
|
||||
<system-err><![CDATA[Test
|
||||
]]></system-err>
|
||||
</testsuite>
|
||||
""")
|
||||
|
||||
class NullStream(object):
|
||||
"""A file-like object that discards everything written to it."""
|
||||
def write(self, buffer):
|
||||
pass
|
||||
|
||||
def test_unittests_changing_stdout(self):
|
||||
"""Check whether the XMLTestRunner recovers gracefully from unit tests
|
||||
that change stdout, but don't change it back properly.
|
||||
|
||||
"""
|
||||
class TestTest(unittest.TestCase):
|
||||
def test_foo(self):
|
||||
sys.stdout = XMLTestRunnerTest.NullStream()
|
||||
|
||||
runner = XMLTestRunner(self._stream)
|
||||
runner.run(unittest.makeSuite(TestTest))
|
||||
|
||||
def test_unittests_changing_stderr(self):
|
||||
"""Check whether the XMLTestRunner recovers gracefully from unit tests
|
||||
that change stderr, but don't change it back properly.
|
||||
|
||||
"""
|
||||
class TestTest(unittest.TestCase):
|
||||
def test_foo(self):
|
||||
sys.stderr = XMLTestRunnerTest.NullStream()
|
||||
|
||||
runner = XMLTestRunner(self._stream)
|
||||
runner.run(unittest.makeSuite(TestTest))
|
||||
|
||||
|
||||
class XMLTestProgram(unittest.TestProgram):
|
||||
def runTests(self):
|
||||
if self.testRunner is None:
|
||||
self.testRunner = XMLTestRunner()
|
||||
unittest.TestProgram.runTests(self)
|
||||
|
||||
main = XMLTestProgram
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(module=None)
|
@ -1,77 +0,0 @@
|
||||
# $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 = 'REQUEST_METHOD' in os.environ
|
||||
wk = self._CHEETAH__isControlledByWebKit
|
||||
return env and not wk
|
||||
|
||||
|
||||
|
||||
# vim: shiftwidth=4 tabstop=4 expandtab
|
@ -1,464 +0,0 @@
|
||||
"""
|
||||
@@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
|
||||
try:
|
||||
from functools import reduce
|
||||
except ImportError:
|
||||
# If functools doesn't exist, we must be on an old
|
||||
# enough version that has reduce() in builtins
|
||||
pass
|
||||
|
||||
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.
|
||||
|
||||
########## PUBLIC GENERIC FUNCTIONS ##############################
|
||||
|
||||
class NegativeError(ValueError):
|
||||
pass
|
||||
|
||||
def isNumeric(v):
|
||||
return isinstance(v, (int, float))
|
||||
|
||||
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 = sorted(lis[:])
|
||||
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 = list(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
|
@ -1,391 +0,0 @@
|
||||
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 => $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
|
@ -1,28 +0,0 @@
|
||||
"""
|
||||
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(object):
|
||||
def __getattr__(self, attr):
|
||||
return self
|
||||
def __getitem__(self, item):
|
||||
return self
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self
|
||||
def __str__(self):
|
||||
return ''
|
||||
def __repr__(self):
|
||||
return ''
|
||||
def __nonzero__(self):
|
||||
return 0
|
||||
def __eq__(self, x):
|
||||
if x:
|
||||
return False
|
||||
return True
|
||||
def __ne__(self, x):
|
||||
return x and True or False
|
||||
|
@ -1,166 +0,0 @@
|
||||
# $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.
|
||||
"""
|
||||
|
||||
##################################################
|
||||
## DEPENDENCIES
|
||||
import string
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
##################################################
|
||||
## 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 True:
|
||||
## 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 isinstance(item, tuple):
|
||||
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' % (' '*2*indent,
|
||||
self._menuCSSClass, text)
|
||||
else:
|
||||
return '%s<A HREF="%s%s"%s>%s</A> <BR>\n' % \
|
||||
(' '*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 ' > '
|
||||
|
||||
def crumbTerminator(self):
|
||||
return ''
|
||||
|
||||
def emptyCrumb(self):
|
||||
"""When you are at the homepage"""
|
||||
return ''
|
||||
|
||||
## internal methods
|
||||
|
||||
def _menubarRecurse(self, contents, indent, stream):
|
||||
if isinstance(contents, tuple):
|
||||
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 isinstance(contents, tuple):
|
||||
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())
|
@ -1,8 +0,0 @@
|
||||
"""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
|
@ -1,5 +0,0 @@
|
||||
from turbocheetah import cheetahsupport
|
||||
|
||||
TurboCheetah = cheetahsupport.TurboCheetah
|
||||
|
||||
__all__ = ["TurboCheetah"]
|
@ -1,110 +0,0 @@
|
||||
"Template support for Cheetah"
|
||||
|
||||
import sys, os, imp
|
||||
|
||||
from Cheetah import Compiler
|
||||
import pkg_resources
|
||||
|
||||
def _recompile_template(package, basename, tfile, classname):
|
||||
tmpl = pkg_resources.resource_string(package, "%s.tmpl" % basename)
|
||||
c = Compiler.Compiler(source=tmpl, mainClassName='GenTemplate')
|
||||
code = str(c)
|
||||
mod = imp.new_module(classname)
|
||||
ns = dict()
|
||||
exec(code, ns)
|
||||
tempclass = ns.get("GenTemplate",
|
||||
ns.get('DynamicallyCompiledCheetahTemplate'))
|
||||
assert tempclass
|
||||
tempclass.__name__ = basename
|
||||
setattr(mod, basename, tempclass)
|
||||
sys.modules[classname] = mod
|
||||
return mod
|
||||
|
||||
class TurboCheetah:
|
||||
extension = "tmpl"
|
||||
|
||||
def __init__(self, extra_vars_func=None, options=None):
|
||||
if options is None:
|
||||
options = dict()
|
||||
self.get_extra_vars = extra_vars_func
|
||||
self.options = options
|
||||
self.compiledTemplates = {}
|
||||
self.search_path = []
|
||||
|
||||
def load_template(self, template=None,
|
||||
template_string=None, template_file=None,
|
||||
loadingSite=False):
|
||||
"""Searches for a template along the Python path.
|
||||
|
||||
Template files must end in ".tmpl" and be in legitimate packages.
|
||||
"""
|
||||
given = len([_f for _f in (template, template_string, template_file) if _f])
|
||||
if given > 1:
|
||||
raise TypeError(
|
||||
"You may give only one of template, template_string, and "
|
||||
"template_file")
|
||||
if not given:
|
||||
raise TypeError(
|
||||
"You must give one of template, template_string, or "
|
||||
"template_file")
|
||||
if template:
|
||||
return self.load_template_module(template)
|
||||
elif template_string:
|
||||
return self.load_template_string(template_string)
|
||||
elif template_file:
|
||||
return self.load_template_file(template_file)
|
||||
|
||||
def load_template_module(self, classname):
|
||||
|
||||
ct = self.compiledTemplates
|
||||
|
||||
divider = classname.rfind(".")
|
||||
if divider > -1:
|
||||
package = classname[0:divider]
|
||||
basename = classname[divider+1:]
|
||||
else:
|
||||
raise ValueError("All templates must be in a package")
|
||||
|
||||
if not self.options.get("cheetah.precompiled", False):
|
||||
tfile = pkg_resources.resource_filename(package,
|
||||
"%s.%s" %
|
||||
(basename,
|
||||
self.extension))
|
||||
if classname in ct:
|
||||
mtime = os.stat(tfile).st_mtime
|
||||
if ct[classname] != mtime:
|
||||
ct[classname] = mtime
|
||||
del sys.modules[classname]
|
||||
mod = _recompile_template(package, basename,
|
||||
tfile, classname)
|
||||
else:
|
||||
mod = __import__(classname, dict(), dict(), [basename])
|
||||
else:
|
||||
ct[classname] = os.stat(tfile).st_mtime
|
||||
mod = _recompile_template(package, basename,
|
||||
tfile, classname)
|
||||
else:
|
||||
mod = __import__(classname, dict(), dict(), [basename])
|
||||
tempclass = getattr(mod, basename)
|
||||
return tempclass
|
||||
|
||||
def load_template_string(self, content):
|
||||
raise NotImplementedError
|
||||
|
||||
def load_template_file(self, filename):
|
||||
raise NotImplementedError
|
||||
|
||||
def render(self, info, format="html", fragment=False, template=None,
|
||||
template_string=None, template_file=None):
|
||||
tclass = self.load_template(
|
||||
template=template, template_string=template_string,
|
||||
template_file=template_file)
|
||||
if self.get_extra_vars:
|
||||
extra = self.get_extra_vars()
|
||||
else:
|
||||
extra = {}
|
||||
tempobj = tclass(searchList=[info, extra])
|
||||
if fragment:
|
||||
return tempobj.fragment()
|
||||
else:
|
||||
return tempobj.respond()
|
@ -1 +0,0 @@
|
||||
#
|
@ -1,66 +0,0 @@
|
||||
import os
|
||||
from turbocheetah import TurboCheetah
|
||||
|
||||
here = os.path.dirname(__file__)
|
||||
|
||||
values = {
|
||||
'v': 'VV',
|
||||
'one': 1,
|
||||
}
|
||||
|
||||
def test_normal():
|
||||
plugin = TurboCheetah()
|
||||
# Make sure a simple test works:
|
||||
s = plugin.render(values, template='turbocheetah.tests.simple1')
|
||||
assert s.strip() == 'This is a test: VV'
|
||||
# Make sure one template can inherit from another:
|
||||
s = plugin.render(values, template='turbocheetah.tests.import_inherit')
|
||||
assert s.strip() == 'Inherited: import'
|
||||
|
||||
def test_path():
|
||||
plugin = TurboCheetah()
|
||||
plugin.search_path = [here]
|
||||
# Make sure we pick up filenames (basic test):
|
||||
s = plugin.render(values, template_file='simple1')
|
||||
assert s.strip() == 'This is a test: VV'
|
||||
# Make sure we pick up subdirectories:
|
||||
s = plugin.render(values, template_file='sub/master')
|
||||
assert s.strip() == 'sub1: 1'
|
||||
|
||||
def test_search():
|
||||
plugin = TurboCheetah()
|
||||
plugin.search_path = [os.path.join(here, 'sub'),
|
||||
os.path.join(here, 'sub2'),
|
||||
here]
|
||||
# Pick up from third entry:
|
||||
s = plugin.render(values, template_file='simple1')
|
||||
assert s.strip() == 'This is a test: VV'
|
||||
# Pick up from sub/master, non-ambiguous:
|
||||
s = plugin.render(values, template_file='master')
|
||||
assert s.strip() == 'sub1: 1'
|
||||
# Pick up from sub/page, inherit from sub/template:
|
||||
s = plugin.render(values, template_file='page')
|
||||
assert s.strip() == 'SUB: sub content'
|
||||
# Pick up from sub2/page_over, inherit from sub/template:
|
||||
s = plugin.render(values, template_file='page_over')
|
||||
assert s.strip() == 'SUB: override content'
|
||||
# Pick up from sub/page_template_over, inherit from
|
||||
# sub2/template_over:
|
||||
s = plugin.render(values, template_file='page_template_over')
|
||||
assert s.strip() == 'OVER: sub content'
|
||||
# Change page, make sure that undoes overrides:
|
||||
plugin.search_path = [os.path.join(here, 'sub'),
|
||||
here]
|
||||
s = plugin.render(values, template_file='page_over')
|
||||
assert s.strip() == 'SUB: sub content'
|
||||
|
||||
def test_string():
|
||||
# Make sure simple string evaluation works:
|
||||
plugin = TurboCheetah()
|
||||
s = plugin.render(values, template_string="""Hey $v""")
|
||||
assert s == "Hey VV"
|
||||
# Make sure a string can inherit from a file:
|
||||
plugin.search_path = [here]
|
||||
s = plugin.render(values, template_string="#extends inherit_from\ns value")
|
||||
assert s.strip() == 'inherit: s value'
|
||||
|
@ -1,9 +0,0 @@
|
||||
try:
|
||||
from ds.sys.Unspecified import Unspecified
|
||||
except ImportError:
|
||||
class _Unspecified:
|
||||
def __repr__(self):
|
||||
return 'Unspecified'
|
||||
def __str__(self):
|
||||
return 'Unspecified'
|
||||
Unspecified = _Unspecified()
|
@ -1,123 +0,0 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
def indentize(source):
|
||||
return IndentProcessor().process(source)
|
||||
|
||||
class IndentProcessor(object):
|
||||
"""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(object):
|
||||
"""
|
||||
A class that keeps track of the current indentation level.
|
||||
.indent() returns the appropriate amount of indentation.
|
||||
"""
|
||||
On = 1
|
||||
Level = 0
|
||||
Chars = ' '
|
||||
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
|
||||
return " " * _default
|
||||
|
@ -1,67 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Miscellaneous functions/objects used by Cheetah but also useful standalone.
|
||||
"""
|
||||
import os # Used in mkdirsWithPyInitFile.
|
||||
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 isinstance(thing, type) 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
|
@ -1,102 +0,0 @@
|
||||
# $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
|
@ -1 +0,0 @@
|
||||
#
|
@ -1,14 +0,0 @@
|
||||
"""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
|
@ -1,21 +0,0 @@
|
||||
"""This is a copy of the htmlEncode function in Webware.
|
||||
|
||||
|
||||
@@TR: It implemented more efficiently.
|
||||
|
||||
"""
|
||||
htmlCodes = [
|
||||
['&', '&'],
|
||||
['<', '<'],
|
||||
['>', '>'],
|
||||
['"', '"'],
|
||||
]
|
||||
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
|
@ -1,304 +0,0 @@
|
||||
## statprof.py
|
||||
## Copyright (C) 2004,2005 Andy Wingo <wingo at pobox dot com>
|
||||
## Copyright (C) 2001 Rob Browning <rlb at defaultvalue dot org>
|
||||
|
||||
## This library is free software; you can redistribute it and/or
|
||||
## modify it under the terms of the GNU Lesser General Public
|
||||
## License as published by the Free Software Foundation; either
|
||||
## version 2.1 of the License, or (at your option) any later version.
|
||||
##
|
||||
## This library is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
## Lesser General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU Lesser General Public
|
||||
## License along with this program; if not, contact:
|
||||
##
|
||||
## Free Software Foundation Voice: +1-617-542-5942
|
||||
## 59 Temple Place - Suite 330 Fax: +1-617-542-2652
|
||||
## Boston, MA 02111-1307, USA gnu@gnu.org
|
||||
|
||||
"""
|
||||
statprof is intended to be a fairly simple statistical profiler for
|
||||
python. It was ported directly from a statistical profiler for guile,
|
||||
also named statprof, available from guile-lib [0].
|
||||
|
||||
[0] http://wingolog.org/software/guile-lib/statprof/
|
||||
|
||||
To start profiling, call statprof.start():
|
||||
>>> start()
|
||||
|
||||
Then run whatever it is that you want to profile, for example:
|
||||
>>> import test.pystone; test.pystone.pystones()
|
||||
|
||||
Then stop the profiling and print out the results:
|
||||
>>> stop()
|
||||
>>> display()
|
||||
% cumulative self
|
||||
time seconds seconds name
|
||||
26.72 1.40 0.37 pystone.py:79:Proc0
|
||||
13.79 0.56 0.19 pystone.py:133:Proc1
|
||||
13.79 0.19 0.19 pystone.py:208:Proc8
|
||||
10.34 0.16 0.14 pystone.py:229:Func2
|
||||
6.90 0.10 0.10 pystone.py:45:__init__
|
||||
4.31 0.16 0.06 pystone.py:53:copy
|
||||
...
|
||||
|
||||
All of the numerical data with the exception of the calls column is
|
||||
statistically approximate. In the following column descriptions, and
|
||||
in all of statprof, "time" refers to execution time (both user and
|
||||
system), not wall clock time.
|
||||
|
||||
% time
|
||||
The percent of the time spent inside the procedure itself (not
|
||||
counting children).
|
||||
|
||||
cumulative seconds
|
||||
The total number of seconds spent in the procedure, including
|
||||
children.
|
||||
|
||||
self seconds
|
||||
The total number of seconds spent in the procedure itself (not
|
||||
counting children).
|
||||
|
||||
name
|
||||
The name of the procedure.
|
||||
|
||||
By default statprof keeps the data collected from previous runs. If you
|
||||
want to clear the collected data, call reset():
|
||||
>>> reset()
|
||||
|
||||
reset() can also be used to change the sampling frequency. For example,
|
||||
to tell statprof to sample 50 times a second:
|
||||
>>> reset(50)
|
||||
|
||||
This means that statprof will sample the call stack after every 1/50 of
|
||||
a second of user + system time spent running on behalf of the python
|
||||
process. When your process is idle (for example, blocking in a read(),
|
||||
as is the case at the listener), the clock does not advance. For this
|
||||
reason statprof is not currently not suitable for profiling io-bound
|
||||
operations.
|
||||
|
||||
The profiler uses the hash of the code object itself to identify the
|
||||
procedures, so it won't confuse different procedures with the same name.
|
||||
They will show up as two different rows in the output.
|
||||
|
||||
Right now the profiler is quite simplistic. I cannot provide
|
||||
call-graphs or other higher level information. What you see in the
|
||||
table is pretty much all there is. Patches are welcome :-)
|
||||
|
||||
|
||||
Threading
|
||||
---------
|
||||
|
||||
Because signals only get delivered to the main thread in Python,
|
||||
statprof only profiles the main thread. However because the time
|
||||
reporting function uses per-process timers, the results can be
|
||||
significantly off if other threads' work patterns are not similar to the
|
||||
main thread's work patterns.
|
||||
|
||||
|
||||
Implementation notes
|
||||
--------------------
|
||||
|
||||
The profiler works by setting the unix profiling signal ITIMER_PROF to
|
||||
go off after the interval you define in the call to reset(). When the
|
||||
signal fires, a sampling routine is run which looks at the current
|
||||
procedure that's executing, and then crawls up the stack, and for each
|
||||
frame encountered, increments that frame's code object's sample count.
|
||||
Note that if a procedure is encountered multiple times on a given stack,
|
||||
it is only counted once. After the sampling is complete, the profiler
|
||||
resets profiling timer to fire again after the appropriate interval.
|
||||
|
||||
Meanwhile, the profiler keeps track, via os.times(), how much CPU time
|
||||
(system and user -- which is also what ITIMER_PROF tracks), has elapsed
|
||||
while code has been executing within a start()/stop() block.
|
||||
|
||||
The profiler also tries to avoid counting or timing its own code as
|
||||
much as possible.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
|
||||
try:
|
||||
import itimer
|
||||
except ImportError:
|
||||
raise ImportError('''statprof requires the itimer python extension.
|
||||
To install it, enter the following commands from a terminal:
|
||||
|
||||
wget http://www.cute.fi/~torppa/py-itimer/py-itimer.tar.gz
|
||||
tar zxvf py-itimer.tar.gz
|
||||
cd py-itimer
|
||||
sudo python setup.py install
|
||||
''')
|
||||
|
||||
import signal
|
||||
import os
|
||||
|
||||
|
||||
__all__ = ['start', 'stop', 'reset', 'display']
|
||||
|
||||
|
||||
###########################################################################
|
||||
## Utils
|
||||
|
||||
def clock():
|
||||
times = os.times()
|
||||
return times[0] + times[1]
|
||||
|
||||
|
||||
###########################################################################
|
||||
## Collection data structures
|
||||
|
||||
class ProfileState(object):
|
||||
def __init__(self, frequency=None):
|
||||
self.reset(frequency)
|
||||
|
||||
def reset(self, frequency=None):
|
||||
# total so far
|
||||
self.accumulated_time = 0.0
|
||||
# start_time when timer is active
|
||||
self.last_start_time = None
|
||||
# total count of sampler calls
|
||||
self.sample_count = 0
|
||||
# a float
|
||||
if frequency:
|
||||
self.sample_interval = 1.0/frequency
|
||||
elif not hasattr(self, 'sample_interval'):
|
||||
# default to 100 Hz
|
||||
self.sample_interval = 1.0/100.0
|
||||
else:
|
||||
# leave the frequency as it was
|
||||
pass
|
||||
self.remaining_prof_time = None
|
||||
# for user start/stop nesting
|
||||
self.profile_level = 0
|
||||
# whether to catch apply-frame
|
||||
self.count_calls = False
|
||||
# gc time between start() and stop()
|
||||
self.gc_time_taken = 0
|
||||
|
||||
def accumulate_time(self, stop_time):
|
||||
self.accumulated_time += stop_time - self.last_start_time
|
||||
|
||||
state = ProfileState()
|
||||
|
||||
## call_data := { code object: CallData }
|
||||
call_data = {}
|
||||
class CallData(object):
|
||||
def __init__(self, code):
|
||||
self.name = code.co_name
|
||||
self.filename = code.co_filename
|
||||
self.lineno = code.co_firstlineno
|
||||
self.call_count = 0
|
||||
self.cum_sample_count = 0
|
||||
self.self_sample_count = 0
|
||||
call_data[code] = self
|
||||
|
||||
def get_call_data(code):
|
||||
return call_data.get(code, None) or CallData(code)
|
||||
|
||||
|
||||
###########################################################################
|
||||
## SIGPROF handler
|
||||
|
||||
def sample_stack_procs(frame):
|
||||
state.sample_count += 1
|
||||
get_call_data(frame.f_code).self_sample_count += 1
|
||||
|
||||
code_seen = {}
|
||||
while frame:
|
||||
code_seen[frame.f_code] = True
|
||||
frame = frame.f_back
|
||||
for code in code_seen.iterkeys():
|
||||
get_call_data(code).cum_sample_count += 1
|
||||
|
||||
def profile_signal_handler(signum, frame):
|
||||
if state.profile_level > 0:
|
||||
state.accumulate_time(clock())
|
||||
sample_stack_procs(frame)
|
||||
itimer.setitimer(itimer.ITIMER_PROF,
|
||||
state.sample_interval, 0.0)
|
||||
state.last_start_time = clock()
|
||||
|
||||
|
||||
###########################################################################
|
||||
## Profiling API
|
||||
|
||||
def is_active():
|
||||
return state.profile_level > 0
|
||||
|
||||
def start():
|
||||
state.profile_level += 1
|
||||
if state.profile_level == 1:
|
||||
state.last_start_time = clock()
|
||||
rpt = state.remaining_prof_time
|
||||
state.remaining_prof_time = None
|
||||
signal.signal(signal.SIGPROF, profile_signal_handler)
|
||||
itimer.setitimer(itimer.ITIMER_PROF,
|
||||
rpt or state.sample_interval, 0.0)
|
||||
state.gc_time_taken = 0 # dunno
|
||||
|
||||
def stop():
|
||||
state.profile_level -= 1
|
||||
if state.profile_level == 0:
|
||||
state.accumulate_time(clock())
|
||||
state.last_start_time = None
|
||||
rpt = itimer.setitimer(itimer.ITIMER_PROF, 0.0, 0.0)
|
||||
signal.signal(signal.SIGPROF, signal.SIG_IGN)
|
||||
state.remaining_prof_time = rpt[0]
|
||||
state.gc_time_taken = 0 # dunno
|
||||
|
||||
def reset(frequency=None):
|
||||
assert state.profile_level == 0, "Can't reset() while statprof is running"
|
||||
call_data.clear()
|
||||
state.reset(frequency)
|
||||
|
||||
|
||||
###########################################################################
|
||||
## Reporting API
|
||||
|
||||
class CallStats(object):
|
||||
def __init__(self, call_data):
|
||||
self_samples = call_data.self_sample_count
|
||||
cum_samples = call_data.cum_sample_count
|
||||
nsamples = state.sample_count
|
||||
secs_per_sample = state.accumulated_time / nsamples
|
||||
basename = os.path.basename(call_data.filename)
|
||||
|
||||
self.name = '%s:%d:%s' % (basename, call_data.lineno, call_data.name)
|
||||
self.pcnt_time_in_proc = self_samples / nsamples * 100
|
||||
self.cum_secs_in_proc = cum_samples * secs_per_sample
|
||||
self.self_secs_in_proc = self_samples * secs_per_sample
|
||||
self.num_calls = None
|
||||
self.self_secs_per_call = None
|
||||
self.cum_secs_per_call = None
|
||||
|
||||
def display(self):
|
||||
print('%6.2f %9.2f %9.2f %s' % (self.pcnt_time_in_proc,
|
||||
self.cum_secs_in_proc,
|
||||
self.self_secs_in_proc,
|
||||
self.name))
|
||||
|
||||
|
||||
def display():
|
||||
if state.sample_count == 0:
|
||||
print('No samples recorded.')
|
||||
return
|
||||
|
||||
l = [CallStats(x) for x in call_data.itervalues()]
|
||||
l = [(x.self_secs_in_proc, x.cum_secs_in_proc, x) for x in l]
|
||||
l.sort(reverse=True)
|
||||
l = [x[2] for x in l]
|
||||
|
||||
print('%5.5s %10.10s %7.7s %-8.8s' % ('% ', 'cumulative', 'self', ''))
|
||||
print('%5.5s %9.9s %8.8s %-8.8s' % ("time", "seconds", "seconds", "name"))
|
||||
|
||||
for x in l:
|
||||
x.display()
|
||||
|
||||
print('---')
|
||||
print('Sample count: %d' % state.sample_count)
|
||||
print('Total time: %f seconds' % state.accumulated_time)
|
@ -1,58 +0,0 @@
|
||||
Version = '2.4.3'
|
||||
VersionTuple = (2, 4, 3, 'development', 0)
|
||||
|
||||
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')
|
@ -1,20 +0,0 @@
|
||||
'''
|
||||
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
|
||||
http://cheetahtemplate.org/learn.html
|
||||
|
||||
Mailing list
|
||||
cheetahtemplate-discuss@lists.sourceforge.net
|
||||
Subscribe at
|
||||
http://lists.sourceforge.net/lists/listinfo/cheetahtemplate-discuss
|
||||
'''
|
||||
|
||||
from Version import *
|
@ -1,494 +0,0 @@
|
||||
/* ***************************************************************************
|
||||
This is the C language version of NameMapper.py. See the comments and
|
||||
DocStrings in NameMapper for details on the purpose and interface of this
|
||||
module.
|
||||
|
||||
===============================================================================
|
||||
$Id: _namemapper.c,v 1.34 2007/12/10 18:25:20 tavis_rudd Exp $
|
||||
Authors: Tavis Rudd <tavis@damnsimple.com>
|
||||
Version: $Revision: 1.34 $
|
||||
Start Date: 2001/08/07
|
||||
Last Revision Date: $Date: 2007/12/10 18:25:20 $
|
||||
*/
|
||||
|
||||
/* *************************************************************************** */
|
||||
#include <Python.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "cheetah.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
static PyObject *NotFound; /* locally-raised exception */
|
||||
static PyObject *TooManyPeriods; /* locally-raised exception */
|
||||
static PyObject* pprintMod_pformat; /* used for exception formatting */
|
||||
|
||||
|
||||
/* *************************************************************************** */
|
||||
/* First the c versions of the functions */
|
||||
/* *************************************************************************** */
|
||||
|
||||
static void setNotFoundException(char *key, PyObject *namespace)
|
||||
{
|
||||
PyObject *exceptionStr = NULL;
|
||||
exceptionStr = PyUnicode_FromFormat("cannot find \'%s\'", key);
|
||||
PyErr_SetObject(NotFound, exceptionStr);
|
||||
Py_XDECREF(exceptionStr);
|
||||
}
|
||||
|
||||
static int wrapInternalNotFoundException(char *fullName, PyObject *namespace)
|
||||
{
|
||||
PyObject *excType, *excValue, *excTraceback, *isAlreadyWrapped = NULL;
|
||||
PyObject *newExcValue = NULL;
|
||||
if (!ALLOW_WRAPPING_OF_NOTFOUND_EXCEPTIONS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!PyErr_Occurred()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (PyErr_GivenExceptionMatches(PyErr_Occurred(), NotFound)) {
|
||||
PyErr_Fetch(&excType, &excValue, &excTraceback);
|
||||
isAlreadyWrapped = PyObject_CallMethod(excValue, "find", "s", "while searching");
|
||||
|
||||
if (isAlreadyWrapped != NULL) {
|
||||
if (PyLong_AsLong(isAlreadyWrapped) == -1) {
|
||||
newExcValue = PyUnicode_FromFormat("%U while searching for \'%s\'",
|
||||
excValue, fullName);
|
||||
}
|
||||
Py_DECREF(isAlreadyWrapped);
|
||||
}
|
||||
else {
|
||||
newExcValue = excValue;
|
||||
}
|
||||
PyErr_Restore(excType, newExcValue, excTraceback);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int isInstanceOrClass(PyObject *nextVal) {
|
||||
#ifndef IS_PYTHON3
|
||||
/* old style classes or instances */
|
||||
if((PyInstance_Check(nextVal)) || (PyClass_Check(nextVal))) {
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!PyObject_HasAttrString(nextVal, "__class__")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* new style classes or instances */
|
||||
if (PyType_Check(nextVal) || PyObject_HasAttrString(nextVal, "mro")) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strncmp(nextVal->ob_type->tp_name, "function", 9) == 0)
|
||||
return 0;
|
||||
|
||||
/* method, func, or builtin func */
|
||||
if (PyObject_HasAttrString(nextVal, "im_func")
|
||||
|| PyObject_HasAttrString(nextVal, "func_code")
|
||||
|| PyObject_HasAttrString(nextVal, "__self__")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* instance */
|
||||
if ((!PyObject_HasAttrString(nextVal, "mro")) &&
|
||||
PyObject_HasAttrString(nextVal, "__init__")) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int getNameChunks(char *nameChunks[], char *name, char *nameCopy)
|
||||
{
|
||||
char c;
|
||||
char *currChunk;
|
||||
int currChunkNum = 0;
|
||||
|
||||
currChunk = nameCopy;
|
||||
while ('\0' != (c = *nameCopy)){
|
||||
if ('.' == c) {
|
||||
if (currChunkNum >= (MAXCHUNKS-2)) { /* avoid overflowing nameChunks[] */
|
||||
PyErr_SetString(TooManyPeriods, name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*nameCopy ='\0';
|
||||
nameChunks[currChunkNum++] = currChunk;
|
||||
nameCopy++;
|
||||
currChunk = nameCopy;
|
||||
} else
|
||||
nameCopy++;
|
||||
}
|
||||
if (nameCopy > currChunk) {
|
||||
nameChunks[currChunkNum++] = currChunk;
|
||||
}
|
||||
return currChunkNum;
|
||||
}
|
||||
|
||||
|
||||
static int PyNamemapper_hasKey(PyObject *obj, char *key)
|
||||
{
|
||||
if (PyMapping_Check(obj) && PyMapping_HasKeyString(obj, key)) {
|
||||
return TRUE;
|
||||
} else if (PyObject_HasAttrString(obj, key)) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static PyObject *PyNamemapper_valueForKey(PyObject *obj, char *key)
|
||||
{
|
||||
PyObject *theValue = NULL;
|
||||
|
||||
if (PyMapping_Check(obj) && PyMapping_HasKeyString(obj, key)) {
|
||||
theValue = PyMapping_GetItemString(obj, key);
|
||||
} else if (PyObject_HasAttrString(obj, key)) {
|
||||
theValue = PyObject_GetAttrString(obj, key);
|
||||
} else {
|
||||
setNotFoundException(key, obj);
|
||||
}
|
||||
return theValue;
|
||||
}
|
||||
|
||||
static PyObject *PyNamemapper_valueForName(PyObject *obj, char *nameChunks[], int numChunks, int executeCallables)
|
||||
{
|
||||
int i;
|
||||
char *currentKey;
|
||||
PyObject *currentVal = NULL;
|
||||
PyObject *nextVal = NULL;
|
||||
|
||||
currentVal = obj;
|
||||
for (i=0; i < numChunks;i++) {
|
||||
currentKey = nameChunks[i];
|
||||
if (PyErr_CheckSignals()) { /* not sure if I really need to do this here, but what the hell */
|
||||
if (i>0) {
|
||||
Py_DECREF(currentVal);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (PyMapping_Check(currentVal) && PyMapping_HasKeyString(currentVal, currentKey)) {
|
||||
nextVal = PyMapping_GetItemString(currentVal, currentKey);
|
||||
}
|
||||
else {
|
||||
PyObject *exc;
|
||||
nextVal = PyObject_GetAttrString(currentVal, currentKey);
|
||||
exc = PyErr_Occurred();
|
||||
if (exc != NULL) {
|
||||
// if exception == AttributeError, report our own exception
|
||||
if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
|
||||
setNotFoundException(currentKey, currentVal);
|
||||
}
|
||||
// any exceptions results in failure
|
||||
if (i > 0) {
|
||||
Py_DECREF(currentVal);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (i > 0) {
|
||||
Py_DECREF(currentVal);
|
||||
}
|
||||
|
||||
if (executeCallables && PyCallable_Check(nextVal) &&
|
||||
(isInstanceOrClass(nextVal) == 0) ) {
|
||||
if (!(currentVal = PyObject_CallObject(nextVal, NULL))) {
|
||||
Py_DECREF(nextVal);
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(nextVal);
|
||||
} else {
|
||||
currentVal = nextVal;
|
||||
}
|
||||
}
|
||||
|
||||
return currentVal;
|
||||
}
|
||||
|
||||
|
||||
/* *************************************************************************** */
|
||||
/* Now the wrapper functions to export into the Python module */
|
||||
/* *************************************************************************** */
|
||||
|
||||
|
||||
static PyObject *namemapper_valueForKey(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyObject *obj;
|
||||
char *key;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "Os", &obj, &key)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return PyNamemapper_valueForKey(obj, key);
|
||||
}
|
||||
|
||||
static PyObject *namemapper_valueForName(PYARGS)
|
||||
{
|
||||
PyObject *obj;
|
||||
char *name;
|
||||
int executeCallables = 0;
|
||||
|
||||
char *nameCopy = NULL;
|
||||
char *tmpPntr1 = NULL;
|
||||
char *tmpPntr2 = NULL;
|
||||
char *nameChunks[MAXCHUNKS];
|
||||
int numChunks;
|
||||
|
||||
PyObject *theValue;
|
||||
|
||||
static char *kwlist[] = {"obj", "name", "executeCallables", NULL};
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Os|i", kwlist, &obj, &name, &executeCallables)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
createNameCopyAndChunks();
|
||||
|
||||
theValue = PyNamemapper_valueForName(obj, nameChunks, numChunks, executeCallables);
|
||||
free(nameCopy);
|
||||
if (wrapInternalNotFoundException(name, obj)) {
|
||||
theValue = NULL;
|
||||
}
|
||||
return theValue;
|
||||
}
|
||||
|
||||
static PyObject *namemapper_valueFromSearchList(PYARGS)
|
||||
{
|
||||
PyObject *searchList;
|
||||
char *name;
|
||||
int executeCallables = 0;
|
||||
|
||||
char *nameCopy = NULL;
|
||||
char *tmpPntr1 = NULL;
|
||||
char *tmpPntr2 = NULL;
|
||||
char *nameChunks[MAXCHUNKS];
|
||||
int numChunks;
|
||||
|
||||
PyObject *nameSpace = NULL;
|
||||
PyObject *theValue = NULL;
|
||||
PyObject *iterator = NULL;
|
||||
|
||||
static char *kwlist[] = {"searchList", "name", "executeCallables", NULL};
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Os|i", kwlist, &searchList, &name, &executeCallables)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
createNameCopyAndChunks();
|
||||
|
||||
iterator = PyObject_GetIter(searchList);
|
||||
if (iterator == NULL) {
|
||||
PyErr_SetString(PyExc_TypeError,"This searchList is not iterable!");
|
||||
goto done;
|
||||
}
|
||||
|
||||
while ((nameSpace = PyIter_Next(iterator))) {
|
||||
checkForNameInNameSpaceAndReturnIfFound(TRUE);
|
||||
Py_DECREF(nameSpace);
|
||||
if(PyErr_CheckSignals()) {
|
||||
theValue = NULL;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
if (PyErr_Occurred()) {
|
||||
theValue = NULL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
setNotFoundException(nameChunks[0], searchList);
|
||||
|
||||
done:
|
||||
Py_XDECREF(iterator);
|
||||
free(nameCopy);
|
||||
return theValue;
|
||||
}
|
||||
|
||||
static PyObject *namemapper_valueFromFrameOrSearchList(PyObject *self, PyObject *args, PyObject *keywds)
|
||||
{
|
||||
/* python function args */
|
||||
char *name;
|
||||
int executeCallables = 0;
|
||||
PyObject *searchList = NULL;
|
||||
|
||||
/* locals */
|
||||
char *nameCopy = NULL;
|
||||
char *tmpPntr1 = NULL;
|
||||
char *tmpPntr2 = NULL;
|
||||
char *nameChunks[MAXCHUNKS];
|
||||
int numChunks;
|
||||
|
||||
PyObject *nameSpace = NULL;
|
||||
PyObject *theValue = NULL;
|
||||
PyObject *excString = NULL;
|
||||
PyObject *iterator = NULL;
|
||||
|
||||
static char *kwlist[] = {"searchList", "name", "executeCallables", NULL};
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "Os|i", kwlist, &searchList, &name,
|
||||
&executeCallables)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
createNameCopyAndChunks();
|
||||
|
||||
nameSpace = PyEval_GetLocals();
|
||||
checkForNameInNameSpaceAndReturnIfFound(FALSE);
|
||||
|
||||
iterator = PyObject_GetIter(searchList);
|
||||
if (iterator == NULL) {
|
||||
PyErr_SetString(PyExc_TypeError,"This searchList is not iterable!");
|
||||
goto done;
|
||||
}
|
||||
while ( (nameSpace = PyIter_Next(iterator)) ) {
|
||||
checkForNameInNameSpaceAndReturnIfFound(TRUE);
|
||||
Py_DECREF(nameSpace);
|
||||
if(PyErr_CheckSignals()) {
|
||||
theValue = NULL;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
if (PyErr_Occurred()) {
|
||||
theValue = NULL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
nameSpace = PyEval_GetGlobals();
|
||||
checkForNameInNameSpaceAndReturnIfFound(FALSE);
|
||||
|
||||
nameSpace = PyEval_GetBuiltins();
|
||||
checkForNameInNameSpaceAndReturnIfFound(FALSE);
|
||||
|
||||
excString = Py_BuildValue("s", "[locals()]+searchList+[globals(), __builtins__]");
|
||||
setNotFoundException(nameChunks[0], excString);
|
||||
Py_DECREF(excString);
|
||||
|
||||
done:
|
||||
Py_XDECREF(iterator);
|
||||
free(nameCopy);
|
||||
return theValue;
|
||||
}
|
||||
|
||||
static PyObject *namemapper_valueFromFrame(PyObject *self, PyObject *args, PyObject *keywds)
|
||||
{
|
||||
/* python function args */
|
||||
char *name;
|
||||
int executeCallables = 0;
|
||||
|
||||
/* locals */
|
||||
char *tmpPntr1 = NULL;
|
||||
char *tmpPntr2 = NULL;
|
||||
|
||||
char *nameCopy = NULL;
|
||||
char *nameChunks[MAXCHUNKS];
|
||||
int numChunks;
|
||||
|
||||
PyObject *nameSpace = NULL;
|
||||
PyObject *theValue = NULL;
|
||||
PyObject *excString = NULL;
|
||||
|
||||
static char *kwlist[] = {"name", "executeCallables", NULL};
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s|i", kwlist, &name, &executeCallables)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
createNameCopyAndChunks();
|
||||
|
||||
nameSpace = PyEval_GetLocals();
|
||||
checkForNameInNameSpaceAndReturnIfFound(FALSE);
|
||||
|
||||
nameSpace = PyEval_GetGlobals();
|
||||
checkForNameInNameSpaceAndReturnIfFound(FALSE);
|
||||
|
||||
nameSpace = PyEval_GetBuiltins();
|
||||
checkForNameInNameSpaceAndReturnIfFound(FALSE);
|
||||
|
||||
excString = Py_BuildValue("s", "[locals(), globals(), __builtins__]");
|
||||
setNotFoundException(nameChunks[0], excString);
|
||||
Py_DECREF(excString);
|
||||
done:
|
||||
free(nameCopy);
|
||||
return theValue;
|
||||
}
|
||||
|
||||
/* *************************************************************************** */
|
||||
/* Method registration table: name-string -> function-pointer */
|
||||
|
||||
static struct PyMethodDef namemapper_methods[] = {
|
||||
{"valueForKey", namemapper_valueForKey, 1},
|
||||
{"valueForName", (PyCFunction)namemapper_valueForName, METH_VARARGS|METH_KEYWORDS},
|
||||
{"valueFromSearchList", (PyCFunction)namemapper_valueFromSearchList, METH_VARARGS|METH_KEYWORDS},
|
||||
{"valueFromFrame", (PyCFunction)namemapper_valueFromFrame, METH_VARARGS|METH_KEYWORDS},
|
||||
{"valueFromFrameOrSearchList", (PyCFunction)namemapper_valueFromFrameOrSearchList, METH_VARARGS|METH_KEYWORDS},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
||||
/* *************************************************************************** */
|
||||
/* Initialization function (import-time) */
|
||||
|
||||
#ifdef IS_PYTHON3
|
||||
static struct PyModuleDef namemappermodule = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"_namemapper",
|
||||
NULL, /* docstring */
|
||||
-1,
|
||||
namemapper_methods,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL};
|
||||
|
||||
PyMODINIT_FUNC PyInit__namemapper(void)
|
||||
{
|
||||
PyObject *m = PyModule_Create(&namemappermodule);
|
||||
#else
|
||||
DL_EXPORT(void) init_namemapper(void)
|
||||
{
|
||||
PyObject *m = Py_InitModule3("_namemapper", namemapper_methods, NULL);
|
||||
#endif
|
||||
|
||||
PyObject *d, *pprintMod;
|
||||
|
||||
/* add symbolic constants to the module */
|
||||
d = PyModule_GetDict(m);
|
||||
NotFound = PyErr_NewException("NameMapper.NotFound",PyExc_LookupError,NULL);
|
||||
TooManyPeriods = PyErr_NewException("NameMapper.TooManyPeriodsInName",NULL,NULL);
|
||||
PyDict_SetItemString(d, "NotFound", NotFound);
|
||||
PyDict_SetItemString(d, "TooManyPeriodsInName", TooManyPeriods);
|
||||
pprintMod = PyImport_ImportModule("pprint");
|
||||
if (!pprintMod) {
|
||||
#ifdef IS_PYTHON3
|
||||
return NULL;
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
pprintMod_pformat = PyObject_GetAttrString(pprintMod, "pformat");
|
||||
Py_DECREF(pprintMod);
|
||||
/* check for errors */
|
||||
if (PyErr_Occurred()) {
|
||||
Py_FatalError("Can't initialize module _namemapper");
|
||||
}
|
||||
#ifdef IS_PYTHON3
|
||||
return m;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009, R. Tyler Ballance <tyler@monkeypox.org>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of R. Tyler Ballance nor the names of its
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _CHEETAH_H_
|
||||
#define _CHEETAH_H_
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
#define IS_PYTHON3
|
||||
#endif
|
||||
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
|
||||
#define PYARGS PyObject *self, PyObject *args, PyObject *kwargs
|
||||
|
||||
|
||||
/*
|
||||
* _namemapper.c specific definitions
|
||||
*/
|
||||
#define MAXCHUNKS 15 /* max num of nameChunks for the arrays */
|
||||
#define ALLOW_WRAPPING_OF_NOTFOUND_EXCEPTIONS 1
|
||||
#define createNameCopyAndChunks() {\
|
||||
nameCopy = malloc(strlen(name) + 1);\
|
||||
tmpPntr1 = name; \
|
||||
tmpPntr2 = nameCopy;\
|
||||
while ((*tmpPntr2++ = *tmpPntr1++)); \
|
||||
numChunks = getNameChunks(nameChunks, name, nameCopy); \
|
||||
if (PyErr_Occurred()) { /* there might have been TooManyPeriods */\
|
||||
free(nameCopy);\
|
||||
return NULL;\
|
||||
}\
|
||||
}
|
||||
|
||||
#define checkForNameInNameSpaceAndReturnIfFound(namespace_decref) { \
|
||||
if ( PyNamemapper_hasKey(nameSpace, nameChunks[0]) ) {\
|
||||
theValue = PyNamemapper_valueForName(nameSpace, nameChunks, numChunks, executeCallables);\
|
||||
if (namespace_decref) {\
|
||||
Py_DECREF(nameSpace);\
|
||||
}\
|
||||
if (wrapInternalNotFoundException(name, nameSpace)) {\
|
||||
theValue = NULL;\
|
||||
}\
|
||||
goto done;\
|
||||
}\
|
||||
}
|
||||
|
||||
#endif
|
@ -1,20 +0,0 @@
|
||||
import os.path
|
||||
import string
|
||||
|
||||
letters = None
|
||||
try:
|
||||
letters = string.ascii_letters
|
||||
except AttributeError:
|
||||
letters = string.letters
|
||||
|
||||
l = ['_'] * 256
|
||||
for c in string.digits + letters:
|
||||
l[ord(c)] = c
|
||||
_pathNameTransChars = ''.join(l)
|
||||
del l, c
|
||||
|
||||
def convertTmplPathToModuleName(tmplPath,
|
||||
_pathNameTransChars=_pathNameTransChars,
|
||||
splitdrive=os.path.splitdrive,
|
||||
):
|
||||
return splitdrive(tmplPath)[1].translate(_pathNameTransChars)
|
@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en'
|
||||
import os, re
|
||||
|
||||
from lxml import etree
|
||||
from Cheetah.Template import Template
|
||||
from templite import Templite
|
||||
|
||||
from calibre.customize.conversion import OutputFormatPlugin
|
||||
from calibre import CurrentDir
|
||||
@ -29,7 +29,8 @@ class HTMLOutput(OutputFormatPlugin):
|
||||
output_path = re.sub(r'\.html', '', output_path)+'_files'
|
||||
|
||||
with open(output_file, 'wb') as f:
|
||||
root = oeb_book.html_toc()
|
||||
link_prefix=os.path.basename(output_path)+'/'
|
||||
root = oeb_book.html_toc(link_prefix=link_prefix)
|
||||
html_txt = etree.tostring(root, pretty_print=True, encoding='utf-8', xml_declaration=False)
|
||||
f.write(html_txt)
|
||||
|
||||
@ -70,13 +71,13 @@ class HTMLOutput(OutputFormatPlugin):
|
||||
else:
|
||||
prevLink = None
|
||||
vars = {
|
||||
'ebookContent': ebook_content,
|
||||
'prevLink': prevLink,
|
||||
'nextLink': nextLink
|
||||
|
||||
}
|
||||
template_file = os.path.dirname(__file__)+'/outputtemplates/default.tmpl'
|
||||
t = Template(file=template_file, searchList=[ vars ]) # compilerSettings={'useStackFrames': False}
|
||||
templite = Templite(open(template_file).read())
|
||||
t = templite.render(ebookContent=ebook_content, prevLink=prevLink, nextLink=nextLink)
|
||||
|
||||
with open(path, 'wb') as f:
|
||||
f.write(str(t))
|
||||
f.write(t)
|
||||
|
||||
item.unload_data_from_memory(memory=path)
|
||||
|
@ -5,17 +5,17 @@
|
||||
<title>Unbenanntes Dokument</title>
|
||||
</head>
|
||||
<body>
|
||||
$ebookContent
|
||||
|
||||
#if $prevLink
|
||||
<a href="$prevLink">previous page</a>
|
||||
#end if
|
||||
${ebookContent}$
|
||||
|
||||
#if $nextLink
|
||||
<a href="$nextLink">next page</a>
|
||||
#end if
|
||||
${if prevLink:}$
|
||||
<a href="${prevLink}$">previous page</a>
|
||||
${:endif}$
|
||||
|
||||
${if nextLink:}$
|
||||
<a href="${nextLink}$">next page</a>
|
||||
${:endif}$
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
@ -1644,19 +1644,20 @@ class TOC(object):
|
||||
node.to_ncx(point)
|
||||
return parent
|
||||
|
||||
def to_xhtml(self, parent=None):
|
||||
def to_xhtml(self, parent=None, link_prefix=''):
|
||||
if parent is None:
|
||||
parent = etree.Element(XHTML('ul'))
|
||||
elif len(self.nodes):
|
||||
parent = element(parent, (XHTML('ul')))
|
||||
for node in self.nodes:
|
||||
point = element(parent, XHTML('li'))
|
||||
link = element(point, XHTML('a'), href=urlunquote(node.href))
|
||||
href = link_prefix+urlunquote(node.href)
|
||||
link = element(point, XHTML('a'), href=href)
|
||||
title = node.title
|
||||
if title:
|
||||
title = re.sub(r'\s+', ' ', title)
|
||||
link.text=title
|
||||
node.to_xhtml(point)
|
||||
node.to_xhtml(point, link_prefix=link_prefix)
|
||||
return parent
|
||||
|
||||
def rationalize_play_orders(self):
|
||||
@ -1978,7 +1979,7 @@ class OEBBook(object):
|
||||
results[PAGE_MAP_MIME] = (href, self.pages.to_page_map())
|
||||
return results
|
||||
|
||||
def html_toc(self):
|
||||
def html_toc(self, link_prefix=''):
|
||||
lang = unicode(self.metadata.language[0])
|
||||
html = etree.Element(XHTML('html'),
|
||||
attrib={XML('lang'): lang},
|
||||
@ -1986,5 +1987,5 @@ class OEBBook(object):
|
||||
head = etree.SubElement(html, XHTML('head'))
|
||||
title = etree.SubElement(head, XHTML('title'))
|
||||
body = etree.SubElement(html, XHTML('body'))
|
||||
body.append(self.toc.to_xhtml())
|
||||
body.append(self.toc.to_xhtml(link_prefix=link_prefix))
|
||||
return html
|
||||
|
87
src/templite/__init__.py
Normal file
87
src/templite/__init__.py
Normal file
@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Templite+
|
||||
# A light-weight, fully functional, general purpose templating engine
|
||||
#
|
||||
# Copyright (c) 2009 joonis new media
|
||||
# Author: Thimo Kraemer <thimo.kraemer@joonis.de>
|
||||
#
|
||||
# Based on Templite - Tomer Filiba
|
||||
# http://code.activestate.com/recipes/496702/
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
# MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
import sys, re
|
||||
|
||||
class Templite(object):
|
||||
auto_emit = re.compile('(^[\'\"])|(^[a-zA-Z0-9_\[\]\'\"]+$)')
|
||||
|
||||
def __init__(self, template, start='${', end='}$'):
|
||||
if len(start) != 2 or len(end) != 2:
|
||||
raise ValueError('each delimiter must be two characters long')
|
||||
delimiter = re.compile('%s(.*?)%s' % (re.escape(start), re.escape(end)), re.DOTALL)
|
||||
offset = 0
|
||||
tokens = []
|
||||
for i, part in enumerate(delimiter.split(template)):
|
||||
part = part.replace('\\'.join(list(start)), start)
|
||||
part = part.replace('\\'.join(list(end)), end)
|
||||
if i % 2 == 0:
|
||||
if not part: continue
|
||||
part = part.replace('\\', '\\\\').replace('"', '\\"')
|
||||
part = '\t' * offset + 'emit("""%s""")' % part
|
||||
else:
|
||||
part = part.rstrip()
|
||||
if not part: continue
|
||||
if part.lstrip().startswith(':'):
|
||||
if not offset:
|
||||
raise SyntaxError('no block statement to terminate: ${%s}$' % part)
|
||||
offset -= 1
|
||||
part = part.lstrip()[1:]
|
||||
if not part.endswith(':'): continue
|
||||
elif self.auto_emit.match(part.lstrip()):
|
||||
part = 'emit(%s)' % part.lstrip()
|
||||
lines = part.splitlines()
|
||||
margin = min(len(l) - len(l.lstrip()) for l in lines if l.strip())
|
||||
part = '\n'.join('\t' * offset + l[margin:] for l in lines)
|
||||
if part.endswith(':'):
|
||||
offset += 1
|
||||
tokens.append(part)
|
||||
if offset:
|
||||
raise SyntaxError('%i block statement(s) not terminated' % offset)
|
||||
self.__code = compile('\n'.join(tokens), '<templite %r>' % template[:20], 'exec')
|
||||
|
||||
def render(self, __namespace=None, **kw):
|
||||
"""
|
||||
renders the template according to the given namespace.
|
||||
__namespace - a dictionary serving as a namespace for evaluation
|
||||
**kw - keyword arguments which are added to the namespace
|
||||
"""
|
||||
namespace = {}
|
||||
if __namespace: namespace.update(__namespace)
|
||||
if kw: namespace.update(kw)
|
||||
namespace['emit'] = self.write
|
||||
|
||||
__stdout = sys.stdout
|
||||
sys.stdout = self
|
||||
self.__output = []
|
||||
eval(self.__code, namespace)
|
||||
sys.stdout = __stdout
|
||||
return ''.join(self.__output)
|
||||
|
||||
def write(self, *args):
|
||||
for a in args:
|
||||
self.__output.append(str(a))
|
Loading…
x
Reference in New Issue
Block a user