summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorR. Tyler Ballance <tyler@monkeypox.org>2009-12-19 15:27:36 -0800
committerR. Tyler Ballance <tyler@monkeypox.org>2009-12-19 15:27:36 -0800
commita753724d68880488a27141d1e4f174880d39e154 (patch)
tree26cdf49d72e1b967e9e072a9f7a291e971f86468
parent7b1c2ad9f4e5830b5c88d95715014bb4044c090c (diff)
parentffa377c79b12f6b3674cba2267f186d37078d0d7 (diff)
downloadpython-cheetah-a753724d68880488a27141d1e4f174880d39e154.tar.gz
Merge branch 'next'
-rw-r--r--CHANGES9
-rw-r--r--SetupConfig.py7
-rw-r--r--SetupTools.py24
-rw-r--r--bin/cheetah-analyze6
-rwxr-xr-xbuildandrun66
-rw-r--r--cheetah/CacheRegion.py2
-rw-r--r--cheetah/CacheStore.py4
-rw-r--r--cheetah/CheetahWrapper.py19
-rw-r--r--cheetah/Compiler.py142
-rw-r--r--cheetah/DirectiveAnalyzer.py98
-rw-r--r--cheetah/DummyTransaction.py2
-rw-r--r--cheetah/FileUtils.py54
-rw-r--r--cheetah/Filters.py30
-rwxr-xr-xcheetah/ImportHooks.py4
-rwxr-xr-xcheetah/ImportManager.py54
-rw-r--r--cheetah/NameMapper.py24
-rw-r--r--cheetah/Parser.py237
-rw-r--r--cheetah/Servlet.py2
-rw-r--r--cheetah/SettingsManager.py24
-rw-r--r--cheetah/SourceReader.py19
-rw-r--r--cheetah/Template.py81
-rw-r--r--cheetah/TemplateCmdLineIface.py14
-rw-r--r--cheetah/Templates/SkeletonPage.py30
-rw-r--r--cheetah/Templates/_SkeletonPage.py32
-rw-r--r--cheetah/Tests/Analyzer.py29
-rw-r--r--cheetah/Tests/CheetahWrapper.py33
-rw-r--r--cheetah/Tests/Filters.py8
-rw-r--r--cheetah/Tests/Misc.py20
-rw-r--r--cheetah/Tests/NameMapper.py72
-rw-r--r--cheetah/Tests/Parser.py49
-rw-r--r--cheetah/Tests/Performance.py16
-rw-r--r--cheetah/Tests/Regressions.py2
-rw-r--r--cheetah/Tests/SyntaxAndOutput.py96
-rw-r--r--cheetah/Tests/Template.py12
-rwxr-xr-xcheetah/Tests/Test.py22
-rw-r--r--cheetah/Tests/Unicode.py41
-rw-r--r--cheetah/Tests/VerifyType.py158
-rwxr-xr-xcheetah/Tests/unittest_local_copy.py978
-rw-r--r--cheetah/Tests/xmlrunner.py4
-rw-r--r--cheetah/Tools/CGITemplate.py2
-rw-r--r--cheetah/Tools/MondoReport.py25
-rw-r--r--cheetah/Tools/SiteHierarchy.py288
-rw-r--r--cheetah/Tools/turbocheetah/cheetahsupport.py8
-rw-r--r--cheetah/Utils/Misc.py22
-rw-r--r--cheetah/Utils/VerifyType.py83
-rw-r--r--cheetah/Utils/memcache.py58
-rw-r--r--cheetah/Utils/statprof.py18
-rw-r--r--cheetah/Version.py16
-rw-r--r--cheetah/c/_verifytype.c107
-rw-r--r--www/conf.py2
-rw-r--r--www/dev_guide/bnf.rst6
-rw-r--r--www/dev_guide/cache.rst404
-rw-r--r--www/dev_guide/comments.rst103
-rw-r--r--www/dev_guide/compiler.rst8
-rw-r--r--www/dev_guide/design.rst104
-rw-r--r--www/dev_guide/errorHandling.rst329
-rw-r--r--www/dev_guide/files.rst11
-rw-r--r--www/dev_guide/flowControl.rst394
-rw-r--r--www/dev_guide/history.rst94
-rw-r--r--www/dev_guide/index.rst30
-rw-r--r--www/dev_guide/inheritanceEtc.rst255
-rw-r--r--www/dev_guide/introduction.rst28
-rw-r--r--www/dev_guide/output.rst315
-rw-r--r--www/dev_guide/parser.rst9
-rw-r--r--www/dev_guide/parserInstructions.rst67
-rw-r--r--www/dev_guide/patching.rst149
-rw-r--r--www/dev_guide/placeholders.rst489
-rw-r--r--www/dev_guide/pyModules.rst252
-rw-r--r--www/dev_guide/safeDelegation.rst40
-rw-r--r--www/dev_guide/template.rst11
-rw-r--r--www/documentation.rst4
-rw-r--r--www/download.rst10
-rw-r--r--www/index.rst6
-rw-r--r--www/roadmap.rst27
-rw-r--r--www/users_guide/comments.rst101
-rw-r--r--www/users_guide/comparisons.rst515
-rw-r--r--www/users_guide/editors.rst37
-rw-r--r--www/users_guide/errorHandling.rst144
-rw-r--r--www/users_guide/examples.rst27
-rw-r--r--www/users_guide/flowControl.rst436
-rw-r--r--www/users_guide/gettingStarted.rst278
-rw-r--r--www/users_guide/glossary.rst99
-rw-r--r--www/users_guide/howItWorks.rst0
-rw-r--r--www/users_guide/index.rst28
-rw-r--r--www/users_guide/inheritanceEtc.rst517
-rw-r--r--www/users_guide/intro.rst313
-rw-r--r--www/users_guide/language.rst741
-rw-r--r--www/users_guide/libraries.rst315
-rw-r--r--www/users_guide/links.rst142
-rw-r--r--www/users_guide/nonHtml.rst16
-rw-r--r--www/users_guide/optikLicense.rst48
-rw-r--r--www/users_guide/otherHtml.rst101
-rw-r--r--www/users_guide/output.rst468
-rw-r--r--www/users_guide/parserInstructions.rst129
-rw-r--r--www/users_guide/tipsAndTricks.rst586
-rw-r--r--www/users_guide/webware.rst598
96 files changed, 9798 insertions, 2139 deletions
diff --git a/CHANGES b/CHANGES
index 1aff910..b87779b 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,12 @@
+2.4.1 (December 19th, 2009)
+ - --quiet flag added to `cheetah` to silence printing to stdout (abbeyj)
+ - Refactoring to minimize the amount of forked code for Python3 (rtyler)
+ - Template.compile() will no longer create class names with numerous leading
+ underscores (rtyler; reported by Kirill Uhanov)
+ - DirectiveAnalyzer (cheetah-analyze script) added to report directive usage in templates (rtyler)
+ - Older LaTeX docs converted to rst for Sphinx (rtyler)
+ - Prevent #raw blocks from evaluating $-placeholders and escaped strings (karmix0)
+ - New tests added to verify PSP behavior and other untested internals (rtyler)
2.4.0 (October 24th, 2009)
- Fix a major performance regression in Template.__init__()
diff --git a/SetupConfig.py b/SetupConfig.py
index c95001d..5620416 100644
--- a/SetupConfig.py
+++ b/SetupConfig.py
@@ -49,9 +49,10 @@ ext_modules=[
]
## Data Files and Scripts
-scripts = ['bin/cheetah-compile',
+scripts = ('bin/cheetah-compile',
'bin/cheetah',
- ]
+ 'bin/cheetah-analyze',
+ )
data_files = ['recursive: cheetah *.tmpl *.txt LICENSE README TODO CHANGES',]
@@ -71,7 +72,7 @@ if not os.getenv('CHEETAH_INSTALL_WITHOUT_SETUPTOOLS'):
]
}
except ImportError:
- print 'Not using setuptools, so we cannot install the Markdown dependency'
+ print('Not using setuptools, so we cannot install the Markdown dependency')
description = "Cheetah is a template engine and code generation tool."
diff --git a/SetupTools.py b/SetupTools.py
index d08eef2..788f81b 100644
--- a/SetupTools.py
+++ b/SetupTools.py
@@ -77,8 +77,8 @@ class mod_install_data(install_data):
data_files = self.get_inputs()
for entry in data_files:
- if type(entry) != types.StringType:
- raise ValueError, 'The entries in "data_files" must be strings'
+ if not isinstance(entry, basestring):
+ raise ValueError('The entries in "data_files" must be strings')
entry = string.join(string.split(entry, '/'), os.sep)
# entry is a filename or glob pattern
@@ -137,13 +137,7 @@ def run_setup(configurations):
for configuration in configurations:
kws.update(vars(configuration))
for name, value in kws.items():
- if name[:1] == '_' or \
- type(value) not in (types.StringType,
- types.ListType,
- types.TupleType,
- types.DictType,
- types.IntType,
- ):
+ if name[:1] == '_' or not isinstance(value, (basestring, list, tuple, dict, int)):
del kws[name]
# Add setup extensions
@@ -158,14 +152,14 @@ def run_setup(configurations):
try:
apply(setup, (), kws)
except BuildFailed, x:
- print "One or more C extensions failed to build."
- print "Details: %s" % x
- print "Retrying without C extensions enabled."
+ print("One or more C extensions failed to build.")
+ print("Details: %s" % x)
+ print("Retrying without C extensions enabled.")
del kws['ext_modules']
apply(setup, (), kws)
- print "One or more C extensions failed to build."
- print "Performance enhancements will not be available."
- print "Pure Python installation succeeded."
+ print("One or more C extensions failed to build.")
+ print("Performance enhancements will not be available.")
+ print("Pure Python installation succeeded.")
diff --git a/bin/cheetah-analyze b/bin/cheetah-analyze
new file mode 100644
index 0000000..097db5f
--- /dev/null
+++ b/bin/cheetah-analyze
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+
+from Cheetah import DirectiveAnalyzer
+
+if __name__ == '__main__':
+ DirectiveAnalyzer.main()
diff --git a/buildandrun b/buildandrun
new file mode 100755
index 0000000..d3b4e42
--- /dev/null
+++ b/buildandrun
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+
+import logging
+import os
+import os.path
+import subprocess
+import sys
+
+from optparse import OptionParser
+
+if os.getenv('DEBUG'):
+ logging.basicConfig(level=logging.DEBUG)
+
+def recursiverm(d):
+ for root, dirs, files in os.walk(d):
+ for f in files:
+ f = root + os.path.sep + f
+ logging.debug('Removing file: %s' % f)
+ os.unlink(f)
+
+def main():
+ op = OptionParser()
+ op.add_option('-c', '--clean', dest='clean', action='store_true',
+ help='Remove the contents of the build directory')
+ opts, args = op.parse_args()
+ if not args:
+ print('Specify a test script')
+ return
+
+
+ if opts.clean:
+ logging.debug('Removing build/')
+ recursiverm('build')
+
+ logging.debug('Building Cheetah')
+ rc = subprocess.call(('python', 'setup.py', 'build'))
+
+ if rc:
+ logging.debug('Build failed!')
+ return
+
+ logging.debug('Adjusting PATH and PYTHONPATH')
+ cwd = os.getcwd()
+
+ libdir = None
+ scriptdir = None
+
+ for sub in os.listdir('build'):
+ if sub.startswith('scripts'):
+ scriptdir = os.path.sep.join((cwd, 'build', sub))
+ if sub.startswith('lib'):
+ libdir = os.path.sep.join((cwd, 'build', sub))
+
+ newpath = '%s:%s' % (scriptdir, os.getenv('PATH'))
+ logging.debug('Setting PATH to: %s' % newpath)
+ os.putenv('PATH', newpath)
+ logging.debug('Setting PYTHONPATH to: %s' % libdir)
+ os.putenv('PYTHONPATH', libdir)
+
+ rc = subprocess.call( ['python',] + args )
+
+
+
+if __name__ == '__main__':
+ main()
+
diff --git a/cheetah/CacheRegion.py b/cheetah/CacheRegion.py
index dd0d099..2586b72 100644
--- a/cheetah/CacheRegion.py
+++ b/cheetah/CacheRegion.py
@@ -128,7 +128,7 @@ class CacheRegion(object):
"""
cacheItemID = md5(str(cacheItemID)).hexdigest()
- if not self._cacheItems.has_key(cacheItemID):
+ if cacheItemID not in self._cacheItems:
cacheItem = self._cacheItemClass(
cacheItemID=cacheItemID, cacheStore=self._wrappedCacheDataStore)
self._cacheItems[cacheItemID] = cacheItem
diff --git a/cheetah/CacheStore.py b/cheetah/CacheStore.py
index 9c41656..8f57f33 100644
--- a/cheetah/CacheStore.py
+++ b/cheetah/CacheStore.py
@@ -46,12 +46,12 @@ class MemoryCacheStore(AbstractCacheStore):
self._data[key] = (val, time)
def add(self, key, val, time=0):
- if self._data.has_key(key):
+ 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 self._data.has_key(key):
+ if key in self._data:
raise Error('a value for key %r is already in the cache'%key)
self._data[key] = (val, time)
diff --git a/cheetah/CheetahWrapper.py b/cheetah/CheetahWrapper.py
index a7fef80..6203e0b 100644
--- a/cheetah/CheetahWrapper.py
+++ b/cheetah/CheetahWrapper.py
@@ -161,7 +161,8 @@ class CheetahWrapper(object):
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='Verbosely print informational messages to stdout')
+ 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')
@@ -194,14 +195,14 @@ Files are %s""", args, pprint.pformat(vars(opts)), files)
if opts.print_settings:
- print
- print '>> Available Cheetah compiler 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
+ print('\t%s (default: "%s")\t%s' % l)
sys.exit(0)
#cleanup trailing path separators
@@ -222,7 +223,6 @@ Files are %s""", args, pprint.pformat(vars(opts)), files)
unpickled = pickle.load(f)
f.close()
self.searchList.insert(0, unpickled)
- opts.verbose = not opts.stdout
##################################################
## COMMAND METHODS
@@ -266,7 +266,7 @@ you do have write permission to and re-run the tests.""")
runner.run(unittest.TestSuite(Test.suites))
def version(self):
- print Version
+ print(Version)
# If you add a command, also add it to the 'meths' variable in main().
@@ -387,12 +387,11 @@ you do have write permission to and re-run the tests.""")
isError = False
dstSources = {}
for b in bundles:
- if dstSources.has_key(b.dst):
+ if b.dst in dstSources:
dstSources[b.dst].append(b.src)
else:
dstSources[b.dst] = [b.src]
- keys = dstSources.keys()
- keys.sort()
+ keys = sorted(dstSources.keys())
for dst in keys:
sources = dstSources[dst]
if len(sources) > 1:
@@ -537,7 +536,7 @@ you do have write permission to and re-run the tests.""")
return kws
if self.opts.compilerSettingsString:
try:
- exec 'settings = getkws(%s)'%self.opts.compilerSettingsString
+ 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"
diff --git a/cheetah/Compiler.py b/cheetah/Compiler.py
index 39c7f51..8946710 100644
--- a/cheetah/Compiler.py
+++ b/cheetah/Compiler.py
@@ -25,8 +25,8 @@ from Cheetah.Utils.Indenter import indentize # an undocumented preprocessor
from Cheetah import ErrorCatchers
from Cheetah import NameMapper
from Cheetah.Parser import Parser, ParseError, specialVarRE, \
- STATIC_CACHE, REFRESH_CACHE, SET_LOCAL, SET_GLOBAL,SET_MODULE, \
- unicodeDirectiveRE, encodingDirectiveRE,escapedNewlineRE
+ STATIC_CACHE, REFRESH_CACHE, SET_LOCAL, SET_GLOBAL, SET_MODULE, \
+ unicodeDirectiveRE, encodingDirectiveRE, escapedNewlineRE
from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList
VFFSL=valueFromFrameOrSearchList
@@ -56,8 +56,8 @@ _DEFAULT_COMPILER_SETTINGS = [
('commentOffset', 1, ''),
('outputRowColComments', True, ''),
('includeBlockMarkers', False, 'Wrap #block\'s in a comment in the template\'s output'),
- ('blockMarkerStart', ('\n<!-- START BLOCK: ',' -->\n'), ''),
- ('blockMarkerEnd', ('\n<!-- END BLOCK: ',' -->\n'), ''),
+ ('blockMarkerStart', ('\n<!-- START BLOCK: ', ' -->\n'), ''),
+ ('blockMarkerEnd', ('\n<!-- END BLOCK: ', ' -->\n'), ''),
('defDocStrMsg', 'Autogenerated by Cheetah: The Python-Powered Template Engine', ''),
('setup__str__method', False, ''),
('mainMethodName', 'respond', ''),
@@ -379,13 +379,13 @@ class MethodCompiler(GenUtils):
ind = self._indent*2
docStr = (ind + '"""\n' + ind +
- ('\n' + ind).join([ln.replace('"""',"'''") for ln in self._docStringLines]) +
+ ('\n' + ind).join([ln.replace('"""', "'''") for ln in self._docStringLines]) +
'\n' + ind + '"""\n')
return docStr
## methods for adding code
def addMethDocString(self, line):
- self._docStringLines.append(line.replace('%','%%'))
+ self._docStringLines.append(line.replace('%', '%%'))
def addChunk(self, chunk):
self.commitStrConst()
@@ -418,7 +418,7 @@ class MethodCompiler(GenUtils):
self.addChunk("if _v is not None: write(str(_v))")
else:
if self.setting('useFilters'):
- self.addChunk("write(_filter(%s%s))"%(chunk,filterArgs))
+ self.addChunk("write(_filter(%s%s))"%(chunk, filterArgs))
else:
self.addChunk("write(str(%s))"%chunk)
@@ -428,48 +428,35 @@ class MethodCompiler(GenUtils):
else:
self._pendingStrConstChunks = [strConst]
- def _unescapeCheetahVars(self, theString):
- """Unescape any escaped Cheetah \$vars in the string.
- """
-
- token = self.setting('cheetahVarStartToken')
- return theString.replace('\\' + token, token)
-
- def _unescapeDirectives(self, theString):
- """Unescape any escaped Cheetah \$vars in the string.
- """
-
- token = self.setting('directiveStartToken')
- return theString.replace('\\' + token, token)
-
def commitStrConst(self):
"""Add the code for outputting the pending strConst without chopping off
any whitespace from it.
"""
- if self._pendingStrConstChunks:
- strConst = self._unescapeCheetahVars(''.join(self._pendingStrConstChunks))
- strConst = self._unescapeDirectives(strConst)
- self._pendingStrConstChunks = []
- if not strConst:
- return
- else:
- reprstr = repr(strConst).replace('\\012','\n')
- i = 0
- out = []
- if reprstr.startswith('u'):
- i = 1
- out = ['u']
- body = escapedNewlineRE.sub('\n', reprstr[i+1:-1])
-
- if reprstr[i]=="'":
- out.append("'''")
- out.append(body)
- out.append("'''")
- else:
- out.append('"""')
- out.append(body)
- out.append('"""')
- self.addWriteChunk(''.join(out))
+ if not self._pendingStrConstChunks:
+ return
+
+ strConst = ''.join(self._pendingStrConstChunks)
+ self._pendingStrConstChunks = []
+ if not strConst:
+ return
+
+ reprstr = repr(strConst)
+ i = 0
+ out = []
+ if reprstr.startswith('u'):
+ i = 1
+ out = ['u']
+ body = escapedNewlineRE.sub('\\1\n', reprstr[i+1:-1])
+
+ if reprstr[i]=="'":
+ out.append("'''")
+ out.append(body)
+ out.append("'''")
+ else:
+ out.append('"""')
+ out.append(body)
+ out.append('"""')
+ self.addWriteChunk(''.join(out))
def handleWSBeforeDirective(self):
"""Truncate the pending strCont to the beginning of the current line.
@@ -545,7 +532,7 @@ class MethodCompiler(GenUtils):
splitPos2 = LVALUE.find('[')
if splitPos1 > 0 and splitPos2==-1:
splitPos = splitPos1
- elif splitPos1 > 0 and splitPos1 < max(splitPos2,0):
+ elif splitPos1 > 0 and splitPos1 < max(splitPos2, 0):
splitPos = splitPos1
else:
splitPos = splitPos2
@@ -579,7 +566,7 @@ class MethodCompiler(GenUtils):
def addRepeat(self, expr, lineCol=None):
#the _repeatCount stuff here allows nesting of #repeat directives
self._repeatCount = getattr(self, "_repeatCount", -1) + 1
- self.addFor('for __i%s in range(%s)' % (self._repeatCount,expr), lineCol=lineCol)
+ self.addFor('for __i%s in range(%s)' % (self._repeatCount, expr), lineCol=lineCol)
def addIndentingDirective(self, expr, lineCol=None):
if expr and not expr[-1] == ':':
@@ -623,7 +610,7 @@ class MethodCompiler(GenUtils):
self.dedent()
def addElse(self, expr, dedent=True, lineCol=None):
- expr = re.sub(r'else[ \f\t]+if','elif', expr)
+ expr = re.sub(r'else[ \f\t]+if', 'elif', expr)
self.addReIndentingDirective(expr, dedent=dedent, lineCol=lineCol)
def addElif(self, expr, dedent=True, lineCol=None):
@@ -660,7 +647,7 @@ class MethodCompiler(GenUtils):
def addYield(self, expr):
assert not self._hasReturnStatement
self._isGenerator = True
- if expr.replace('yield','').strip():
+ if expr.replace('yield', '').strip():
self.addChunk(expr)
else:
self.addChunk('if _dummyTrans:')
@@ -727,9 +714,9 @@ class MethodCompiler(GenUtils):
# @@TR: we should add some runtime logging to this
ID = self.nextCacheID()
- interval = cacheInfo.get('interval',None)
- test = cacheInfo.get('test',None)
- customID = cacheInfo.get('id',None)
+ interval = cacheInfo.get('interval', None)
+ test = cacheInfo.get('test', None)
+ customID = cacheInfo.get('id', None)
if customID:
ID = customID
varyBy = cacheInfo.get('varyBy', repr(ID))
@@ -896,7 +883,7 @@ class MethodCompiler(GenUtils):
captureDetails.assignTo = assignTo
captureDetails.lineCol = lineCol
- self._captureRegionsStack.append((ID,captureDetails)) # attrib of current methodCompiler
+ self._captureRegionsStack.append((ID, captureDetails)) # attrib of current methodCompiler
self.addChunk('## START CAPTURE REGION: '+ID
+' '+assignTo
+' at line %s, col %s'%lineCol + ' in the source.')
@@ -982,13 +969,13 @@ class AutoMethodCompiler(MethodCompiler):
def _setupState(self):
MethodCompiler._setupState(self)
- self._argStringList = [ ("self",None) ]
+ self._argStringList = [ ("self", None) ]
self._streamingEnabled = True
self._isClassMethod = None
self._isStaticMethod = None
def _useKWsDictArgForPassingTrans(self):
- alreadyHasTransArg = [argname for argname,defval in self._argStringList
+ alreadyHasTransArg = [argname for argname, defval in self._argStringList
if argname=='trans']
return (self.methodName()!='respond'
and not alreadyHasTransArg
@@ -1015,12 +1002,12 @@ class AutoMethodCompiler(MethodCompiler):
if self._streamingEnabled:
kwargsName = None
positionalArgsListName = None
- for argname,defval in self._argStringList:
+ for argname, defval in self._argStringList:
if argname.strip().startswith('**'):
- kwargsName = argname.strip().replace('**','')
+ kwargsName = argname.strip().replace('**', '')
break
elif argname.strip().startswith('*'):
- positionalArgsListName = argname.strip().replace('*','')
+ positionalArgsListName = argname.strip().replace('*', '')
if not kwargsName and self._useKWsDictArgForPassingTrans():
kwargsName = 'KWS'
@@ -1100,7 +1087,7 @@ class AutoMethodCompiler(MethodCompiler):
self.addChunk('return _dummyTrans and trans.response().getvalue() or ""')
def addMethArg(self, name, defVal=None):
- self._argStringList.append( (name,defVal) )
+ self._argStringList.append( (name, defVal) )
def methodSignature(self):
argStringChunks = []
@@ -1136,7 +1123,7 @@ if not self._CHEETAH__instanceInitialized:
for k,v in KWs.items():
if k in allowedKWs: cheetahKWArgs[k] = v
self._initCheetahInstance(**cheetahKWArgs)
-""".replace('\n','\n'+' '*8)
+""".replace('\n', '\n'+' '*8)
class ClassCompiler(GenUtils):
methodCompilerClass = AutoMethodCompiler
@@ -1173,14 +1160,14 @@ class ClassCompiler(GenUtils):
from the methods of this class!!! or you will be assigning to attributes
of this object instead."""
- if self.__dict__.has_key(name):
+ if name in self.__dict__:
return self.__dict__[name]
elif hasattr(self.__class__, name):
return getattr(self.__class__, name)
elif self._activeMethodsList and hasattr(self._activeMethodsList[-1], name):
return getattr(self._activeMethodsList[-1], name)
else:
- raise AttributeError, name
+ raise AttributeError(name)
def _setupState(self):
self._classDef = None
@@ -1333,9 +1320,9 @@ class ClassCompiler(GenUtils):
self._decoratorsForNextMethod.append(decoratorExpr)
def addClassDocString(self, line):
- self._classDocStringLines.append( line.replace('%','%%'))
+ self._classDocStringLines.append( line.replace('%', '%%'))
- def addChunkToInit(self,chunk):
+ def addChunkToInit(self, chunk):
self._initMethChunks.append(chunk)
def addAttribute(self, attribExpr):
@@ -1364,7 +1351,7 @@ class ClassCompiler(GenUtils):
'super(%(className)s, self).%(methodName)s(%(argString)s)'%locals())
def addErrorCatcherCall(self, codeChunk, rawCode='', lineCol=''):
- if self._placeholderToErrorCatcherMap.has_key(rawCode):
+ if rawCode in self._placeholderToErrorCatcherMap:
methodName = self._placeholderToErrorCatcherMap[rawCode]
if not self.setting('outputRowColComments'):
self._methodsIndex[methodName].addMethDocString(
@@ -1601,14 +1588,14 @@ class ModuleCompiler(SettingsManager, GenUtils):
from the methods of this class!!! or you will be assigning to attributes
of this object instead.
"""
- if self.__dict__.has_key(name):
+ if name in self.__dict__:
return self.__dict__[name]
elif hasattr(self.__class__, name):
return getattr(self.__class__, name)
elif self._activeClassesList and hasattr(self._activeClassesList[-1], name):
return getattr(self._activeClassesList[-1], name)
else:
- raise AttributeError, name
+ raise AttributeError(name)
def _initializeSettings(self):
self.updateSettings(copy.deepcopy(DEFAULT_COMPILER_SETTINGS))
@@ -1848,7 +1835,7 @@ class ModuleCompiler(SettingsManager, GenUtils):
self._getActiveClassCompiler().addAttribute(attribName + ' =' + expr)
def addComment(self, comm):
- if re.match(r'#+$',comm): # skip bar comments
+ if re.match(r'#+$', comm): # skip bar comments
return
specialVarMatch = specialVarRE.match(comm)
@@ -1935,14 +1922,14 @@ if not hasattr(%(mainClassName)s, '_initCheetahAttributes'):
templateAPIClass._addCheetahPlumbingCodeToClass(%(mainClassName)s)
%(footer)s
-""" % {'header':self.moduleHeader(),
- 'docstring':self.moduleDocstring(),
- 'specialVars':self.specialVars(),
- 'imports':self.importStatements(),
- 'constants':self.moduleConstants(),
- 'classes':self.classDefs(),
- 'footer':self.moduleFooter(),
- 'mainClassName':self._mainClassName,
+""" % {'header': self.moduleHeader(),
+ 'docstring': self.moduleDocstring(),
+ 'specialVars': self.specialVars(),
+ 'imports': self.importStatements(),
+ 'constants': self.moduleConstants(),
+ 'classes': self.classDefs(),
+ 'footer': self.moduleFooter(),
+ 'mainClassName': self._mainClassName,
}
self._moduleDef = moduleDef
@@ -1976,8 +1963,7 @@ if not hasattr(%(mainClassName)s, '_initCheetahAttributes'):
def specialVars(self):
chunks = []
theVars = self._specialVars
- keys = theVars.keys()
- keys.sort()
+ keys = sorted(theVars.keys())
for key in keys:
chunks.append(key + ' = ' + repr(theVars[key]) )
return '\n'.join(chunks)
diff --git a/cheetah/DirectiveAnalyzer.py b/cheetah/DirectiveAnalyzer.py
new file mode 100644
index 0000000..a9f9387
--- /dev/null
+++ b/cheetah/DirectiveAnalyzer.py
@@ -0,0 +1,98 @@
+#!/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()
+
diff --git a/cheetah/DummyTransaction.py b/cheetah/DummyTransaction.py
index f84ade4..72f8662 100644
--- a/cheetah/DummyTransaction.py
+++ b/cheetah/DummyTransaction.py
@@ -92,7 +92,7 @@ class TransformerResponse(DummyResponse):
output = super(TransformerResponse, self).getvalue(**kwargs)
if self._filter:
_filter = self._filter
- if isinstance(_filter, types.TypeType):
+ if isinstance(_filter, type):
_filter = _filter()
return _filter.filter(output)
return output
diff --git a/cheetah/FileUtils.py b/cheetah/FileUtils.py
index 97e77f8..c4e65f3 100644
--- a/cheetah/FileUtils.py
+++ b/cheetah/FileUtils.py
@@ -1,30 +1,14 @@
-# $Id: FileUtils.py,v 1.12 2005/11/02 22:26:07 tavis_rudd Exp $
-"""File utitilies for Python:
-
-Meta-Data
-================================================================================
-Author: Tavis Rudd <tavis@damnsimple.com>
-License: This software is released for unlimited distribution under the
- terms of the MIT license. See the LICENSE file.
-Version: $Revision: 1.12 $
-Start Date: 2001/09/26
-Last Revision Date: $Date: 2005/11/02 22:26:07 $
-"""
-__author__ = "Tavis Rudd <tavis@damnsimple.com>"
-__revision__ = "$Revision: 1.12 $"[11:-2]
-
from glob import glob
import os
from os import listdir
import os.path
import re
-from types import StringType
from tempfile import mktemp
def _escapeRegexChars(txt,
escapeRE=re.compile(r'([\$\^\*\+\.\?\{\}\[\]\(\)\|\\])')):
- return escapeRE.sub(r'\\\1' , txt)
+ return escapeRE.sub(r'\\\1', txt)
def findFiles(*args, **kw):
"""Recursively find all the files matching a glob pattern.
@@ -70,7 +54,7 @@ class FileFinder:
def __init__(self, rootPath,
globPatterns=('*',),
- ignoreBasenames=('CVS','.svn'),
+ ignoreBasenames=('CVS', '.svn'),
ignoreDirs=(),
):
@@ -220,7 +204,7 @@ class _GenSubberFunc:
return "def subber(m):\n\treturn ''.join([%s])\n" % (self.codeBody())
def subberFunc(self):
- exec self.code()
+ exec(self.code())
return subber
@@ -238,11 +222,11 @@ class FindAndReplace:
recordResults=True):
- if type(patternOrRE) == StringType:
+ if isinstance(patternOrRE, basestring):
self._regex = re.compile(patternOrRE)
else:
self._regex = patternOrRE
- if type(replacement) == StringType:
+ if isinstance(replacement, basestring):
self._subber = _GenSubberFunc(replacement).subberFunc()
else:
self._subber = replacement
@@ -279,7 +263,7 @@ class FindAndReplace:
self._currFile = file
found = False
- if locals().has_key('orig'):
+ if 'orig' in locals():
del orig
if self._usePgrep:
if os.popen('pgrep "' + pattern + '" ' + file ).read():
@@ -289,23 +273,23 @@ class FindAndReplace:
if regex.search(orig):
found = True
if found:
- if not locals().has_key('orig'):
+ 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 not self._results.has_key(self._currFile):
+ 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(),
+ res['matches'].append({'contents': match.group(),
+ 'start': match.start(),
+ 'end': match.end(),
}
)
return self._subber(match)
@@ -337,10 +321,10 @@ class SourceFileStats:
commentLines += fileStats['commentLines']
totalLines += fileStats['totalLines']
- stats = {'codeLines':codeLines,
- 'blankLines':blankLines,
- 'commentLines':commentLines,
- 'totalLines':totalLines,
+ stats = {'codeLines': codeLines,
+ 'blankLines': blankLines,
+ 'commentLines': commentLines,
+ 'totalLines': totalLines,
}
return stats
@@ -364,10 +348,10 @@ class SourceFileStats:
else:
codeLines += 1
- stats = {'codeLines':codeLines,
- 'blankLines':blankLines,
- 'commentLines':commentLines,
- 'totalLines':totalLines,
+ stats = {'codeLines': codeLines,
+ 'blankLines': blankLines,
+ 'commentLines': commentLines,
+ 'totalLines': totalLines,
}
return stats
diff --git a/cheetah/Filters.py b/cheetah/Filters.py
index d452439..47858b1 100644
--- a/cheetah/Filters.py
+++ b/cheetah/Filters.py
@@ -65,9 +65,9 @@ class Markdown(EncodeUnicode):
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'
+ 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)
@@ -97,8 +97,8 @@ class CodeHighlighter(EncodeUnicode):
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'
+ 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
@@ -121,7 +121,7 @@ class MaxLen(Filter):
"""Replace None with '' and cut off at maxlen."""
output = super(MaxLen, self).filter(val, **kw)
- if kw.has_key('maxlen') and len(output) > kw['maxlen']:
+ if 'maxlen' in kw and len(output) > kw['maxlen']:
return output[:kw['maxlen']]
return output
@@ -135,7 +135,7 @@ class WebSafe(Filter):
s = s.replace("<", "&lt;")
s = s.replace(">", "&gt;")
# Process the additional transformations if any.
- if kw.has_key('also'):
+ if 'also' in kw:
also = kw['also']
entities = webSafeEntities # Global variable.
for k in also:
@@ -166,7 +166,7 @@ class Strip(Filter):
s = super(Strip, self).filter(val, **kw)
result = []
start = 0 # The current line will be s[start:end].
- while 1: # Loop through each line.
+ while True: # Loop through each line.
end = s.find('\n', start) # Find next newline.
if end == -1: # If no more newlines.
break
@@ -196,15 +196,15 @@ class StripSqueeze(Filter):
def test():
s1 = "abc <=> &"
s2 = " asdf \n\t 1 2 3\n"
- print "WebSafe INPUT:", `s1`
- print " WebSafe:", `WebSafe().filter(s1)`
+ print("WebSafe INPUT:", repr(s1))
+ print(" WebSafe:", repr(WebSafe().filter(s1)))
- print
- print " Strip INPUT:", `s2`
- print " Strip:", `Strip().filter(s2)`
- print "StripSqueeze:", `StripSqueeze().filter(s2)`
+ print()
+ print(" Strip INPUT:", repr(s2))
+ print(" Strip:", repr(Strip().filter(s2)))
+ print("StripSqueeze:", repr(StripSqueeze().filter(s2)))
- print "Unicode:", `EncodeUnicode().filter(u'aoeu12345\u1234')`
+ print("Unicode:", repr(EncodeUnicode().filter(u'aoeu12345\u1234')))
if __name__ == "__main__":
test()
diff --git a/cheetah/ImportHooks.py b/cheetah/ImportHooks.py
index 261fb01..a29dfed 100755
--- a/cheetah/ImportHooks.py
+++ b/cheetah/ImportHooks.py
@@ -114,7 +114,7 @@ def install(templateFileExtensions=('.tmpl',)):
if not _installed:
CheetahDirOwner.templateFileExtensions = templateFileExtensions
import __builtin__
- if type(__builtin__.__import__) == types.BuiltinFunctionType:
+ if isinstance(__builtin__.__import__, types.BuiltinFunctionType):
global __oldimport__
__oldimport__ = __builtin__.__import__
ImportManager._globalOwnerTypes.insert(0, CheetahDirOwner)
@@ -129,7 +129,7 @@ def uninstall():
global _installed
if not _installed:
import __builtin__
- if type(__builtin__.__import__) == types.MethodType:
+ if isinstance(__builtin__.__import__, types.MethodType):
__builtin__.__import__ = __oldimport__
global _manager
del _manager
diff --git a/cheetah/ImportManager.py b/cheetah/ImportManager.py
index 743360e..a043cce 100755
--- a/cheetah/ImportManager.py
+++ b/cheetah/ImportManager.py
@@ -1,6 +1,5 @@
-# $Id: ImportManager.py,v 1.6 2007/04/03 01:56:24 tavis_rudd Exp $
-
-"""Provides an emulator/replacement for Python's standard import system.
+"""
+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
@@ -18,37 +17,14 @@ This is a hacked/documented version of Gordon McMillan's iu.py. I have:
- reorganized the code layout to enhance readability
-Meta-Data
-================================================================================
-Author: Tavis Rudd <tavis@damnsimple.com> based on Gordon McMillan's iu.py
-License: This software is released for unlimited distribution under the
- terms of the MIT license. See the LICENSE file.
-Version: $Revision: 1.6 $
-Start Date: 2001/03/30
-Last Revision Date: $Date: 2007/04/03 01:56:24 $
"""
-__author__ = "Tavis Rudd <tavis@damnsimple.com>"
-__revision__ = "$Revision: 1.6 $"[11:-2]
-
-##################################################
-## DEPENDENCIES
import sys
import imp
import marshal
-##################################################
-## CONSTANTS & GLOBALS
-
-try:
- True,False
-except NameError:
- True, False = (1==1),(1==0)
-
_installed = False
-STRINGTYPE = type('')
-
# _globalOwnerTypes is defined at the bottom of this file
_os_stat = _os_path_join = _os_getcwd = _os_path_dirname = None
@@ -85,7 +61,7 @@ def _os_bootstrap():
a = a + ':'
return a + b
else:
- raise ImportError, 'no os specific module found'
+ raise ImportError('no os specific module found')
if join is None:
def join(a, b, sep=sep):
@@ -184,7 +160,7 @@ class DirOwner(Owner):
if path == '':
path = _os_getcwd()
if not pathIsDir(path):
- raise ValueError, "%s is not a directory" % path
+ raise ValueError("%s is not a directory" % path)
Owner.__init__(self, path)
def getmod(self, nm,
@@ -217,14 +193,14 @@ class DirOwner(Owner):
break
if py is None and pyc is None:
return None
- while 1:
+ 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
+ print("Invalid syntax in %s" % py[0])
+ print(e.args)
raise
elif pyc:
stuff = open(pyc[0], 'rb').read()
@@ -260,7 +236,7 @@ class BuiltinImportDirector(ImportDirector):
def getmod(self, nm, isbuiltin=imp.is_builtin):
if isbuiltin(nm):
- mod = imp.load_module(nm, None, nm, ('','',imp.C_BUILTIN))
+ mod = imp.load_module(nm, None, nm, ('', '', imp.C_BUILTIN))
return mod
return None
@@ -273,7 +249,7 @@ class FrozenImportDirector(ImportDirector):
def getmod(self, nm,
isFrozen=imp.is_frozen, loadMod=imp.load_module):
if isFrozen(nm):
- mod = loadMod(nm, None, nm, ('','',imp.PY_FROZEN))
+ 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
@@ -345,7 +321,7 @@ class PathImportDirector(ImportDirector):
def getmod(self, nm):
mod = None
for thing in self.path:
- if type(thing) is STRINGTYPE:
+ if isinstance(thing, basestring):
owner = self._shadowPath.get(thing, -1)
if owner == -1:
owner = self._shadowPath[thing] = self._makeOwner(thing)
@@ -420,11 +396,11 @@ class ImportManager:
importernm = globals.get('__name__', '')
if importernm:
if hasattr(_sys_modules_get(importernm), '__path__'):
- contexts.insert(0,importernm)
+ contexts.insert(0, importernm)
else:
pkgnm = packageName(importernm)
if pkgnm:
- contexts.insert(0,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]
@@ -462,7 +438,7 @@ class ImportManager:
#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
+ 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:
@@ -487,7 +463,7 @@ class ImportManager:
if self.threaded:
self._release()
if not mod:
- raise ImportError, "%s not found in %s" % (nm, ctx)
+ raise ImportError("%s not found in %s" % (nm, ctx))
#print "importHook done with %s %s %s (case 3)" % (name, globals['__name__'], fromlist)
return bottommod
@@ -519,7 +495,7 @@ class ImportManager:
if hasattr(mod, '__co__'):
co = mod.__co__
del mod.__co__
- exec co in mod.__dict__
+ exec(co, mod.__dict__)
if fqname == 'thread' and not self.threaded:
## print "thread detected!"
self.setThreaded()
diff --git a/cheetah/NameMapper.py b/cheetah/NameMapper.py
index 3a6322e..12257db 100644
--- a/cheetah/NameMapper.py
+++ b/cheetah/NameMapper.py
@@ -138,7 +138,7 @@ Version: $Revision: 1.32 $
Start Date: 2001/04/03
Last Revision Date: $Date: 2007/12/10 19:20:09 $
"""
-from __future__ import generators
+
__author__ = "Tavis Rudd <tavis@damnsimple.com>," +\
"\nChuck Esterbrook <echuck@mindspring.com>"
__revision__ = "$Revision: 1.32 $"[11:-2]
@@ -211,7 +211,7 @@ def _isInstanceOrClass(obj):
def hasKey(obj, key):
"""Determine if 'obj' has 'key' """
- if hasattr(obj,'has_key') and obj.has_key(key):
+ if hasattr(obj, 'has_key') and key in obj:
return True
elif hasattr(obj, key):
return True
@@ -219,7 +219,7 @@ def hasKey(obj, key):
return False
def valueForKey(obj, key):
- if hasattr(obj, 'has_key') and obj.has_key(key):
+ if hasattr(obj, 'has_key') and key in obj:
return obj[key]
elif hasattr(obj, key):
return getattr(obj, key)
@@ -230,7 +230,7 @@ def _valueForName(obj, name, executeCallables=False):
nameChunks=name.split('.')
for i in range(len(nameChunks)):
key = nameChunks[i]
- if hasattr(obj, 'has_key') and obj.has_key(key):
+ if hasattr(obj, 'has_key') and key in obj:
nextObj = obj[key]
else:
try:
@@ -238,7 +238,7 @@ def _valueForName(obj, name, executeCallables=False):
except AttributeError:
_raiseNotFoundException(key, obj)
- if executeCallables and callable(nextObj) and not _isInstanceOrClass(nextObj):
+ if executeCallables and hasattr(nextObj, '__call__') and not _isInstanceOrClass(nextObj):
obj = nextObj()
else:
obj = nextObj
@@ -364,13 +364,13 @@ def example():
}
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)
+ print(valueForKey(a.dic, 'subDict'))
+ print(valueForName(a, 'dic.item'))
+ print(valueForName(vars(), 'b'))
+ print(valueForName(__builtins__, 'dir')())
+ print(valueForName(vars(), 'a.classVar'))
+ print(valueForName(vars(), 'a.dic.func', executeCallables=True))
+ print(valueForName(vars(), 'a.method2.item1', executeCallables=True))
if __name__ == '__main__':
example()
diff --git a/cheetah/Parser.py b/cheetah/Parser.py
index 7436e9c..8810ac8 100644
--- a/cheetah/Parser.py
+++ b/cheetah/Parser.py
@@ -1,21 +1,12 @@
-# $Id: Parser.py,v 1.137 2008/03/10 05:25:13 tavis_rudd Exp $
-"""Parser classes for Cheetah's Compiler
+"""
+Parser classes for Cheetah's Compiler
Classes:
ParseError( Exception )
_LowLevelParser( Cheetah.SourceReader.SourceReader ), basically a lexer
_HighLevelParser( _LowLevelParser )
Parser === _HighLevelParser (an alias)
-
-Meta-Data
-================================================================================
-Author: Tavis Rudd <tavis@damnsimple.com>
-Version: $Revision: 1.137 $
-Start Date: 2001/08/01
-Last Revision Date: $Date: 2008/03/10 05:25:13 $
"""
-__author__ = "Tavis Rudd <tavis@damnsimple.com>"
-__revision__ = "$Revision: 1.137 $"[11:-2]
import os
import sys
@@ -46,13 +37,13 @@ def escapeRegexChars(txt,
"""Return a txt with all special regular expressions chars escaped."""
- return escapeRE.sub(r'\\\1' , txt)
+ return escapeRE.sub(r'\\\1', txt)
def group(*choices): return '(' + '|'.join(choices) + ')'
def nongroup(*choices): return '(?:' + '|'.join(choices) + ')'
def namedGroup(name, *choices): return '(P:<' + name +'>' + '|'.join(choices) + ')'
-def any(*choices): return apply(group, choices) + '*'
-def maybe(*choices): return apply(group, choices) + '?'
+def any(*choices): return group(*choices) + '*'
+def maybe(*choices): return group(*choices) + '?'
##################################################
## CONSTANTS & GLOBALS ##
@@ -76,22 +67,22 @@ namechars = identchars + "0123456789"
#operators
powerOp = '**'
unaryArithOps = ('+', '-', '~')
-binaryArithOps = ('+', '-', '/', '//','%')
-shiftOps = ('>>','<<')
-bitwiseOps = ('&','|','^')
+binaryArithOps = ('+', '-', '/', '//', '%')
+shiftOps = ('>>', '<<')
+bitwiseOps = ('&', '|', '^')
assignOp = '='
-augAssignOps = ('+=','-=','/=','*=', '**=','^=','%=',
- '>>=','<<=','&=','|=', )
+augAssignOps = ('+=', '-=', '/=', '*=', '**=', '^=', '%=',
+ '>>=', '<<=', '&=', '|=', )
assignmentOps = (assignOp,) + augAssignOps
-compOps = ('<','>','==','!=','<=','>=', '<>', 'is', 'in',)
-booleanOps = ('and','or','not')
+compOps = ('<', '>', '==', '!=', '<=', '>=', '<>', 'is', 'in',)
+booleanOps = ('and', 'or', 'not')
operators = (powerOp,) + unaryArithOps + binaryArithOps \
+ shiftOps + bitwiseOps + assignmentOps \
+ compOps + booleanOps
-delimeters = ('(',')','{','}','[',']',
- ',','.',':',';','=','`') + augAssignOps
+delimeters = ('(', ')', '{', '}', '[', ']',
+ ',', '.', ':', ';', '=', '`') + augAssignOps
keywords = ('and', 'del', 'for', 'is', 'raise',
@@ -140,7 +131,7 @@ for start, end in tripleQuotedStringPairs.items():
WS = r'[ \f\t]*'
EOL = r'\r\n|\n|\r'
EOLZ = EOL + r'|\Z'
-escCharLookBehind = nongroup(r'(?<=\A)',r'(?<!\\)')
+escCharLookBehind = nongroup(r'(?<=\A)', r'(?<!\\)')
nameCharLookAhead = r'(?=[A-Za-z_])'
identRE=re.compile(r'[a-zA-Z_][a-zA-Z_0-9]*')
EOLre=re.compile(r'(?:\r\n|\r|\n)')
@@ -153,12 +144,12 @@ unicodeDirectiveRE = re.compile(
encodingDirectiveRE = re.compile(
r'(?:^|\r\n|\r|\n)\s*#\s{0,5}encoding[:\s]*([-\w.]*)\s*(?:\r\n|\r|\n)', re.MULTILINE)
-escapedNewlineRE = re.compile(r'(?<!\\)\\n')
+escapedNewlineRE = re.compile(r'(?<!\\)((\\\\)*)\\(n|012)')
directiveNamesAndParsers = {
# importing and inheritance
- 'import':None,
- 'from':None,
+ 'import': None,
+ 'from': None,
'extends': 'eatExtends',
'implements': 'eatImplements',
'super': 'eatSuper',
@@ -171,7 +162,7 @@ directiveNamesAndParsers = {
'filter': 'eatFilter',
'echo': None,
'silent': None,
- 'transform' : 'eatTransform',
+ 'transform': 'eatTransform',
'call': 'eatCall',
'arg': 'eatCallArg',
@@ -235,7 +226,7 @@ endDirectiveNamesAndHandlers = {
'call': None, # has short-form
'capture': None, # has short-form
'filter': None,
- 'errorCatcher':None,
+ 'errorCatcher': None,
'while': None, # has short-form
'for': None, # has short-form
'if': None, # has short-form
@@ -279,16 +270,16 @@ class ParseError(ValueError):
## get the surrounding lines
lines = stream.splitlines()
prevLines = [] # (rowNum, content)
- for i in range(1,4):
+ for i in range(1, 4):
if row-1-i <=0:
break
- prevLines.append( (row-i,lines[row-1-i]) )
+ prevLines.append( (row-i, lines[row-1-i]) )
nextLines = [] # (rowNum, content)
- for i in range(1,4):
+ for i in range(1, 4):
if not row-1+i < len(lines):
break
- nextLines.append( (row+i,lines[row-1+i]) )
+ nextLines.append( (row+i, lines[row-1+i]) )
nextLines.reverse()
## print the main message
@@ -311,11 +302,14 @@ class ParseError(ValueError):
return report
-class ForbiddenSyntax(ParseError): pass
-class ForbiddenExpression(ForbiddenSyntax): pass
-class ForbiddenDirective(ForbiddenSyntax): pass
+class ForbiddenSyntax(ParseError):
+ pass
+class ForbiddenExpression(ForbiddenSyntax):
+ pass
+class ForbiddenDirective(ForbiddenSyntax):
+ pass
-class CheetahVariable:
+class CheetahVariable(object):
def __init__(self, nameChunks, useNameMapper=True, cacheToken=None,
rawSource=None):
self.nameChunks = nameChunks
@@ -323,36 +317,33 @@ class CheetahVariable:
self.cacheToken = cacheToken
self.rawSource = rawSource
-class Placeholder(CheetahVariable): pass
+class Placeholder(CheetahVariable):
+ pass
-class ArgList:
+class ArgList(object):
"""Used by _LowLevelParser.getArgList()"""
def __init__(self):
- self.argNames = []
- self.defVals = []
- self.i = 0
+ self.arguments = []
+ self.defaults = []
+ self.count = 0
- def addArgName(self, name):
- self.argNames.append( name )
- self.defVals.append( None )
+ def add_argument(self, name):
+ self.arguments.append(name)
+ self.defaults.append(None)
def next(self):
- self.i += 1
+ self.count += 1
- def addToDefVal(self, token):
- i = self.i
- if self.defVals[i] == None:
- self.defVals[i] = ''
- self.defVals[i] += token
+ def add_default(self, token):
+ count = self.count
+ if self.defaults[count] is None:
+ self.defaults[count] = ''
+ self.defaults[count] += token
def merge(self):
- defVals = self.defVals
- for i in range(len(defVals)):
- if type(defVals[i]) == StringType:
- defVals[i] = defVals[i].strip()
-
- return map(None, [i.strip() for i in self.argNames], defVals)
+ defaults = (isinstance(d, basestring) and d.strip() or None for d in self.defaults)
+ return list(map(None, (a.strip() for a in self.arguments), defaults))
def __str__(self):
return str(self.merge())
@@ -519,6 +510,19 @@ class _LowLevelParser(SourceReader):
endTokenEsc = escapeRegexChars(endToken)
self.PSPEndTokenRE = cachedRegex(escCharLookBehind + endTokenEsc)
+ def _unescapeCheetahVars(self, theString):
+ """Unescape any escaped Cheetah \$vars in the string.
+ """
+
+ token = self.setting('cheetahVarStartToken')
+ return theString.replace('\\' + token, token)
+
+ def _unescapeDirectives(self, theString):
+ """Unescape any escaped Cheetah directives in the string.
+ """
+
+ token = self.setting('directiveStartToken')
+ return theString.replace('\\' + token, token)
def isLineClearToStartToken(self, pos=None):
return self.isLineClearToPos(pos)
@@ -942,7 +946,7 @@ class _LowLevelParser(SourceReader):
argStringBits = ['(']
addBit = argStringBits.append
- while 1:
+ while True:
if self.atEnd():
open = enclosures[-1][0]
close = closurePairsRev[open]
@@ -987,7 +991,7 @@ class _LowLevelParser(SourceReader):
else:
beforeTokenPos = self.pos()
token = self.getPyToken()
- if token in ('{','(','['):
+ if token in ('{', '(', '['):
self.rev()
token = self.getExpression(enclosed=True)
token = self.transformToken(token, beforeTokenPos)
@@ -1027,7 +1031,7 @@ class _LowLevelParser(SourceReader):
useNameMapper_orig = self.setting('useNameMapper')
self.setSetting('useNameMapper', useNameMapper)
- while 1:
+ while True:
if self.atEnd():
raise ParseError(
self, msg="EOF was reached before a matching ')'"+
@@ -1043,7 +1047,7 @@ class _LowLevelParser(SourceReader):
break
elif c in " \t\f\r\n":
if onDefVal:
- argList.addToDefVal(c)
+ argList.add_default(c)
self.advance()
elif c == '=':
onDefVal = True
@@ -1055,7 +1059,7 @@ class _LowLevelParser(SourceReader):
elif self.startswith(self.cheetahVarStartToken) and not onDefVal:
self.advance(len(self.cheetahVarStartToken))
elif self.matchIdentifier() and not onDefVal:
- argList.addArgName( self.getIdentifier() )
+ argList.add_argument( self.getIdentifier() )
elif onDefVal:
if self.matchCheetahVarInExpressionStartToken():
token = self.getCheetahVar()
@@ -1065,11 +1069,11 @@ class _LowLevelParser(SourceReader):
else:
beforeTokenPos = self.pos()
token = self.getPyToken()
- if token in ('{','(','['):
+ if token in ('{', '(', '['):
self.rev()
token = self.getExpression(enclosed=True)
token = self.transformToken(token, beforeTokenPos)
- argList.addToDefVal(token)
+ argList.add_default(token)
elif c == '*' and not onDefVal:
varName = self.getc()
if self.peek() == '*':
@@ -1077,7 +1081,7 @@ class _LowLevelParser(SourceReader):
if not self.matchIdentifier():
raise ParseError(self)
varName += self.getIdentifier()
- argList.addArgName(varName)
+ argList.add_argument(varName)
else:
raise ParseError(self)
@@ -1106,7 +1110,7 @@ class _LowLevelParser(SourceReader):
srcLen = len(self)
exprBits = []
- while 1:
+ while True:
if self.atEnd():
if enclosures:
open = enclosures[-1][0]
@@ -1224,7 +1228,6 @@ class _LowLevelParser(SourceReader):
startPosIdx = 3
else:
startPosIdx = 1
- #print 'CHEETAH STRING', nextToken, theStr, startPosIdx
self.setPos(beforeTokenPos+startPosIdx+1)
outputExprs = []
strConst = ''
@@ -1240,8 +1243,6 @@ class _LowLevelParser(SourceReader):
self.setPos(endPos)
if strConst:
outputExprs.append(repr(strConst))
- #if not self.atEnd() and self.matches('.join('):
- # print 'DEBUG***'
token = "''.join(["+','.join(outputExprs)+"])"
return token
@@ -1318,8 +1319,8 @@ class _LowLevelParser(SourceReader):
expr = expr[:-1]
rawPlaceholder=self[startPos: self.pos()]
- expr = self._applyExpressionFilters(expr,'placeholder',
- rawExpr=rawPlaceholder,startPos=startPos)
+ expr = self._applyExpressionFilters(expr, 'placeholder',
+ rawExpr=rawPlaceholder, startPos=startPos)
for callback in self.setting('postparsePlaceholderHooks'):
callback(parser=self)
@@ -1336,7 +1337,7 @@ class _HighLevelParser(_LowLevelParser):
Cheetah.Compiler.Compiler.
"""
def __init__(self, src, filename=None, breakPoint=None, compiler=None):
- _LowLevelParser.__init__(self, src, filename=filename, breakPoint=breakPoint)
+ super(_HighLevelParser, self).__init__(src, filename=filename, breakPoint=breakPoint)
self.setSettingsManager(compiler)
self._compiler = compiler
self.setupState()
@@ -1357,16 +1358,16 @@ class _HighLevelParser(_LowLevelParser):
self._macroDetails.clear()
def configureParser(self):
- _LowLevelParser.configureParser(self)
+ super(_HighLevelParser, self).configureParser()
self._initDirectives()
def _initDirectives(self):
def normalizeParserVal(val):
- if isinstance(val, (str,unicode)):
+ if isinstance(val, (str, unicode)):
handler = getattr(self, val)
elif type(val) in (ClassType, TypeType):
handler = val(self)
- elif callable(val):
+ elif hasattr(val, '__call__'):
handler = val
elif val is None:
handler = val
@@ -1377,11 +1378,11 @@ class _HighLevelParser(_LowLevelParser):
normalizeHandlerVal = normalizeParserVal
_directiveNamesAndParsers = directiveNamesAndParsers.copy()
- customNamesAndParsers = self.setting('directiveNamesAndParsers',{})
+ customNamesAndParsers = self.setting('directiveNamesAndParsers', {})
_directiveNamesAndParsers.update(customNamesAndParsers)
_endDirectiveNamesAndHandlers = endDirectiveNamesAndHandlers.copy()
- customNamesAndHandlers = self.setting('endDirectiveNamesAndHandlers',{})
+ customNamesAndHandlers = self.setting('endDirectiveNamesAndHandlers', {})
_endDirectiveNamesAndHandlers.update(customNamesAndHandlers)
self._directiveNamesAndParsers = {}
@@ -1396,21 +1397,21 @@ class _HighLevelParser(_LowLevelParser):
continue
self._endDirectiveNamesAndHandlers[name] = normalizeHandlerVal(val)
- self._closeableDirectives = ['def','block','closure','defmacro',
+ self._closeableDirectives = ['def', 'block', 'closure', 'defmacro',
'call',
'capture',
'cache',
'filter',
- 'if','unless',
- 'for','while','repeat',
+ 'if', 'unless',
+ 'for', 'while', 'repeat',
'try',
]
- for directiveName in self.setting('closeableDirectives',[]):
+ for directiveName in self.setting('closeableDirectives', []):
self._closeableDirectives.append(directiveName)
- macroDirectives = self.setting('macroDirectives',{})
+ macroDirectives = self.setting('macroDirectives', {})
macroDirectives['i18n'] = I18n
@@ -1509,6 +1510,8 @@ class _HighLevelParser(_LowLevelParser):
else:
self.advance()
strConst = self.readTo(self.pos(), start=startPos)
+ strConst = self._unescapeCheetahVars(strConst)
+ strConst = self._unescapeDirectives(strConst)
self._compiler.addStrConst(strConst)
return match
@@ -1527,7 +1530,7 @@ class _HighLevelParser(_LowLevelParser):
self.getMultiLineCommentStartToken()
endPos = startPos = self.pos()
level = 1
- while 1:
+ while True:
endPos = self.pos()
if self.atEnd():
break
@@ -1594,8 +1597,8 @@ class _HighLevelParser(_LowLevelParser):
del assert raise
silent echo
import from'''.split()
- _directiveHandlerNames = {'import':'addImportStatement',
- 'from':'addImportStatement', }
+ _directiveHandlerNames = {'import': 'addImportStatement',
+ 'from': 'addImportStatement', }
def eatDirective(self):
directiveName = self.matchDirective()
self._filterDisabledDirectives(directiveName)
@@ -1854,13 +1857,11 @@ class _HighLevelParser(_LowLevelParser):
try:
self._compiler.setCompilerSetting(settingName, valueExpr)
except:
- out = sys.stderr
- print >> out, 'An error occurred while processing the following #compiler directive.'
- print >> out, '-'*80
- print >> out, self[startPos:endPos]
- print >> out, '-'*80
- print >> out, 'Please check the syntax of these settings.'
- print >> out, 'A full Python exception traceback follows.'
+ sys.stderr.write('An error occurred while processing the following #compiler directive.\n')
+ sys.stderr.write('----------------------------------------------------------------------\n')
+ sys.stderr.write('%s\n' % self[startPos:endPos])
+ sys.stderr.write('----------------------------------------------------------------------\n')
+ sys.stderr.write('Please check the syntax of these settings.\n\n')
raise
@@ -1890,13 +1891,11 @@ class _HighLevelParser(_LowLevelParser):
try:
self._compiler.setCompilerSettings(keywords=keywords, settingsStr=settingsStr)
except:
- out = sys.stderr
- print >> out, 'An error occurred while processing the following compiler settings.'
- print >> out, '-'*80
- print >> out, settingsStr.strip()
- print >> out, '-'*80
- print >> out, 'Please check the syntax of these settings.'
- print >> out, 'A full Python exception traceback follows.'
+ sys.stderr.write('An error occurred while processing the following compiler settings.\n')
+ sys.stderr.write('----------------------------------------------------------------------\n')
+ sys.stderr.write('%s\n' % settingsStr.strip())
+ sys.stderr.write('----------------------------------------------------------------------\n')
+ sys.stderr.write('Please check the syntax of these settings.\n\n')
raise
def eatAttr(self):
@@ -1946,8 +1945,8 @@ class _HighLevelParser(_LowLevelParser):
startPos = self.pos()
methodName, rawSignature = self._eatDefOrBlock('block')
self._compiler._blockMetaData[methodName] = {
- 'raw':rawSignature,
- 'lineCol':self.getRowCol(startPos),
+ 'raw': rawSignature,
+ 'lineCol': self.getRowCol(startPos),
}
def eatClosure(self):
@@ -1956,7 +1955,7 @@ class _HighLevelParser(_LowLevelParser):
def _eatDefOrBlock(self, directiveName):
# filtered
- assert directiveName in ('def','block','closure')
+ assert directiveName in ('def', 'block', 'closure')
isLineClearToStartToken = self.isLineClearToStartToken()
endOfFirstLinePos = self.findEOL()
startPos = self.pos()
@@ -2249,15 +2248,15 @@ class _HighLevelParser(_LowLevelParser):
else:
argsList=[]
- assert not self._directiveNamesAndParsers.has_key(macroName)
- argsList.insert(0, ('src',None))
- argsList.append(('parser','None'))
- argsList.append(('macros','None'))
- argsList.append(('compilerSettings','None'))
- argsList.append(('isShortForm','None'))
- argsList.append(('EOLCharsInShortForm','None'))
- argsList.append(('startPos','None'))
- argsList.append(('endPos','None'))
+ assert macroName not in self._directiveNamesAndParsers
+ argsList.insert(0, ('src', None))
+ argsList.append(('parser', 'None'))
+ argsList.append(('macros', 'None'))
+ argsList.append(('compilerSettings', 'None'))
+ argsList.append(('isShortForm', 'None'))
+ argsList.append(('EOLCharsInShortForm', 'None'))
+ argsList.append(('startPos', 'None'))
+ argsList.append(('endPos', 'None'))
if self.matchColonForSingleLineShortFormDirective():
self.advance() # skip over :
@@ -2273,8 +2272,8 @@ class _HighLevelParser(_LowLevelParser):
#print argsList
normalizedMacroSrc = ''.join(
- ['%def callMacro('+','.join([defv and '%s=%s'%(n,defv) or n
- for n,defv in argsList])
+ ['%def callMacro('+','.join([defv and '%s=%s'%(n, defv) or n
+ for n, defv in argsList])
+')\n',
macroSrc,
'%end def'])
@@ -2285,9 +2284,9 @@ class _HighLevelParser(_LowLevelParser):
compilerSettings = self.setting('compilerSettingsForDefMacro', default={})
searchListForMacros = self.setting('searchListForDefMacro', default=[])
searchListForMacros = list(searchListForMacros) # copy to avoid mutation bugs
- searchListForMacros.append({'macros':self._macros,
- 'parser':self,
- 'compilerSettings':self.settings(),
+ searchListForMacros.append({'macros': self._macros,
+ 'parser': self,
+ 'compilerSettings': self.settings(),
})
templateAPIClass._updateSettingsWithPreprocessTokens(
@@ -2347,12 +2346,12 @@ class _HighLevelParser(_LowLevelParser):
else:
def getArgs(*pargs, **kws):
return pargs, kws
- exec 'positionalArgs, kwArgs = getArgs(%(args)s)'%locals()
+ exec('positionalArgs, kwArgs = getArgs(%(args)s)'%locals())
- assert not kwArgs.has_key('src')
+ assert 'src' not in kwArgs
kwArgs['src'] = srcBlock
- if type(macro)==new.instancemethod:
+ if isinstance(macro, new.instancemethod):
co = macro.im_func.func_code
elif (hasattr(macro, '__call__')
and hasattr(macro.__call__, 'im_func')):
diff --git a/cheetah/Servlet.py b/cheetah/Servlet.py
index f19e508..e122aca 100644
--- a/cheetah/Servlet.py
+++ b/cheetah/Servlet.py
@@ -103,7 +103,7 @@ definition.""")
if self._CHEETAH__isControlledByWebKit:
return super(Servlet, self).serverSidePath(path)
elif path:
- return normpath(abspath(path.replace("\\",'/')))
+ return normpath(abspath(path.replace("\\", '/')))
elif hasattr(self, '_filePath') and self._filePath:
return normpath(abspath(self._filePath))
else:
diff --git a/cheetah/SettingsManager.py b/cheetah/SettingsManager.py
index dfb396b..94f7723 100644
--- a/cheetah/SettingsManager.py
+++ b/cheetah/SettingsManager.py
@@ -34,10 +34,8 @@ def mergeNestedDictionaries(dict1, dict2, copy=False, deepcopy=False):
elif deepcopy:
dict1 = copyModule.deepcopy(dict1)
- for key,val in dict2.items():
- if dict1.has_key(key) and type(val) == types.DictType and \
- type(dict1[key]) == types.DictType:
-
+ 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
@@ -96,7 +94,7 @@ class _SettingsCollector(object):
"""
S = {}
attrs = vars(mod)
- for k, v in attrs.items():
+ for k, v in attrs.iteritems():
if (ignoreUnderscored and k.startswith('_')):
continue
else:
@@ -106,11 +104,11 @@ class _SettingsCollector(object):
def readSettingsFromPySrcStr(self, theString):
"""Return a dictionary of the settings in a Python src string."""
- globalsDict = {'True':(1==1),
- 'False':(0==1),
+ globalsDict = {'True': (1==1),
+ 'False': (0==1),
}
newSettings = {'self':self}
- exec (theString+os.linesep) in globalsDict, newSettings
+ exec((theString+os.linesep), globalsDict, newSettings)
del newSettings['self']
module = new.module('temp_settings_module')
module.__dict__.update(newSettings)
@@ -156,7 +154,7 @@ class _SettingsCollector(object):
newSettings[s] = {}
for o in p.options(s):
if o != '__name__':
- newSettings[s][o] = p.get(s,o)
+ newSettings[s][o] = p.get(s, o)
## loop through new settings -> deal with global settings, numbers,
## booleans and None ++ also deal with 'importSettings' commands
@@ -165,7 +163,7 @@ class _SettingsCollector(object):
for key, val in subDict.items():
if convert:
if val.lower().startswith('python:'):
- subDict[key] = eval(val[7:],{},{})
+ subDict[key] = eval(val[7:], {}, {})
if val.lower() == 'none':
subDict[key] = None
if val.lower() == 'true':
@@ -267,7 +265,7 @@ class SettingsManager(_SettingsCollector):
newSettings = self.readSettingsFromPySrcStr(theString)
self.updateSettings(newSettings,
- merge=newSettings.get('mergeSettings',merge) )
+ merge=newSettings.get('mergeSettings', merge) )
def updateSettingsFromConfigFileObj(self, inFile, convert=True, merge=True):
@@ -278,7 +276,7 @@ class SettingsManager(_SettingsCollector):
newSettings = self.readSettingsFromConfigFileObj(inFile, convert=convert)
self.updateSettings(newSettings,
- merge=newSettings.get('mergeSettings',merge))
+ merge=newSettings.get('mergeSettings', merge))
def updateSettingsFromConfigStr(self, configStr, convert=True, merge=True):
"""See the docstring for .updateSettingsFromConfigFile()
@@ -288,5 +286,5 @@ class SettingsManager(_SettingsCollector):
inFile = StringIO(configStr)
newSettings = self.readSettingsFromConfigFileObj(inFile, convert=convert)
self.updateSettings(newSettings,
- merge=newSettings.get('mergeSettings',merge))
+ merge=newSettings.get('mergeSettings', merge))
diff --git a/cheetah/SourceReader.py b/cheetah/SourceReader.py
index 0dc0e60..7a08837 100644
--- a/cheetah/SourceReader.py
+++ b/cheetah/SourceReader.py
@@ -1,18 +1,5 @@
-# $Id: SourceReader.py,v 1.15 2007/04/03 01:57:42 tavis_rudd Exp $
"""SourceReader class for Cheetah's Parser and CodeGenerator
-
-Meta-Data
-================================================================================
-Author: Tavis Rudd <tavis@damnsimple.com>
-License: This software is released for unlimited distribution under the
- terms of the MIT license. See the LICENSE file.
-Version: $Revision: 1.15 $
-Start Date: 2001/09/19
-Last Revision Date: $Date: 2007/04/03 01:57:42 $
"""
-__author__ = "Tavis Rudd <tavis@damnsimple.com>"
-__revision__ = "$Revision: 1.15 $"[11:-2]
-
import re
import sys
@@ -23,7 +10,7 @@ ENCODINGsearch = re.compile("coding[=:]\s*([-\w.]+)").search
class Error(Exception):
pass
-class SourceReader:
+class SourceReader(object):
def __init__(self, src, filename=None, breakPoint=None, encoding=None):
## @@TR 2005-01-17: the following comes from a patch Terrel Shumway
@@ -160,7 +147,7 @@ class SourceReader:
self._posTobookmarkMap[self._pos] = name
def hasBookmark(self, name):
- return self._bookmarks.has_key(name)
+ return name in self._bookmarks
def gotoBookmark(self, name):
if not self.hasBookmark(name):
@@ -247,7 +234,7 @@ class SourceReader:
if pos == None:
pos = self._pos
src = self.src()
- return max(src.rfind('\n',0,pos)+1, src.rfind('\r',0,pos)+1, 0)
+ 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:
diff --git a/cheetah/Template.py b/cheetah/Template.py
index ec92208..0eba725 100644
--- a/cheetah/Template.py
+++ b/cheetah/Template.py
@@ -25,7 +25,7 @@ from types import StringType, ClassType
try:
from types import StringTypes
except ImportError:
- StringTypes = (types.StringType,types.UnicodeType)
+ StringTypes = (types.StringType, types.UnicodeType)
try:
from threading import Lock
@@ -84,15 +84,14 @@ def hashList(l):
return hash(tuple(hashedList))
def hashDict(d):
- items = d.items()
- items.sort()
+ items = sorted(d.items())
hashedList = []
for k, v in items:
if isinstance(v, dict):
v = hashDict(v)
elif isinstance(v, list):
v = hashList(v)
- hashedList.append((k,v))
+ hashedList.append((k, v))
return hash(tuple(hashedList))
@@ -106,7 +105,7 @@ def _genUniqueModuleName(baseModuleName):
finalName = baseModuleName
else:
finalName = ('cheetah_%s_%s_%s'%(baseModuleName,
- str(time.time()).replace('.','_'),
+ str(time.time()).replace('.', '_'),
str(randrange(10000, 99999))))
return finalName
@@ -276,8 +275,8 @@ class Template(Servlet):
'_getTemplateAPIClassForIncludeDirectiveCompilation',
)
_CHEETAH_requiredCheetahClassMethods = ('subclass',)
- _CHEETAH_requiredCheetahClassAttributes = ('cacheRegionClass','cacheStore',
- 'cacheStoreIdPrefix','cacheStoreClass')
+ _CHEETAH_requiredCheetahClassAttributes = ('cacheRegionClass', 'cacheStore',
+ 'cacheStoreIdPrefix', 'cacheStoreClass')
## the following are used by .compile(). Most are documented in its docstring.
_CHEETAH_cacheModuleFilesForTracebacks = False
@@ -293,6 +292,7 @@ class Template(Servlet):
_CHEETAH_defaultMainMethodName = None
_CHEETAH_compilerSettings = None
_CHEETAH_compilerClass = Compiler
+ _CHEETAH_compilerInstance = None
_CHEETAH_cacheCompilationResults = True
_CHEETAH_useCompilationCache = True
_CHEETAH_keepRefToGeneratedCode = True
@@ -314,14 +314,15 @@ class Template(Servlet):
_CHEETAH_cacheStore = None
_CHEETAH_cacheStoreIdPrefix = None
+ @classmethod
def _getCompilerClass(klass, source=None, file=None):
return klass._CHEETAH_compilerClass
- _getCompilerClass = classmethod(_getCompilerClass)
+ @classmethod
def _getCompilerSettings(klass, source=None, file=None):
return klass._CHEETAH_compilerSettings
- _getCompilerSettings = classmethod(_getCompilerSettings)
+ @classmethod
def compile(klass, source=None, file=None,
returnAClass=True,
@@ -640,7 +641,7 @@ class Template(Servlet):
if not isinstance(className, (types.NoneType, basestring)):
raise TypeError(errmsg % ('className', 'string or None'))
- className = className or moduleName
+ className = re.sub(r'^_+','', className or moduleName)
if mainMethodName is Unspecified:
mainMethodName = klass._CHEETAH_defaultMainMethodNameForTemplates
@@ -723,6 +724,7 @@ class Template(Servlet):
#@@TR: should add some logging to this
pass
outputEncoding = 'ascii'
+ compiler = None
if useCache and cacheHash and cacheHash in klass._CHEETAH_compileCache:
cacheItem = klass._CHEETAH_compileCache[cacheHash]
generatedModuleCode = cacheItem.code
@@ -780,7 +782,7 @@ class Template(Servlet):
##
try:
co = compile(generatedModuleCode, __file__, 'exec')
- exec co in mod.__dict__
+ exec(co, mod.__dict__)
except SyntaxError, e:
try:
parseError = genParserErrorFromPythonException(
@@ -818,9 +820,13 @@ class Template(Servlet):
if keepRefToGeneratedCode or cacheCompilationResults:
templateClass._CHEETAH_generatedModuleCode = generatedModuleCode
+ # If we have a compiler object, let's set it to the compiler class
+ # to help the directive analyzer code
+ if compiler:
+ templateClass._CHEETAH_compilerInstance = compiler
return templateClass
- compile = classmethod(compile)
+ @classmethod
def subclass(klass, *args, **kws):
"""Takes the same args as the .compile() classmethod and returns a
template that is a subclass of the template this method is called from.
@@ -834,8 +840,8 @@ class Template(Servlet):
else:
templateAPIClass = Template
return templateAPIClass.compile(*args, **kws)
- subclass = classmethod(subclass)
+ @classmethod
def _preprocessSource(klass, source, file, preprocessors):
"""Iterates through the .compile() classmethod's preprocessors argument
and pipes the source code through each each preprocessor.
@@ -849,8 +855,8 @@ class Template(Servlet):
preprocessor = klass._normalizePreprocessorArg(preprocessor)
source, file = preprocessor.preprocess(source, file)
return source, file
- _preprocessSource = classmethod(_preprocessSource)
+ @classmethod
def _normalizePreprocessorArg(klass, arg):
"""Used to convert the items in the .compile() classmethod's
preprocessors argument into real source preprocessors. This permits the
@@ -859,7 +865,7 @@ class Template(Servlet):
if hasattr(arg, 'preprocess'):
return arg
- elif callable(arg):
+ elif hasattr(arg, '__call__'):
class WrapperPreprocessor:
def preprocess(self, source, file):
return arg(source, file)
@@ -880,8 +886,8 @@ class Template(Servlet):
settings = klass._normalizePreprocessorSettings(settings)
return klass._CHEETAH_defaultPreprocessorClass(settings)
- _normalizePreprocessorArg = classmethod(_normalizePreprocessorArg)
+ @classmethod
def _normalizePreprocessorSettings(klass, settings):
settings.keepRefToGeneratedCode = True
@@ -902,7 +908,7 @@ class Template(Servlet):
(settings.placeholderToken,
settings.directiveToken) = normalizeTokens(settings.tokens)
- if (not getattr(settings,'compilerSettings', None)
+ if (not getattr(settings, 'compilerSettings', None)
and not getattr(settings, 'placeholderToken', None) ):
raise TypeError(
@@ -936,8 +942,8 @@ class Template(Servlet):
directiveToken=settings.directiveToken
)
return settings
- _normalizePreprocessorSettings = classmethod(_normalizePreprocessorSettings)
+ @classmethod
def _updateSettingsWithPreprocessTokens(
klass, compilerSettings, placeholderToken, directiveToken):
@@ -958,8 +964,8 @@ class Template(Servlet):
'*'+directiveToken)
if 'EOLSlurpToken' not in compilerSettings:
compilerSettings['EOLSlurpToken'] = directiveToken
- _updateSettingsWithPreprocessTokens = classmethod(_updateSettingsWithPreprocessTokens)
+ @classmethod
def _addCheetahPlumbingCodeToClass(klass, concreteTemplateClass):
"""If concreteTemplateClass is not a subclass of Cheetah.Template, add
the required cheetah methods and attributes to it.
@@ -991,7 +997,7 @@ class Template(Servlet):
or concreteTemplateClass.__str__ is object.__str__):
mainMethNameAttr = '_mainCheetahMethod_for_'+concreteTemplateClass.__name__
- mainMethName = getattr(concreteTemplateClass,mainMethNameAttr, None)
+ mainMethName = getattr(concreteTemplateClass, mainMethNameAttr, None)
if mainMethName:
def __str__(self):
rc = getattr(self, mainMethName)()
@@ -1013,7 +1019,7 @@ class Template(Servlet):
def __str__(self):
rc = None
if hasattr(self, mainMethNameAttr):
- rc = getattr(self,mainMethNameAttr)()
+ rc = getattr(self, mainMethNameAttr)()
elif hasattr(self, 'respond'):
rc = self.respond()
else:
@@ -1023,7 +1029,7 @@ class Template(Servlet):
return rc
def __unicode__(self):
if hasattr(self, mainMethNameAttr):
- return getattr(self,mainMethNameAttr)()
+ return getattr(self, mainMethNameAttr)()
elif hasattr(self, 'respond'):
return self.respond()
else:
@@ -1033,10 +1039,7 @@ class Template(Servlet):
__unicode__ = new.instancemethod(__unicode__, None, concreteTemplateClass)
setattr(concreteTemplateClass, '__str__', __str__)
setattr(concreteTemplateClass, '__unicode__', __unicode__)
-
- _addCheetahPlumbingCodeToClass = classmethod(_addCheetahPlumbingCodeToClass)
- ## end classmethods ##
def __init__(self, source=None,
@@ -1358,7 +1361,7 @@ class Template(Servlet):
"""
try:
- return valueFromSearchList(self.searchList(), varName.replace('$',''), autoCall)
+ return valueFromSearchList(self.searchList(), varName.replace('$', ''), autoCall)
except NotFound:
if default is not Unspecified:
return default
@@ -1369,7 +1372,7 @@ class Template(Servlet):
"""Test if a variable name exists in the searchList.
"""
try:
- valueFromSearchList(self.searchList(), varName.replace('$',''), autoCall)
+ valueFromSearchList(self.searchList(), varName.replace('$', ''), autoCall)
return True
except NotFound:
return False
@@ -1418,7 +1421,7 @@ class Template(Servlet):
various protocols, as PHP allows with its 'URL fopen wrapper'
"""
- fp = open(path,'r')
+ fp = open(path, 'r')
output = fp.read()
fp.close()
return output
@@ -1502,7 +1505,7 @@ class Template(Servlet):
if errorCatcher:
if isinstance(errorCatcher, basestring):
errorCatcherClass = getattr(ErrorCatchers, errorCatcher)
- elif type(errorCatcher) == ClassType:
+ elif isinstance(errorCatcher, ClassType):
errorCatcherClass = errorCatcher
self._CHEETAH__errorCatcher = ec = errorCatcherClass(self)
@@ -1559,7 +1562,7 @@ class Template(Servlet):
"""Called at runtime to handle #include directives.
"""
_includeID = srcArg
- if not self._CHEETAH__cheetahIncludes.has_key(_includeID):
+ if _includeID not in self._CHEETAH__cheetahIncludes:
if not raw:
if includeFrom == 'file':
source = None
@@ -1577,7 +1580,7 @@ class Template(Servlet):
# Template class to be used for compilation so compilerSettings
# can be changed.
compiler = self._getTemplateAPIClassForIncludeDirectiveCompilation(source, file)
- nestedTemplateClass = compiler.compile(source=source,file=file)
+ nestedTemplateClass = compiler.compile(source=source, file=file)
nestedTemplate = nestedTemplateClass(_preBuiltSearchList=self.searchList(),
_globalSetVars=self._CHEETAH__globalSetVars)
# Set the inner template filters to the initial filter of the
@@ -1819,8 +1822,8 @@ class Template(Servlet):
raise TypeError("arg 'src' invalid")
sources = source + 's'
converters = {
- '' : _Converter('string', None, default, default ),
- 'int' : _Converter('int', int, defaultInt, badInt ),
+ '': _Converter('string', None, default, default ),
+ 'int': _Converter('int', int, defaultInt, badInt ),
'float': _Converter('float', float, defaultFloat, badFloat), }
#pprint.pprint(locals()); return {}
dic = {} # Destination.
@@ -1837,7 +1840,7 @@ class Template(Servlet):
# 'dic = super(ThisClass, self).webInput(names, namesMulti, ...)'
# and then the code below.
if debug:
- print "<PRE>\n" + pprint.pformat(dic) + "\n</PRE>\n\n"
+ print("<PRE>\n" + pprint.pformat(dic) + "\n</PRE>\n\n")
self.searchList().insert(0, dic)
return dic
@@ -1862,16 +1865,16 @@ def genParserErrorFromPythonException(source, file, generatedPyCode, exception):
lines = generatedPyCode.splitlines()
prevLines = [] # (i, content)
- for i in range(1,4):
+ for i in range(1, 4):
if pyLineno-i <=0:
break
- prevLines.append( (pyLineno+1-i,lines[pyLineno-i]) )
+ prevLines.append( (pyLineno+1-i, lines[pyLineno-i]) )
nextLines = [] # (i, content)
- for i in range(1,4):
+ for i in range(1, 4):
if not pyLineno+i < len(lines):
break
- nextLines.append( (pyLineno+i,lines[pyLineno+i]) )
+ nextLines.append( (pyLineno+i, lines[pyLineno+i]) )
nextLines.reverse()
report = 'Line|Python Code\n'
report += '----|-------------------------------------------------------------\n'
@@ -1918,7 +1921,7 @@ def genParserErrorFromPythonException(source, file, generatedPyCode, exception):
message = '\n'.join(message)
reader = SourceReader(source, filename=filename)
- return ParseError(reader, message, lineno=lineno,col=col)
+ return ParseError(reader, message, lineno=lineno, col=col)
# vim: shiftwidth=4 tabstop=4 expandtab
diff --git a/cheetah/TemplateCmdLineIface.py b/cheetah/TemplateCmdLineIface.py
index 16a90cf..9787577 100644
--- a/cheetah/TemplateCmdLineIface.py
+++ b/cheetah/TemplateCmdLineIface.py
@@ -41,7 +41,7 @@ class CmdLineIface:
"""The main program controller."""
self._processCmdLineArgs()
- print self._template
+ print(self._template)
def _processCmdLineArgs(self):
try:
@@ -53,13 +53,13 @@ class CmdLineIface:
except getopt.GetoptError, v:
# print help information and exit:
- print v
- print self.usage()
+ print(v)
+ print(self.usage())
sys.exit(2)
for o, a in self._opts:
- if o in ('-h','--help'):
- print self.usage()
+ if o in ('-h', '--help'):
+ print(self.usage())
sys.exit()
if o == '--env':
self._template.searchList().insert(0, os.environ)
@@ -100,8 +100,8 @@ 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,
+""" % {'scriptName': self._scriptName,
+ 'Version': Version,
}
# vim: shiftwidth=4 tabstop=4 expandtab
diff --git a/cheetah/Templates/SkeletonPage.py b/cheetah/Templates/SkeletonPage.py
index 04bf4fc..928ae2b 100644
--- a/cheetah/Templates/SkeletonPage.py
+++ b/cheetah/Templates/SkeletonPage.py
@@ -62,7 +62,7 @@ class SkeletonPage(_SkeletonPage):
if not self._CHEETAH__instanceInitialized:
cheetahKWArgs = {}
allowedKWs = 'searchList namespaces filter filtersLib errorCatcher'.split()
- for k,v in KWs.items():
+ for k, v in KWs.items():
if k in allowedKWs: cheetahKWArgs[k] = v
self._initCheetahInstance(**cheetahKWArgs)
@@ -73,7 +73,7 @@ class SkeletonPage(_SkeletonPage):
## CHEETAH: generated from #block writeHeadTag at line 22, col 1.
trans = KWS.get("trans")
- if (not trans and not self._CHEETAH__isBuffering and not callable(self.transaction)):
+ 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()
@@ -87,16 +87,16 @@ class SkeletonPage(_SkeletonPage):
## START - generated method body
write('<head>\n<title>')
- _v = VFFSL(SL,"title",True) # '$title' on line 24, col 8
+ _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
+ _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
+ _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
+ _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')
@@ -112,7 +112,7 @@ class SkeletonPage(_SkeletonPage):
## CHEETAH: generated from #block writeBody at line 36, col 1.
trans = KWS.get("trans")
- if (not trans and not self._CHEETAH__isBuffering and not callable(self.transaction)):
+ 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()
@@ -138,7 +138,7 @@ class SkeletonPage(_SkeletonPage):
## CHEETAH: main method generated for this template
- if (not trans and not self._CHEETAH__isBuffering and not callable(self.transaction)):
+ 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()
@@ -172,29 +172,29 @@ class SkeletonPage(_SkeletonPage):
_orig_transheader = trans
trans = _cacheCollector_header = DummyTransaction()
write = _cacheCollector_header.response().write
- _v = VFFSL(SL,"docType",True) # '$docType' on line 7, col 1
+ _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
+ _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
+ _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
+ _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
+ _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
+ _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('''
@@ -215,7 +215,7 @@ However, please do not copy any content or images without permission.
## END CACHE REGION: header
write('\n')
- _v = VFFSL(SL,"bodyTag",True) # '$bodyTag' on line 34, col 1
+ _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)
diff --git a/cheetah/Templates/_SkeletonPage.py b/cheetah/Templates/_SkeletonPage.py
index fe01ebf..13f9db3 100644
--- a/cheetah/Templates/_SkeletonPage.py
+++ b/cheetah/Templates/_SkeletonPage.py
@@ -46,8 +46,8 @@ class _SkeletonPage(Template):
def __init__(self, *args, **KWs):
Template.__init__(self, *args, **KWs)
- self._metaTags = {'HTTP-EQUIV':{'keywords':'Cheetah',
- 'Content-Type':'text/html; charset=iso-8859-1',
+ self._metaTags = {'HTTP-EQUIV':{'keywords': 'Cheetah',
+ 'Content-Type': 'text/html; charset=iso-8859-1',
},
'NAME':{'generator':'Cheetah: The Python-Powered Template Engine'}
}
@@ -85,10 +85,10 @@ class _SkeletonPage(Template):
stylesheetTagsTxt += '<style type="text/css"><!--\n'
for identifier in self._stylesheetsOrder:
- if not self._stylesheets.has_key(identifier):
+ if identifier not in self._stylesheets:
warning = '# the identifier ' + identifier + \
'was in stylesheetsOrder, but not in stylesheets'
- print warning
+ print(warning)
stylesheetTagsTxt += warning
continue
@@ -115,18 +115,18 @@ class _SkeletonPage(Template):
SRC filename rather than a code string."""
javascriptTagsTxt = []
- for key, details in self._javascriptTags.items():
- if type(details) not in (types.ListType, types.TupleType):
- details = ['',details]
+ 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.items():
- if type(details) not in (types.ListType, types.TupleType):
- details = ['',details]
+ 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="',
@@ -177,16 +177,16 @@ class _SkeletonPage(Template):
return ''.join(['<img src="', src, '" height="', str(height),
'" alt="', alt, '" border="', str(border), '" />'])
else:
- return ''.join(['<img src="', src, '" alt="', alt, '" border="', str(border),'" />'])
+ 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()))
+ 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()))
+ 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))
@@ -195,19 +195,19 @@ class _SkeletonPage(Template):
"""returns a string containing an HTML <tag> """
tagTxt = ['<', tagName.lower()]
for name, val in attributes.items():
- tagTxt += [' ', name.lower(), '="', str(val),'"']
+ tagTxt += [' ', name.lower(), '="', str(val), '"']
tagTxt.append('>')
return ''.join(tagTxt)
def formatMetaTags(self, metaTags):
"""format a dict of metaTag definitions into an HTML version"""
metaTagsTxt = []
- if metaTags.has_key('HTTP-EQUIV'):
+ 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 metaTags.has_key('NAME'):
+ if 'NAME' in metaTags:
for name, contents in metaTags['NAME'].items():
metaTagsTxt += ['<meta name="', str(name), '" content="', str(contents),
'" />\n']
diff --git a/cheetah/Tests/Analyzer.py b/cheetah/Tests/Analyzer.py
new file mode 100644
index 0000000..59f6c1c
--- /dev/null
+++ b/cheetah/Tests/Analyzer.py
@@ -0,0 +1,29 @@
+#!/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()
+
diff --git a/cheetah/Tests/CheetahWrapper.py b/cheetah/Tests/CheetahWrapper.py
index e152e68..1097aa0 100644
--- a/cheetah/Tests/CheetahWrapper.py
+++ b/cheetah/Tests/CheetahWrapper.py
@@ -12,6 +12,8 @@ Besides unittest usage, recognizes the following command-line options:
Show the output of each subcommand. (Normally suppressed.)
'''
import os
+import os.path
+import pdb
import re # Used by listTests.
import shutil
import sys
@@ -24,10 +26,13 @@ from Cheetah.CheetahWrapper import CheetahWrapper # Used by NoBackup.
try:
from subprocess import Popen, PIPE, STDOUT
class Popen4(Popen):
- def __init__(self, cmd, bufsize=-1):
- super(Popen4, self).__init__(cmd, bufsize=bufsize,
- shell=True, close_fds=True,
- stdin=PIPE, stdout=PIPE, stderr=STDOUT)
+ 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
@@ -52,7 +57,7 @@ class CFBase(unittest.TestCase):
def inform(self, message):
if self.verbose:
- print message
+ print(message)
def setUp(self):
"""Create the top-level directories, subdirectories and .tmpl
@@ -152,6 +157,17 @@ Found %(result)r"""
msg = "backup file exists in spite of --nobackup: %s" % path
self.failIf(exists, msg)
+ def locate_command(self, cmd):
+ paths = os.getenv('PATH')
+ if not paths:
+ return cmd
+ parts = cmd.split(' ')
+ paths = paths.split(':')
+ for p in paths:
+ p = p + os.path.sep + parts[0]
+ if os.path.isfile(p):
+ return ' '.join([p] + parts[1:])
+ return ' '.join(parts)
def assertWin32Subprocess(self, cmd):
_in, _out = os.popen4(cmd)
@@ -163,7 +179,8 @@ Found %(result)r"""
return rc, output
def assertPosixSubprocess(self, cmd):
- process = Popen4(cmd)
+ cmd = self.locate_command(cmd)
+ process = Popen4(cmd, env=os.environ)
process.tochild.close()
output = process.fromchild.read()
status = process.wait()
@@ -514,13 +531,13 @@ def listTests(cheetahWrapperFile):
"""
rx = re.compile( R'self\.go\("(.*?)"\)' )
f = open(cheetahWrapperFile)
- while 1:
+ while True:
lin = f.readline()
if not lin:
break
m = rx.search(lin)
if m:
- print m.group(1)
+ print(m.group(1))
f.close()
def main():
diff --git a/cheetah/Tests/Filters.py b/cheetah/Tests/Filters.py
index bf35440..65b3d93 100644
--- a/cheetah/Tests/Filters.py
+++ b/cheetah/Tests/Filters.py
@@ -1,12 +1,11 @@
#!/usr/bin/env python
import sys
+import unittest
import Cheetah.Template
import Cheetah.Filters
-import unittest_local_copy as unittest
-
majorVer, minorVer = sys.version_info[0], sys.version_info[1]
versionTuple = (majorVer, minorVer)
@@ -29,9 +28,12 @@ Header
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
+ print('>>> NOTE: Support for the Markdown filter will be broken for you. Markdown says: %s' % ex)
return
raise
diff --git a/cheetah/Tests/Misc.py b/cheetah/Tests/Misc.py
new file mode 100644
index 0000000..9ea66f0
--- /dev/null
+++ b/cheetah/Tests/Misc.py
@@ -0,0 +1,20 @@
+#!/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()
+
diff --git a/cheetah/Tests/NameMapper.py b/cheetah/Tests/NameMapper.py
index 8efd6ac..fe6b658 100644
--- a/cheetah/Tests/NameMapper.py
+++ b/cheetah/Tests/NameMapper.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
-from __future__ import generators
+
import sys
import types
import os
@@ -34,7 +34,7 @@ class DummyClass:
x = 'A string'
try:
- for i in [1,2,3,4]:
+ for i in [1, 2, 3, 4]:
if x == 2:
pass
@@ -53,52 +53,52 @@ def funcThatRaises():
testNamespace = {
- 'aStr':'blarg',
- 'anInt':1,
- 'aFloat':1.5,
- 'aDict': {'one':'item1',
- 'two':'item2',
- 'nestedDict':{'one':'nestedItem1',
- 'two':'nestedItem2',
- 'funcThatRaises':funcThatRaises,
+ 'aStr': 'blarg',
+ 'anInt': 1,
+ 'aFloat': 1.5,
+ 'aDict': {'one': 'item1',
+ 'two': 'item2',
+ 'nestedDict': {'one': 'nestedItem1',
+ 'two': 'nestedItem2',
+ 'funcThatRaises': funcThatRaises,
'aClass': DummyClass,
},
- 'nestedFunc':dummyFunc,
+ 'nestedFunc': dummyFunc,
},
'aClass': DummyClass,
'aFunc': dummyFunc,
'anObj': DummyClass(),
'aMeth': DummyClass().meth1,
- 'none' : None,
- 'emptyString':'',
- 'funcThatRaises':funcThatRaises,
+ 'none': None,
+ 'emptyString': '',
+ 'funcThatRaises': funcThatRaises,
}
-autoCallResults = {'aFunc':'Scooby',
- 'aMeth':'doo',
+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',
+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
+ exec('%s = testNamespace[k]'%k)
##################################################
## TEST BASE CLASSES
class NameMapperTest(unittest.TestCase):
- failureException = (NotFound,AssertionError)
+ failureException = (NotFound, AssertionError)
_testNamespace = testNamespace
_results = results
@@ -117,7 +117,7 @@ class NameMapperTest(unittest.TestCase):
def check(self, name):
got = self.get(name)
- if autoCallResults.has_key(name):
+ if name in autoCallResults:
expected = autoCallResults[name]
else:
expected = self._results[name]
@@ -317,7 +317,7 @@ class VFN(NameMapperTest):
def test(self=self):
self.get('anObj.methX')
- self.assertRaises(NotFound,test)
+ self.assertRaises(NotFound, test)
def test44(self):
"""NotFound test in a loop"""
@@ -325,7 +325,7 @@ class VFN(NameMapperTest):
self.get('anObj.methX')
for i in range(10):
- self.assertRaises(NotFound,test)
+ self.assertRaises(NotFound, test)
def test45(self):
"""Other exception from meth test"""
@@ -340,7 +340,7 @@ class VFN(NameMapperTest):
self.get('anObj.meth2')
for i in range(10):
- self.assertRaises(ValueError,test)
+ self.assertRaises(ValueError, test)
def test47(self):
"""None in dict lookup"""
@@ -373,7 +373,7 @@ class VFN(NameMapperTest):
self.get('funcThatRaises')
for i in range(10):
- self.assertRaises(ValueError,test)
+ self.assertRaises(ValueError, test)
def test53(self):
@@ -389,7 +389,7 @@ class VFN(NameMapperTest):
self.get('aDict.nestedDict.funcThatRaises')
for i in range(10):
- self.assertRaises(ValueError,test)
+ self.assertRaises(ValueError, test)
def test55(self):
"""aDict.nestedDict.aClass in dict lookup"""
@@ -428,10 +428,10 @@ class VFS(VFN):
if lng == 1:
return [self.namespace()]
elif lng == 2:
- return [self.namespace(),{'dummy':1234}]
+ return [self.namespace(), {'dummy':1234}]
elif lng == 3:
# a tuple for kicks
- return ({'dummy':1234}, self.namespace(),{'dummy':1234})
+ return ({'dummy':1234}, self.namespace(), {'dummy':1234})
elif lng == 4:
# a generator for more kicks
return self.searchListGenerator()
@@ -439,7 +439,7 @@ class VFS(VFN):
def searchListGenerator(self):
class Test:
pass
- for i in [Test(),{'dummy':1234}, self.namespace(),{'dummy':1234}]:
+ for i in [Test(), {'dummy':1234}, self.namespace(), {'dummy':1234}]:
yield i
def get(self, name, autocall=True):
diff --git a/cheetah/Tests/Parser.py b/cheetah/Tests/Parser.py
new file mode 100644
index 0000000..050b613
--- /dev/null
+++ b/cheetah/Tests/Parser.py
@@ -0,0 +1,49 @@
+#!/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()
+
diff --git a/cheetah/Tests/Performance.py b/cheetah/Tests/Performance.py
index 8101e85..d76cc00 100644
--- a/cheetah/Tests/Performance.py
+++ b/cheetah/Tests/Performance.py
@@ -57,8 +57,8 @@ def perftest(max_num_pystones, current_pystone=None):
pystone_total_time = total_time / pystone_rate
global DEBUG
if DEBUG:
- print 'The test "%s" took: %s pystones' % (function.func_name,
- pystone_total_time)
+ 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, '
@@ -78,7 +78,7 @@ class DynamicTemplatePerformanceTest(unittest.TestCase):
#pass
#end def
'''
- for i in xrange(self.loops):
+ for i in range(self.loops):
klass = Cheetah.Template.Template.compile(template)
assert klass
test_BasicDynamic = perftest(1200)(test_BasicDynamic)
@@ -91,15 +91,15 @@ class PerformanceTest(unittest.TestCase):
def runTest(self):
self.prof = hotshot.Profile('%s.prof' % self.__class__.__name__)
self.prof.start()
- for i in xrange(self.iterations):
+ 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)
+ 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')
@@ -130,7 +130,7 @@ class BunchOfWriteCalls(PerformanceTest):
template = '''
#import sys
#import os
- #for i in xrange(1000)
+ #for i in range(1000)
$i
#end for
'''
@@ -210,7 +210,7 @@ class LongCompileTest(PerformanceTest):
<body>
$header()
- #for $i in $xrange(10)
+ #for $i in $range(10)
This is just some stupid page!
<br/>
#end for
diff --git a/cheetah/Tests/Regressions.py b/cheetah/Tests/Regressions.py
index 4d50348..67a736a 100644
--- a/cheetah/Tests/Regressions.py
+++ b/cheetah/Tests/Regressions.py
@@ -29,7 +29,7 @@ class GetAttrTest(unittest.TestCase):
def test_ValidException(self):
o = CustomGetAttrClass()
try:
- print o.attr
+ print(o.attr)
except GetAttrException, e:
# expected
return
diff --git a/cheetah/Tests/SyntaxAndOutput.py b/cheetah/Tests/SyntaxAndOutput.py
index 72721bc..92c129b 100644
--- a/cheetah/Tests/SyntaxAndOutput.py
+++ b/cheetah/Tests/SyntaxAndOutput.py
@@ -67,37 +67,37 @@ def dummyFunc(arg="Scooby"):
return arg
defaultTestNameSpace = {
- 'aStr':'blarg',
- 'anInt':1,
- 'aFloat':1.5,
- 'aList': ['item0','item1','item2'],
- 'aDict': {'one':'item1',
- 'two':'item2',
- 'nestedDict':{1:'nestedItem1',
+ 'aStr': 'blarg',
+ 'anInt': 1,
+ 'aFloat': 1.5,
+ 'aList': ['item0', 'item1', 'item2'],
+ 'aDict': {'one': 'item1',
+ 'two': 'item2',
+ 'nestedDict': {1:'nestedItem1',
'two':'nestedItem2'
},
- 'nestedFunc':dummyFunc,
+ 'nestedFunc': dummyFunc,
},
'aFunc': dummyFunc,
'anObj': DummyClass(),
'aMeth': DummyClass().meth1,
'aStrToBeIncluded': "$aStr $anInt",
- 'none' : None,
- 'emptyString':'',
- 'numOne':1,
- 'numTwo':2,
- 'zero':0,
+ 'none': None,
+ 'emptyString': '',
+ 'numOne': 1,
+ 'numTwo': 2,
+ 'zero': 0,
'tenDigits': 1234567890,
'webSafeTest': 'abc <=> &',
'strip1': ' \t strippable whitespace \t\t \n',
'strip2': ' \t strippable whitespace \t\t ',
'strip3': ' \t strippable whitespace \t\t\n1 2 3\n',
- 'blockToBeParsed':"""$numOne $numTwo""",
- 'includeBlock2':"""$numOne $numTwo $aSetVar""",
+ 'blockToBeParsed': """$numOne $numTwo""",
+ 'includeBlock2': """$numOne $numTwo $aSetVar""",
- 'includeFileName':'parseTest.txt',
- 'listOfLambdas':[lambda x: x, lambda x: x, lambda x: x,],
+ 'includeFileName': 'parseTest.txt',
+ 'listOfLambdas': [lambda x: x, lambda x: x, lambda x: x,],
'list': [
{'index': 0, 'numOne': 1, 'numTwo': 2},
{'index': 1, 'numOne': 1, 'numTwo': 2},
@@ -105,7 +105,7 @@ defaultTestNameSpace = {
'nameList': [('john', 'doe'), ('jane', 'smith')],
'letterList': ['a', 'b', 'c'],
'_': lambda x: 'Translated: ' + x,
- 'unicodeData':u'aoeu12345\u1234',
+ 'unicodeData': u'aoeu12345\u1234',
}
@@ -162,7 +162,8 @@ Template output mismatch:
**extraKwArgs
)
moduleCode = templateClass._CHEETAH_generatedModuleCode
- self.template = templateObj = templateClass(searchList=self.searchList())
+ searchList = self.searchList() or self._searchList
+ self.template = templateObj = templateClass(searchList=searchList)
else:
self.template = templateObj = Template(
input,
@@ -171,7 +172,7 @@ Template output mismatch:
)
moduleCode = templateObj._CHEETAH_generatedModuleCode
if self.DEBUGLEV >= 1:
- print moduleCode
+ print(moduleCode)
try:
output = templateObj.respond() # rather than __str__, because of unicode
assert output==expectedOutput, self._outputMismatchReport(output, expectedOutput)
@@ -185,9 +186,9 @@ Template output mismatch:
if self._debugEOLReplacement and self._EOLreplacement:
EOLrepl = self._EOLreplacement
marker = '*EOL*'
- return self.report % {'template': self._input.replace(EOLrepl,marker),
- 'expected': expectedOutput.replace(EOLrepl,marker),
- 'actual': output.replace(EOLrepl,marker),
+ return self.report % {'template': self._input.replace(EOLrepl, marker),
+ 'expected': expectedOutput.replace(EOLrepl, marker),
+ 'actual': output.replace(EOLrepl, marker),
'end': '(end)'}
else:
return self.report % {'template': self._input,
@@ -237,7 +238,7 @@ class Backslashes(OutputTest):
convertEOLs = False
def setUp(self):
- fp = open('backslashes.txt','w')
+ fp = open('backslashes.txt', 'w')
fp.write(r'\ #LogFormat "%h %l %u %t \"%r\" %>s %b"' + '\n\n\n\n\n\n\n')
fp.flush()
fp.close
@@ -620,7 +621,7 @@ class Placeholders(OutputTest):
tmpl = tmpl.subclass('#for name in $names: $*1*(name) ')
assert str(tmpl({'names':names}))=='You '*len(names)
- if versionTuple > (2,2):
+ if versionTuple > (2, 2):
tmpl = tmpl.subclass('#for name in $names: $*1*(name) ')
assert str(tmpl(names=names))=='You '*len(names)
@@ -1359,6 +1360,12 @@ class RawDirective(OutputTest):
self.verify("#raw: $aFunc().\n$anInt",
"$aFunc().\n1")
+ def test6(self):
+ """ Escape characters in a #raw block """
+ self.verify( """#raw: This escape should be preserved: \\$unexpanded So should this one: \\#blah The string "\\012" should not disappear.""",
+ r"""This escape should be preserved: \$unexpanded So should this one: \#blah The string "\012" should not disappear.""")
+
+
class BreakpointDirective(OutputTest):
def test1(self):
"""#breakpoint part way through source code"""
@@ -1477,7 +1484,7 @@ class YieldDirective(OutputTest):
)
- for src in (src1,src2,src3):
+ for src in (src1, src2, src3):
klass = Template.compile(src, keepRefToGeneratedCode=True)
#print klass._CHEETAH_generatedModuleCode
iter = klass().respond()
@@ -1487,7 +1494,7 @@ class YieldDirective(OutputTest):
# @@TR: need to expand this to cover error conditions etc.
-if versionTuple < (2,3):
+if versionTuple < (2, 3):
del YieldDirective
class ForDirective(OutputTest):
@@ -1589,7 +1596,7 @@ class ForDirective(OutputTest):
self.verify("#for $i in range(5): \n$i\n#end for",
"0\n1\n2\n3\n4\n")
-if versionTuple < (2,3):
+if versionTuple < (2, 3):
del ForDirective.test12
class RepeatDirective(OutputTest):
@@ -1826,7 +1833,7 @@ class DecoratorDirective(OutputTest):
"$testMeth",
"1234\n")
-if versionTuple < (2,4):
+if versionTuple < (2, 4):
del DecoratorDirective
class BlockDirective(OutputTest):
@@ -1941,7 +1948,7 @@ inner
class IncludeDirective(OutputTest):
def setUp(self):
- fp = open('parseTest.txt','w')
+ fp = open('parseTest.txt', 'w')
fp.write("$numOne $numTwo")
fp.flush()
fp.close
@@ -2338,6 +2345,8 @@ class UnlessDirective(OutputTest):
self.verify("#unless 0: 1234\n"*2, "1234\n"*2)
class PSP(OutputTest):
+ def searchList(self):
+ return None
def test1(self):
"""simple <%= [int] %>"""
@@ -2376,6 +2385,19 @@ class PSP(OutputTest):
self.verify("""<% for i in range(5):
i=i*2$%><%=i%>-<%end%>""", "0-2-4-6-8-")
+ def test10(self):
+ """ Using getVar and write within a PSP """
+ self._searchList = [{'me' : 1}]
+ template = '''This is my template
+<%
+me = self.getVar('me')
+if isinstance(me, int):
+ write('Bork')
+else:
+ write('Nork')
+%>'''
+ self.verify(template, 'This is my template\nBork')
+
class WhileDirective(OutputTest):
def test1(self):
@@ -3192,26 +3214,26 @@ public class X
##################################################
## CREATE CONVERTED EOL VERSIONS OF THE TEST CASES
-if OutputTest._useNewStyleCompilation and versionTuple >= (2,3):
+if OutputTest._useNewStyleCompilation and versionTuple >= (2, 3):
extraCompileKwArgsForDiffBaseclass = {'baseclass':dict}
else:
extraCompileKwArgsForDiffBaseclass = {'baseclass':object}
def install_eols():
- klasses = [v for v in globals().values() if isinstance(v, (types.ClassType, types.TypeType)) and issubclass(v, unittest.TestCase)]
+ klasses = [v for v in globals().values() if isinstance(v, type) and issubclass(v, unittest.TestCase)]
for klass in klasses:
name = klass.__name__
- if hasattr(klass,'convertEOLs') and klass.convertEOLs:
+ if hasattr(klass, 'convertEOLs') and klass.convertEOLs:
win32Src = r"class %(name)s_Win32EOL(%(name)s): _EOLreplacement = '\r\n'"%locals()
macSrc = r"class %(name)s_MacEOL(%(name)s): _EOLreplacement = '\r'"%locals()
- exec win32Src in globals()
- exec macSrc in globals()
+ exec(win32Src, globals())
+ exec(macSrc, globals())
- if versionTuple >= (2,3):
+ if versionTuple >= (2, 3):
src = r"class %(name)s_DiffBaseClass(%(name)s): "%locals()
src += " _extraCompileKwArgs = extraCompileKwArgsForDiffBaseclass"
- exec src in globals()
+ exec(src, globals())
del name
del klass
diff --git a/cheetah/Tests/Template.py b/cheetah/Tests/Template.py
index 144ae6f..331c0f9 100644
--- a/cheetah/Tests/Template.py
+++ b/cheetah/Tests/Template.py
@@ -44,7 +44,7 @@ class ClassMethods_compile(TemplateTest):
assert str(t)=='1234'
def test_moduleFileCaching(self):
- if versionTuple < (2,3):
+ if versionTuple < (2, 3):
return
tmpDir = tempfile.mkdtemp()
try:
@@ -220,9 +220,9 @@ class Preprocessors(TemplateTest):
class TemplateSubclass(Template):
pass
- compilerSettings = {'cheetahVarStartToken':'@',
- 'directiveStartToken':'%',
- 'commentStartToken':'%%',
+ compilerSettings = {'cheetahVarStartToken': '@',
+ 'directiveStartToken': '%',
+ 'commentStartToken': '%%',
}
for arg in ['@ %',
@@ -293,7 +293,7 @@ class TryExceptImportTest(TemplateTest):
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'
+ print('This version of Python doesn\'t support decorators, skipping tests')
return
template = '''
#@classmethod
@@ -311,7 +311,7 @@ class ClassMethodSupport(TemplateTest):
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'
+ print('This version of Python doesn\'t support decorators, skipping tests')
return
template = '''
#@staticmethod
diff --git a/cheetah/Tests/Test.py b/cheetah/Tests/Test.py
index 13b0171..8e78a8e 100755
--- a/cheetah/Tests/Test.py
+++ b/cheetah/Tests/Test.py
@@ -13,14 +13,17 @@ TODO
import sys
import unittest
-import SyntaxAndOutput
-import NameMapper
-import Filters
-import Template
-import Cheps
-import Regressions
-import Unicode
-import CheetahWrapper
+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()
@@ -32,6 +35,9 @@ suites = [
#unittest.findTestCases(Cheps),
unittest.findTestCases(Regressions),
unittest.findTestCases(Unicode),
+ unittest.findTestCases(Misc),
+ unittest.findTestCases(Parser),
+ unittest.findTestCases(Analyzer),
]
if not sys.platform.startswith('java'):
diff --git a/cheetah/Tests/Unicode.py b/cheetah/Tests/Unicode.py
index 12c00ac..c881d86 100644
--- a/cheetah/Tests/Unicode.py
+++ b/cheetah/Tests/Unicode.py
@@ -21,7 +21,7 @@ class CommandLineTest(unittest.TestCase):
fd.close()
wrap = CheetahWrapper.CheetahWrapper()
- wrap.main(['cheetah', 'compile', '--nobackup', sourcefile])
+ 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)
@@ -194,5 +194,44 @@ class Unicode_in_SearchList_Test(CommandLineTest):
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()
diff --git a/cheetah/Tests/VerifyType.py b/cheetah/Tests/VerifyType.py
deleted file mode 100644
index a581d70..0000000
--- a/cheetah/Tests/VerifyType.py
+++ /dev/null
@@ -1,158 +0,0 @@
-#!/usr/bin/env python
-import unittest
-
-from Cheetah.Utils import VerifyType
-from Cheetah import _verifytype
-
-class VerifyType_Test(unittest.TestCase):
- def test_Verified(self):
- arg = 'foo'
- legalTypes = [str, unicode]
- try:
- rc = VerifyType.VerifyType(arg, 'arg', legalTypes, 'string')
- assert rc
- except TypeError:
- self.fail('Should not have raised a TypeError here')
-
- try:
- rc = _verifytype.verifyType(arg, 'arg', legalTypes, 'string')
- assert rc
- except TypeError:
- self.fail('Should not have raised a TypeError here')
-
- def test_Unverified(self):
- arg = 'foo'
- legalTypes = [list, dict]
- self.failUnlessRaises(TypeError, VerifyType.VerifyType, arg,
- 'arg', legalTypes, 'list or dict')
- self.failUnlessRaises(TypeError, _verifytype.verifyType, arg,
- 'arg', legalTypes, 'list or dict')
-
- def test_IncorrectNumberOfArgs(self):
- arg = 'foo'
- legalTypes = [str, unicode]
-
- self.failUnlessRaises(TypeError, VerifyType.VerifyType)
- self.failUnlessRaises(TypeError, _verifytype.verifyType)
-
- self.failUnlessRaises(TypeError, VerifyType.VerifyType, arg)
- self.failUnlessRaises(TypeError, _verifytype.verifyType, arg)
-
- self.failUnlessRaises(TypeError, VerifyType.VerifyType, arg,
- 'arg')
- self.failUnlessRaises(TypeError, _verifytype.verifyType, arg,
- 'arg')
-
- self.failUnlessRaises(TypeError, VerifyType.VerifyType, arg,
- 'arg', legalTypes)
- self.failUnlessRaises(TypeError, _verifytype.verifyType, arg,
- 'arg', legalTypes)
-
- self.failUnlessRaises(TypeError, VerifyType.VerifyType, arg,
- 'arg', legalTypes, 'string', 'errmsgExtra', 'one more')
- self.failUnlessRaises(TypeError, _verifytype.verifyType, arg,
- 'arg', legalTypes, 'string', 'errmsgExtra', 'one more')
-
- def test_LegalTypesNotIterable(self):
- arg = 'foo'
- legalTypes = 1
-
- self.failUnlessRaises(TypeError, VerifyType.VerifyType, arg,
- 'arg', legalTypes, 'string')
- self.failUnlessRaises(TypeError, _verifytype.verifyType, arg,
- 'arg', legalTypes, 'string')
-
-class FakeClass(dict):
- pass
-
-class VerifyTypeClass_Test(unittest.TestCase):
- def test_VerifiedClass(self):
- arg = FakeClass
- legalTypes = [type]
- try:
- rc = VerifyType.VerifyTypeClass(arg, 'arg', legalTypes, '', dict)
- assert rc
- except TypeError:
- self.fail('Should not have raised a TypeError here')
-
- try:
- rc = _verifytype.verifyTypeClass(arg, 'arg', legalTypes, 'foo', dict)
- assert rc
- except TypeError:
- self.fail('Should not have raised a TypeError here')
-
- def test_UnverifiedClass(self):
- arg = FakeClass
- legalTypes = [type]
- self.failUnlessRaises(TypeError, VerifyType.VerifyTypeClass, arg,
- legalTypes, 'subclass of list', list)
- self.failUnlessRaises(TypeError, _verifytype.verifyTypeClass, arg,
- legalTypes, 'subclass of list', list)
-
- def test_Verified(self):
- arg = 'foo'
- legalTypes = [str, unicode]
- try:
- rc = VerifyType.VerifyTypeClass(arg, 'arg', legalTypes, 'string', int)
- assert rc
- except TypeError:
- self.fail('Should not have raised a TypeError here')
-
- try:
- rc = _verifytype.verifyTypeClass(arg, 'arg', legalTypes, 'string', int)
- assert rc
- except TypeError:
- self.fail('Should not have raised a TypeError here')
-
- def test_Unverified(self):
- arg = 'foo'
- legalTypes = [list, dict]
- self.failUnlessRaises(TypeError, VerifyType.VerifyTypeClass, arg,
- 'arg', legalTypes, 'list or dict', int)
- self.failUnlessRaises(TypeError, _verifytype.verifyTypeClass, arg,
- 'arg', legalTypes, 'list or dict', int)
-
- def test_IncorrectNumberOfArgs(self):
- arg = 'foo'
- legalTypes = [str, unicode]
-
- self.failUnlessRaises(TypeError, VerifyType.VerifyTypeClass)
- self.failUnlessRaises(TypeError, _verifytype.verifyTypeClass)
-
- self.failUnlessRaises(TypeError, VerifyType.VerifyTypeClass, arg)
- self.failUnlessRaises(TypeError, _verifytype.verifyTypeClass, arg)
-
- self.failUnlessRaises(TypeError, VerifyType.VerifyTypeClass, arg,
- 'arg')
- self.failUnlessRaises(TypeError, _verifytype.verifyTypeClass, arg,
- 'arg')
-
- self.failUnlessRaises(TypeError, VerifyType.VerifyTypeClass, arg,
- 'arg', legalTypes)
- self.failUnlessRaises(TypeError, _verifytype.verifyTypeClass, arg,
- 'arg', legalTypes)
-
- self.failUnlessRaises(TypeError, VerifyType.VerifyTypeClass, arg,
- 'arg', legalTypes, 'string')
- self.failUnlessRaises(TypeError, _verifytype.verifyTypeClass, arg,
- 'arg', legalTypes, 'string')
-
- self.failUnlessRaises(TypeError, VerifyType.VerifyTypeClass, arg,
- 'arg', legalTypes, 'string', int, 'errmsgExtra', 'one more')
- self.failUnlessRaises(TypeError, _verifytype.verifyTypeClass, arg,
- 'arg', legalTypes, 'string', int, 'errmsgExtra', 'one more')
-
- def test_LegalTypesNotIterable(self):
- arg = 'foo'
- legalTypes = 1
-
- self.failUnlessRaises(TypeError, VerifyType.VerifyTypeClass, arg,
- 'arg', legalTypes, 'string', int)
- self.failUnlessRaises(TypeError, _verifytype.verifyTypeClass, arg,
- 'arg', legalTypes, 'string', int)
-
-
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/cheetah/Tests/unittest_local_copy.py b/cheetah/Tests/unittest_local_copy.py
deleted file mode 100755
index a5f5499..0000000
--- a/cheetah/Tests/unittest_local_copy.py
+++ /dev/null
@@ -1,978 +0,0 @@
-#!/usr/bin/env python
-""" This is a hacked version of PyUnit that extends its reporting capabilities
-with optional meta data on the test cases. It also makes it possible to
-separate the standard and error output streams in TextTestRunner.
-
-It's a hack rather than a set of subclasses because a) Steve had used double
-underscore private attributes for some things I needed access to, and b) the
-changes affected so many classes that it was easier just to hack it.
-
-The changes are in the following places:
-TestCase:
- - minor refactoring of __init__ and __call__ internals
- - added some attributes and methods for storing and retrieving meta data
-
-_TextTestResult
- - refactored the stream handling
- - incorporated all the output code from TextTestRunner
- - made the output of FAIL and ERROR information more flexible and
- incorporated the new meta data from TestCase
- - added a flag called 'explain' to __init__ that controls whether the new '
- explanation' meta data from TestCase is printed along with tracebacks
-
-TextTestRunner
- - delegated all output to _TextTestResult
- - added 'err' and 'explain' to the __init__ signature to match the changes
- in _TextTestResult
-
-TestProgram
- - added -e and --explain as flags on the command line
-
--- Tavis Rudd <tavis@redonions.net> (Sept 28th, 2001)
-
-- _TestTextResult.printErrorList(): print blank line after each traceback
-
--- Mike Orr <mso@oz.net> (Nov 11, 2002)
-
-TestCase methods copied from unittest in Python 2.3:
- - .assertAlmostEqual(first, second, places=7, msg=None): to N decimal places.
- - .failIfAlmostEqual(first, second, places=7, msg=None)
-
--- Mike Orr (Jan 5, 2004)
-
-
-Below is the original docstring for unittest.
----------------------------------------------------------------------------
-Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's
-Smalltalk testing framework.
-
-This module contains the core framework classes that form the basis of
-specific test cases and suites (TestCase, TestSuite etc.), and also a
-text-based utility class for running the tests and reporting the results
-(TextTestRunner).
-
-Simple usage:
-
- import unittest
-
- class IntegerArithmenticTestCase(unittest.TestCase):
- def testAdd(self): ## test method names begin 'test*'
- self.assertEquals((1 + 2), 3)
- self.assertEquals(0 + 1, 1)
- def testMultiply(self);
- self.assertEquals((0 * 10), 0)
- self.assertEquals((5 * 8), 40)
-
- if __name__ == '__main__':
- unittest.main()
-
-Further information is available in the bundled documentation, and from
-
- http://pyunit.sourceforge.net/
-
-Copyright (c) 1999, 2000, 2001 Steve Purcell
-This module is free software, and you may redistribute it and/or modify
-it under the same terms as Python itself, so long as this copyright message
-and disclaimer are retained in their original form.
-
-IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
-SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
-THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
-DAMAGE.
-
-THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
-PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
-AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
-SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
-"""
-
-__author__ = "Steve Purcell"
-__email__ = "stephen_purcell at yahoo dot com"
-__revision__ = "$Revision: 1.11 $"[11:-2]
-
-
-##################################################
-## DEPENDENCIES ##
-
-import os
-import re
-import string
-import sys
-import time
-import traceback
-import types
-import pprint
-
-##################################################
-## CONSTANTS & GLOBALS
-
-try:
- True,False
-except NameError:
- True, False = (1==1),(1==0)
-
-##############################################################################
-# Test framework core
-##############################################################################
-
-
-class TestResult:
- """Holder for test result information.
-
- Test results are automatically managed by the TestCase and TestSuite
- classes, and do not need to be explicitly manipulated by writers of tests.
-
- Each instance holds the total number of tests run, and collections of
- failures and errors that occurred among those test runs. The collections
- contain tuples of (testcase, exceptioninfo), where exceptioninfo is a
- tuple of values as returned by sys.exc_info().
- """
- def __init__(self):
- self.failures = []
- self.errors = []
- self.testsRun = 0
- self.shouldStop = 0
-
- def startTest(self, test):
- "Called when the given test is about to be run"
- self.testsRun = self.testsRun + 1
-
- def stopTest(self, test):
- "Called when the given test has been run"
- pass
-
- def addError(self, test, err):
- "Called when an error has occurred"
- self.errors.append((test, err))
-
- def addFailure(self, test, err):
- "Called when a failure has occurred"
- self.failures.append((test, err))
-
- def addSuccess(self, test):
- "Called when a test has completed successfully"
- pass
-
- def wasSuccessful(self):
- "Tells whether or not this result was a success"
- return len(self.failures) == len(self.errors) == 0
-
- def stop(self):
- "Indicates that the tests should be aborted"
- self.shouldStop = 1
-
- def __repr__(self):
- return "<%s run=%i errors=%i failures=%i>" % \
- (self.__class__, self.testsRun, len(self.errors),
- len(self.failures))
-
-class TestCase:
- """A class whose instances are single test cases.
-
- By default, the test code itself should be placed in a method named
- 'runTest'.
-
- If the fixture may be used for many test cases, create as
- many test methods as are needed. When instantiating such a TestCase
- subclass, specify in the constructor arguments the name of the test method
- that the instance is to execute.
-
- Test authors should subclass TestCase for their own tests. Construction
- and deconstruction of the test's environment ('fixture') can be
- implemented by overriding the 'setUp' and 'tearDown' methods respectively.
-
- If it is necessary to override the __init__ method, the base class
- __init__ method must always be called. It is important that subclasses
- should not change the signature of their __init__ method, since instances
- of the classes are instantiated automatically by parts of the framework
- in order to be run.
- """
-
- # This attribute determines which exception will be raised when
- # the instance's assertion methods fail; test methods raising this
- # exception will be deemed to have 'failed' rather than 'errored'
-
- failureException = AssertionError
-
- # the name of the fixture. Used for displaying meta data about the test
- name = None
-
- def __init__(self, methodName='runTest'):
- """Create an instance of the class that will use the named test
- method when executed. Raises a ValueError if the instance does
- not have a method with the specified name.
- """
- self._testMethodName = methodName
- self._setupTestMethod()
- self._setupMetaData()
-
- def _setupTestMethod(self):
- try:
- self._testMethod = getattr(self, self._testMethodName)
- except AttributeError:
- raise ValueError, "no such test method in %s: %s" % \
- (self.__class__, self._testMethodName)
-
- ## meta data methods
-
- def _setupMetaData(self):
- """Setup the default meta data for the test case:
-
- - id: self.__class__.__name__ + testMethodName OR self.name + testMethodName
- - description: 1st line of Class docstring + 1st line of method docstring
- - explanation: rest of Class docstring + rest of method docstring
-
- """
-
-
- testDoc = self._testMethod.__doc__ or '\n'
- testDocLines = testDoc.splitlines()
-
- testDescription = testDocLines[0].strip()
- if len(testDocLines) > 1:
- testExplanation = '\n'.join(
- [ln.strip() for ln in testDocLines[1:]]
- ).strip()
- else:
- testExplanation = ''
-
- fixtureDoc = self.__doc__ or '\n'
- fixtureDocLines = fixtureDoc.splitlines()
- fixtureDescription = fixtureDocLines[0].strip()
- if len(fixtureDocLines) > 1:
- fixtureExplanation = '\n'.join(
- [ln.strip() for ln in fixtureDocLines[1:]]
- ).strip()
- else:
- fixtureExplanation = ''
-
- if not self.name:
- self.name = self.__class__
- self._id = "%s.%s" % (self.name, self._testMethodName)
-
- if not fixtureDescription:
- self._description = testDescription
- else:
- self._description = fixtureDescription + ', ' + testDescription
-
- if not fixtureExplanation:
- self._explanation = testExplanation
- else:
- self._explanation = ['Fixture Explanation:',
- '--------------------',
- fixtureExplanation,
- '',
- 'Test Explanation:',
- '-----------------',
- testExplanation
- ]
- self._explanation = '\n'.join(self._explanation)
-
- def id(self):
- return self._id
-
- def setId(self, id):
- self._id = id
-
- def describe(self):
- """Returns a one-line description of the test, or None if no
- description has been provided.
-
- The default implementation of this method returns the first line of
- the specified test method's docstring.
- """
- return self._description
-
- shortDescription = describe
-
- def setDescription(self, descr):
- self._description = descr
-
- def explain(self):
- return self._explanation
-
- def setExplanation(self, expln):
- self._explanation = expln
-
- ## core methods
-
- def setUp(self):
- "Hook method for setting up the test fixture before exercising it."
- pass
-
- def run(self, result=None):
- return self(result)
-
- def tearDown(self):
- "Hook method for deconstructing the test fixture after testing it."
- pass
-
- def debug(self):
- """Run the test without collecting errors in a TestResult"""
- self.setUp()
- self._testMethod()
- self.tearDown()
-
- ## internal methods
-
- def defaultTestResult(self):
- return TestResult()
-
- def __call__(self, result=None):
- if result is None:
- result = self.defaultTestResult()
-
- result.startTest(self)
- try:
- try:
- self.setUp()
- except:
- result.addError(self, self.__exc_info())
- return
-
- ok = 0
- try:
- self._testMethod()
- ok = 1
- except self.failureException, e:
- result.addFailure(self, self.__exc_info())
- except:
- result.addError(self, self.__exc_info())
- try:
- self.tearDown()
- except:
- result.addError(self, self.__exc_info())
- ok = 0
- if ok:
- result.addSuccess(self)
- finally:
- result.stopTest(self)
-
- return result
-
- def countTestCases(self):
- return 1
-
- def __str__(self):
- return "%s (%s)" % (self._testMethodName, self.__class__)
-
- def __repr__(self):
- return "<%s testMethod=%s>" % \
- (self.__class__, self._testMethodName)
-
- def __exc_info(self):
- """Return a version of sys.exc_info() with the traceback frame
- minimised; usually the top level of the traceback frame is not
- needed.
- """
- exctype, excvalue, tb = sys.exc_info()
- if sys.platform[:4] == 'java': ## tracebacks look different in Jython
- return (exctype, excvalue, tb)
- newtb = tb.tb_next
- if newtb is None:
- return (exctype, excvalue, tb)
- return (exctype, excvalue, newtb)
-
- ## methods for use by the test cases
-
- def fail(self, msg=None):
- """Fail immediately, with the given message."""
- raise self.failureException, msg
-
- def failIf(self, expr, msg=None):
- "Fail the test if the expression is true."
- if expr: raise self.failureException, msg
-
- def failUnless(self, expr, msg=None):
- """Fail the test unless the expression is true."""
- if not expr: raise self.failureException, msg
-
- def failUnlessRaises(self, excClass, callableObj, *args, **kwargs):
- """Fail unless an exception of class excClass is thrown
- by callableObj when invoked with arguments args and keyword
- arguments kwargs. If a different type of exception is
- thrown, it will not be caught, and the test case will be
- deemed to have suffered an error, exactly as for an
- unexpected exception.
- """
- try:
- apply(callableObj, args, kwargs)
- except excClass:
- return
- else:
- if hasattr(excClass,'__name__'): excName = excClass.__name__
- else: excName = str(excClass)
- raise self.failureException, excName
-
- def failUnlessEqual(self, first, second, msg=None):
- """Fail if the two objects are unequal as determined by the '!='
- operator.
- """
- if first != second:
- raise self.failureException, (msg or '%s != %s' % (first, second))
-
- def failIfEqual(self, first, second, msg=None):
- """Fail if the two objects are equal as determined by the '=='
- operator.
- """
- if first == second:
- raise self.failureException, (msg or '%s == %s' % (first, second))
-
- def failUnlessAlmostEqual(self, first, second, places=7, msg=None):
- """Fail if the two objects are unequal as determined by their
- difference rounded to the given number of decimal places
- (default 7) and comparing to zero.
-
- Note that decimal places (from zero) is usually not the same
- as significant digits (measured from the most signficant digit).
- """
- if round(second-first, places) != 0:
- raise self.failureException, \
- (msg or '%s != %s within %s places' % (`first`, `second`, `places` ))
-
- def failIfAlmostEqual(self, first, second, places=7, msg=None):
- """Fail if the two objects are equal as determined by their
- difference rounded to the given number of decimal places
- (default 7) and comparing to zero.
-
- Note that decimal places (from zero) is usually not the same
- as significant digits (measured from the most signficant digit).
- """
- if round(second-first, places) == 0:
- raise self.failureException, \
- (msg or '%s == %s within %s places' % (`first`, `second`, `places`))
-
- ## aliases
-
- assertEqual = assertEquals = failUnlessEqual
-
- assertNotEqual = assertNotEquals = failIfEqual
-
- assertAlmostEqual = assertAlmostEquals = failUnlessAlmostEqual
-
- assertNotAlmostEqual = assertNotAlmostEquals = failIfAlmostEqual
-
- assertRaises = failUnlessRaises
-
- assert_ = failUnless
-
-
-class FunctionTestCase(TestCase):
- """A test case that wraps a test function.
-
- This is useful for slipping pre-existing test functions into the
- PyUnit framework. Optionally, set-up and tidy-up functions can be
- supplied. As with TestCase, the tidy-up ('tearDown') function will
- always be called if the set-up ('setUp') function ran successfully.
- """
-
- def __init__(self, testFunc, setUp=None, tearDown=None,
- description=None):
- TestCase.__init__(self)
- self.__setUpFunc = setUp
- self.__tearDownFunc = tearDown
- self.__testFunc = testFunc
- self.__description = description
-
- def setUp(self):
- if self.__setUpFunc is not None:
- self.__setUpFunc()
-
- def tearDown(self):
- if self.__tearDownFunc is not None:
- self.__tearDownFunc()
-
- def runTest(self):
- self.__testFunc()
-
- def id(self):
- return self.__testFunc.__name__
-
- def __str__(self):
- return "%s (%s)" % (self.__class__, self.__testFunc.__name__)
-
- def __repr__(self):
- return "<%s testFunc=%s>" % (self.__class__, self.__testFunc)
-
-
- def describe(self):
- if self.__description is not None: return self.__description
- doc = self.__testFunc.__doc__
- return doc and string.strip(string.split(doc, "\n")[0]) or None
-
- ## aliases
- shortDescription = describe
-
-class TestSuite:
- """A test suite is a composite test consisting of a number of TestCases.
-
- For use, create an instance of TestSuite, then add test case instances.
- When all tests have been added, the suite can be passed to a test
- runner, such as TextTestRunner. It will run the individual test cases
- in the order in which they were added, aggregating the results. When
- subclassing, do not forget to call the base class constructor.
- """
- def __init__(self, tests=(), suiteName=None):
- self._tests = []
- self._testMap = {}
- self.suiteName = suiteName
- self.addTests(tests)
-
- def __repr__(self):
- return "<%s tests=%s>" % (self.__class__, pprint.pformat(self._tests))
-
- __str__ = __repr__
-
- def countTestCases(self):
- cases = 0
- for test in self._tests:
- cases = cases + test.countTestCases()
- return cases
-
- def addTest(self, test):
- self._tests.append(test)
- if isinstance(test, TestSuite) and test.suiteName:
- name = test.suiteName
- elif isinstance(test, TestCase):
- #print test, test._testMethodName
- name = test._testMethodName
- else:
- name = test.__class__.__name__
- self._testMap[name] = test
-
- def addTests(self, tests):
- for test in tests:
- self.addTest(test)
-
- def getTestForName(self, name):
- return self._testMap[name]
-
- def run(self, result):
- return self(result)
-
- def __call__(self, result):
- for test in self._tests:
- if result.shouldStop:
- break
- test(result)
- return result
-
- def debug(self):
- """Run the tests without collecting errors in a TestResult"""
- for test in self._tests: test.debug()
-
-
-##############################################################################
-# Text UI
-##############################################################################
-
-class StreamWrapper:
- def __init__(self, out=sys.stdout, err=sys.stderr):
- self._streamOut = out
- self._streamErr = err
-
- def write(self, txt):
- self._streamOut.write(txt)
- self._streamOut.flush()
-
- def writeln(self, *lines):
- for line in lines:
- self.write(line + '\n')
- if not lines:
- self.write('\n')
-
- def writeErr(self, txt):
- self._streamErr.write(txt)
-
- def writelnErr(self, *lines):
- for line in lines:
- self.writeErr(line + '\n')
- if not lines:
- self.writeErr('\n')
-
-
-class _TextTestResult(TestResult, StreamWrapper):
- _separatorWidth = 70
- _sep1 = '='
- _sep2 = '-'
- _errorSep1 = '*'
- _errorSep2 = '-'
- _errorSep3 = ''
-
- def __init__(self,
- stream=sys.stdout,
- errStream=sys.stderr,
- verbosity=1,
- explain=False):
-
- TestResult.__init__(self)
- StreamWrapper.__init__(self, out=stream, err=errStream)
-
- self._verbosity = verbosity
- self._showAll = verbosity > 1
- self._dots = (verbosity == 1)
- self._explain = explain
-
- ## startup and shutdown methods
-
- def beginTests(self):
- self._startTime = time.time()
-
- def endTests(self):
- self._stopTime = time.time()
- self._timeTaken = float(self._stopTime - self._startTime)
-
- def stop(self):
- self.shouldStop = 1
-
- ## methods called for each test
-
- def startTest(self, test):
- TestResult.startTest(self, test)
- if self._showAll:
- self.write("%s (%s)" %( test.id(), test.describe() ) )
- self.write(" ... ")
-
- def addSuccess(self, test):
- TestResult.addSuccess(self, test)
- if self._showAll:
- self.writeln("ok")
- elif self._dots:
- self.write('.')
-
- def addError(self, test, err):
- TestResult.addError(self, test, err)
- if self._showAll:
- self.writeln("ERROR")
- elif self._dots:
- self.write('E')
- if err[0] is KeyboardInterrupt:
- self.stop()
-
- def addFailure(self, test, err):
- TestResult.addFailure(self, test, err)
- if self._showAll:
- self.writeln("FAIL")
- elif self._dots:
- self.write('F')
-
- ## display methods
-
- def summarize(self):
- self.printErrors()
- self.writeSep2()
- run = self.testsRun
- self.writeln("Ran %d test%s in %.3fs" %
- (run, run == 1 and "" or "s", self._timeTaken))
- self.writeln()
- if not self.wasSuccessful():
- self.writeErr("FAILED (")
- failed, errored = map(len, (self.failures, self.errors))
- if failed:
- self.writeErr("failures=%d" % failed)
- if errored:
- if failed: self.writeErr(", ")
- self.writeErr("errors=%d" % errored)
- self.writelnErr(")")
- else:
- self.writelnErr("OK")
-
- def writeSep1(self):
- self.writeln(self._sep1 * self._separatorWidth)
-
- def writeSep2(self):
- self.writeln(self._sep2 * self._separatorWidth)
-
- def writeErrSep1(self):
- self.writeln(self._errorSep1 * self._separatorWidth)
-
- def writeErrSep2(self):
- self.writeln(self._errorSep2 * self._separatorWidth)
-
- def printErrors(self):
- if self._dots or self._showAll:
- self.writeln()
- self.printErrorList('ERROR', self.errors)
- self.printErrorList('FAIL', self.failures)
-
- def printErrorList(self, flavour, errors):
- for test, err in errors:
- self.writeErrSep1()
- self.writelnErr("%s %s (%s)" % (flavour, test.id(), test.describe() ))
- if self._explain:
- expln = test.explain()
- if expln:
- self.writeErrSep2()
- self.writeErr( expln )
- self.writelnErr()
-
- self.writeErrSep2()
- for line in apply(traceback.format_exception, err):
- for l in line.split("\n")[:-1]:
- self.writelnErr(l)
- self.writelnErr("")
-
-class TextTestRunner:
- def __init__(self,
- stream=sys.stdout,
- errStream=sys.stderr,
- verbosity=1,
- explain=False):
-
- self._out = stream
- self._err = errStream
- self._verbosity = verbosity
- self._explain = explain
-
- ## main methods
-
- def run(self, test):
- result = self._makeResult()
- result.beginTests()
- test( result )
- result.endTests()
- result.summarize()
-
- return result
-
- ## internal methods
-
- def _makeResult(self):
- return _TextTestResult(stream=self._out,
- errStream=self._err,
- verbosity=self._verbosity,
- explain=self._explain,
- )
-
-##############################################################################
-# Locating and loading tests
-##############################################################################
-
-class TestLoader:
- """This class is responsible for loading tests according to various
- criteria and returning them wrapped in a Test
- """
- testMethodPrefix = 'test'
- sortTestMethodsUsing = cmp
- suiteClass = TestSuite
-
- def loadTestsFromTestCase(self, testCaseClass):
- """Return a suite of all tests cases contained in testCaseClass"""
- return self.suiteClass(tests=map(testCaseClass,
- self.getTestCaseNames(testCaseClass)),
- suiteName=testCaseClass.__name__)
-
- def loadTestsFromModule(self, module):
- """Return a suite of all tests cases contained in the given module"""
- tests = []
- for name in dir(module):
- obj = getattr(module, name)
- if type(obj) == types.ClassType and issubclass(obj, TestCase):
- tests.append(self.loadTestsFromTestCase(obj))
- return self.suiteClass(tests)
-
- def loadTestsFromName(self, name, module=None):
- """Return a suite of all tests cases given a string specifier.
-
- The name may resolve either to a module, a test case class, a
- test method within a test case class, or a callable object which
- returns a TestCase or TestSuite instance.
-
- The method optionally resolves the names relative to a given module.
- """
- parts = string.split(name, '.')
- if module is None:
- if not parts:
- raise ValueError, "incomplete test name: %s" % name
- else:
- parts_copy = parts[:]
- while parts_copy:
- try:
- module = __import__(string.join(parts_copy,'.'))
- break
- except ImportError:
- del parts_copy[-1]
- if not parts_copy: raise
- parts = parts[1:]
- obj = module
- for part in parts:
- if isinstance(obj, TestSuite):
- obj = obj.getTestForName(part)
- else:
- obj = getattr(obj, part)
-
- if type(obj) == types.ModuleType:
- return self.loadTestsFromModule(obj)
- elif type(obj) == types.ClassType and issubclass(obj, TestCase):
- return self.loadTestsFromTestCase(obj)
- elif type(obj) == types.UnboundMethodType:
- return obj.im_class(obj.__name__)
- elif isinstance(obj, TestSuite):
- return obj
- elif isinstance(obj, TestCase):
- return obj
- elif callable(obj):
- test = obj()
- if not isinstance(test, TestCase) and \
- not isinstance(test, TestSuite):
- raise ValueError, \
- "calling %s returned %s, not a test" %(obj,test)
- return test
- else:
- raise ValueError, "don't know how to make test from: %s" % obj
-
- def loadTestsFromNames(self, names, module=None):
- """Return a suite of all tests cases found using the given sequence
- of string specifiers. See 'loadTestsFromName()'.
- """
- suites = []
- for name in names:
- suites.append(self.loadTestsFromName(name, module))
- return self.suiteClass(suites)
-
- def getTestCaseNames(self, testCaseClass):
- """Return a sorted sequence of method names found within testCaseClass.
- """
- testFnNames = [fn for fn in dir(testCaseClass) if fn.startswith(self.testMethodPrefix)]
- if hasattr(testCaseClass, 'runTest'):
- testFnNames.append('runTest')
- for baseclass in testCaseClass.__bases__:
- for testFnName in self.getTestCaseNames(baseclass):
- if testFnName not in testFnNames: # handle overridden methods
- testFnNames.append(testFnName)
- if self.sortTestMethodsUsing:
- testFnNames.sort(self.sortTestMethodsUsing)
- return testFnNames
-
-
-
-defaultTestLoader = TestLoader()
-
-
-##############################################################################
-# Patches for old functions: these functions should be considered obsolete
-##############################################################################
-
-def _makeLoader(prefix, sortUsing, suiteClass=None):
- loader = TestLoader()
- loader.sortTestMethodsUsing = sortUsing
- loader.testMethodPrefix = prefix
- if suiteClass: loader.suiteClass = suiteClass
- return loader
-
-def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp):
- return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass)
-
-def makeSuite(testCaseClass, prefix='test', sortUsing=cmp, suiteClass=TestSuite):
- return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass)
-
-def findTestCases(module, prefix='test', sortUsing=cmp, suiteClass=TestSuite):
- return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(module)
-
-##############################################################################
-# Facilities for running tests from the command line
-##############################################################################
-
-class TestProgram:
- """A command-line program that runs a set of tests; this is primarily
- for making test modules conveniently executable.
- """
- USAGE = """\
-Usage: %(progName)s [options] [test] [...]
-
-Options:
- -h, --help Show this message
- -v, --verbose Verbose output
- -q, --quiet Minimal output
- -e, --expain Output extra test details if there is a failure or error
-
-Examples:
- %(progName)s - run default set of tests
- %(progName)s MyTestSuite - run suite 'MyTestSuite'
- %(progName)s MyTestSuite.MyTestCase - run suite 'MyTestSuite'
- %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething
- %(progName)s MyTestCase - run all 'test*' test methods
- in MyTestCase
-"""
- def __init__(self, module='__main__', defaultTest=None,
- argv=None, testRunner=None, testLoader=defaultTestLoader,
- testSuite=None):
- if type(module) == type(''):
- self.module = __import__(module)
- for part in string.split(module,'.')[1:]:
- self.module = getattr(self.module, part)
- else:
- self.module = module
- if argv is None:
- argv = sys.argv
- self.test = testSuite
- self.verbosity = 1
- self.explain = 0
- self.defaultTest = defaultTest
- self.testRunner = testRunner
- self.testLoader = testLoader
- self.progName = os.path.basename(argv[0])
- self.parseArgs(argv)
- self.runTests()
-
- def usageExit(self, msg=None):
- if msg: print msg
- print self.USAGE % self.__dict__
- sys.exit(2)
-
- def parseArgs(self, argv):
- import getopt
- try:
- options, args = getopt.getopt(argv[1:], 'hHvqer',
- ['help','verbose','quiet','explain', 'raise'])
- for opt, value in options:
- if opt in ('-h','-H','--help'):
- self.usageExit()
- if opt in ('-q','--quiet'):
- self.verbosity = 0
- if opt in ('-v','--verbose'):
- self.verbosity = 2
- if opt in ('-e','--explain'):
- self.explain = True
- if len(args) == 0 and self.defaultTest is None and self.test is None:
- self.test = self.testLoader.loadTestsFromModule(self.module)
- return
- if len(args) > 0:
- self.testNames = args
- else:
- self.testNames = (self.defaultTest,)
- self.createTests()
- except getopt.error, msg:
- self.usageExit(msg)
-
- def createTests(self):
- if self.test == None:
- self.test = self.testLoader.loadTestsFromNames(self.testNames,
- self.module)
-
- def runTests(self):
- if self.testRunner is None:
- self.testRunner = TextTestRunner(verbosity=self.verbosity,
- explain=self.explain)
- result = self.testRunner.run(self.test)
- self._cleanupAfterRunningTests()
- sys.exit(not result.wasSuccessful())
-
- def _cleanupAfterRunningTests(self):
- """A hook method that is called immediately prior to calling
- sys.exit(not result.wasSuccessful()) in self.runTests().
- """
- pass
-
-main = TestProgram
-
-
-##############################################################################
-# Executing this module from the command line
-##############################################################################
-
-if __name__ == "__main__":
- main(module=None)
-
-# vim: shiftwidth=4 tabstop=4 expandtab
diff --git a/cheetah/Tests/xmlrunner.py b/cheetah/Tests/xmlrunner.py
index dc49c56..36b5d8d 100644
--- a/cheetah/Tests/xmlrunner.py
+++ b/cheetah/Tests/xmlrunner.py
@@ -313,7 +313,7 @@ class XMLTestRunnerTest(unittest.TestCase):
"""
class TestTest(unittest.TestCase):
def test_foo(self):
- print "Test"
+ 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
@@ -329,7 +329,7 @@ class XMLTestRunnerTest(unittest.TestCase):
"""
class TestTest(unittest.TestCase):
def test_foo(self):
- print >>sys.stderr, "Test"
+ 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>
diff --git a/cheetah/Tools/CGITemplate.py b/cheetah/Tools/CGITemplate.py
index c94ddc4..1349b5b 100644
--- a/cheetah/Tools/CGITemplate.py
+++ b/cheetah/Tools/CGITemplate.py
@@ -68,7 +68,7 @@ class CGITemplate(Template):
def isCgi(self):
"""Is this a CGI script?
"""
- env = os.environ.has_key('REQUEST_METHOD')
+ env = 'REQUEST_METHOD' in os.environ
wk = self._CHEETAH__isControlledByWebKit
return env and not wk
diff --git a/cheetah/Tools/MondoReport.py b/cheetah/Tools/MondoReport.py
index d0fada2..de4a8b5 100644
--- a/cheetah/Tools/MondoReport.py
+++ b/cheetah/Tools/MondoReport.py
@@ -12,7 +12,14 @@ next.query.
How about Report: .page(), .all(), .summary()? Or PageBreaker.
"""
-import operator, types
+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:
@@ -22,18 +29,13 @@ except ImportError:
else:
return obj[name] # Raises KeyError.
-########## CONSTANTS ##############################
-
-True, False = (1==1), (1==0)
-numericTypes = types.IntType, types.LongType, types.FloatType
-
########## PUBLIC GENERIC FUNCTIONS ##############################
class NegativeError(ValueError):
pass
def isNumeric(v):
- return type(v) in numericTypes
+ return isinstance(v, (int, float))
def isNonNegative(v):
ret = isNumeric(v)
@@ -91,8 +93,7 @@ def mean(lis):
return total / lis_len
def median(lis):
- lis = lis[:]
- lis.sort()
+ lis = sorted(lis[:])
return lis[int(len(lis)/2)]
@@ -169,7 +170,7 @@ class ValuesGetterMixin:
else:
ret = self._origList
if criteria:
- ret = filter(criteria, ret)
+ ret = list(filter(criteria, ret))
return ret
@@ -271,7 +272,7 @@ class RecordStats(IndexFormats, ValuesGetterMixin):
- def _prevNextHelper(self, start,end,size,orphan,sequence):
+ def _prevNextHelper(self, start, end, size, orphan, sequence):
"""Copied from Zope's DT_InSV.py's "opt" function.
"""
if size < 1:
@@ -304,7 +305,7 @@ class RecordStats(IndexFormats, ValuesGetterMixin):
try: sequence[end+orphan-1]
except: end=len(sequence)
# if l - end < orphan: end=l
- return start,end,size
+ return start, end, size
diff --git a/cheetah/Tools/SiteHierarchy.py b/cheetah/Tools/SiteHierarchy.py
index 06da56f..dece01e 100644
--- a/cheetah/Tools/SiteHierarchy.py
+++ b/cheetah/Tools/SiteHierarchy.py
@@ -12,171 +12,155 @@ currentURL is the position you are currently in. The menubar and crumbs methods
give you the HTML output.
There are methods you can override to customize the HTML output.
-
-Meta-Data
-================================================================================
-Author: Ian Bicking <ianb@colorstudy.com>
-Version: $Revision: 1.1 $
-Start Date: 2001/07/23
-Last Revision Date: $Date: 2001/10/11 03:25:54 $
"""
-__author__ = "Ian Bicking <ianb@colorstudy.com>"
-__version__ = "$Revision: 1.1 $"[11:-2]
##################################################
## DEPENDENCIES
import string
try:
- from cStringIO import StringIO
+ from cStringIO import StringIO
except ImportError:
- from StringIO import StringIO
-
-
-##################################################
-## GLOBALS & CONSTANTS
-
-True, False = (1==1), (0==1)
+ 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 1:
- ## This is not the fastest algorithm, I'm afraid.
- ## But it probably won't be for a huge hierarchy anyway.
- foundAny = False
- path.append(pos[0])
- for item in pos[1:]:
- if self._inContents(item):
- if type(item) is type(()):
- path.append(item)
- break
- else:
- pos = item
- foundAny = True
- break
- if not foundAny:
- break
- if len(path) == 1:
- return self.emptyCrumb()
- return string.join(map(lambda x, self=self: self.crumbLink(x[0], x[1]),
- path), self.crumbSeperator()) + \
- self.crumbTerminator()
-
- ## Methods to control the Aesthetics
- # - override these methods for your own look
-
- def menuLink(self, url, text, indent):
- if url == self._currentURL or self._prefix + url == self._currentURL:
- return '%s<B%s>%s</B> <BR>\n' % ('&nbsp;'*2*indent,
- self._menuCSSClass, text)
- else:
- return '%s<A HREF="%s%s"%s>%s</A> <BR>\n' % \
- ('&nbsp;'*2*indent, self._prefix, url,
- self._menuCSSClass, text)
-
- def crumbLink(self, url, text):
- if url == self._currentURL or self._prefix + url == self._currentURL:
- return '<B%s>%s</B>' % (text, self._crumbCSSClass)
- else:
- return '<A HREF="%s%s"%s>%s</A>' % \
- (self._prefix, url, self._crumbCSSClass, text)
-
- def crumbSeperator(self):
- return '&nbsp;&gt;&nbsp;'
-
- def crumbTerminator(self):
- return ''
-
- def emptyCrumb(self):
- """When you are at the homepage"""
- return ''
-
- ## internal methods
-
- def _menubarRecurse(self, contents, indent, stream):
- if type(contents) is type(()):
- url, text = contents
- rest = []
- else:
- url, text = contents[0]
- rest = contents[1:]
- stream.write(self.menuLink(url, text, indent))
- if self._inContents(contents):
- for item in rest:
- self._menubarRecurse(item, indent+1, stream)
-
- def _inContents(self, contents):
- if type(contents) is type(()):
- return self._currentURL == contents[0]
- for item in contents:
- if self._inContents(item):
- return True
- return False
-
+ 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' % ('&nbsp;'*2*indent,
+ self._menuCSSClass, text)
+ else:
+ return '%s<A HREF="%s%s"%s>%s</A> <BR>\n' % \
+ ('&nbsp;'*2*indent, self._prefix, url,
+ self._menuCSSClass, text)
+
+ def crumbLink(self, url, text):
+ if url == self._currentURL or self._prefix + url == self._currentURL:
+ return '<B%s>%s</B>' % (text, self._crumbCSSClass)
+ else:
+ return '<A HREF="%s%s"%s>%s</A>' % \
+ (self._prefix, url, self._crumbCSSClass, text)
+
+ def crumbSeperator(self):
+ return '&nbsp;&gt;&nbsp;'
+
+ def crumbTerminator(self):
+ return ''
+
+ def emptyCrumb(self):
+ """When you are at the homepage"""
+ return ''
+
+ ## internal methods
+
+ def _menubarRecurse(self, contents, indent, stream):
+ if 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()
+ hierarchy = [('/', 'home'),
+ ('/about', 'About Us'),
+ [('/services', 'Services'),
+ [('/services/products', 'Products'),
+ ('/services/products/widget', 'The Widget'),
+ ('/services/products/wedge', 'The Wedge'),
+ ('/services/products/thimble', 'The Thimble'),
+ ],
+ ('/services/prices', 'Prices'),
+ ],
+ ('/contact', 'Contact Us'),
+ ]
+
+ for url in ['/', '/services', '/services/products/widget', '/contact']:
+ print('<p>', '='*50)
+ print('<br> %s: <br>\n' % url)
+ n = Hierarchy(hierarchy, url, menuCSSClass='menu', crumbCSSClass='crumb',
+ prefix='/here')
+ print(n.menuList())
+ print('<p>', '-'*50)
+ print(n.crumbs())
diff --git a/cheetah/Tools/turbocheetah/cheetahsupport.py b/cheetah/Tools/turbocheetah/cheetahsupport.py
index 682206f..1a70286 100644
--- a/cheetah/Tools/turbocheetah/cheetahsupport.py
+++ b/cheetah/Tools/turbocheetah/cheetahsupport.py
@@ -11,7 +11,7 @@ def _recompile_template(package, basename, tfile, classname):
code = str(c)
mod = imp.new_module(classname)
ns = dict()
- exec code in ns
+ exec(code, ns)
tempclass = ns.get("GenTemplate",
ns.get('DynamicallyCompiledCheetahTemplate'))
assert tempclass
@@ -38,7 +38,7 @@ class TurboCheetah:
Template files must end in ".tmpl" and be in legitimate packages.
"""
- given = len(filter(None, (template, template_string, template_file)))
+ 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 "
@@ -63,14 +63,14 @@ class TurboCheetah:
package = classname[0:divider]
basename = classname[divider+1:]
else:
- raise ValueError, "All templates must be in a package"
+ 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 ct.has_key(classname):
+ if classname in ct:
mtime = os.stat(tfile).st_mtime
if ct[classname] != mtime:
ct[classname] = mtime
diff --git a/cheetah/Utils/Misc.py b/cheetah/Utils/Misc.py
index 6ff5bb2..81949d1 100644
--- a/cheetah/Utils/Misc.py
+++ b/cheetah/Utils/Misc.py
@@ -1,20 +1,8 @@
-# $Id: Misc.py,v 1.8 2005/11/02 22:26:08 tavis_rudd Exp $
-"""Miscellaneous functions/objects used by Cheetah but also useful standalone.
-
-Meta-Data
-================================================================================
-Author: Mike Orr <iron@mso.oz.net>
-License: This software is released for unlimited distribution under the
- terms of the MIT license. See the LICENSE file.
-Version: $Revision: 1.8 $
-Start Date: 2001/11/07
-Last Revision Date: $Date: 2005/11/02 22:26:08 $
+#!/usr/bin/env python
+"""
+ Miscellaneous functions/objects used by Cheetah but also useful standalone.
"""
-__author__ = "Mike Orr <iron@mso.oz.net>"
-__revision__ = "$Revision: 1.8 $"[11:-2]
-
import os # Used in mkdirsWithPyInitFile.
-import types # Used in useOrRaise.
import sys # Used in die.
##################################################
@@ -29,7 +17,7 @@ def useOrRaise(thing, errmsg=''):
Called by: Cheetah.Servlet.cgiImport()
"""
- if type(thing) == types.ClassType and issubclass(thing, Exception):
+ if isinstance(thing, type) and issubclass(thing, Exception):
raise thing(errmsg)
return thing
@@ -76,6 +64,4 @@ def mkdirsWithPyInitFiles(path):
f = open(init, 'w') # Open and close to produce empty file.
f.close()
-
-
# vim: shiftwidth=4 tabstop=4 expandtab
diff --git a/cheetah/Utils/VerifyType.py b/cheetah/Utils/VerifyType.py
deleted file mode 100644
index 11a435d..0000000
--- a/cheetah/Utils/VerifyType.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# $Id: VerifyType.py,v 1.4 2005/11/02 22:26:08 tavis_rudd Exp $
-"""Functions to verify an argument's type
-
-Meta-Data
-================================================================================
-Author: Mike Orr <iron@mso.oz.net>
-License: This software is released for unlimited distribution under the
- terms of the MIT license. See the LICENSE file.
-Version: $Revision: 1.4 $
-Start Date: 2001/11/07
-Last Revision Date: $Date: 2005/11/02 22:26:08 $
-"""
-__author__ = "Mike Orr <iron@mso.oz.net>"
-__revision__ = "$Revision: 1.4 $"[11:-2]
-
-##################################################
-## DEPENDENCIES
-
-import types # Used in VerifyTypeClass.
-
-##################################################
-## PRIVATE FUNCTIONS
-
-def _errmsg(argname, ltd, errmsgExtra=''):
- """Construct an error message.
-
- argname, string, the argument name.
- ltd, string, description of the legal types.
- errmsgExtra, string, text to append to error mssage.
- Returns: string, the error message.
- """
- if errmsgExtra:
- errmsgExtra = '\n' + errmsgExtra
- return "arg '%s' must be %s%s" % (argname, ltd, errmsgExtra)
-
-
-##################################################
-## TYPE VERIFICATION FUNCTIONS
-
-def VerifyType(arg, argname, legalTypes, ltd, errmsgExtra=''):
- """Verify the type of an argument.
-
- arg, any, the argument.
- argname, string, name of the argument.
- legalTypes, list of type objects, the allowed types.
- ltd, string, description of legal types (for error message).
- errmsgExtra, string, text to append to error message.
- Returns: None.
- Exceptions: TypeError if 'arg' is the wrong type.
- """
- if type(arg) not in legalTypes:
- m = _errmsg(argname, ltd, errmsgExtra)
- raise TypeError(m)
- return True
-
-
-def VerifyTypeClass(arg, argname, legalTypes, ltd, klass, errmsgExtra=''):
- """Same, but if it's a class, verify it's a subclass of the right class.
-
- arg, any, the argument.
- argname, string, name of the argument.
- legalTypes, list of type objects, the allowed types.
- ltd, string, description of legal types (for error message).
- klass, class, the parent class.
- errmsgExtra, string, text to append to the error message.
- Returns: None.
- Exceptions: TypeError if 'arg' is the wrong type.
- """
- VerifyType(arg, argname, legalTypes, ltd, errmsgExtra)
- # If no exception, the arg is a legal type.
- if type(arg) == types.ClassType and not issubclass(arg, klass):
- # Must test for "is class type" to avoid TypeError from issubclass().
- m = _errmsg(argname, ltd, errmsgExtra)
- raise TypeError(m)
- return True
-
-# @@MO: Commented until we determine whether it's useful.
-#def VerifyClass(arg, argname, klass, ltd):
-# """Same, but allow *only* a subclass of the right class.
-# """
-# VerifyTypeClass(arg, argname, [types.ClassType], ltd, klass)
-
-# vim: shiftwidth=4 tabstop=4 expandtab
diff --git a/cheetah/Utils/memcache.py b/cheetah/Utils/memcache.py
index ee9678d..f10324d 100644
--- a/cheetah/Utils/memcache.py
+++ b/cheetah/Utils/memcache.py
@@ -45,7 +45,6 @@ More detailed documentation is available in the L{Client} class.
import sys
import socket
import time
-import types
try:
import cPickle as pickle
except ImportError:
@@ -129,7 +128,7 @@ class Client:
serverData = {}
data.append(( name, serverData ))
readline = s.readline
- while 1:
+ while True:
line = readline()
if not line or line.strip() == 'END': break
stats = line.split(' ', 2)
@@ -149,7 +148,7 @@ class Client:
sys.stderr.write("MemCached: %s\n" % str)
def _statlog(self, func):
- if not self.stats.has_key(func):
+ if func not in self.stats:
self.stats[func] = 1
else:
self.stats[func] += 1
@@ -168,7 +167,7 @@ class Client:
self.buckets.append(server)
def _get_server(self, key):
- if type(key) == types.TupleType:
+ if isinstance(key, tuple):
serverhash = key[0]
key = key[1]
else:
@@ -177,7 +176,6 @@ class Client:
for i in range(Client._SERVER_RETRIES):
server = self.buckets[serverhash % len(self.buckets)]
if server.connect():
- #print "(using server %s)" % server,
return server, key
serverhash = hash(str(serverhash) + str(i))
return None, None
@@ -302,7 +300,7 @@ class Client:
self._statlog(cmd)
flags = 0
- if isinstance(val, types.StringTypes):
+ if isinstance(val, str):
pass
elif isinstance(val, int):
flags |= Client._FLAG_INTEGER
@@ -344,7 +342,7 @@ class Client:
value = self._recv_value(server, flags, rlen)
server.expect("END")
except (_Error, socket.error), msg:
- if type(msg) is types.TupleType:
+ if isinstance(msg, tuple):
msg = msg[1]
server.mark_dead(msg)
return None
@@ -378,7 +376,7 @@ class Client:
server, key = self._get_server(key)
if not server:
continue
- if not server_keys.has_key(server):
+ if server not in server_keys:
server_keys[server] = []
server_keys[server].append(key)
@@ -452,7 +450,7 @@ class _Host:
_DEAD_RETRY = 30 # number of seconds before retrying a dead server.
def __init__(self, host, debugfunc=None):
- if isinstance(host, types.TupleType):
+ if isinstance(host, tuple):
host = host[0]
self.weight = host[1]
else:
@@ -517,7 +515,7 @@ class _Host:
def readline(self):
buffers = ''
recv = self.socket.recv
- while 1:
+ while True:
data = recv(1)
if not data:
self.mark_dead('Connection closed while reading from %s'
@@ -555,27 +553,26 @@ def _doctest():
return doctest.testmod(memcache, globs=globs)
if __name__ == "__main__":
- print "Testing docstrings..."
+ print("Testing docstrings...")
_doctest()
- print "Running tests:"
- print
+ print("Running tests:")
#servers = ["127.0.0.1:11211", "127.0.0.1:11212"]
servers = ["127.0.0.1:11211"]
mc = Client(servers, debug=1)
def to_s(val):
- if not isinstance(val, types.StringTypes):
+ if not isinstance(val, str):
return "%s (%s)" % (val, type(val))
return "%s" % val
def test_setget(key, val):
- print "Testing set/get {'%s': %s} ..." % (to_s(key), to_s(val)),
+ print("Testing set/get {'%s': %s} ..." % (to_s(key), to_s(val)))
mc.set(key, val)
newval = mc.get(key)
if newval == val:
- print "OK"
+ print("OK")
return 1
else:
- print "FAIL"
+ print("FAIL")
return 0
class FooStruct:
@@ -591,34 +588,33 @@ if __name__ == "__main__":
test_setget("a_string", "some random string")
test_setget("an_integer", 42)
if test_setget("long", long(1<<30)):
- print "Testing delete ...",
+ print("Testing delete ...")
if mc.delete("long"):
- print "OK"
+ print("OK")
else:
- print "FAIL"
- print "Testing get_multi ...",
- print mc.get_multi(["a_string", "an_integer"])
+ print("FAIL")
+ print("Testing get_multi ...")
+ print(mc.get_multi(["a_string", "an_integer"]))
- print "Testing get(unknown value) ...",
- print to_s(mc.get("unknown_value"))
+ print("Testing get(unknown value) ...")
+ print(to_s(mc.get("unknown_value")))
f = FooStruct()
test_setget("foostruct", f)
- print "Testing incr ...",
+ print("Testing incr ...")
x = mc.incr("an_integer", 1)
if x == 43:
- print "OK"
+ print("OK")
else:
- print "FAIL"
+ print("FAIL")
- print "Testing decr ...",
+ print("Testing decr ...")
x = mc.decr("an_integer", 1)
if x == 42:
- print "OK"
+ print("OK")
else:
- print "FAIL"
-
+ print("FAIL")
# vim: ts=4 sw=4 et :
diff --git a/cheetah/Utils/statprof.py b/cheetah/Utils/statprof.py
index 55638eb..0431628 100644
--- a/cheetah/Utils/statprof.py
+++ b/cheetah/Utils/statprof.py
@@ -120,7 +120,7 @@ much as possible.
"""
-from __future__ import division
+
try:
import itimer
@@ -277,15 +277,15 @@ class CallStats(object):
self.cum_secs_per_call = None
def display(self):
- print '%6.2f %9.2f %9.2f %s' % (self.pcnt_time_in_proc,
+ 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)
+ self.name))
def display():
if state.sample_count == 0:
- print 'No samples recorded.'
+ print('No samples recorded.')
return
l = [CallStats(x) for x in call_data.itervalues()]
@@ -293,12 +293,12 @@ def display():
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")
+ 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
+ print('---')
+ print('Sample count: %d' % state.sample_count)
+ print('Total time: %f seconds' % state.accumulated_time)
diff --git a/cheetah/Version.py b/cheetah/Version.py
index 7fdf82f..69afbb0 100644
--- a/cheetah/Version.py
+++ b/cheetah/Version.py
@@ -2,11 +2,11 @@ Version = '2.4.0'
VersionTuple = (2, 4, 0, 'final', 0)
MinCompatibleVersion = '2.0rc6'
-MinCompatibleVersionTuple = (2,0,0,'candidate',6)
+MinCompatibleVersionTuple = (2, 0, 0, 'candidate', 6)
####
def convertVersionStringToTuple(s):
- versionNum = [0,0,0]
+ versionNum = [0, 0, 0]
releaseType = 'final'
releaseTypeSubNum = 0
if s.find('a')!=-1:
@@ -27,16 +27,16 @@ def convertVersionStringToTuple(s):
versionNum += [0]
releaseTypeSubNum = int(releaseTypeSubNum)
- return tuple(versionNum+[releaseType,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')
+ 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')
diff --git a/cheetah/c/_verifytype.c b/cheetah/c/_verifytype.c
deleted file mode 100644
index 6dadf22..0000000
--- a/cheetah/c/_verifytype.c
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * C-version of the src/Utils/VerifyType.py module.
- *
- * (c) 2009, R. Tyler Ballance <tyler@slide.com>
- */
-#include <Python.h>
-#if __STDC_VERSION__ >= 199901L
-#include <stdbool.h>
-#else
-typedef enum { false, true } bool;
-#endif
-
-#include "Cheetah.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-static PyObject *_errorMessage(char *arg, char *legalTypes, char *extra)
-{
- return PyString_FromFormat("Argument '%s' must be %s\n", arg, legalTypes);
-}
-
-static PyObject *py_verifytype(PyObject *self, PyObject *args, PyObject *kwargs)
-{
- PyObject *argument, *legalTypes;
- char *arg_string, *types_string, *extra;
- PyObject *iterator, *item;
- bool rc = false;
- char *kwlist[] = {"argument", "argument_name", "legalType",
- "types_string", "errmsgExtra", NULL};
-
- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OsOs|s", kwlist, &argument,
- &arg_string, &legalTypes, &types_string, &extra))
- return NULL;
-
- iterator = PyObject_GetIter(legalTypes);
- if (iterator == NULL) {
- return NULL;
- }
-
- while (item = PyIter_Next(iterator)) {
- if ((PyObject *)argument->ob_type == item) {
- rc = true;
- Py_DECREF(item);
- break;
- }
- Py_DECREF(item);
- }
- Py_DECREF(iterator);
-
- if (rc)
- Py_RETURN_TRUE;
-
- PyErr_SetObject(PyExc_TypeError, _errorMessage(arg_string,
- types_string, extra));
- return NULL;
-}
-
-static PyObject *py_verifytypeclass(PyObject *self, PyObject *args, PyObject *kwargs)
-{
- PyObject *argument, *legalTypes, *klass;
- PyObject *verifyTypeArgs, *v;
- char *arg_string, *types_string, *extra;
- bool rc = false;
-
- char *kwlist[] = {"argument", "argument_name", "legalTypes",
- "types_string", "klass", "errmsgExtra", NULL};
-
- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OsOsO|s", kwlist, &argument,
- &arg_string, &legalTypes, &types_string, &klass, &extra))
- return NULL;
-
- verifyTypeArgs = Py_BuildValue("OsOs", argument, arg_string, legalTypes,
- types_string);
- v = py_verifytype(self, verifyTypeArgs, NULL);
-
- if (v == NULL)
- return NULL;
- Py_DECREF(v);
-
- if (PyClass_Check(argument) && (!PyClass_IsSubclass(argument, klass)) ) {
- PyErr_SetObject(PyExc_TypeError, _errorMessage(arg_string,
- types_string, extra));
- return NULL;
- }
- Py_RETURN_TRUE;
-}
-
-static const char _verifytypedoc[] = "\
-\n\
-";
-static struct PyMethodDef _verifytype_methods[] = {
- {"verifyType", (PyCFunction)py_verifytype, METH_VARARGS | METH_KEYWORDS, NULL},
- {"verifyTypeClass", (PyCFunction)py_verifytypeclass, METH_VARARGS | METH_KEYWORDS, NULL},
- {NULL}
-};
-
-PyMODINIT_FUNC init_verifytype()
-{
- PyObject *module = Py_InitModule3("_verifytype", _verifytype_methods,
- _verifytypedoc);
-}
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/www/conf.py b/www/conf.py
index 9a55e8f..e9ae69b 100644
--- a/www/conf.py
+++ b/www/conf.py
@@ -24,7 +24,7 @@ import sys, os
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc',
- 'github.tools.sphinx',
+ #'github.tools.sphinx',
]
# Add any paths that contain templates here, relative to this directory.
diff --git a/www/dev_guide/bnf.rst b/www/dev_guide/bnf.rst
new file mode 100644
index 0000000..ef214b4
--- /dev/null
+++ b/www/dev_guide/bnf.rst
@@ -0,0 +1,6 @@
+A BNF Grammar of Cheetah
+========================
+
+(bnf)
+
+
diff --git a/www/dev_guide/cache.rst b/www/dev_guide/cache.rst
new file mode 100644
index 0000000..07775f2
--- /dev/null
+++ b/www/dev_guide/cache.rst
@@ -0,0 +1,404 @@
+Caching placeholders and #cache
+===============================
+
+(cache)
+
+Dynamic placeholder - no cache
+------------------------------
+
+(cache.dynamic)
+
+The template:
+
+::
+
+ Dynamic variable: $voom
+
+The command line and the output:
+
+::
+
+ % voom='Voom!' python x.py --env
+ Dynamic variable: Voom!
+
+The generated code:
+
+::
+
+ write('Dynamic variable: ')
+ write(filter(VFS(SL,"voom",1))) # generated from '$voom' at line 1, col 20.
+ write('\n')
+
+Just what we expected, like any other dynamic placeholder.
+
+Static placeholder
+------------------
+
+(cache.static)
+
+The template:
+
+::
+
+ Cached variable: $*voom
+
+The command line and output:
+
+::
+
+ % voom='Voom!' python x.py --env
+ Cached variable: Voom!
+
+The generated code, with line numbers:
+
+::
+
+ 1 write('Cached variable: ')
+ 2 ## START CACHE REGION: at line, col (1, 19) in the source.
+ 3 RECACHE = True
+ 4 if not self._cacheData.has_key('19760169'):
+ 5 pass
+ 6 else:
+ 7 RECACHE = False
+ 8 if RECACHE:
+ 9 orig_trans = trans
+ 10 trans = cacheCollector = DummyTransaction()
+ 11 write = cacheCollector.response().write
+ 12 write(filter(VFS(SL,"voom",1))) # generated from '$*voom' at line 1,
+ # col 19.
+ 13 trans = orig_trans
+ 14 write = trans.response().write
+ 15 self._cacheData['19760169'] = cacheCollector.response().getvalue()
+ 16 del cacheCollector
+ 17 write(self._cacheData['19760169'])
+ 18 ## END CACHE REGION
+
+ 19 write('\n')
+
+That one little star generated a whole lotta code. First, instead
+of an ordinary {VFS} lookup (searchList) lookup, it converted the
+placeholder to a lookup in the {.\_cacheData} dictionary. Cheetah
+also generated a unique key ({'19760169'}) for our cached item -
+this is its cache ID.
+
+Second, Cheetah put a pair of if-blocks before the {write}. The
+first (lines 3-7) determine whether the cache value is missing or
+out of date, and sets local variable {RECACHE} true or false. This
+stanza may look unnecessarily verbose - lines 3-7 could be
+eliminated if line 8 was changed to
+
+::
+
+ if not self._cacheData.has_key('19760169'):
+
+- but this model is expandable for some of the cache features we'll
+see below.
+
+The second if-block, lines 8-16, do the cache updating if
+necessary. Clearly, the programmer is trying to stick as close to
+normal (dynamic) workflow as possible. Remember that {write}, even
+though it looks like a local function, is actually a method of a
+file-like object. So we create a temporary file-like object to
+divert the {write} object into, then read the result and stuff it
+into the cache.
+
+Timed-refresh placeholder
+-------------------------
+
+(cache.timed)
+
+The template:
+
+::
+
+ Timed cache: $*.5m*voom
+
+The command line and the output:
+
+::
+
+ % voom='Voom!' python x.py --env
+ Timed cache: Voom!
+
+The generated method's docstring:
+
+::
+
+ """
+ This is the main method generated by Cheetah
+ This cache will be refreshed every 30.0 seconds.
+ """
+
+The generated code:
+
+::
+
+ 1 write('Timed cache: ')
+ 2 ## START CACHE REGION: at line, col (1, 15) in the source.
+ 3 RECACHE = True
+ 4 if not self._cacheData.has_key('55048032'):
+ 5 self.__cache55048032__refreshTime = currentTime() + 30.0
+ 6 elif currentTime() > self.__cache55048032__refreshTime:
+ 7 self.__cache55048032__refreshTime = currentTime() + 30.0
+ 8 else:
+ 9 RECACHE = False
+ 10 if RECACHE:
+ 11 orig_trans = trans
+ 12 trans = cacheCollector = DummyTransaction()
+ 13 write = cacheCollector.response().write
+ 14 write(filter(VFS(SL,"voom",1))) # generated from '$*.5m*voom' at
+ # line 1, col 15.
+ 15 trans = orig_trans
+ 16 write = trans.response().write
+ 17 self._cacheData['55048032'] = cacheCollector.response().getvalue()
+ 18 del cacheCollector
+ 19 write(self._cacheData['55048032'])
+ 20 ## END CACHE REGION
+
+ 21 write('\n')
+
+This code is identical to the static cache example except for the
+docstring and the first if-block. (OK, so the cache ID is different
+and the comment on line 14 is different too. Big deal.)
+
+Each timed-refresh cache item has a corrsponding private attribute
+{.\_\_cache########\_\_refreshTime} giving the refresh time in
+ticks (=seconds since January 1, 1970). The first if-block (lines
+3-9) checks whether the cache value is missing or its update time
+has passed, and if so, sets {RECACHE} to true and also schedules
+another refresh at the next interval.
+
+The method docstring reminds the user how often the cache will be
+refreshed. This information is unfortunately not as robust as it
+could be. Each timed-cache placeholder blindly generates a line in
+the docstring. If all refreshes are at the same interval, there
+will be multiple identical lines in the docstring. If the refreshes
+are at different intervals, you get a situation like this:
+
+::
+
+ """
+ This is the main method generated by Cheetah
+ This cache will be refreshed every 30.0 seconds.
+ This cache will be refreshed every 60.0 seconds.
+ This cache will be refreshed every 120.0 seconds.
+ """
+
+The docstring tells only that "something" will be refreshed every
+60.0 seconds, but doesn't reveal { which} placeholder that is. Only
+if you know the relative order of the placeholders in the template
+can you figure that out.
+
+Timed-refresh placeholder with braces
+-------------------------------------
+
+(cache.timed.braces)
+
+This example is the same but with the long placeholder syntax. It's
+here because it's a Cheetah FAQ whether to put the cache interval
+inside or outside the braces. (It's also here so I can look it up
+because I frequently forget.) The answer is: outside. The braces go
+around only the placeholder name (and perhaps some output-filter
+arguments.)
+
+The template:
+
+::
+
+ Timed with {}: $*.5m*{voom}
+
+The output:
+
+::
+
+ Timed with {}: Voom!
+
+The generated code differs only in the comment. Inside the
+cache-refresh if-block:
+
+::
+
+ write(filter(VFS(SL,"voom",1))) # generated from '$*.5m*{voom}' at line 1,
+ #col 17.
+
+If you try to do it this way:
+
+::
+
+ Timed with {}: ${*.5m*voom} ## Wrong!
+
+you get:
+
+::
+
+ Timed with {}: ${*.5m*voom}
+
+``${`` is not a valid placeholder, so it gets treated as ordinary
+text.
+
+#cache
+------
+
+(cache.directive)
+
+The template:
+
+::
+
+ #cache
+ This is a cached region. $voom
+ #end cache
+
+The output:
+
+::
+
+ This is a cached region. Voom!
+
+The generated code:
+
+::
+
+ 1 ## START CACHE REGION: at line, col (1, 1) in the source.
+ 2 RECACHE = True
+ 3 if not self._cacheData.has_key('23711421'):
+ 4 pass
+ 5 else:
+ 6 RECACHE = False
+ 7 if RECACHE:
+ 8 orig_trans = trans
+ 9 trans = cacheCollector = DummyTransaction()
+ 10 write = cacheCollector.response().write
+ 11 write('This is a cached region. ')
+ 12 write(filter(VFS(SL,"voom",1))) # generated from '$voom' at line 2,
+ # col 27.
+ 13 write('\n')
+ 14 trans = orig_trans
+ 15 write = trans.response().write
+ 16 self._cacheData['23711421'] = cacheCollector.response().getvalue()
+ 17 del cacheCollector
+ 18 write(self._cacheData['23711421'])
+ 19 ## END CACHE REGION
+
+This is the same as the {$\*voom} example, except that the plain
+text around the placeholder is inside the second if-block.
+
+#cache with timer and id
+------------------------
+
+(cache.directive.timer)
+
+The template:
+
+::
+
+ #cache timer='.5m', id='cache1'
+ This is a cached region. $voom
+ #end cache
+
+The output:
+
+::
+
+ This is a cached region. Voom!
+
+The generated code is the same as the previous example except the
+first if-block:
+
+::
+
+ RECACHE = True
+ if not self._cacheData.has_key('13925129'):
+ self._cacheIndex['cache1'] = '13925129'
+ self.__cache13925129__refreshTime = currentTime() + 30.0
+ elif currentTime() > self.__cache13925129__refreshTime:
+ self.__cache13925129__refreshTime = currentTime() + 30.0
+ else:
+ RECACHE = False
+
+#cache with test: expression and method conditions
+--------------------------------------------------
+
+(cache.directive.test)
+
+The template:
+
+::
+
+ #cache test=$isDBUpdated
+ This is a cached region. $voom
+ #end cache
+
+(Analysis postponed: bug in Cheetah produces invalid Python.)
+
+The template:
+
+::
+
+ #cache id='cache1', test=($isDBUpdated or $someOtherCondition)
+ This is a cached region. $voom
+ #end cache
+
+The output:
+
+::
+
+ This is a cached region. Voom!
+
+The first if-block in the generated code:
+
+::
+
+ RECACHE = True
+ if not self._cacheData.has_key('36798144'):
+ self._cacheIndex['cache1'] = '36798144'
+ elif (VFS(SL,"isDBUpdated",1) or VFS(SL,"someOtherCondition",1)):
+ RECACHE = True
+ else:
+ RECACHE = False
+
+The second if-block is the same as in the previous example. If you
+leave out the {()} around the test expression, the result is the
+same, although it may be harder for the template maintainer to
+read.
+
+You can even combine arguments, although this is of questionable
+value.
+
+The template:
+
+::
+
+ #cache id='cache1', timer='30m', test=$isDBUpdated or $someOtherCondition
+ This is a cached region. $voom
+ #end cache
+
+The output:
+
+::
+
+ This is a cached region. Voom!
+
+The first if-block:
+
+::
+
+ RECACHE = True
+ if not self._cacheData.has_key('88939345'):
+ self._cacheIndex['cache1'] = '88939345'
+ self.__cache88939345__refreshTime = currentTime() + 1800.0
+ elif currentTime() > self.__cache88939345__refreshTime:
+ self.__cache88939345__refreshTime = currentTime() + 1800.0
+ elif VFS(SL,"isDBUpdated",1) or VFS(SL,"someOtherCondition",1):
+ RECACHE = True
+ else:
+ RECACHE = False
+
+We are planning to add a {'varyBy'} keyword argument in the future
+that will allow separate cache instances to be created for a
+variety of conditions, such as different query string parameters or
+browser types. This is inspired by ASP.net's varyByParam and
+varyByBrowser output caching keywords. Since this is not
+implemented yet, I cannot provide examples here.
+
+
diff --git a/www/dev_guide/comments.rst b/www/dev_guide/comments.rst
new file mode 100644
index 0000000..1195974
--- /dev/null
+++ b/www/dev_guide/comments.rst
@@ -0,0 +1,103 @@
+Directives: Comments
+====================
+
+(comments)
+
+The template:
+
+::
+
+ Text before the comment.
+ ## The comment.
+ Text after the comment.
+ #* A multi-line comment spanning several lines.
+ It spans several lines, too.
+ *#
+ Text after the multi-line comment.
+
+The output:
+
+::
+
+ Text before the comment.
+ Text after the comment.
+
+ Text after the multi-line comment.
+
+The generated code:
+
+::
+
+ write('Text before the comment.\n')
+ # The comment.
+ write('Text after the comment.\n')
+ # A multi-line comment spanning several lines.
+ # It spans several lines, too.
+ write('\nText after the multi-line comment.\n')
+
+Docstring and header comments
+-----------------------------
+
+(comments.docstring)
+
+The template:
+
+::
+
+ ##doc: .respond() method comment.
+ ##doc-method: Another .respond() method comment.
+ ##doc-class: A class comment.
+ ##doc-module: A module comment.
+ ##header: A header comment.
+
+The output:
+
+::
+
+
+
+The beginning of the generated {.respond} method:
+
+::
+
+ def respond(self,
+ trans=None,
+ dummyTrans=False,
+ VFS=valueFromSearchList,
+ VFN=valueForName,
+ getmtime=getmtime,
+ currentTime=time.time):
+
+ """
+ This is the main method generated by Cheetah
+ .respond() method comment.
+ Another .respond() method comment.
+ """
+
+The class docstring:
+
+::
+
+ """
+ A class comment.
+
+ Autogenerated by CHEETAH: The Python-Powered Template Engine
+ """
+
+The top of the module:
+
+::
+
+ #!/usr/bin/env python
+ # A header comment.
+
+ """A module comment.
+
+ Autogenerated by CHEETAH: The Python-Powered Template Engine
+ CHEETAH VERSION: 0.9.13a1
+ Generation time: Fri Apr 26 22:39:23 2002
+ Source file: x.tmpl
+ Source file last modified: Fri Apr 26 22:36:23 2002
+ """
+
+
diff --git a/www/dev_guide/compiler.rst b/www/dev_guide/compiler.rst
new file mode 100644
index 0000000..33f3bcd
--- /dev/null
+++ b/www/dev_guide/compiler.rst
@@ -0,0 +1,8 @@
+The compiler
+============
+
+(compiler)
+
+How templates are compiled: a walk through Compiler.py.
+
+
diff --git a/www/dev_guide/design.rst b/www/dev_guide/design.rst
new file mode 100644
index 0000000..1ffabbd
--- /dev/null
+++ b/www/dev_guide/design.rst
@@ -0,0 +1,104 @@
+Design Decisions and Tradeoffs
+==============================
+
+(design)
+
+Delimiters
+----------
+
+(design.Delimiters)
+
+One of the first decisions we encountered was which delimiter
+syntax to use. We decided to follow Velocity's {$placeholder} and
+{#directive} syntax because the former is widely used in other
+languages for the same purpose, and the latter stands out in an
+HTML or text document. We also implemented the
+``${longPlaceholder}`` syntax like the shells for cases where
+Cheetah or you might be confused where a placeholder ends. Tavis
+went ahead and made ``${longPlaceholder}`` and
+``$[longPlaceholder]`` interchangeable with it since it was trivial
+to implement. Finally, the {#compiler} directive allows you to
+change the delimiters if you don't like them or if they conflict
+with the text in your document. (Obviously, if your document
+contains a Perl program listing, you don't necessarily want to
+backslash each and every {$} and {#}, do you?)
+
+The choice of comment delimiters was more arbitrary. {##} and {#\*
+... \*#} doesn't match any language, but it's reminiscent of Python
+and C while also being consistent with our "{#} is for directives"
+convention.
+
+We specifically chose { not} to use pseudo HTML tags for
+placeholders and directives, as described more thoroughly in the
+Cheetah Users' Guide introduction. Pseudo HTML tags may be easier
+to see in a visual editor (supposedly), but in text editors they're
+hard to distinguish from "real" HTML tags unless you look closely,
+and they're many more keystrokes to type. Also, if you make a
+mistake, the tag will show up as literal text in the rendered HTML
+page where it will be easy to notice and eradicate, rather than
+disappearing as bogus HTML tags do in browsers.
+
+Late binding
+------------
+
+(design.lateBinding)
+
+One of Cheetah's unique features is the name mapper, which lets you
+write {$a.b} without worrying much about the type of {a} or {b}.
+Prior to version 0.9.7, Cheetah did the entire NameMapper lookup at
+runtime. This provided maximum flexibility at the expense of speed.
+Doing a NameMapper lookup is intrinsically more expensive than an
+ordinary Python expression because Cheetah has to decide what type
+of container {a} is, whether the the value is a function (autocall
+it), issue the appropriate Python incantation to look up {b} in it,
+autocall again if necessary, and then convert the result to a
+string.
+
+To maximize run-time (filling-time) performance, Cheetah 0.9.7
+pushed much of this work back into the compiler. The compiler
+looked up {a} in the searchList at compile time, noted its type,
+and generated an eval'able Python expression based on that.
+
+This approach had two significant drawbacks. What if {a} later
+changes type before a template filling? Answer: unpredictable
+exceptions occur. What if {a} does not exist in the searchList at
+compile time? Answer: the template can't compile.
+
+To prevent these catastrophes, users were required to prepopulate
+the searchList before instantiating the template instance, and then
+not to change {a}'s type. Static typing is repugnant in a dynamic
+language like Python, and having to prepopulate the searchList made
+certain usages impossible. For example, you couldn't instantiate
+the template object without a searchList and then set {self}
+attributes to specify the values.
+
+After significant user complaints about the fragility of this
+system, Tavis rewrote placeholder handling, and in version 0.9.8a3
+(August 2001), Tavis moved the name mapper lookup back into
+runtime. Performance wasn't crippled because he discovered that
+writing a C version of the name mapper was easier than anticipated,
+and the C version completed the lookup quickly. Now Cheetah had
+"late binding", meaning the compiler does not look up {a} or care
+whether it exists. This allows users to create {a} or change its
+type anytime before a template filling.
+
+The lesson we learned is that it's better to decide what you want
+and then figure out how to do it, rather than assuming that certain
+goals are unattainable due to performance considerations.
+
+Caching framework
+-----------------
+
+(design.cache)
+
+Webware compatibility and the transaction framework
+---------------------------------------------------
+
+(design.webware)
+
+Single inheritance
+------------------
+
+(design.singleInheritance)
+
+
diff --git a/www/dev_guide/errorHandling.rst b/www/dev_guide/errorHandling.rst
new file mode 100644
index 0000000..16aae35
--- /dev/null
+++ b/www/dev_guide/errorHandling.rst
@@ -0,0 +1,329 @@
+Directives: Error Handling
+==========================
+
+(errorHandling)
+
+#try and #raise
+---------------
+
+(errorHandling.try)
+
+The template:
+
+::
+
+ #import traceback
+ #try
+ #raise RuntimeError
+ #except RuntimeError
+ A runtime error occurred.
+ #end try
+
+ #try
+ #raise RuntimeError("Hahaha!")
+ #except RuntimeError
+ #echo $sys.exc_info()[1]
+ #end try
+
+ #try
+ #echo 1/0
+ #except ZeroDivisionError
+ You can't divide by zero, idiot!
+ #end try
+
+The output:
+
+::
+
+ A runtime error occurred.
+
+ Hahaha!
+
+ You can't divide by zero, idiot!
+
+The generated code:
+
+::
+
+ try:
+ raise RuntimeError
+ except RuntimeError:
+ write('A runtime error occurred.\n')
+ write('\n')
+ try:
+ raise RuntimeError("Hahaha!")
+ except RuntimeError:
+ write(filter(VFN(sys,"exc_info",0)()[1]))
+ write('\n')
+ write('\n')
+ try:
+ write(filter(1/0))
+ write('\n')
+ except ZeroDivisionError:
+ write("You can't divide by zero, idiot!\n")
+
+{#finally} works just like in Python.
+
+#assert
+-------
+
+(errorHandling.assert)
+
+The template:
+
+::
+
+ #assert False, "You lose, buster!"
+
+The output:
+
+::
+
+ Traceback (most recent call last):
+ File "x.py", line 117, in ?
+ x().runAsMainProgram()
+ File "/local/opt/Python/lib/python2.2/site-packages/Webware/Cheetah/
+ Template.py", line 331, in runAsMainProgram
+ CmdLineIface(templateObj=self).run()
+ File "/local/opt/Python/lib/python2.2/site-packages/Webware/Cheetah/
+ TemplateCmdLineIface.py", line 59, in run
+ print self._template
+ File "x.py", line 91, in respond
+ assert False, "You lose, buster!"
+ AssertionError: You lose, buster!
+
+The generated code:
+
+::
+
+ assert False, "You lose, buster!"
+
+#errorCatcher
+-------------
+
+(errorHandling.errorCatcher)
+
+No error catcher
+~~~~~~~~~~~~~~~~
+
+(errorHandling.errorCatcher.no)
+
+The template:
+
+::
+
+ $noValue
+
+The output:
+
+::
+
+ Traceback (most recent call last):
+ File "x.py", line 118, in ?
+ x().runAsMainProgram()
+ File "/local/opt/Python/lib/python2.2/site-packages/Webware/Cheetah/
+ Template.py", line 331, in runAsMainProgram
+ CmdLineIface(templateObj=self).run()
+ File "/local/opt/Python/lib/python2.2/site-packages/Webware/Cheetah/
+ TemplateCmdLineIface.py", line 59, in run
+ print self._template
+ File "x.py", line 91, in respond
+ write(filter(VFS(SL,"noValue",1))) # generated from '$noValue' at line
+ 1, col 1.
+ NameMapper.NotFound: noValue
+
+The generated code:
+
+::
+
+ write(filter(VFS(SL,"noValue",1))) # generated from '$noValue' at line 1,
+ # col 1.
+ write('\n')
+
+Echo and BigEcho
+~~~~~~~~~~~~~~~~
+
+(errorHandling.errorCatcher.echo)
+
+The template:
+
+::
+
+ #errorCatcher Echo
+ $noValue
+ #errorCatcher BigEcho
+ $noValue
+
+The output:
+
+::
+
+ $noValue
+ ===============&lt;$noValue could not be found&gt;===============
+
+The generated code:
+
+::
+
+ if self._errorCatchers.has_key("Echo"):
+ self._errorCatcher = self._errorCatchers["Echo"]
+ else:
+ self._errorCatcher = self._errorCatchers["Echo"] = ErrorCatchers.Echo(self)
+ write(filter(self.__errorCatcher1(localsDict=locals())))
+ # generated from '$noValue' at line 2, col 1.
+ write('\n')
+ if self._errorCatchers.has_key("BigEcho"):
+ self._errorCatcher = self._errorCatchers["BigEcho"]
+ else:
+ self._errorCatcher = self._errorCatchers["BigEcho"] = \
+ ErrorCatchers.BigEcho(self)
+ write(filter(self.__errorCatcher1(localsDict=locals())))
+ # generated from '$noValue' at line 4, col 1.
+ write('\n')
+
+ListErrors
+~~~~~~~~~~
+
+(errorHandling.errorCatcher.listErrors)
+
+The template:
+
+::
+
+ #import pprint
+ #errorCatcher ListErrors
+ $noValue
+ $anotherMissingValue.really
+ $pprint.pformat($errorCatcher.listErrors)
+ ## This is really self.errorCatcher().listErrors()
+
+The output:
+
+::
+
+ $noValue
+ $anotherMissingValue.really
+ [{'code': 'VFS(SL,"noValue",1)',
+ 'exc_val': <NameMapper.NotFound instance at 0x8170ecc>,
+ 'lineCol': (3, 1),
+ 'rawCode': '$noValue',
+ 'time': 'Wed May 15 00:38:23 2002'},
+ {'code': 'VFS(SL,"anotherMissingValue.really",1)',
+ 'exc_val': <NameMapper.NotFound instance at 0x816d0fc>,
+ 'lineCol': (4, 1),
+ 'rawCode': '$anotherMissingValue.really',
+ 'time': 'Wed May 15 00:38:23 2002'}]
+
+The generated import:
+
+::
+
+ import pprint
+
+Then in the generated class, we have our familiar {.respond} method
+and several new methods:
+
+::
+
+ def __errorCatcher1(self, localsDict={}):
+ """
+ Generated from $noValue at line, col (3, 1).
+ """
+
+ try:
+ return eval('''VFS(SL,"noValue",1)''', globals(), localsDict)
+ except self._errorCatcher.exceptions(), e:
+ return self._errorCatcher.warn(exc_val=e, code= 'VFS(SL,"noValue",1)' ,
+ rawCode= '$noValue' , lineCol=(3, 1))
+
+ def __errorCatcher2(self, localsDict={}):
+ """
+ Generated from $anotherMissingValue.really at line, col (4, 1).
+ """
+
+ try:
+ return eval('''VFS(SL,"anotherMissingValue.really",1)''', globals(),
+ localsDict)
+ except self._errorCatcher.exceptions(), e:
+ return self._errorCatcher.warn(exc_val=e,
+ code= 'VFS(SL,"anotherMissingValue.really",1)' ,
+ rawCode= '$anotherMissingValue.really' , lineCol=(4, 1))
+
+ def __errorCatcher3(self, localsDict={}):
+ """
+ Generated from $pprint.pformat($errorCatcher.listErrors) at line, col
+ (5, 1).
+ """
+
+ try:
+ return eval('''VFN(pprint,"pformat",0)(VFS(SL,
+ "errorCatcher.listErrors",1))''', globals(), localsDict)
+ except self._errorCatcher.exceptions(), e:
+ return self._errorCatcher.warn(exc_val=e, code=
+ 'VFN(pprint,"pformat",0)(VFS(SL,"errorCatcher.listErrors",1))' ,
+ rawCode= '$pprint.pformat($errorCatcher.listErrors)' ,
+ lineCol=(5, 1))
+
+::
+
+ def respond(self,
+ trans=None,
+ dummyTrans=False,
+ VFS=valueFromSearchList,
+ VFN=valueForName,
+ getmtime=getmtime,
+ currentTime=time.time):
+
+
+ """
+ This is the main method generated by Cheetah
+ """
+
+ if not trans:
+ trans = DummyTransaction()
+ dummyTrans = True
+ write = trans.response().write
+ SL = self._searchList
+ filter = self._currentFilter
+ globalSetVars = self._globalSetVars
+
+ ########################################
+ ## START - generated method body
+
+ if exists(self._filePath) and getmtime(self._filePath) > self._fileMtime:
+ self.compile(file=self._filePath)
+ write(getattr(self, self._mainCheetahMethod_for_x)(trans=trans))
+ if dummyTrans:
+ return trans.response().getvalue()
+ else:
+ return ""
+ if self._errorCatchers.has_key("ListErrors"):
+ self._errorCatcher = self._errorCatchers["ListErrors"]
+ else:
+ self._errorCatcher = self._errorCatchers["ListErrors"] = \
+ ErrorCatchers.ListErrors(self)
+ write(filter(self.__errorCatcher1(localsDict=locals())))
+ # generated from '$noValue' at line 3, col 1.
+ write('\n')
+ write(filter(self.__errorCatcher2(localsDict=locals())))
+ # generated from '$anotherMissingValue.really' at line 4, col 1.
+ write('\n')
+ write(filter(self.__errorCatcher3(localsDict=locals())))
+ # generated from '$pprint.pformat($errorCatcher.listErrors)' at line
+ # 5, col 1.
+ write('\n')
+ # This is really self.errorCatcher().listErrors()
+
+ ########################################
+ ## END - generated method body
+
+ if dummyTrans:
+ return trans.response().getvalue()
+ else:
+ return ""
+
+So whenever an error catcher is active, each placeholder gets
+wrapped in its own method. No wonder error catchers slow down the
+system!
+
+
diff --git a/www/dev_guide/files.rst b/www/dev_guide/files.rst
new file mode 100644
index 0000000..ab8d7a1
--- /dev/null
+++ b/www/dev_guide/files.rst
@@ -0,0 +1,11 @@
+Files
+=====
+
+(files)
+
+This chapter will be an overview of the files in the Cheetah
+package, and how they interrelate in compiling and filling a
+template. We'll also look at files in the Cheetah tarball that
+don't get copied into the package.
+
+
diff --git a/www/dev_guide/flowControl.rst b/www/dev_guide/flowControl.rst
new file mode 100644
index 0000000..8b97f31
--- /dev/null
+++ b/www/dev_guide/flowControl.rst
@@ -0,0 +1,394 @@
+Directives: Flow Control
+========================
+
+(flowControl)
+
+#for
+----
+
+(flowControl.for)
+
+The template:
+
+::
+
+ #for $i in $range(10)
+ $i #slurp
+ #end for
+
+The output:
+
+::
+
+ 0 1 2 3 4 5 6 7 8 9
+
+The generated code:
+
+::
+
+ for i in range(10):
+ write(filter(i)) # generated from '$i' at line 2, col 1.
+ write(' ')
+
+#repeat
+-------
+
+(flowControl.repeat)
+
+The template:
+
+::
+
+ #repeat 3
+ My bonnie lies over the ocean
+ #end repeat
+ O, bring back my bonnie to me!
+
+The output:
+
+::
+
+ My bonnie lies over the ocean
+ My bonnie lies over the ocean
+ My bonnie lies over the ocean
+ O, bring back my bonnie to me!
+
+(OK, so the second line should be "sea" instead of "ocean".)
+
+The generated code:
+
+::
+
+ for __i0 in range(3):
+ write('My bonnie lies over the ocean\n')
+ write('O, bring back my bonnie to me!\n')
+
+Note that a new local variable of the form {\_\_i$num} will be used
+for each instance of {repeat} in order to permit nesting.
+
+#while
+------
+
+(flowControl.while)
+
+The template:
+
+::
+
+ #set $alive = True
+ #while $alive
+ I am alive!
+ #set $alive = False
+ #end while
+
+The output:
+
+::
+
+ I am alive!
+
+The generated code:
+
+::
+
+ alive = True
+ while alive:
+ write('I am alive!\n')
+ alive = False
+
+#if
+---
+
+()
+
+The template:
+
+::
+
+ #set $size = 500
+ #if $size >= 1500
+ It's big
+ #else if $size < 1500 and $size > 0
+ It's small
+ #else
+ It's not there
+ #end if
+
+The output:
+
+::
+
+ It's small
+
+The generated code:
+
+::
+
+ size = 500
+ if size >= 1500:
+ write("It's big\n")
+ elif size < 1500 and size > 0:
+ write("It's small\n")
+ else:
+ write("It's not there\n")
+
+#unless
+-------
+
+(flowControl.unless)
+
+The template:
+
+::
+
+ #set $count = 9
+ #unless $count + 5 > 15
+ Count is in range.
+ #end unless
+
+The output:
+
+::
+
+ Count is in range.
+
+The generated code:
+
+::
+
+ count = 9
+ if not (count + 5 > 15):
+ write('Count is in range.\n')
+
+{ Note:} There is a bug in Cheetah 0.9.13. It's forgetting the
+parentheses in the {if} expression, which could lead to it
+calculating something different than it should.
+
+#break and #continue
+--------------------
+
+(flowControl.break)
+
+The template:
+
+::
+
+ #for $i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 'James', 'Joe', 'Snow']
+ #if $i == 10
+ #continue
+ #end if
+ #if $i == 'Joe'
+ #break
+ #end if
+ $i - #slurp
+ #end for
+
+The output:
+
+::
+
+ 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 11 - 12 - James -
+
+The generated code:
+
+::
+
+ for i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 'James', 'Joe', 'Snow']:
+ if i == 10:
+ write('')
+ continue
+ if i == 'Joe':
+ write('')
+ break
+ write(filter(i)) # generated from '$i' at line 8, col 1.
+ write(' - ')
+
+#pass
+-----
+
+(flowControl.pass)
+
+The template:
+
+::
+
+ Let's check the number.
+ #set $size = 500
+ #if $size >= 1500
+ It's big
+ #elif $size > 0
+ #pass
+ #else
+ Invalid entry
+ #end if
+ Done checking the number.
+
+The output:
+
+::
+
+ Let's check the number.
+ Done checking the number.
+
+The generated code:
+
+::
+
+ write("Let's check the number.\n")
+ size = 500
+ if size >= 1500:
+ write("It's big\n")
+ elif size > 0:
+ pass
+ else:
+ write('Invalid entry\n')
+ write('Done checking the number.\n')
+
+#stop
+-----
+
+(flowControl.stop)
+
+The template:
+
+::
+
+ A cat
+ #if 1
+ sat on a mat
+ #stop
+ watching a rat
+ #end if
+ in a flat.
+
+The output:
+
+::
+
+ A cat
+ sat on a mat
+
+The generated code:
+
+::
+
+ write('A cat\n')
+ if 1:
+ write(' sat on a mat\n')
+ if dummyTrans:
+ return trans.response().getvalue()
+ else:
+ return ""
+ write(' watching a rat\n')
+ write('in a flat.\n')
+
+#return
+-------
+
+(flowControl.return)
+
+The template:
+
+::
+
+ 1
+ $test[1]
+ 3
+ #def test
+ 1.5
+ #if 1
+ #return '123'
+ #else
+ 99999
+ #end if
+ #end def
+
+The output:
+
+::
+
+ 1
+ 2
+ 3
+
+The generated code:
+
+::
+
+ def test(self,
+ trans=None,
+ dummyTrans=False,
+ VFS=valueFromSearchList,
+ VFN=valueForName,
+ getmtime=getmtime,
+ currentTime=time.time):
+
+
+ """
+ Generated from #def test at line 5, col 1.
+ """
+
+ if not trans:
+ trans = DummyTransaction()
+ dummyTrans = True
+ write = trans.response().write
+ SL = self._searchList
+ filter = self._currentFilter
+ globalSetVars = self._globalSetVars
+
+ ########################################
+ ## START - generated method body
+
+ write('1.5\n')
+ if 1:
+ return '123'
+ else:
+ write('99999\n')
+
+ ########################################
+ ## END - generated method body
+
+ if dummyTrans:
+ return trans.response().getvalue()
+ else:
+ return ""
+
+::
+
+ def respond(self,
+ trans=None,
+ dummyTrans=False,
+ VFS=valueFromSearchList,
+ VFN=valueForName,
+ getmtime=getmtime,
+ currentTime=time.time):
+
+
+ """
+ This is the main method generated by Cheetah
+ """
+
+ if not trans:
+ trans = DummyTransaction()
+ dummyTrans = True
+ write = trans.response().write
+ SL = self._searchList
+ filter = self._currentFilter
+ globalSetVars = self._globalSetVars
+
+ ########################################
+ ## START - generated method body
+
+ write('\n1\n')
+ write(filter(VFS(SL,"test",1)[1])) # generated from '$test[1]' at line 3, col 1.
+ write('\n3\n')
+
+ ########################################
+ ## END - generated method body
+
+ if dummyTrans:
+ return trans.response().getvalue()
+ else:
+ return ""
+
+
diff --git a/www/dev_guide/history.rst b/www/dev_guide/history.rst
new file mode 100644
index 0000000..44e2200
--- /dev/null
+++ b/www/dev_guide/history.rst
@@ -0,0 +1,94 @@
+History of Cheetah
+==================
+
+(history)
+
+In Spring 2001, several members of the webware-discuss mailing list
+expressed the need for a template engine. Webware like Python is
+great for organizing logic, but they both suffer when you need to
+do extensive variable interpolation into large pieces of text, or
+to build up a text string from its nested parts. Python's {%}
+operator gets you only so far, the syntax is cumbersome, and you
+have to use a separate format string for each nested part. Most of
+us had used template systems from other platforms-chiefly Zope's
+DTML, PHPLib's Template object and Java's Velocity-and wanted to
+port something like those so it could be used both in Webware
+servlets and in standalone Python programs.
+
+Since I (Mike Orr) am writing this history, I'll describe how I
+encountered Cheetah. I had written a template module called
+PlowPlate based on PHPLib's Template library. Like PHPLib, it used
+regular expressions to search and destroy-er, replace-placeholders,
+behaved like a dictionary to specify placeholder values, contained
+no directives, but did have BEGIN and END markers which could be
+used to extract a named block (subtemplate). Meanwhile, Tavis Rudd
+was also on webware-discuss and interested in templates, and he
+lived just a few hours away. So on 12 May 2001 we met in Vancouver
+at a gelato shop on Denman Street and discussed Webware, and he
+drew on a napkin the outline of a template system he was working
+on.
+
+[Note from Tavis: Mikes got the dates and sequence of things a
+little out of order, but what the hell ...]
+
+Instead of filling the template by search-and-replace, he wanted to
+break it up into parts. This was a primitive form of template
+compiling: do the time-consuming work once and put it to a state
+where you can fill the template quickly multiple times. A template
+without directives happens to break down naturally into a list of
+alternating text/placeholder pairs. The odd subscript values are
+literal strings; the even subscripts are string keys into a
+dictionary of placeholder values. The project was called
+TemplateServer.
+
+In a couple months, Tavis decided that instead of compiling to a
+list, he wanted to compile to Python source code: a series of
+{write} calls that would output into a file-like object. This was
+the nucleus that became Cheetah. I thought that idea was stupid,
+but it turned out that this not-so-stupid idea blew all the others
+out of the water in terms of performance.
+
+Another thing Tavis pushed hard for from near the beginning was
+"display logic", or simple directives like {#for}, {#if} and
+{#echo}. (OK, {#echo} came later, but conceptually it belongs here.
+I thought display logic was even stupider than compiling to Python
+source code because it would just lead to "DTML hell"-complicated
+templates that are hard to read and maintain, and for which you
+have to learn (and debug) a whole new language when Python does it
+just fine. But others (hi Chuck!) had templates that were
+maintained by secretaries who didn't know Python, and the
+secretaries needed display logic, so that was that. Finally, after
+working with Cheetah templates (with display logic) and PlowPlate
+templates (with just blocks rather than display logic), I realized
+Tavis was smarter than I was and display logic really did belong in
+the template.
+
+The next step was making directives for all the Python flow-control
+statements: {#while}, {#try}, {#assert}, etc. Some of them we
+couldn't think of a use for. Nevertheless, they were easy to code,
+and "somebody" would probably need them "someday", so we may as
+well implement them now.
+
+During all this, Chuck Esterbrook, Ian Bicking and others offered
+(and still offer) their support and suggestions, and Chuck gave us
+feedback about his use of Cheetah-its first deployment in a
+commercial production environment. Later, Edmund Lian became our #1
+bug reporter and suggester as he used Cheetah in his web
+applications.
+
+We were going to release 1.0 in January 2002, but we decided to
+delay it until more people used it in real-world situations and
+gave us feedback about what is still needed. This has led to many
+refinements, and we have added (and removed) features according to
+this feedback. Nevertheless, Cheetah has been changing but stable
+since the late-binding rewrite in fall 2001, and anybody who keeps
+up with the cheetah-discuss mailing list will know when changes
+occur that require modifying one's template, and since most people
+use point releases rather than CVS, they generally have a few
+week's warning about any significant changes.
+
+More detail on Cheetah's history and evolution, and why it is the
+way it is, can be found in our paper for the Python10 conference,
+http://www.cheetahtemplate.org/Py10.html.
+
+
diff --git a/www/dev_guide/index.rst b/www/dev_guide/index.rst
new file mode 100644
index 0000000..45b74b4
--- /dev/null
+++ b/www/dev_guide/index.rst
@@ -0,0 +1,30 @@
+Cheetah Developer's Guide
+==========================
+
+Overview
+--------
+This guide needs to really be filled out more
+
+.. toctree::
+ :maxdepth: 1
+
+ introduction.rst
+ compiler.rst
+ parser.rst
+ errorHandling.rst
+ placeholders.rst
+ patching.rst
+ flowControl.rst
+ design.rst
+ safeDelegation.rst
+ history.rst
+ output.rst
+ files.rst
+ cache.rst
+ bnf.rst
+ pyModules.rst
+ comments.rst
+ parserInstructions.rst
+ template.rst
+ inheritanceEtc.rst
+
diff --git a/www/dev_guide/inheritanceEtc.rst b/www/dev_guide/inheritanceEtc.rst
new file mode 100644
index 0000000..7dbed40
--- /dev/null
+++ b/www/dev_guide/inheritanceEtc.rst
@@ -0,0 +1,255 @@
+Directives: Import, Inheritance, Declaration and Assignment
+===========================================================
+
+(inheritanceEtc)
+
+#import and #from
+-----------------
+
+(inheritanceEtc.import)
+
+The template:
+
+::
+
+ #import math
+
+This construct does not produce any output.
+
+The generated module, at the bottom of the import section:
+
+::
+
+ import math
+
+#extends
+--------
+
+(inheritanceEtc.extends)
+
+The template:
+
+::
+
+ #extends SomeClass
+
+The generated import (skipped if {SomeClass} has already been
+imported):
+
+::
+
+ from SomeClass import SomeClass
+
+The generated class:
+
+::
+
+ class x(SomeClass):
+
+#implements
+-----------
+
+(inheritanceEtc.implements)
+
+The template:
+
+::
+
+ #implements doOutput
+
+In the generated class, the main method is {.doOutput} instead of
+{.respond}, and the attribute naming this method is:
+
+::
+
+ _mainCheetahMethod_for_x2= 'doOutput'
+
+#set and #set global
+--------------------
+
+(inheritanceEtc.set)
+
+The template:
+
+::
+
+ #set $namesList = ['Moe','Larry','Curly']
+ $namesList
+ #set global $toes = ['eeny', 'meeny', 'miney', 'moe']
+ $toes
+
+The output:
+
+::
+
+ ['Moe', 'Larry', 'Curly']
+ ['eeny', 'meeny', 'miney', 'moe']
+
+The generated code:
+
+::
+
+ 1 namesList = ['Moe','Larry','Curly']
+ 2 write(filter(namesList)) # generated from '$namesList' at line 2, col 1.
+ 3 write('\n')
+ 4 globalSetVars["toes"] = ['eeny', 'meeny', 'miney', 'moe']
+ 5 write(filter(VFS(SL,"toes",1))) # generated from '$toes' at line 4, col 1.
+ 6 write('\n')
+
+{globalSetVars} is a local variable referencing {.\_globalSetVars}.
+Writes go into it directly, but reads take advantage of the fact
+that {.\_globalSetVars} is on the searchList. (In fact, it's the
+very first namespace.)
+
+#del
+----
+
+(inheritanceEtc.del)
+
+The template:
+
+::
+
+ #set $a = 1
+ #del $a
+ #set $a = 2
+ #set $arr = [0, 1, 2]
+ #del $a, $arr[1]
+
+In the generated class:
+
+::
+
+ 1 a = 1
+ 2 del a
+ 3 a = 2
+ 4 arr = [0, 1, 2]
+ 5 del a, arr[1]
+
+#attr
+-----
+
+(inheritanceEtc.attr)
+
+The template:
+
+::
+
+ #attr $namesList = ['Moe', 'Larry', 'Curly']
+
+In the generated class:
+
+::
+
+ ## GENERATED ATTRIBUTES
+
+ namesList = ['Moe', 'Larry', 'Curly']
+
+#def
+----
+
+(inheritanceEtc.def)
+
+The template:
+
+::
+
+ #def printArg($arg)
+ The argument is $arg.
+ #end def
+ My method returned $printArg(5).
+
+The output:
+
+::
+
+ My method returned The argument is 5.
+ .
+
+Hmm, not exactly what we expected. The method returns a trailing
+newline because we didn't end the last line with {#slurp}. So the
+second period (outside the method) appears on a separate line.
+
+The {#def} generates a method {.printArg} whose structure is
+similar to the main method:
+
+::
+
+ def printArg(self,
+ arg,
+ trans=None,
+ dummyTrans=False,
+ VFS=valueFromSearchList,
+ VFN=valueForName,
+ getmtime=getmtime,
+ currentTime=time.time):
+
+
+ """
+ Generated from #def printArg($arg) at line 1, col 1.
+ """
+
+ if not trans:
+ trans = DummyTransaction()
+ dummyTrans = True
+ write = trans.response().write
+ SL = self._searchList
+ filter = self._currentFilter
+ globalSetVars = self._globalSetVars
+
+ ########################################
+ ## START - generated method body
+
+ write('The argument is ')
+ write(filter(arg)) # generated from '$arg' at line 2, col 17.
+ write('.\n')
+
+ ########################################
+ ## END - generated method body
+
+ if dummyTrans:
+ return trans.response().getvalue()
+ else:
+ return ""
+
+When {.printArg} is called from a placeholder, only the arguments
+the user supplied are passed. The other arguments retain their
+default values.
+
+#block
+------
+
+(inheritanceEtc.block)
+
+The template:
+
+::
+
+ #block content
+ This page is under construction.
+ #end block
+
+The output:
+
+::
+
+ This page is under construction.
+
+This construct generates a method {.content} in the same structure
+as {.printArg} above, containing the write code:
+
+::
+
+ write('This page is under construction.\n')
+
+In the main method, the write code is:
+
+::
+
+ self.content(trans=trans) # generated from ('content', '#block content')
+ # at line 1, col 1.
+
+So a block placeholder implicitly passes the current transaction to
+the method.
+
+
diff --git a/www/dev_guide/introduction.rst b/www/dev_guide/introduction.rst
new file mode 100644
index 0000000..c77e1e5
--- /dev/null
+++ b/www/dev_guide/introduction.rst
@@ -0,0 +1,28 @@
+Introduction
+============
+
+Who should read this Guide?
+---------------------------
+
+The Cheetah Developers' Guide is for those who want to learn how
+Cheetah works internally, or wish to modify or extend Cheetah. It
+is assumed that you've read the Cheetah Users' Guide and have an
+intermediate knowledge of Python.
+
+Contents
+--------
+
+This Guide takes a behaviorist approach. First we'll look at what
+the Cheetah compiler generates when it compiles a template
+definition, and how it compiles the various $placeholder features
+and #directives. Then we'll stroll through the files in the Cheetah
+source distribution and show how each file contributes to the
+compilation and/or filling of templates. Then we'll list every
+method/attribute inherited by a template object. Finally, we'll
+describe how to submit bugfixes/enhancements to Cheetah, and how to
+add to the documentation.
+
+Appendix A will contain a BNF syntax of the Cheetah template
+language.
+
+
diff --git a/www/dev_guide/output.rst b/www/dev_guide/output.rst
new file mode 100644
index 0000000..e29270f
--- /dev/null
+++ b/www/dev_guide/output.rst
@@ -0,0 +1,315 @@
+Directives: Output
+==================
+
+(output)
+
+#echo
+-----
+
+(output.echo)
+
+The template:
+
+::
+
+ Here is my #echo ', '.join(['silly']*5) # example
+
+The output:
+
+::
+
+ Here is my silly, silly, silly, silly, silly example
+
+The generated code:
+
+::
+
+ write('Here is my ')
+ write(filter(', '.join(['silly']*5) ))
+ write(' example\n')
+
+#silent
+-------
+
+(output.silent)
+
+The template:
+
+::
+
+ Here is my #silent ', '.join(['silly']*5) # example
+
+The output:
+
+::
+
+ Here is my example
+
+The generated code:
+
+::
+
+ write('Here is my ')
+ ', '.join(['silly']*5)
+ write(' example\n')
+
+OK, it's not quite covert because that extra space gives it away,
+but it almost succeeds.
+
+#raw
+----
+
+(output.raw)
+
+The template:
+
+::
+
+ Text before raw.
+ #raw
+ Text in raw. $alligator. $croc.o['dile']. #set $a = $b + $c.
+ #end raw
+ Text after raw.
+
+The output:
+
+::
+
+ Text before raw.
+ Text in raw. $alligator. $croc.o['dile']. #set $a = $b + $c.
+ Text after raw.
+
+The generated code:
+
+::
+
+ write('''Text before raw.
+ Text in raw. $alligator. $croc.o['dile']. #set $a = $b + $c.
+ Text after raw.
+ ''')
+
+So we see that {#raw} is really like a quoting mechanism. It says
+that anything inside it is ordinary text, and Cheetah joins a
+{#raw} section with adjacent string literals rather than generating
+a separate {write} call.
+
+#include
+--------
+
+(output.include)
+
+The main template:
+
+::
+
+ #include "y.tmpl"
+
+The included template y.tmpl:
+
+::
+
+ Let's go $voom!
+
+The shell command and output:
+
+::
+
+ % voom="VOOM" x.py --env
+ Let's go VOOM!
+
+The generated code:
+
+::
+
+ write(self._includeCheetahSource("y.tmpl", trans=trans, includeFrom="file",
+ raw=0))
+
+#include raw
+~~~~~~~~~~~~
+
+(output.include.raw)
+
+The main template:
+
+::
+
+ #include raw "y.tmpl"
+
+The shell command and output:
+
+::
+
+ % voom="VOOM" x.py --env
+ Let's go $voom!
+
+The generated code:
+
+::
+
+ write(self._includeCheetahSource("y.tmpl", trans=trans, includeFrom="fil
+ e", raw=1))
+
+That last argument, {raw}, makes the difference.
+
+#include from a string or expression (eval)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+(output.include.expression)
+
+The template:
+
+::
+
+ #attr $y = "Let's go $voom!"
+ #include source=$y
+ #include raw source=$y
+ #include source="Bam! Bam!"
+
+The output:
+
+::
+
+ % voom="VOOM" x.py --env
+ Let's go VOOM!Let's go $voom!Bam! Bam!
+
+The generated code:
+
+::
+
+ write(self._includeCheetahSource(VFS(SL,"y",1), trans=trans,
+ includeFrom="str", raw=0, includeID="481020889808.74"))
+ write(self._includeCheetahSource(VFS(SL,"y",1), trans=trans,
+ includeFrom="str", raw=1, includeID="711020889808.75"))
+ write(self._includeCheetahSource("Bam! Bam!", trans=trans,
+ includeFrom="str", raw=0, includeID="1001020889808.75"))
+
+Later in the generated class:
+
+::
+
+ y = "Let's go $voom!"
+
+#slurp
+------
+
+(output.slurp)
+
+The template:
+
+::
+
+ #for $i in range(5)
+ $i
+ #end for
+ #for $i in range(5)
+ $i #slurp
+ #end for
+ Line after slurp.
+
+The output:
+
+::
+
+ 0
+ 1
+ 2
+ 3
+ 4
+ 0 1 2 3 4 Line after slurp.
+
+The generated code:
+
+::
+
+ for i in range(5):
+ write(filter(i)) # generated from '$i' at line 2, col 1.
+ write('\n')
+ for i in range(5):
+ write(filter(i)) # generated from '$i' at line 5, col 1.
+ write(' ')
+ write('Line after slurp.\n')
+
+The space after each number is because of the space before {#slurp}
+in the template definition.
+
+#filter
+-------
+
+(output.filter)
+
+The template:
+
+::
+
+ #attr $ode = ">> Rubber Ducky, you're the one! You make bathtime so much fun! <<"
+ $ode
+ #filter WebSafe
+ $ode
+ #filter MaxLen
+ ${ode, maxlen=13}
+ #filter None
+ ${ode, maxlen=13}
+
+The output:
+
+::
+
+ >> Rubber Ducky, you're the one! You make bathtime so much fun! <<
+ &gt;&gt; Rubber Ducky, you're the one! You make bathtime so much fun! &lt;&lt;
+ >> Rubber Duc
+ >> Rubber Ducky, you're the one! You make bathtime so much fun! <<
+
+The {WebSafe} filter escapes characters that have a special meaning
+in HTML. The {MaxLen} filter chops off values at the specified
+length. {#filter None} returns to the default filter, which ignores
+the {maxlen} argument.
+
+The generated code:
+
+::
+
+ 1 write(filter(VFS(SL,"ode",1))) # generated from '$ode' at line 2, col 1.
+ 2 write('\n')
+ 3 filterName = 'WebSafe'
+ 4 if self._filters.has_key("WebSafe"):
+ 5 filter = self._currentFilter = self._filters[filterName]
+ 6 else:
+ 7 filter = self._currentFilter = \
+ 8 self._filters[filterName] = getattr(self._filtersLib,
+ filterName)(self).filter
+ 9 write(filter(VFS(SL,"ode",1))) # generated from '$ode' at line 4, col 1.
+ 10 write('\n')
+ 11 filterName = 'MaxLen'
+ 12 if self._filters.has_key("MaxLen"):
+ 13 filter = self._currentFilter = self._filters[filterName]
+ 14 else:
+ 15 filter = self._currentFilter = \
+ 16 self._filters[filterName] = getattr(self._filtersLib,
+ filterName)(self).filter
+ 17 write(filter(VFS(SL,"ode",1), maxlen=13)) # generated from
+ #'${ode, maxlen=13}' at line 6, col 1.
+ 18 write('\n')
+ 19 filter = self._initialFilter
+ 20 write(filter(VFS(SL,"ode",1), maxlen=13)) # generated from
+ #'${ode, maxlen=13}' at line 8, col 1.
+ 21 write('\n')
+
+As we've seen many times, Cheetah wraps all placeholder lookups in
+a {filter} call. (This also applies to non-searchList lookups:
+local, global and builtin variables.) The {filter} "function" is
+actually an alias to the current filter object:
+
+::
+
+ filter = self._currentFilter
+
+as set at the top of the main method. Here in lines 3-8 and 11-16
+we see the filter being changed. Whoops, I lied. {filter} is not an
+alias to the filter object itself but to that object's {.filter}
+method. Line 19 switches back to the default filter.
+
+In line 17 we see the {maxlen} argument being passed as a keyword
+argument to {filter} (not to {VFS}). In line 20 the same thing
+happens although the default filter ignores the argument.
+
+
diff --git a/www/dev_guide/parser.rst b/www/dev_guide/parser.rst
new file mode 100644
index 0000000..802b43f
--- /dev/null
+++ b/www/dev_guide/parser.rst
@@ -0,0 +1,9 @@
+The parser
+==========
+
+(parser)
+
+How templates are compiled: a walk through Parser.py's source.
+(Also need to look at Lexer.py, but not too closely.)
+
+
diff --git a/www/dev_guide/parserInstructions.rst b/www/dev_guide/parserInstructions.rst
new file mode 100644
index 0000000..805df7c
--- /dev/null
+++ b/www/dev_guide/parserInstructions.rst
@@ -0,0 +1,67 @@
+Directives: Parser Instructions
+===============================
+
+(parserInstructions)
+
+#breakpoint
+-----------
+
+(parserInstructions.breakpoint)
+
+The template:
+
+::
+
+ Text before breakpoint.
+ #breakpoint
+ Text after breakpoint.
+ #raise RuntimeError
+
+The output:
+
+::
+
+ Text before breakpoint.
+
+The generated code:
+
+::
+
+ write('Text before breakpoint.\n')
+
+Nothing after the breakpoint was compiled.
+
+#compiler
+---------
+
+(parserInstructions.compiler)
+
+The template:
+
+::
+
+ // Not a comment
+ #compiler commentStartToken = '//'
+ // A comment
+ #compiler reset
+ // Not a comment
+
+The output:
+
+::
+
+ // Not a comment
+ // Not a comment
+
+The generated code:
+
+::
+
+ write('// Not a comment\n')
+ # A comment
+ write('// Not a comment\n')
+
+So this didn't affect the generated program, it just affected how
+the template definition was read.
+
+
diff --git a/www/dev_guide/patching.rst b/www/dev_guide/patching.rst
new file mode 100644
index 0000000..8e67490
--- /dev/null
+++ b/www/dev_guide/patching.rst
@@ -0,0 +1,149 @@
+Patching Cheetah
+================
+
+(patching)
+
+How to commit changes to CVS or submit patches, how to run the test
+suite. Describe distutils and how the regression tests work.
+
+File Requirements
+-----------------
+
+(patching.fileRequirements)
+
+The code{Template} class contains not only the Cheetah
+infrastructure, but also some convenience methods useful in all
+templates. More methods may be added if it's generally agreed among
+Cheetah developers that the method is sufficiently useful to all
+types of templates, or at least to all types of HTML-output
+templates. If a method is too long to fit into {Template} -
+especially if it has helper methods - put it in a mixin class under
+{Cheetah.Utils} and inherit it.
+
+Routines for a specific problem domain should be put under
+{Cheetah.Tools}, so that it doesn't clutter the namespace unless
+the user asks for it.
+
+Remember: {Cheetah.Utils} is for objects required by any part of
+Cheetah's core. {Cheetah.Tools} is for completely optional objects.
+It should always be possible to delete {Cheetah.Tools} without
+breaking Cheetah's core services.
+
+If a core method needs to look up an attribute defined under
+{Cheetah.Tools}, it should use {hasattr()} and gracefully provide a
+default if the attribute does not exist (meaning the user has not
+imported that subsystem).
+
+Testing Changes and Building Regression Tests
+---------------------------------------------
+
+(patching.testing)
+
+Cheetah ships with a regression test suite. To run the built-in
+tests, execute at the shell prompt:
+
+::
+
+ cheetah test
+
+Before checking any changes in, run the tests and verify they all
+pass. That way, users can check out the CVS version of Cheetah at
+any time with a fairly high confidence that it will work. If you
+fix a bug or add a feature, please take the time to add a test that
+exploits the bug/feature. This will help in the future, to prevent
+somebody else from breaking it again without realizing it. Users
+can also run the test suite to verify all the features work on
+their particular platform and computer.
+
+The general procedure for modifying Cheetah is as follows:
+
+
+#. Write a simple Python program that exploits the bug/feature
+ you're working on. You can either write a regression test (see
+ below), or a separate program that writes the template output to
+ one file and put the expected output in another file; then you can
+ run {diff} on the two outputs. ({diff} is a utility included on all
+ Unix-like systems. It shows the differences between two files line
+ by line. A precompiled Windows version is at
+ http://gnuwin32.sourceforge.net/packages/diffutils.htm, and MacOS
+ sources at
+ http://perso.wanadoo.fr/gilles.depeyrot/DevTools\_en.html.)
+
+#. Make the change in your Cheetah CVS sandbox or in your installed
+ version of Cheetah. If you make it in the sandbox, you'll have to
+ run {python setup.py install} before testing it. If you make it in
+ the installed version, do { not} run the installer or it will
+ overwrite your changes!
+
+#. Run {cheetah test} to verify you didn't break anything. Then run
+ your little test program.
+
+#. Repeat steps 2-3 until everything is correct.
+
+#. Turn your little program into a regression test as described
+ below.
+
+#. When {cheetah test} runs cleanly with your regression test
+ included, update the {CHANGES} file and check in your changes. If
+ you made the changes in your installed copy of Cheetah, you'll have
+ to copy them back into the CVS sandbox first. If you added any
+ files that must be distributed, { be sure to} {cvs add} them before
+ committing. Otherwise Cheetah will run fine on your computer but
+ fail on anybody else's, and the test suite can't check for this.
+
+#. Announce the change on the cheetahtemplate-discuss list and
+ provide a tutorial if necessary. The documentation maintainer will
+ update the Users' Guide and Developers' Guide based on this message
+ and on the changelog.
+
+
+If you add a directory to Cheetah, you have to mention it in
+{setup.py} or it won't be installed.
+
+The tests are in the {Cheetah.Tests} package, aka the {src/Tests/}
+directory of your CVS sandbox. Most of the tests are in
+{SyntaxAndOutput.py}. You can either run all the tests or choose
+which to run:
+
+ Run all the tests. (Equivalent to {cheetah test}.)
+
+ Run only the tests in that module.
+
+ Run only the tests in the class {CGI} inside the module. The class
+ must be a direct or indirect subclass of
+ {unittest\_local\_copy.TestCase}.
+
+ Run the tests in classes {CGI} and {Indenter}.
+
+ Run only test {test1}, which is a method in the {CGI} class.
+
+
+To make a SyntaxAndOutput test, first see if your test logically
+fits into one of the existing classes. If so, simply add a method;
+e.g., {test16}. The method should not require any arguments except
+{self}, and should call {.verify(source, expectedOutput)}, where
+the two arguments are a template definition string and a control
+string. The tester will complain if the template output does not
+match the control string. You have a wide variety of placeholder
+variables to choose from, anything that's included in the
+{defaultTestNameSpace} global dictionary. If that's not enough, add
+items to the dictionary, but please keep it from being cluttered
+with wordy esoteric items for a single test).
+
+If your test logically belongs in a separate class, create a
+subclass of {OutputTest}. You do not need to do anything else; the
+test suite will automatically find your class in the module. Having
+a separate class allows you to define state variables needed by
+your tests (see the {CGI} class) or override {.searchList()} (see
+the {Indenter} class) to provide your own searchList.
+
+To modify another test module or create your own test module,
+you'll have to study the existing modules, the
+{unittest\_local\_copy} source, and the {unittest} documentation in
+the Python Library Reference. Note that we are using a hacked
+version of {unittest} to make a more convenient test structure for
+Cheetah. The differences between {unittest\_local\_copy} and
+Python's standard {unittest} are documented at the top of the
+module.
+
+
diff --git a/www/dev_guide/placeholders.rst b/www/dev_guide/placeholders.rst
new file mode 100644
index 0000000..13b91d6
--- /dev/null
+++ b/www/dev_guide/placeholders.rst
@@ -0,0 +1,489 @@
+Placeholders
+============
+
+(placeholders)
+
+Simple placeholders
+-------------------
+
+(placeholders.simple)
+
+Let's add a few $placeholders to our template:
+
+::
+
+ >>> from Cheetah.Template import Template
+ >>> values = {'what': 'surreal', 'punctuation': '?'}
+ >>> t = Template("""\
+ ... Hello, $what world$punctuation
+ ... One of Python's least-used functions is $xrange.
+ ... """, [values])
+ >>> print t
+ Hello, surreal world?
+ One of Python's least-used functions is <built-in function xrange>.
+
+ >>> print t.generatedModuleCode()
+ 1 #!/usr/bin/env python
+
+ 2 """
+ 3 Autogenerated by CHEETAH: The Python-Powered Template Engine
+ 4 CHEETAH VERSION: 0.9.12
+ 5 Generation time: Sun Apr 21 00:53:01 2002
+ 6 """
+
+ 7 __CHEETAH_genTime__ = 'Sun Apr 21 00:53:01 2002'
+ 8 __CHEETAH_version__ = '0.9.12'
+
+ 9 ##################################################
+ 10 ## DEPENDENCIES
+
+ 11 import sys
+ 12 import os
+ 13 import os.path
+ 14 from os.path import getmtime, exists
+ 15 import time
+ 16 import types
+ 17 from Cheetah.Template import Template
+ 18 from Cheetah.DummyTransaction import DummyTransaction
+ 19 from Cheetah.NameMapper import NotFound, valueForName,
+ valueFromSearchList
+ 20 import Cheetah.Filters as Filters
+ 21 import Cheetah.ErrorCatchers as ErrorCatchers
+
+ 22 ##################################################
+ 23 ## MODULE CONSTANTS
+
+ 24 try:
+ 25 True, False
+ 26 except NameError:
+ 27 True, False = (1==1), (1==0)
+
+ 28 ##################################################
+ 29 ## CLASSES
+
+ 30 class GenTemplate(Template):
+ 31 """
+ 32
+ 33 Autogenerated by CHEETAH: The Python-Powered Template Engine
+ 34 """
+
+ 35 ##################################################
+ 36 ## GENERATED METHODS
+
+
+::
+
+ 37 def __init__(self, *args, **KWs):
+ 38 """
+ 39
+ 40 """
+
+ 41 Template.__init__(self, *args, **KWs)
+
+ 42 def respond(self,
+ 43 trans=None,
+ 44 dummyTrans=False,
+ 45 VFS=valueFromSearchList,
+ 46 VFN=valueForName,
+ 47 getmtime=getmtime,
+ 48 currentTime=time.time):
+
+
+ 49 """
+ 50 This is the main method generated by Cheetah
+ 51 """
+
+ 52 if not trans:
+ 53 trans = DummyTransaction()
+ 54 dummyTrans = True
+ 55 write = trans.response().write
+ 56 SL = self._searchList
+ 57 filter = self._currentFilter
+ 58 globalSetVars = self._globalSetVars
+ 59
+ 60 ########################################
+ 61 ## START - generated method body
+ 62
+ 63 write('Hello, ')
+ 64 write(filter(VFS(SL,"what",1))) # generated from '$what' at
+ # line 1, col 8.
+ 65 write(' world')
+ 66 write(filter(VFS(SL,"punctuation",1))) # generated from
+ # '$punctuation' at line 1, col 19.
+ 67 write("\nOne of Python's least-used methods is ")
+ 68 write(filter(xrange)) # generated from '$xrange' at line 2,
+ # col 39.
+ 69 write('.\n')
+ 70
+ 71 ########################################
+ 72 ## END - generated method body
+ 73
+ 74 if dummyTrans:
+ 75 return trans.response().getvalue()
+ 76 else:
+ 77 return ""
+
+::
+
+ 78
+ 79 ##################################################
+ 80 ## GENERATED ATTRIBUTES
+
+ 81 __str__ = respond
+ 82 _mainCheetahMethod_for_GenTemplate= 'respond'
+
+ 83 # CHEETAH was developed by Tavis Rudd, Chuck Esterbrook, Ian Bicking
+ # and Mike Orr;
+ 84 # with code, advice and input from many other volunteers.
+ 85 # For more information visit http://www.CheetahTemplate.org
+
+ 86 ##################################################
+ 87 ## if run from command line:
+ 88 if __name__ == '__main__':
+ 89 GenTemplate().runAsMainProgram()
+
+
+(Again, I have added line numbers and split the lines as in the
+previous chapter.)
+
+This generated template module is different from the previous one
+in several trivial respects and one important respect. Trivially,
+{.\_filePath} and {.\_fileMtime} are not updated in
+{.\_\_init\_\_}, so they inherit the value {None} from {Template}.
+Also, that if-stanza in {.respond} that recompiles the template if
+the source file changes is missing - because there is no source
+file. So this module is several lines shorter than the other one.
+
+But the important way this module is different is that instead of
+the one {write} call outputting a string literal, this module has a
+series of {write} calls (lines 63-69) outputting successive chunks
+of the template. Regular text has been translated into a string
+literal, and placeholders into function calls. Every placeholder is
+wrapped inside a {filter} call to apply the current output filter.
+(The default output filter converts all objects to strings, and
+{None} to {""}.)
+
+Placeholders referring to a Python builtin like {xrange} (line 68)
+generate a bare variable name. Placeholders to be looked up in the
+searchList have a nested function call; e.g.,
+
+::
+
+ write(filter(VFS(SL,"what",1))) # generated from '$what' at line 1, col 8.
+
+{VFS}, remember, is a function imported from {Cheetah.NameMapper}
+that looks up a value in a searchList. So we pass it the
+searchList, the name to look up, and a boolean (1) indicating we
+want autocalling. (It's {1} rather than {True} because it's
+generated from an {and} expression, and that's what Python 2.2
+outputs for true {and} expressions.)
+
+Complex placeholders
+--------------------
+
+(placeholders.complex)
+
+Placeholders can get far more complicated than that. This example
+shows what kind of code the various NameMapper features produce.
+The formulas are taken from Cheetah's test suite, in the
+{Cheetah.Tests.SyntaxAndOutput.Placeholders} class.
+
+::
+
+ 1 placeholder: $aStr
+ 2 placeholders: $aStr $anInt
+ 2 placeholders, back-to-back: $aStr$anInt
+ 1 placeholder enclosed in {}: ${aStr}
+ 1 escaped placeholder: \$var
+ func placeholder - with (): $aFunc()
+ func placeholder - with (int): $aFunc(1234)
+ func placeholder - with (string): $aFunc('aoeu')
+ func placeholder - with ('''\nstring'\n'''): $aFunc('''\naoeu'\n''')
+ func placeholder - with (string*int): $aFunc('aoeu'*2)
+ func placeholder - with (int*float): $aFunc(2*2.0)
+ Python builtin values: $None $True $False
+ func placeholder - with ($arg=float): $aFunc($arg=4.0)
+ deeply nested argstring: $aFunc( $arg = $aMeth( $arg = $aFunc( 1 ) ) ):
+ function with None: $aFunc(None)
+ autocalling: $aFunc! $aFunc().
+ nested autocalling: $aFunc($aFunc).
+ list subscription: $aList[0]
+ list slicing: $aList[:2]
+ list slicing and subcription combined: $aList[:2][0]
+ dict - NameMapper style: $aDict.one
+ dict - Python style: $aDict['one']
+ dict combined with autocalled string method: $aDict.one.upper
+ dict combined with string method: $aDict.one.upper()
+ nested dict - NameMapper style: $aDict.nestedDict.two
+ nested dict - Python style: $aDict['nestedDict']['two']
+ nested dict - alternating style: $aDict['nestedDict'].two
+ nested dict - NameMapper style + method: $aDict.nestedDict.two.upper
+ nested dict - alternating style + method: $aDict['nestedDict'].two.upper
+ nested dict - NameMapper style + method + slice: $aDict.nestedDict.two.upper[:4]
+ nested dict - Python style, variable key: $aDict[$anObj.meth('nestedDict')].two
+ object method: $anObj.meth1
+ object method + complex slice: $anObj.meth1[0: ((4/4*2)*2)/$anObj.meth1(2) ]
+ very complex slice: $( anObj.meth1[0: ((4/4*2)*2)/$anObj.meth1(2) ] )
+ $_('a call to gettext')
+
+We'll need a big program to set up the placeholder values. Here it
+is:
+
+::
+
+ #!/usr/bin/env python
+ from ComplexExample import ComplexExample
+
+ try: # Python >= 2.2.1
+ True, False
+ except NameError: # Older Python
+ True, False = (1==1), (1==0)
+
+ class DummyClass:
+ _called = False
+ def __str__(self):
+ return 'object'
+
+ def meth(self, arg="arff"):
+ return str(arg)
+
+ def meth1(self, arg="doo"):
+ return arg
+
+ def meth2(self, arg1="a1", arg2="a2"):
+ return str(arg1) + str(arg2)
+
+ def callIt(self, arg=1234):
+ self._called = True
+ self._callArg = arg
+
+ def dummyFunc(arg="Scooby"):
+ return arg
+
+ defaultTestNameSpace = {
+ 'aStr':'blarg',
+ 'anInt':1,
+ 'aFloat':1.5,
+ 'aList': ['item0','item1','item2'],
+ 'aDict': {'one':'item1',
+ 'two':'item2',
+ 'nestedDict':{1:'nestedItem1',
+ 'two':'nestedItem2'
+ },
+ 'nestedFunc':dummyFunc,
+ },
+ 'aFunc': dummyFunc,
+ 'anObj': DummyClass(),
+ 'aMeth': DummyClass().meth1,
+ '_': lambda x: 'translated ' + x
+ }
+
+ print ComplexExample( searchList=[defaultTestNameSpace] )
+
+Here's the output:
+
+::
+
+ 1 placeholder: blarg
+ 2 placeholders: blarg 1
+ 2 placeholders, back-to-back: blarg1
+ 1 placeholder enclosed in {}: blarg
+ 1 escaped placeholder: $var
+ func placeholder - with (): Scooby
+ func placeholder - with (int): 1234
+ func placeholder - with (string): aoeu
+ func placeholder - with ('''\nstring'\n'''):
+ aoeu'
+
+ func placeholder - with (string*int): aoeuaoeu
+ func placeholder - with (int*float): 4.0
+ Python builtin values: 1 0
+ func placeholder - with ($arg=float): 4.0
+ deeply nested argstring: 1:
+ function with None:
+ autocalling: Scooby! Scooby.
+ nested autocalling: Scooby.
+ list subscription: item0
+ list slicing: ['item0', 'item1']
+ list slicing and subcription combined: item0
+ dict - NameMapper style: item1
+ dict - Python style: item1
+ dict combined with autocalled string method: ITEM1
+ dict combined with string method: ITEM1
+ nested dict - NameMapper style: nestedItem2
+ nested dict - Python style: nestedItem2
+ nested dict - alternating style: nestedItem2
+ nested dict - NameMapper style + method: NESTEDITEM2
+ nested dict - alternating style + method: NESTEDITEM2
+ nested dict - NameMapper style + method + slice: NEST
+ nested dict - Python style, variable key: nestedItem2
+ object method: doo
+ object method + complex slice: do
+ very complex slice: do
+ translated a call to gettext
+
+And here - tada! - is the generated module. To save space, I've
+included only the lines containing the {write} calls. The rest of
+the module is the same as in the first example, chapter
+pyModules.example. I've split some of the lines to make them fit on
+the page.
+
+::
+
+ 1 write('1 placeholder: ')
+ 2 write(filter(VFS(SL,"aStr",1))) # generated from '$aStr' at line 1, col 16.
+ 3 write('\n2 placeholders: ')
+ 4 write(filter(VFS(SL,"aStr",1))) # generated from '$aStr' at line 2, col 17.
+ 5 write(' ')
+ 6 write(filter(VFS(SL,"anInt",1)))
+ # generated from '$anInt' at line 2, col 23.
+ 7 write('\n2 placeholders, back-to-back: ')
+ 8 write(filter(VFS(SL,"aStr",1))) # generated from '$aStr' at line 3, col 31.
+ 9 write(filter(VFS(SL,"anInt",1)))
+ # generated from '$anInt' at line 3, col 36.
+ 10 write('\n1 placeholder enclosed in {}: ')
+ 11 write(filter(VFS(SL,"aStr",1))) # generated from '${aStr}' at line 4,
+ # col 31.
+ 12 write('\n1 escaped placeholder: $var\nfunc placeholder - with (): ')
+ 13 write(filter(VFS(SL,"aFunc",0)())) # generated from '$aFunc()' at line 6,
+ # col 29.
+ 14 write('\nfunc placeholder - with (int): ')
+ 15 write(filter(VFS(SL,"aFunc",0)(1234))) # generated from '$aFunc(1234)' at
+ # line 7, col 32.
+ 16 write('\nfunc placeholder - with (string): ')
+ 17 write(filter(VFS(SL,"aFunc",0)('aoeu'))) # generated from "$aFunc('aoeu')"
+ # at line 8, col 35.
+ 18 write("\nfunc placeholder - with ('''\\nstring'\\n'''): ")
+ 19 write(filter(VFS(SL,"aFunc",0)('''\naoeu'\n'''))) # generated from
+ # "$aFunc('''\\naoeu'\\n''')" at line 9, col 46.
+ 20 write('\nfunc placeholder - with (string*int): ')
+ 21 write(filter(VFS(SL,"aFunc",0)('aoeu'*2))) # generated from
+ # "$aFunc('aoeu'*2)" at line 10, col 39.
+ 22 write('\nfunc placeholder - with (int*float): ')
+ 23 write(filter(VFS(SL,"aFunc",0)(2*2.0))) # generated from '$aFunc(2*2.0)'
+ # at line 11, col 38.
+ 24 write('\nPython builtin values: ')
+ 25 write(filter(None)) # generated from '$None' at line 12, col 24.
+ 26 write(' ')
+ 27 write(filter(True)) # generated from '$True' at line 12, col 30.
+ 28 write(' ')
+ 29 write(filter(False)) # generated from '$False' at line 12, col 36.
+ 30 write('\nfunc placeholder - with ($arg=float): ')
+ 31 write(filter(VFS(SL,"aFunc",0)(arg=4.0))) # generated from
+ # '$aFunc($arg=4.0)' at line 13, col 40.
+ 32 write('\ndeeply nested argstring: ')
+ 33 write(filter(VFS(SL,"aFunc",0)(
+ arg = VFS(SL,"aMeth",0)( arg = VFS(SL,"aFunc",0)( 1 ) ) )))
+ # generated from '$aFunc( $arg = $aMeth( $arg = $aFunc( 1 ) ) )'
+ # at line 14, col 26.
+ 34 write(':\nfunction with None: ')
+ 35 write(filter(VFS(SL,"aFunc",0)(None))) # generated from '$aFunc(None)' at
+ # line 15, col 21.
+ 36 write('\nautocalling: ')
+ 37 write(filter(VFS(SL,"aFunc",1))) # generated from '$aFunc' at line 16,
+ # col 14.
+ 38 write('! ')
+ 39 write(filter(VFS(SL,"aFunc",0)())) # generated from '$aFunc()' at line 16,
+ # col 22.
+
+::
+
+ 40 write('.\nnested autocalling: ')
+ 41 write(filter(VFS(SL,"aFunc",0)(VFS(SL,"aFunc",1)))) # generated from
+ # '$aFunc($aFunc)' at line 17, col 21.
+ 42 write('.\nlist subscription: ')
+ 43 write(filter(VFS(SL,"aList",1)[0])) # generated from '$aList[0]' at line
+ # 18, col 20.
+ 44 write('\nlist slicing: ')
+ 45 write(filter(VFS(SL,"aList",1)[:2])) # generated from '$aList[:2]' at
+ # line 19, col 15.
+ 46 write('\nlist slicing and subcription combined: ')
+ 47 write(filter(VFS(SL,"aList",1)[:2][0])) # generated from '$aList[:2][0]'
+ # at line 20, col 40.
+ 48 write('\ndict - NameMapper style: ')
+ 49 write(filter(VFS(SL,"aDict.one",1))) # generated from '$aDict.one' at line
+ # 21, col 26.
+ 50 write('\ndict - Python style: ')
+ 51 write(filter(VFS(SL,"aDict",1)['one'])) # generated from "$aDict['one']"
+ # at line 22, col 22.
+ 52 write('\ndict combined with autocalled string method: ')
+ 53 write(filter(VFS(SL,"aDict.one.upper",1))) # generated from
+ # '$aDict.one.upper' at line 23, col 46.
+ 54 write('\ndict combined with string method: ')
+ 55 write(filter(VFN(VFS(SL,"aDict.one",1),"upper",0)())) # generated from
+ # '$aDict.one.upper()' at line 24, col 35.
+ 56 write('\nnested dict - NameMapper style: ')
+ 57 write(filter(VFS(SL,"aDict.nestedDict.two",1))) # generated from
+ # '$aDict.nestedDict.two' at line 25, col 33.
+ 58 write('\nnested dict - Python style: ')
+ 59 write(filter(VFS(SL,"aDict",1)['nestedDict']['two'])) # generated from
+ # "$aDict['nestedDict']['two']" at line 26, col 29.
+ 60 write('\nnested dict - alternating style: ')
+ 61 write(filter(VFN(VFS(SL,"aDict",1)['nestedDict'],"two",1))) # generated
+ # from "$aDict['nestedDict'].two" at line 27, col 34.
+ 62 write('\nnested dict - NameMapper style + method: ')
+ 63 write(filter(VFS(SL,"aDict.nestedDict.two.upper",1))) # generated from
+ # '$aDict.nestedDict.two.upper' at line 28, col 42.
+ 64 write('\nnested dict - alternating style + method: ')
+ 65 write(filter(VFN(VFS(SL,"aDict",1)['nestedDict'],"two.upper",1)))
+ # generated from "$aDict['nestedDict'].two.upper" at line 29, col 43.
+ 66 write('\nnested dict - NameMapper style + method + slice: ')
+
+::
+
+ 67 write(filter(VFN(VFS(SL,"aDict.nestedDict.two",1),"upper",1)[:4]))
+ # generated from '$aDict.nestedDict.two.upper[:4]' at line 30, col 50.
+ 68 write('\nnested dict - Python style, variable key: ')
+ 69 write(filter(VFN(VFS(SL,"aDict",1)
+ [VFN(VFS(SL,"anObj",1),"meth",0)('nestedDict')],"two",1)))
+ # generated from "$aDict[$anObj.meth('nestedDict')].two" at line 31,
+ # col 43.
+ 70 write('\nobject method: ')
+ 71 write(filter(VFS(SL,"anObj.meth1",1))) # generated from '$anObj.meth1' at
+ # line 32, col 16.
+ 72 write('\nobject method + complex slice: ')
+ 73 write(filter(VFN(VFS(SL,"anObj",1),"meth1",1)
+ [0: ((4/4*2)*2)/VFN(VFS(SL,"anObj",1),"meth1",0)(2) ]))
+ # generated from '$anObj.meth1[0: ((4/4*2)*2)/$anObj.meth1(2) ]'
+ # at line 33, col 32.
+ 74 write('\nvery complex slice: ')
+ 75 write(filter(VFN(VFS(SL,"anObj",1),"meth1",1)
+ [0: ((4/4*2)*2)/VFN(VFS(SL,"anObj",1),"meth1",0)(2) ] ))
+ # generated from '$( anObj.meth1[0: ((4/4*2)*2)/$anObj.meth1(2) ] )'
+ # at line 34, col 21.
+ 76 if False:
+ 77 _('foo')
+ 78 write(filter(VFS(SL,"_",0)("a call to gettext")))
+ # generated from "$_('a call to gettext')"
+ # at line 35, col 1.
+ 79 write('\n')
+
+For each placeholder lookup, the the innermost level of nesting is
+a {VFS} call, which looks up the first (leftmost) placeholder
+component in the searchList. This is wrapped by zero or more {VFN}
+calls, which perform Universal Dotted Notation lookup on the next
+dotted component of the placeholder, looking for an attribute or
+key by that name within the previous object (not in the
+searchList). Autocalling is performed by {VFS} and {VFN}: that's
+the reason for their third argument.
+
+Explicit function/method arguments, subscripts and keys (which are
+all expressions) are left unchanged, besides expanding any embedded
+$placeholders in them. This means they must result in valid Python
+expressions, following the standard Python quoting rules.
+
+Built-in Python values ({None}, {True} and {False}) are converted
+to {filter(None)}, etc. They use normal Python variable lookup
+rather than {VFS}. (Cheetah emulates {True} and {False} using
+global variables for Python < 2.2.1, when they weren't builtins
+yet.)
+
+Notice the last line is a call to {\_} (i.e. {gettext}) which is
+used for internationalization (see
+http://docs.python.org/lib/module-gettext.html). The code is
+converted normally, but an {if False} block is used so that gettext
+can successfully mark the string for translation when parsing the
+generated Python. Otherwise, the NameMapper syntax would get in the
+way of this.
+
+
diff --git a/www/dev_guide/pyModules.rst b/www/dev_guide/pyModules.rst
new file mode 100644
index 0000000..ceaf53f
--- /dev/null
+++ b/www/dev_guide/pyModules.rst
@@ -0,0 +1,252 @@
+.py Template Modules
+====================
+
+(pyModules)
+
+This chapter examines the structure of a .py template module. The
+following few chapters will then show how each placeholder and
+directive affects the generated Python code.
+
+An example
+----------
+
+(pyModules.example)
+
+Our first template follows a long noble tradition in computer
+tutorials. It produces a familiar, friendly greeting. Here's the
+template:
+
+::
+
+ Hello, world!
+
+... the output:
+
+::
+
+ Hello, world!
+
+... and the .py template module cheetah-compile produced, with line
+numbers added:
+
+::
+
+ 1 #!/usr/bin/env python
+
+ 2 """
+ 3 Autogenerated by CHEETAH: The Python-Powered Template Engine
+ 4 CHEETAH VERSION: 0.9.12
+ 5 Generation time: Sat Apr 20 14:27:47 2002
+ 6 Source file: x.tmpl
+ 7 Source file last modified: Wed Apr 17 22:10:59 2002
+ 8 """
+
+ 9 __CHEETAH_genTime__ = 'Sat Apr 20 14:27:47 2002'
+ 10 __CHEETAH_src__ = 'x.tmpl'
+ 11 __CHEETAH_version__ = '0.9.12'
+
+ 12 ##################################################
+ 13 ## DEPENDENCIES
+
+ 14 import sys
+ 15 import os
+ 16 import os.path
+ 17 from os.path import getmtime, exists
+ 18 import time
+ 19 import types
+ 20 from Cheetah.Template import Template
+ 21 from Cheetah.DummyTransaction import DummyTransaction
+ 22 from Cheetah.NameMapper import NotFound, valueForName,
+ valueFromSearchList
+ 23 import Cheetah.Filters as Filters
+ 24 import Cheetah.ErrorCatchers as ErrorCatchers
+
+ 25 ##################################################
+ 26 ## MODULE CONSTANTS
+
+ 27 try:
+ 28 True, False
+ 29 except NameError:
+ 30 True, False = (1==1), (1==0)
+
+ 31 ##################################################
+ 32 ## CLASSES
+
+ 33 class x(Template):
+ 34 """
+ 35
+ 36 Autogenerated by CHEETAH: The Python-Powered Template Engine
+ 37 """
+
+::
+
+ 38 ##################################################
+ 39 ## GENERATED METHODS
+
+
+ 40 def __init__(self, *args, **KWs):
+ 41 """
+ 42
+ 43 """
+
+ 44 Template.__init__(self, *args, **KWs)
+ 45 self._filePath = 'x.tmpl'
+ 46 self._fileMtime = 1019106659
+
+ 47 def respond(self,
+ 48 trans=None,
+ 49 dummyTrans=False,
+ 50 VFS=valueFromSearchList,
+ 51 VFN=valueForName,
+ 52 getmtime=getmtime,
+ 53 currentTime=time.time):
+
+
+ 54 """
+ 55 This is the main method generated by Cheetah
+ 56 """
+
+ 57 if not trans:
+ 58 trans = DummyTransaction()
+ 59 dummyTrans = True
+ 60 write = trans.response().write
+ 61 SL = self._searchList
+ 62 filter = self._currentFilter
+ 63 globalSetVars = self._globalSetVars
+ 64
+ 65 ########################################
+ 66 ## START - generated method body
+ 67
+ 68 if exists(self._filePath) and getmtime(self._filePath) > \
+ self._fileMtime:
+ 69 self.compile(file=self._filePath)
+ 70 write(getattr(self, self._mainCheetahMethod_for_x)
+ (trans=trans))
+ 71 if dummyTrans:
+ 72 return trans.response().getvalue()
+ 73 else:
+ 74 return ""
+ 75 write('Hello, world!\n')
+ 76
+ 77 ########################################
+ 78 ## END - generated method body
+ 79
+ 80 if dummyTrans:
+ 81 return trans.response().getvalue()
+ 82 else:
+ 83 return ""
+
+::
+
+ 84
+ 85 ##################################################
+ 86 ## GENERATED ATTRIBUTES
+
+
+ 87 __str__ = respond
+
+ 88 _mainCheetahMethod_for_x= 'respond'
+
+
+ 89 # CHEETAH was developed by Tavis Rudd, Chuck Esterbrook, Ian Bicking
+ # and Mike Orr;
+ 90 # with code, advice and input from many other volunteers.
+ 91 # For more information visit http://www.CheetahTemplate.org
+
+ 92 ##################################################
+ 93 ## if run from command line:
+ 94 if __name__ == '__main__':
+ 95 x().runAsMainProgram()
+
+
+(I added the line numbers for this Guide, and split a few lines to
+fit the page width. The continuation lines don't have line numbers,
+and I added indentation, backslashes and '#' as necessary to make
+the result a valid Python program.)
+
+The examples were generated from CVS versions of Cheetah between
+0.9.12 and 0.9.14.
+
+A walk through the example
+--------------------------
+
+(pyModules.walk)
+
+Lines 20-24 are the Cheetah-specific imports. Line 33 introduces
+our generated class, {x}, a subclass of {Template}. It's called x
+because the source file was x.tmpl.
+
+Lines 40-46 are the {.\_\_init\_\_} method called when the template
+is instantiated or used as a Webware servlet, or when the module is
+run as a standalone program. We can see it calling its superclass
+constructor and setting {.\_filePath} and {.\_fileMtime} to the
+filename and modification time (in Unix ticks) of the source .tmpl
+file.
+
+Lines 47-84 are the main method {.respond}, the one that fills the
+template. Normally you call it without arguments, but Webware calls
+it with a Webware {Transaction} object representing the current
+request. Lines 57-59 set up the {trans} variable. If a real or
+dummy transaction is passed in, the method uses it. Otherwise (if
+the {trans} argument is {None}), the method creates a
+{DummyTransaction} instance. {dummyTrans} is a flag that just tells
+whether a dummy transaction is in effect; it'll be used at the end
+of the method.
+
+The other four {.respond} arguments aren't anything you'd ever want
+to pass in; they exist solely to speed up access to these
+frequently-used global functions. This is a standard Python trick
+described in question 4.7 of the Python FAQ
+(http://www.python.org/cgi-bin/faqw.py). {VFS} and {VFN} are the
+functions that give your template the benefits of NameMapper
+lookup, such as the ability to use the searchList.
+
+Line 60 initializes the {write} variable. This important variable
+is discussed below.
+
+Lines 60-63 initialize a few more local variables. {SL} is the
+searchList. {filter} is the current output filter. {globalSetVars}
+are the variables that have been defined with {#set global}.
+
+The comments at lines 65 and 78 delimit the start and end of the
+code that varies with each template. The code outside this region
+is identical in all template modules. That's not quite true -
+{#import} for instance generates additional {import} statements at
+the top of the module - but it's true enough for the most part.
+
+Lines 68-74 exist only if the template source was a named file
+rather than a string or file object. The stanza recompiles the
+template if the source file has changed. Lines 70-74 seem to be
+redundant with 75-83: both fill the template and send the output.
+The reason the first set of lines exists is because the second set
+may become invalid when the template is recompiled. (This is for {
+re} compilation only. The initial compilation happened in the
+{.\_\_init\_\_} method if the template wasn't precompiled.)
+
+Line 75 is the most interesting line in this module. It's a direct
+translation of what we put in the template definition,
+"Hello, world!" Here the content is a single string literal.
+{write} looks like an ordinary function call, but remember that
+line 60 made it an alias to {trans.response().write}, a method in
+the transaction. The next few chapters describe how the different
+placeholders and directives influence this portion of the generated
+class.
+
+Lines 80-83 finish the template filling. If {trans} is a real
+Webware transaction, {write} has already sent the output to Webware
+for handling, so we return {""}. If {trans} is a dummy transaction,
+{write} has been accumulating the output in a Python {StringIO}
+object rather than sending it anywhere, so we have to return it.
+
+Line 83 is the end of the {.respond} method.
+
+Line 87 makes code{.\_\_str\_\_} an alias for the main method, so
+that you can {print} it or apply {str} to it and it will fill the
+template. Line 88 gives the name of the main method, because
+sometimes it's not {.respond}.
+
+Lines 94-95 allow the module to be run directly as a script.
+Essentially, they process the command-line arguments and them make
+the template fill itself.
+
+
diff --git a/www/dev_guide/safeDelegation.rst b/www/dev_guide/safeDelegation.rst
new file mode 100644
index 0000000..87f3dc1
--- /dev/null
+++ b/www/dev_guide/safeDelegation.rst
@@ -0,0 +1,40 @@
+Safe Delegation
+===============
+
+(safeDelegation)
+
+Safe delegation, as provided by Zope and Allaire's Spectra, is not
+implemented in Cheetah. The core aim has been to help developers
+and template maintainers get things done, without throwing
+unnecessary complications in their way. So you should give write
+access to your templates only to those whom you trust. However,
+several hooks have been built into Cheetah so that safe delegation
+can be implemented at a later date.
+
+It should be possible to implement safe delegation via a future
+configuration Setting {safeDelegationLevel} (0=none, 1=semi-secure,
+2-alcatraz). This is not implemented but the steps are listed here
+in case somebody wants to try them out and test them.
+
+Of course, you would also need to benchmark your code and verify it
+does not impact performance when safe delegation is off, and
+impacts it only modestly when it is on." All necessary changes can
+be made at compile time, so there should be no performance impact
+when filling the same TO multiple times.
+
+
+#. Only give untrusted developers access to the .tmpl files.
+ (Verifying what this means. Why can't trusted developers access
+ them?)
+
+#. Disable the {#attr} directive and maybe the {#set} directive.
+
+#. Use Cheetah's directive validation hooks to disallow references
+ to {self}, etc (e.g. {#if $steal(self.thePrivateVar)} )
+
+#. Implement a validator for the $placeholders and use it to
+ disallow '\_\_' in $placeholders so that tricks like
+ {$obj.\_\_class\_\_.\_\_dict\_\_} are not possible.
+
+
+
diff --git a/www/dev_guide/template.rst b/www/dev_guide/template.rst
new file mode 100644
index 0000000..8dd23d9
--- /dev/null
+++ b/www/dev_guide/template.rst
@@ -0,0 +1,11 @@
+Template
+========
+
+(template)
+
+This chapter will mainly walk through the {Cheetah.Template}
+constructor and not at what point the template is compiled.
+
+(Also need to look at Transaction,py and Servlet.py.)
+
+
diff --git a/www/documentation.rst b/www/documentation.rst
index fd7c086..af5cc44 100644
--- a/www/documentation.rst
+++ b/www/documentation.rst
@@ -1,8 +1,6 @@
-User Documentation
+Cheetah Recipes
==================
-Simple Recipes
---------------
.. toctree::
:maxdepth: 2
diff --git a/www/download.rst b/www/download.rst
index 36009c1..fd4a9fb 100644
--- a/www/download.rst
+++ b/www/download.rst
@@ -1,13 +1,13 @@
Downloading Cheetah
===================
-The most recent stable release of Cheetah **v2.2.1** which was released on
-June 1st, 2009.
+The most recent stable release of Cheetah **v2.4.0** which was released on
+Oct 24th, 2009.
-Download v2.2.1
+Download v2.4.0
^^^^^^^^^^^^^^^
-* `v2.2.1 tarball <http://github.com/cheetahtemplate/cheetah/tarball/v2.2.1>`_
-* `v2.2.1 zip <http://github.com/cheetahtemplate/cheetah/zipball/v2.2.1>`_
+* `v2.4.0 tarball <http://github.com/cheetahtemplate/cheetah/tarball/v2.4.0>`_
+* `v2.4.0 zip <http://github.com/cheetahtemplate/cheetah/zipball/v2.4.0>`_
You can keep up to date with release candidates or other downloads of Cheetah by
visiting the `cheetahtemplate GitHub page <http://github.com/cheetahtemplate/cheetah/downloads>`_
diff --git a/www/index.rst b/www/index.rst
index fe3fa98..6059d6b 100644
--- a/www/index.rst
+++ b/www/index.rst
@@ -27,12 +27,14 @@ Contents
^^^^^^^^^
.. toctree::
- :maxdepth: 1
+ :maxdepth: 2
developers.rst
download.rst
- roadmap.rst
+ users_guide/index.rst
documentation.rst
+ roadmap.rst
+ dev_guide/index.rst
chep.rst
diff --git a/www/roadmap.rst b/www/roadmap.rst
index 48fe964..2251471 100644
--- a/www/roadmap.rst
+++ b/www/roadmap.rst
@@ -1,27 +1,18 @@
Cheetah Roadmap
===============
-Cheetah v2.2
-------------
-The first release in the v2.2 series (*v2.2.0*) introduced an overhaul of
-Cheetah's string handling to convert everything internally to use `unicode()`
-objects instead of encoded string buffers.
-
-The subsequent releases in the v2.2 series are planned to have a number of
-important upgrades to Cheetah's infrastructure:
-
-* Built-in Django support
-* Cleaner code generation
-* Finish and document #defmacro support
-
Cheetah v2.3
-------------
-*still in planning*
+^^^^^^^^^^^^^
+The Cheetah v2.3 is maintenance only for Python 2.3 support
-The third major series in the Cheetah 2 line of releases will have one focal point, **performance**
+Cheetah v2.4
+^^^^^^^^^^^^^
+Primary development and improvements towards Cheetah will go into
+Cheetah v2.4 which will support Python v2.4 to v2.6.
Cheetah v3.0
-------------
+^^^^^^^^^^^^^
*still in planning*
-Cheetah 3000 will be the seminal milestone for running Cheetah on Python 3.0
+Cheetah v3.0 is for support for Python 3.xx
+
diff --git a/www/users_guide/comments.rst b/www/users_guide/comments.rst
new file mode 100644
index 0000000..296719b
--- /dev/null
+++ b/www/users_guide/comments.rst
@@ -0,0 +1,101 @@
+Comments
+========
+
+(comments)
+
+Comments are used to mark notes, explanations, and decorative text
+that should not appear in the output. Cheetah maintains the
+comments in the Python module it generates from the Cheetah source
+code. There are two forms of the comment directive: single-line and
+multi-line.
+
+All text in a template definition that lies between two hash
+characters ({##}) and the end of the line is treated as a
+single-line comment and will not show up in the output, unless the
+two hash characters are escaped with a backslash.
+
+::
+
+ ##============================= this is a decorative comment-bar
+ $var ## this is an end-of-line comment
+ ##=============================
+
+Any text between {#\*} and {\*#} will be treated as a multi-line
+comment.
+
+::
+
+ #*
+ Here is some multiline
+ comment text
+ *#
+
+If you put blank lines around method definitions or loops to
+separate them, be aware that the blank lines will be output as is.
+To avoid this, make sure the blank lines are enclosed in a comment.
+Since you normally have a comment before the next method definition
+(right?), you can just extend that comment to include the blank
+lines after the previous method definition, like so:
+
+::
+
+ #def method1
+ ... lines ...
+ #end def
+ #*
+
+
+ Description of method2.
+ $arg1, string, a phrase.
+ *#
+ #def method2($arg1)
+ ... lines ...
+ #end def
+
+Docstring Comments
+------------------
+
+(comments.docstring)
+
+Python modules, classes, and methods can be documented with inline
+'documentation strings' (aka 'docstrings'). Docstrings, unlike
+comments, are accesible at run-time. Thus, they provide a useful
+hook for interactive help utilities.
+
+Cheetah comments can be transformed into doctrings by adding one of
+the following prefixes:
+
+::
+
+ ##doc: This text will be added to the method docstring
+ #*doc: If your template file is MyTemplate.tmpl, running "cheetah compile"
+ on it will produce MyTemplate.py, with a class MyTemplate in it,
+ containing a method .respond(). This text will be in the .respond()
+ method's docstring. *#
+
+ ##doc-method: This text will also be added to .respond()'s docstring
+ #*doc-method: This text will also be added to .respond()'s docstring *#
+
+ ##doc-class: This text will be added to the MyTemplate class docstring
+ #*doc-class: This text will be added to the MyTemplate class docstring *#
+
+ ##doc-module: This text will be added to the module docstring MyTemplate.py
+ #*doc-module: This text will be added to the module docstring MyTemplate.py*#
+
+Header Comments
+---------------
+
+(comments.headers) Cheetah comments can also be transformed into
+module header comments using the following syntax:
+
+::
+
+ ##header: This text will be added to the module header comment
+ #*header: This text will be added to the module header comment *#
+
+Note the difference between {##doc-module: } and {header: }:
+"cheetah-compile" puts {##doc-module: } text inside the module
+docstring. {header: } makes the text go { above} the docstring, as
+a set of #-prefixed comment lines.
+
+
diff --git a/www/users_guide/comparisons.rst b/www/users_guide/comparisons.rst
new file mode 100644
index 0000000..b6adbba
--- /dev/null
+++ b/www/users_guide/comparisons.rst
@@ -0,0 +1,515 @@
+Cheetah vs. Other Template Engines
+==================================
+
+(comparisons)
+
+This appendix compares Cheetah with various other template/emdedded
+scripting languages and Internet development frameworks. As Cheetah
+is similar to Velocity at a superficial level, you may also wish to
+read comparisons between Velocity and other languages at
+http://jakarta.apache.org/velocity/ymtd/ymtd.html.
+
+Which features are unique to Cheetah
+------------------------------------
+
+(comparisons.unique)
+
+
+- The { block framework} (section inheritanceEtc.block)
+
+- Cheetah's powerful yet simple { caching framework} (section
+ output.caching)
+
+- Cheetah's { Unified Dotted Notation} and { autocalling}
+ (sections language.namemapper.dict and
+ language.namemapper.autocalling)
+
+- Cheetah's searchList (section language.searchList) information.
+
+- Cheetah's {#raw} directive (section output.raw)
+
+- Cheetah's {#slurp} directive (section output.slurp)
+
+- Cheetah's tight integration with Webware for Python (section
+ webware)
+
+- Cheetah's { SkeletonPage framework} (section
+ libraries.templates.skeletonPage)
+
+- Cheetah's ability to mix PSP-style code with Cheetah Language
+ syntax (section tips.PSP) Because of Cheetah's design and Python's
+ flexibility it is relatively easy to extend Cheetah's syntax with
+ syntax elements from almost any other template or embedded
+ scripting language.
+
+
+Cheetah vs. Velocity
+--------------------
+
+(comparisons.velocity)
+
+For a basic introduction to Velocity, visit
+http://jakarta.apache.org/velocity.
+
+Velocity is a Java template engine. It's older than Cheetah, has a
+larger user base, and has better examples and docs at the moment.
+Cheetah, however, has a number of advantages over Velocity:
+
+
+- Cheetah is written in Python. Thus, it's easier to use and
+ extend.
+
+- Cheetah's syntax is closer to Python's syntax than Velocity's is
+ to Java's.
+
+- Cheetah has a powerful caching mechanism. Velocity has no
+ equivalent.
+
+- It's far easier to add data/objects into the namespace where
+ $placeholder values are extracted from in Cheetah. Velocity calls
+ this namespace a 'context'. Contexts are dictionaries/hashtables.
+ You can put anything you want into a context, BUT you have to use
+ the .put() method to populate the context; e.g.,
+
+ ::
+
+ VelocityContext context1 = new VelocityContext();
+ context1.put("name","Velocity");
+ context1.put("project", "Jakarta");
+ context1.put("duplicate", "I am in context1");
+
+ Cheetah takes a different approach. Rather than require you to
+ manually populate the 'namespace' like Velocity, Cheetah will
+ accept any existing Python object or dictionary AS the 'namespace'.
+ Furthermore, Cheetah allows you to specify a list namespaces that
+ will be searched in sequence to find a varname-to-value mapping.
+ This searchList can be extended at run-time.
+
+ If you add a 'foo' object to the searchList and the 'foo' has an
+ attribute called 'bar', you can simply type {$bar} in the template.
+ If the second item in the searchList is dictionary 'foofoo'
+ containing {{'spam':1234, 'parrot':666}}, Cheetah will first look
+ in the 'foo' object for a 'spam' attribute. Not finding it, Cheetah
+ will then go to 'foofoo' (the second element in the searchList) and
+ look among its dictionary keys for 'spam'. Finding it, Cheetah will
+ select {foofoo['spam']} as {$spam}'s value.
+
+- In Cheetah, the tokens that are used to signal the start of
+ $placeholders and #directives are configurable. You can set them to
+ any character sequences, not just $ and #.
+
+
+Cheetah vs. WebMacro
+--------------------
+
+(comparisons.webmacro)
+
+For a basic introduction to WebMacro, visit http://webmacro.org.
+
+The points discussed in section comparisons.velocity also apply to
+the comparison between Cheetah and WebMacro. For further
+differences please refer to
+http://jakarta.apache.org/velocity/differences.html.
+
+Cheetah vs. Zope's DTML
+-----------------------
+
+(comparisons.dtml)
+
+For a basic introduction to DTML, visit
+http://www.zope.org/Members/michel/ZB/DTML.dtml.
+
+
+- Cheetah is faster than DTML.
+
+- Cheetah does not use HTML-style tags; DTML does. Thus, Cheetah
+ tags are visible in rendered HTML output if something goes wrong.
+
+- DTML can only be used with ZOPE for web development; Cheetah can
+ be used as a standalone tool for any purpose.
+
+- Cheetah's documentation is more complete than DTML's.
+
+- Cheetah's learning curve is shorter than DTML's.
+
+- DTML has no equivalent of Cheetah's blocks, caching framework,
+ unified dotted notation, and {#raw} directive.
+
+
+Here are some examples of syntax differences between DTML and
+Cheetah:
+
+::
+
+ <ul>
+ <dtml-in frogQuery>
+ <li><dtml-var animal_name></li>
+ </dtml-in>
+ </ul>
+
+::
+
+ <ul>
+ #for $animal_name in $frogQuery
+ <li>$animal_name</li>
+ #end for
+ </ul>
+
+::
+
+ <dtml-if expr="monkeys > monkey_limit">
+ <p>There are too many monkeys!</p>
+ <dtml-elif expr="monkeys < minimum_monkeys">
+ <p>There aren't enough monkeys!</p>
+ <dtml-else>
+ <p>There are just enough monkeys.</p>
+ </dtml-if>
+
+::
+
+ #if $monkeys > $monkey_limit
+ <p>There are too many monkeys!</p>
+ #else if $monkeys < $minimum_monkeys
+ <p>There aren't enough monkeys!</p>
+ #else
+ <p>There are just enough monkeys.</p>
+ #end if
+
+::
+
+ <table>
+ <dtml-in expr="objectValues('File')">
+ <dtml-if sequence-even>
+ <tr bgcolor="grey">
+ <dtml-else>
+ <tr>
+ </dtml-if>
+ <td>
+ <a href="&dtml-absolute_url;"><dtml-var title_or_id></a>
+ </td></tr>
+ </dtml-in>
+ </table>
+
+::
+
+ <table>
+ #set $evenRow = 0
+ #for $file in $files('File')
+ #if $evenRow
+ <tr bgcolor="grey">
+ #set $evenRow = 0
+ #else
+ <tr>
+ #set $evenRow = 1
+ #end if
+ <td>
+ <a href="$file.absolute_url">$file.title_or_id</a>
+ </td></tr>
+ #end for
+ </table>
+
+The last example changed the name of {$objectValues} to {$files}
+because that's what a Cheetah developer would write. The developer
+would be responsible for ensuring {$files} returned a list (or
+tuple) of objects (or dictionaries) containing the attributes (or
+methods or dictionary keys) 'absolute\_url' and 'title\_or\_id'.
+All these names ('objectValues', 'absolute\_url' and
+'title\_or\_id') are standard parts of Zope, but in Cheetah the
+developer is in charge of writing them and giving them a reasonable
+behaviour.
+
+Some of DTML's features are being ported to Cheetah, such as
+{Cheetah.Tools.MondoReport}, which is based on the {<dtml-in>} tag.
+We are also planning an output filter as flexible as the
+{<dtml-var>} formatting options. However, neither of these are
+complete yet.
+
+Cheetah vs. Zope Page Templates
+-------------------------------
+
+(comparisons.zpt)
+
+For a basic introduction to Zope Page Templates, please visit
+http://www.zope.org/Documentation/Articles/ZPT2.
+
+Cheetah vs. PHP's Smarty templates
+----------------------------------
+
+(comparisons.smarty)
+
+PHP (http://www.php.net/) is one of the few scripting languages
+expressly designed for web servlets. However, it's also a
+full-fledged programming language with libraries similar to
+Python's and Perl's. The syntax and functions are like a cross
+between Perl and C plus some original ideas (e.g.; a single array
+type serves as both a list and a dictionary, ``$arr[]="value";``
+appends to an array).
+
+Smarty (http://smarty.php.net/) is an advanced template engine for
+PHP. ({ Note:} this comparision is based on Smarty's on-line
+documentation. The author has not used Smarty. Please send
+corrections or ommissions to the Cheetah mailing list.) Like
+Cheetah, Smarty:
+
+
+- compiles to the target programming language (PHP).
+
+- has configurable delimeters.
+
+- passes if-blocks directly to PHP, so you can use any PHP
+ expression in them.
+
+- allows you to embed PHP code in a template.
+
+- has a caching framework (although it works quite differently).
+
+- can read the template definition from any arbitrary source.
+
+
+Features Smarty has that Cheetah lacks:
+
+
+- Preprocessors, postprocessors and output filters. You can
+ emulate a preprocessor in Cheetah by running your template
+ definition through a filter program or function before Cheetah sees
+ it. To emulate a postprocessor, run a .py template module through a
+ filter program/function. To emulate a Smarty output filter, run the
+ template output through a filter program/function. If you want to
+ use "cheetah compile" or "cheetah fill" in a pipeline, use {-} as
+ the input file name and {-stdout} to send the result to standard
+ output. Note that Cheetah uses the term "output filter" differently
+ than Smarty: Cheetah output filters ({#filter}) operate on
+ placeholders, while Smarty output filters operate on the entire
+ template output. There has been a proposed {#sed} directive that
+ would operate on the entire output line by line, but it has not
+ been implemented.
+
+- Variable modifiers. In some cases, Python has equivalent string
+ methods ({.strip}, {.capitalize}, {.replace(SEARCH, REPL)}), but in
+ other cases you must wrap the result in a function call or write a
+ custom output filter ({#filter}).
+
+- Certain web-specific functions, which can be emulated with
+ third-party functions.
+
+- The ability to "plug in" new directives in a modular way.
+ Cheetah directives are tightly bound to the compiler. However,
+ third-party { functions} can be freely imported and called from
+ placeholders, and { methods} can be mixed in via {#extends}. Part
+ of this is because Cheetah distinguishes between functions and
+ directives, while Smarty treats them all as "functions". Cheetah's
+ design does not allow functions to have flow control effect outside
+ the function (e.g., {#if} and {#for}, which operate on template
+ body lines), so directives like these cannot be encoded as
+ functions.
+
+- Configuration variables read from an .ini-style file. The
+ {Cheetah.SettingsManager} module can parse such a file, but you'd
+ have to invoke it manually. (See the docstrings in the module for
+ details.) In Smarty, this feature is used for multilingual
+ applications. In Cheetah, the developers maintain that everybody
+ has their own preferred way to do this (such as using Python's
+ {gettext} module), and it's not worth blessing one particular
+ strategy in Cheetah since it's easy enough to integrate third-party
+ code around the template, or to add the resulting values to the
+ searchList.
+
+
+Features Cheetah has that Smarty lacks:
+
+
+- Saving the compilation result in a Python (PHP) module for quick
+ reading later.
+
+- Caching individual placeholders or portions of a template.
+ Smarty caches only the entire template output as a unit.
+
+
+Comparisions of various Smarty constructs:
+
+::
+
+ {assign var="name" value="Bob"} (#set has better syntax in the author's opinion)
+ counter (looks like equivalent to #for)
+ eval (same as #include with variable)
+ fetch: insert file content into output (#include raw)
+ fetch: insert URL content into output (no euqivalent, user can write
+ function calling urllib, call as $fetchURL('URL') )
+ fetch: read file into variable (no equivalent, user can write function
+ based on the 'open/file' builtin, or on .getFileContents() in
+ Template.)
+ fetch: read URL content into variable (no equivalent, use above
+ function and call as: #set $var = $fetchURL('URL')
+ html_options: output an HTML option list (no equivalent, user can
+ write custom function. Maybe FunFormKit can help.)
+ html_select_date: output three dropdown controls to specify a date
+ (no equivalent, user can write custom function)
+ html_select_time: output four dropdown controls to specify a time
+ (no equvalent, user can write custom function)
+ math: eval calculation and output result (same as #echo)
+ math: eval calculation and assign to variable (same as #set)
+ popup_init: library for popup windows (no equivalent, user can write
+ custom method outputting Javascript)
+
+
+ Other commands:
+ capture (no equivalent, collects output into variable. A Python
+ program would create a StringIO instance, set sys.stdout to
+ it temporarily, print the output, set sys.stdout back, then use
+ .getvalue() to get the result.)
+ config_load (roughly analagous to #settings, which was removed
+ from Cheetah. Use Cheetah.SettingsManager manually or write
+ a custom function.)
+ include (same as #include, but can include into variable.
+ Variables are apparently shared between parent and child.)
+ include_php: include a PHP script (e.g., functions)
+ (use #extends or #import instead)
+ insert (same as #include not in a #cache region)
+ {ldelim}{rdelim} (escape literal $ and # with a backslash,
+ use #compiler-settings to change the delimeters)
+ literal (#raw)
+ php (``<% %>'' tags)
+ section (#for $i in $range(...) )
+ foreach (#for)
+ strip (like the #sed tag which was never implemented. Strips
+ leading/trailing whitespace from lines, joins several lines
+ together.)
+
+
+ Variable modifiers:
+ capitalize ( $STRING.capitalize() )
+ count_characters ( $len(STRING) )
+ count_paragraphs/sentances/words (no equivalent, user can write function)
+ date_format (use 'time' module or download Egenix's mx.DateTime)
+ default ($getVar('varName', 'default value') )
+ escape: html encode ($cgi.escape(VALUE) )
+ escape: url encode ($urllib.quote_plus(VALUE) )
+ escape: hex encode (no equivalent? user can write function)
+ escape: hex entity encode (no equivalent? user can write function)
+ indent: indent all lines of a var's output (may be part of future
+ #indent directive)
+ lower ($STRING.lower() )
+ regex_replace ('re' module)
+ replace ($STRING.replace(OLD, NEW, MAXSPLIT) )
+ spacify (#echo "SEPARATOR".join(SEQUENCE) )
+ string_format (#echo "%.2f" % FLOAT , etc.)
+ strip_tags (no equivalent, user can write function to strip HTML tags,
+ or customize the WebSafe filter)
+ truncate (no equivalent, user can write function)
+ upper ($STRING.upper() )
+ wordwrap ('writer' module, or a new module coming in Python 2.3)
+
+Some of these modifiers could be added to the super output filter
+we want to write someday.
+
+Cheetah vs. PHPLib's Template class
+-----------------------------------
+
+(comparisons.php)
+
+PHPLib ((http://phplib.netuse.de/) is a collection of classes for
+various web objects (authentication, shopping cart, sessions, etc),
+but what we're interested in is the {Template} object. It's much
+more primitive than Smarty, and was based on an old Perl template
+class. In fact, one of the precursors to Cheetah was based on it
+too. Differences from Cheetah:
+
+
+- Templates consist of text with {{placeholders}} in braces.
+
+- Instead of a searchList, there is one flat namespace. Every
+ variable must be assigned via the {set\_var} method. However, you
+ can pass this method an array (dictionary) of several variables at
+ once.
+
+- You cannot embed lookups or calculations into the template.
+ Every placeholder must be an exact variable name.
+
+- There are no directives. You must do all display logic (if, for,
+ etc) in the calling routine.
+
+- There is, however, a "block" construct. A block is a portion of
+ text between the comment markers {<!- BEGIN blockName -> ... <!-
+ END blockName>}. The {set\_block} method extracts this text into a
+ namespace variable and puts a placeholder referring to it in the
+ template. This has a few parallels with Cheetah's {#block}
+ directive but is overall quite different.
+
+- To do the equivalent of {#if}, extract the block. Then if true,
+ do nothing. If false, assign the empty string to the namespace
+ variable.
+
+- To do the equivalent of {#for}, extract the block. Set any
+ namespace variables needed inside the loop. To parse one iteration,
+ use the {parse} method to fill the block variable (a mini-template)
+ into another namespace variable, appending to it. Refresh the
+ namespace variables needed inside the loop and parse again; repeat
+ for each iteration. You'll end up with a mini-result that will be
+ plugged into the main template's placeholder.
+
+- To read a template definition from a file, use the {set\_file}
+ method. This places the file's content in a namespace variable. To
+ read a template definition from a string, assign it to a namespace
+ variable.
+
+- Thus, for complicated templates, you are doing a lot of
+ recursive block filling and file reading and parsing mini-templates
+ all into one flat namespace as you finally build up values for the
+ main template. In Cheetah, all this display logic can be embedded
+ into the template using directives, calling out to Python methods
+ for the more complicated tasks.
+
+- Although you can nest blocks in the template, it becomes tedious
+ and arguably hard to read, because all blocks have identical
+ syntax. Unless you choose your block names carefully and put
+ comments around them, it's hard to tell which blocks are if-blocks
+ and which are for-blocks, or what their nesting order is.
+
+- PHPLib templates do not have caching, output filters, etc.
+
+
+Cheetah vs. PSP, PHP, ASP, JSP, Embperl, etc.
+---------------------------------------------
+
+(comparisons.pspEtc)
+
+Webware's PSP Component
+ - http://webware.sourceforge.net/Webware/PSP/Docs/
+
+Tomcat JSP Information
+ - http://jakarta.apache.org/tomcat/index.html
+
+ASP Information at ASP101
+ - http://www.asp101.com/
+
+Embperl
+ - http://perl.apache.org/embperl/
+
+
+Here's a basic Cheetah example:
+
+::
+
+ <TABLE>
+ #for $client in $service.clients
+ <TR>
+ <TD>$client.surname, $client.firstname</TD>
+ <TD><A HREF="mailto:$client.email" >$client.email</A></TD>
+ </TR>
+ #end for
+ </TABLE>
+
+Compare this with PSP:
+
+::
+
+ <TABLE>
+ <% for client in service.clients(): %>
+ <TR>
+ <TD><%=client.surname()%>, <%=client.firstname()%></TD>
+ <TD><A HREF="mailto:<%=client.email()%>"><%=client.email()%></A></TD>
+ </TR>
+ <%end%>
+ </TABLE>
+
+
diff --git a/www/users_guide/editors.rst b/www/users_guide/editors.rst
new file mode 100644
index 0000000..35bf0a5
--- /dev/null
+++ b/www/users_guide/editors.rst
@@ -0,0 +1,37 @@
+Visual Editors
+==============
+
+(visualEditors)
+
+This chapter is about maintaining Cheetah templates with visual
+editors, and the tradeoffs between making it friendly to both text
+editors and visual editors.
+
+Cheetah's main developers do not use visual editors. Tavis uses
+{emacs}; Mike uses {vim}. So our first priority is to make
+templates easy to maintain in text editors. In particular, we don't
+want to add features like Zope Page Template's
+placeholder-value-with-mock-text-for-visual-editors-all-in-an-XML-tag.
+The syntax is so verbose it makes for a whole lotta typing just to
+insert a simple placeholder, for the benefit of editors we never
+use. However, as users identify features which would help their
+visual editing without making it harder to maintain templates in a
+text editor, we're all for it.
+
+As it said in the introduction, Cheetah purposely does not use
+HTML/XML tags for $placeholders or #directives. That way, when you
+preview the template in an editor that interprets HTML tags, you'll
+still see the placeholder and directive source definitions, which
+provides some "mock text" even if it's not the size the final
+values will be, and allows you to use your imagination to translate
+how the directive output will look visually in the final.
+
+If your editor has syntax highlighting, turn it on. That makes a
+big difference in terms of making the template easier to edit.
+Since no "Cheetah mode" has been invented yet, set your
+highlighting to Perl mode, and at least the directives/placeholders
+will show up in different colors, although the editor won't
+reliably guess where the directive/placeholder ends and normal text
+begins.
+
+
diff --git a/www/users_guide/errorHandling.rst b/www/users_guide/errorHandling.rst
new file mode 100644
index 0000000..2f933e9
--- /dev/null
+++ b/www/users_guide/errorHandling.rst
@@ -0,0 +1,144 @@
+Error Handling
+==============
+
+(errorHandling)
+
+There are two ways to handle runtime errors (exceptions) in
+Cheetah. The first is with the Cheetah directives that mirror
+Python's structured exception handling statements. The second is
+with Cheetah's {ErrorCatcher} framework. These are described
+below.
+
+#try ... #except ... #end try, #finally, and #assert
+----------------------------------------------------
+
+(errorHandling.directives)
+
+Cheetah's exception-handling directives are exact mirrors Python's
+exception-handling statements. See Python's documentation for
+details. The following Cheetah code demonstrates their use:
+
+::
+
+ #try
+ $mightFail()
+ #except
+ It failed
+ #end try
+
+ #try
+ #assert $x == $y
+ #except AssertionError
+ They're not the same!
+ #end try
+
+ #try
+ #raise ValueError
+ #except ValueError
+ #pass
+ #end try
+
+
+ #try
+ $mightFail()
+ #except ValueError
+ Hey, it raised a ValueError!
+ #except NameMapper.NotFound
+ Hey, it raised a NameMapper.NotFound!
+ #else
+ It didn't raise anything!
+ #end try
+
+ #try
+ $mightFail()
+ #finally
+ $cleanup()
+ #end try
+
+Like Python, {#except} and {#finally} cannot appear in the same
+try-block, but can appear in nested try-blocks.
+
+#errorCatcher and ErrorCatcher objects
+--------------------------------------
+
+(errorHandling.errorCatcher)
+
+Syntax:
+
+::
+
+ #errorCatcher CLASS
+ #errorCatcher $PLACEHOLDER_TO_AN_ERROR_CATCHER_INSTANCE
+
+{ErrorCatcher} is a debugging tool that catches exceptions that
+occur inside {$placeholder} tags and provides a customizable
+warning to the developer. Normally, the first missing namespace
+value raises a {NameMapper.NotFound} error and halts the filling of
+the template. This requires the developer to resolve the exceptions
+in order without seeing the subsequent output. When an
+{ErrorCatcher} is enabled, the developer can see all the exceptions
+at once as well as the template output around them.
+
+The {Cheetah.ErrorCatchers} module defines the base class for
+ErrorCatchers:
+
+::
+
+ class ErrorCatcher:
+ _exceptionsToCatch = (NameMapper.NotFound,)
+
+ def __init__(self, templateObj):
+ pass
+
+ def exceptions(self):
+ return self._exceptionsToCatch
+
+ def warn(self, exc_val, code, rawCode, lineCol):
+ return rawCode
+
+This ErrorCatcher catches {NameMapper.NotFound} exceptions and
+leaves the offending placeholder visible in its raw form in the
+template output. If the following template is executed:
+
+::
+
+ #errorCatcher Echo
+ #set $iExist = 'Here I am!'
+ Here's a good placeholder: $iExist
+ Here's bad placeholder: $iDontExist
+
+the output will be:
+
+::
+
+ Here's a good placeholder: Here I am!
+ Here's bad placeholder: $iDontExist
+
+The base class shown above is also accessible under the alias
+{Cheetah.ErrorCatchers.Echo}. {Cheetah.ErrorCatchers} also provides
+a number of specialized subclasses that warn about exceptions in
+different ways. {Cheetah.ErrorCatchers.BigEcho} will output
+
+::
+
+ Here's a good placeholder: Here I am!
+ Here's bad placeholder: ===============&lt;$iDontExist could not be found&gt;===============
+
+ErrorCatcher has a significant performance impact and is turned off
+by default. It can also be turned on with the {Template} class'
+{'errorCatcher'} keyword argument. The value of this argument
+should either be a string specifying which of the classes in
+{Cheetah.ErrorCatchers} to use, or a class that subclasses
+{Cheetah.ErrorCatchers.ErrorCatcher}. The {#errorCatcher} directive
+can also be used to change the errorCatcher part way through a
+template.
+
+{Cheetah.ErrorCatchers.ListErrors} will produce the same ouput as
+{Echo} while maintaining a list of the errors that can be retrieved
+later. To retrieve the list, use the {Template} class'
+{'errorCatcher'} method to retrieve the errorCatcher and then call
+its {listErrors} method.
+
+ErrorCatcher doesn't catch exceptions raised inside directives.
+
+
diff --git a/www/users_guide/examples.rst b/www/users_guide/examples.rst
new file mode 100644
index 0000000..7304cd1
--- /dev/null
+++ b/www/users_guide/examples.rst
@@ -0,0 +1,27 @@
+Examples
+========
+
+(examples)
+
+The Cheetah distribution comes with an 'examples' directory. Browse
+the files in this directory and its subdirectories for examples of
+how Cheetah can be used.
+
+Syntax examples
+---------------
+
+The {Cheetah.Tests} module contains a large number of test cases
+that can double as examples of how the Cheetah Language works. To
+view these cases go to the base directory of your Cheetah
+distribution and open the file {Cheetah/Tests/SyntaxAndOutput.py}
+in a text editor.
+
+Webware Examples
+----------------
+
+For examples of Cheetah in use with Webware visit the Cheetah and
+Webware wikis or use google. We used to have more examples in the
+cheetah source tarball, but they were out of date and confused
+people.
+
+
diff --git a/www/users_guide/flowControl.rst b/www/users_guide/flowControl.rst
new file mode 100644
index 0000000..cbd4926
--- /dev/null
+++ b/www/users_guide/flowControl.rst
@@ -0,0 +1,436 @@
+Flow Control
+============
+
+(flowControl)
+
+#for ... #end for
+-----------------
+
+(flowControl.for)
+
+Syntax:
+
+::
+
+ #for $var in EXPR
+ #end for
+
+The {#for} directive iterates through a sequence. The syntax is the
+same as Python, but remember the {$} before variables.
+
+Here's a simple client listing:
+
+::
+
+ <TABLE>
+ #for $client in $service.clients
+ <TR>
+ <TD>$client.surname, $client.firstname</TD>
+ <TD><A HREF="mailto:$client.email" >$client.email</A></TD>
+ </TR>
+ #end for
+ </TABLE>
+
+Here's how to loop through the keys and values of a dictionary:
+
+::
+
+ <PRE>
+ #for $key, $value in $dict.items()
+ $key: $value
+ #end for
+ </PRE>
+
+Here's how to create list of numbers separated by hyphens. This
+"#end for" tag shares the last line to avoid introducing a newline
+character after each hyphen.
+
+::
+
+ #for $i in range(15)
+ $i - #end for
+
+If the location of the {#end for} offends your sense of
+indentational propriety, you can do this instead:
+
+::
+
+ #for $i in $range(15)
+ $i - #slurp
+ #end for
+
+The previous two examples will put an extra hyphen after last
+number. Here's how to get around that problem, using the {#set}
+directive, which will be dealt with in more detail below.
+
+::
+
+ #set $sep = ''
+ #for $name in $names
+ $sep$name
+ #set $sep = ', '
+ #end for
+
+Although to just put a separator between strings, you don't need a
+for loop:
+
+::
+
+ #echo ', '.join($names)
+
+#repeat ... #end repeat
+-----------------------
+
+(flowControl.repeat)
+
+Syntax:
+
+::
+
+ #repeat EXPR
+ #end repeat
+
+Do something a certain number of times. The argument may be any
+numeric expression. If it's zero or negative, the loop will execute
+zero times.
+
+::
+
+ #repeat $times + 3
+ She loves me, she loves me not.
+ #repeat
+ She loves me.
+
+Inside the loop, there's no way to tell which iteration you're on.
+If you need a counter variable, use {#for} instead with Python's
+{range} function. Since Python's ranges are base 0 by default,
+there are two ways to start counting at 1. Say we want to count
+from 1 to 5, and that {$count} is 5.
+
+::
+
+ #for $i in $range($count)
+ #set $step = $i + 1
+ $step. Counting from 1 to $count.
+ #end for
+
+
+ #for $i in $range(1, $count + 1)
+ $i. Counting from 1 to $count.
+ #end for
+
+A previous implementation used a local variable {$i} as the repeat
+counter. However, this prevented instances of {#repeat} from being
+nested. The current implementation does not have this problem as it
+uses a new local variable for every instance of {#repeat}.
+
+#while ... #end while
+---------------------
+
+(flowControl.while)
+
+Syntax:
+
+::
+
+ #while EXPR
+ #end while
+
+{#while} is the same as Python's {while} statement. It may be
+followed by any boolean expression:
+
+::
+
+ #while $someCondition('arg1', $arg2)
+ The condition is true.
+ #end while
+
+Be careful not to create an infinite loop. {#while 1} will loop
+until the computer runs out of memory.
+
+#if ... #else if ... #else ... #end if
+--------------------------------------
+
+(flowControl.if)
+
+Syntax:
+
+::
+
+ #if EXPR
+ #else if EXPR
+ #elif EXPR
+ #else
+ #end if
+
+The {#if} directive and its kin are used to display a portion of
+text conditionally. {#if} and {#else if} should be followed by a
+true/false expression, while {#else} should not. Any valid Python
+expression is allowed. As in Python, the expression is true unless
+it evaluates to 0, '', None, an empty list, or an empty dictionary.
+In deference to Python, {#elif} is accepted as a synonym for {#else
+if}.
+
+Here are some examples:
+
+::
+
+ #if $size >= 1500
+ It's big
+ #else if $size < 1500 and $size > 0
+ It's small
+ #else
+ It's not there
+ #end if
+
+::
+
+ #if $testItem($item)
+ The item $item.name is OK.
+ #end if
+
+Here's an example that combines an {#if} tag with a {#for} tag.
+
+::
+
+ #if $people
+ <table>
+ <tr>
+ <th>Name</th>
+ <th>Address</th>
+ <th>Phone</th>
+ </tr>
+ #for $p in $people
+ <tr>
+ <td>$p.name</td>
+ <td>$p.address</td>
+ <td>$p.phone</td>
+ </tr>
+ #end for
+ </table>
+ #else
+ <p> Sorry, the search did not find any people. </p>
+ #end if
+
+See section output.oneLineIf for the one-line {#if} directive,
+which is equivalent to Perl's and C's {?:} operator.
+
+#unless ... #end unless
+-----------------------
+
+(flowControl.unless)
+
+Syntax:
+
+::
+
+ #unless EXPR
+ #end unless
+
+{#unless} is the opposite of {#if}: the text is executed if the
+condition is { false}. Sometimes this is more convenient. {#unless
+EXPR} is equivalent to {#if not (EXPR)}.
+
+::
+
+ #unless $alive
+ This parrot is no more! He has ceased to be!
+ 'E's expired and gone to meet 'is maker! ...
+ THIS IS AN EX-PARROT!!
+ #end unless
+
+You cannot use {#else if} or {#else} inside an {#unless} construct.
+If you need those, use {#if} instead.
+
+#break and #continue
+--------------------
+
+(flowControl.break)
+
+Syntax:
+
+::
+
+ #break
+ #continue
+
+These directives are used as in Python. {#break} will exit a {#for}
+loop prematurely, while {#continue} will immediately jump to the
+next iteration in the {#for} loop.
+
+In this example the output list will not contain "10 -".
+
+::
+
+ #for $i in range(15)
+ #if $i == 10
+ #continue
+ #end if
+ $i - #slurp
+ #end for
+
+In this example the loop will exit if it finds a name that equals
+'Joe':
+
+::
+
+ #for $name in $names
+ #if $name == 'Joe'
+ #break
+ #end if
+ $name - #slurp
+ #end for
+
+#pass
+-----
+
+(flowControl.pass)
+
+Syntax:
+
+::
+
+ #pass
+
+The {#pass} directive is identical to Python {pass} statement: it
+does nothing. It can be used when a statement is required
+syntactically but the program requires no action.
+
+The following example does nothing if only $A is true
+
+::
+
+ #if $A and $B
+ do something
+ #elif $A
+ #pass
+ #elif $B
+ do something
+ #else
+ do something
+ #end if
+
+#stop
+-----
+
+(flowControl.stop)
+
+Syntax:
+
+::
+
+ #stop
+
+The {#stop} directive is used to stop processing of a template at a
+certain point. The output will show { only} what has been processed
+up to that point.
+
+When {#stop} is called inside an {#include} it skips the rest of
+the included code and continues on from after the {#include}
+directive. stop the processing of the included code. Likewise, when
+{#stop} is called inside a {#def} or {#block}, it stops only the
+{#def} or {#block}.
+
+::
+
+ A cat
+ #if 1
+ sat on a mat
+ #stop
+ watching a rat
+ #end if
+ in a flat.
+
+will print
+
+::
+
+ A cat
+ sat on a mat
+
+And
+
+::
+
+ A cat
+ #block action
+ sat on a mat
+ #stop
+ watching a rat
+ #end block
+ in a flat.
+
+will print
+
+::
+
+ A cat
+ sat on a mat
+ in a flat.
+
+#return
+-------
+
+(flowControl.return)
+
+Syntax:
+
+::
+
+ #return
+
+This is used as in Python. {#return} will exit the current method
+with a default return value of {None} or the value specified. It
+may be used only inside a {#def} or a {#block}.
+
+Note that {#return} is different from the {#stop} directive, which
+returns the sum of all text output from the method in which it is
+called. The following examples illustrate this point:
+
+::
+
+ 1
+ $test[1]
+ 3
+ #def test
+ 1.5
+ #if 1
+ #return '123'
+ #else
+ 99999
+ #end if
+ #end def
+
+will produce
+
+::
+
+ 1
+ 2
+ 3
+
+while
+
+::
+
+ 1
+ $test
+ 3
+ #def test
+ 1.5
+ #if 1
+ #stop
+ #else
+ 99999
+ #end if
+ #end def
+
+will produce
+
+::
+
+ 1
+ 1.5
+ 3
+
+
diff --git a/www/users_guide/gettingStarted.rst b/www/users_guide/gettingStarted.rst
new file mode 100644
index 0000000..fe0901b
--- /dev/null
+++ b/www/users_guide/gettingStarted.rst
@@ -0,0 +1,278 @@
+Getting Started
+===============
+
+(gettingStarted)
+
+Requirements
+------------
+
+(gettingStarted.requirements)
+
+Cheetah requires Python release 2.0 or greater, and has been tested
+with Python 2.0, 2.1 and 2.2. It is known to run on Linux, Windows
+NT/98/XP, FreeBSD and Solaris, and should run anywhere Python
+runs.
+
+99% of Cheetah is written in Python. There is one small C module
+({\_namemapper.so}) for speed, but Cheetah automatically falls back
+to a Python equivalent ({NameMapper.py}) if the C module is not
+available.
+
+Installation
+------------
+
+(gettingStarted.install)
+
+To install Cheetah in your system-wide Python library:
+
+
+#. Login as a user with privileges to install system-wide Python
+ packages. On POSIX systems (AIX, Solaris, Linux, IRIX, etc.), the
+ command is normally 'su root'. On non-POSIX systems such as Windows
+ NT, login as an administrator.
+
+#. Run {python setup.py install} at the command prompt.
+
+#. The setup program will install the wrapper script { cheetah} to
+ wherever it usually puts Python binaries ("/usr/bin/", "bin/" in
+ the Python install directory, etc.)
+
+
+Cheetah's installation is managed by Python's Distribution
+Utilities ('distutils'). There are many options for customization.
+Type {"python setup.py help"} for more information.
+
+To install Cheetah in an alternate location - someplace outside
+Python's {site-packages/} directory, use one of these options:
+
+::
+
+ python setup.py install --home /home/tavis
+ python setup.py install --install-lib /home/tavis/lib/python
+
+Either way installs to /home/tavis/lib/python/Cheetah/ . Of course,
+/home/tavis/lib/python must be in your Python path in order for
+Python to find Cheetah.
+
+Files
+-----
+
+(gettingstarted.files)
+
+If you do the systemwide install, all Cheetah modules are installed
+in the { site-packages/Cheetah/} subdirectory of your standard
+library directory; e.g.,
+/opt/Python2.2/lib/python2.2/site-packages/Cheetah.
+
+Two commands are installed in Python's {bin/} directory or a system
+bin directory: {cheetah} (section gettingStarted.cheetah) and
+{cheetah-compile} (section howWorks.cheetah-compile).
+
+Uninstalling
+------------
+
+(gettingstarted.uninstalling)
+
+To uninstall Cheetah, merely delete the site-packages/Cheetah/
+directory. Then delete the "cheetah" and "cheetah-compile" commands
+from whichever bin/ directory they were put in.
+
+The 'cheetah' command
+---------------------
+
+(gettingStarted.cheetah)
+
+Cheetah comes with a utility {cheetah} that provides a command-line
+interface to various housekeeping tasks. The command's first
+argument is the name of the task. The following commands are
+currently supported:
+
+::
+
+ 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 : Run Cheetah's regression tests
+ cheetah version : Print Cheetah version number
+
+You only have to type the first letter of the command: {cheetah c}
+is the same as {cheetah compile}.
+
+The test suite is described in the next section. The {compile}
+command will be described in section howWorks.cheetah-compile, and
+the {fill} command in section howWorks.cheetah-fill.
+
+The depreciated {cheetah-compile} program does the same thing as
+{cheetah compile}.
+
+Testing your installation
+-------------------------
+
+(gettingStarted.test)
+
+After installing Cheetah, you can run its self-test routine to
+verify it's working properly on your system. Change directory to
+any directory you have write permission in (the tests write
+temporary files). Do not run the tests in the directory you
+installed Cheetah from, or you'll get unnecessary errors. Type the
+following at the command prompt:
+
+::
+
+ cheetah test
+
+The tests will run for about three minutes and print a
+success/failure message. If the tests pass, start Python in
+interactive mode and try the example in the next section.
+
+Certain test failures are insignificant:
+
+ Python 2.3 changed the string representation of booleans, and the
+ tests haven't yet been updated to reflect this.
+
+ Certain tests run "cheetah" as a subcommand. The failure may mean
+ the command wasn't found in your system path. (What happens if you
+ run "cheetah" on the command line?) The failure also happens on
+ some Windows systems for unknown reasons. This failure has never
+ been observed outside the test suite. Long term, we plan to rewrite
+ the tests to do a function call rather than a subcommand, which
+ will also make the tests run significantly faster.
+
+ The test tried to write a temporary module in the current directory
+ and {import} it. Reread the first paragraph in this section about
+ the current directory.
+
+ May be the same problem as SampleBaseClass; let us know if changing
+ the current directory doesn't work.
+
+
+If any other tests fail, please send a message to the e-mail list
+with a copy of the test output and the following details about your
+installation:
+
+
+#. your version of Cheetah
+
+#. your version of Python
+
+#. your operating system
+
+#. whether you have changed anything in the Cheetah installation
+
+
+Quickstart tutorial
+-------------------
+
+(gettingStarted.tutorial)
+
+This tutorial briefly introduces how to use Cheetah from the Python
+prompt. The following chapters will discuss other ways to use
+templates and more of Cheetah's features.
+
+The core of Cheetah is the {Template} class in the
+{Cheetah.Template} module. The following example shows how to use
+the {Template} class in an interactive Python session. {t} is the
+Template instance. Lines prefixed with {>>>} and {...} are user
+input. The remaining lines are Python output.
+
+::
+
+ >>> from Cheetah.Template import Template
+ >>> templateDef = """
+ ... <HTML>
+ ... <HEAD><TITLE>$title</TITLE></HEAD>
+ ... <BODY>
+ ... $contents
+ ... ## this is a single-line Cheetah comment and won't appear in the output
+ ... #* This is a multi-line comment and won't appear in the output
+ ... blah, blah, blah
+ ... *#
+ ... </BODY>
+ ... </HTML>"""
+ >>> nameSpace = {'title': 'Hello World Example', 'contents': 'Hello World!'}
+ >>> t = Template(templateDef, searchList=[nameSpace])
+ >>> print t
+
+ <HTML>
+ <HEAD><TITLE>Hello World Example</TITLE></HEAD>
+ <BODY>
+ Hello World!
+ </BODY>
+ </HTML>
+ >>> print t # print it as many times as you want
+ [ ... same output as above ... ]
+ >>> nameSpace['title'] = 'Example #2'
+ >>> nameSpace['contents'] = 'Hiya Planet Earth!'
+ >>> print t # Now with different plug-in values.
+ <HTML>
+ <HEAD><TITLE>Example #2</TITLE></HEAD>
+ <BODY>
+ Hiya Planet Earth!
+ </BODY>
+ </HTML>
+
+Since Cheetah is extremely flexible, you can achieve the same
+result this way:
+
+::
+
+ >>> t2 = Template(templateDef)
+ >>> t2.title = 'Hello World Example!'
+ >>> t2.contents = 'Hello World'
+ >>> print t2
+ [ ... same output as the first example above ... ]
+ >>> t2.title = 'Example #2'
+ >>> t2.contents = 'Hello World!'
+ >>> print t2
+ [ ... same as Example #2 above ... ]
+
+Or this way:
+
+::
+
+ >>> class Template3(Template):
+ >>> title = 'Hello World Example!'
+ >>> contents = 'Hello World!'
+ >>> t3 = Template3(templateDef)
+ >>> print t3
+ [ ... you get the picture ... ]
+
+The template definition can also come from a file instead of a
+string, as we will see in section howWorks.constructing.
+
+The above is all fine for short templates, but for long templates
+or for an application that depends on many templates in a
+hierarchy, it's easier to store the templates in separate \*.tmpl
+files and use the { cheetah compile} program to convert them into
+Python classes in their own modules. This will be covered in
+section howWorks.cheetah-compile.
+
+As an appetizer, we'll just briefly mention that you can store
+constant values { inside} the template definition, and they will be
+converted to attributes in the generated class. You can also create
+methods the same way. You can even use inheritance to arrange your
+templates in a hierarchy, with more specific templates overriding
+certain parts of more general templates (e.g., a "page" template
+overriding a sidebar in a "section" template).
+
+For the minimalists out there, here's a template definition,
+instantiation and filling all in one Python statement:
+
+::
+
+ >>> print Template("Templates are pretty useless without placeholders.")
+ Templates are pretty useless without placeholders.
+
+You use a precompiled template the same way, except you don't
+provide a template definition since it was already established:
+
+::
+
+ from MyPrecompiledTemplate import MyPrecompiledTemplate
+ t = MyPrecompiledTemplate()
+ t.name = "Fred Flintstone"
+ t.city = "Bedrock City"
+ print t
+
+
diff --git a/www/users_guide/glossary.rst b/www/users_guide/glossary.rst
new file mode 100644
index 0000000..62e27f9
--- /dev/null
+++ b/www/users_guide/glossary.rst
@@ -0,0 +1,99 @@
+Vocabulary
+==========
+
+(glossary) (vocabulary)
+
+{ Template} is an informal term meaning a template definition, a
+template instance or a template class. A { template definition} is
+what the human { template maintainer} writes: a string consisting
+of text, placeholders and directives. { Placeholders} are variables
+that will be looked up when the template is filled. { Directives}
+are commands to be executed when the template is filled, or
+instructions to the Cheetah compiler. The conventional suffix for a
+file containing a template definition is { .tmpl}.
+
+There are two things you can do with a template: compile it or fill
+it. { Filling} is the reason you have a template in the first
+place: to get a finished string out of it. Compiling is a necessary
+prerequisite: the { Cheetah compiler} takes a template definition
+and produces Python code to create the finished string. Cheetah
+provides several ways to compile and fill templates, either as one
+step or two.
+
+Cheetah's compiler produces a subclass of {Cheetah.Template}
+specific to that template definition; this is called the {
+generated class}. A { template instance} is an instance of a
+generated class.
+
+If the user calls the {Template} constructor directly (rather than
+a subclass constructor), s/he will get what appears to be an
+instance of {Template} but is actually a subclass created
+on-the-fly.
+
+The user can make the subclass explicit by using the
+"cheetah compile" command to write the template class to a Python
+module. Such a module is called a { .py template module}.
+
+The { Template Definition Language} - or the "Cheetah language" for
+short - is the syntax rules governing placeholders and directives.
+These are discussed in sections language and following in this
+Guide.
+
+To fill a template, you call its { main method}. This is normally
+{.respond()}, but it may be something else, and you can use the
+{#implements} directive to choose the method name. (Section
+inheritanceEtc.implements.
+
+A { template-servlet} is a .py template module in a Webware servlet
+directory. Such templates can be filled directly through the web by
+requesting the URL. "Template-servlet" can also refer to the
+instance being filled by a particular web request. If a Webware
+servlet that is not a template-servlet invokes a template, that
+template is not a template-servlet either.
+
+A { placeholder tag} is the substring in the template definition
+that is the placeholder, including the start and end delimeters (if
+there is an end delimeter). The { placeholder name} is the same but
+without the delimeters.
+
+Placeholders consist of one or more { identifiers} separated by
+periods (e.g., {a.b}). Each identifier must follow the same rules
+as Python identifiers; that is, a letter or underscore followed by
+one or more letters, digits or underscores. (This is the regular
+expression ``[A-Za-z_][A-Za-z0-9_]*``.)
+
+The first (or only) identifier of a placeholder name represents a {
+variable} to be looked up. Cheetah looks up variables in various {
+namespaces}: the searchList, local variables, and certain other
+places. The searchList is a list of objects ({ containers}) with
+attributes and/or keys: each container is a namespace. Every
+template instance has exactly one searchList. Identifiers after the
+first are looked up only in the parent object. The final value
+after all lookups have been performed is the { placeholder value}.
+
+Placeholders may occur in three positions: top-level, expression
+and LVALUE. { Top-level} placeholders are those in ordinary text
+("top-level text"). { Expression} placeholders are those in Python
+expressions. { LVALUE} placeholders are those naming a variable to
+receive a value. (LVALUE is computerese for
+"the left side of the equal sign".) Section
+language.placeholders.positions explains the differences between
+these three positions.
+
+The routine that does the placeholder lookups is called the {
+NameMapper}. Cheetah's NameMapper supports universal dotted
+notation and autocalling. { Universal dotted notation} means that
+keys may be written as if they were attributes: {a.b} instead of
+{a['b']}. { Autocalling} means that if any identifier's value is
+found to be a function or method, Cheetah will call it without
+arguments if there is no ``()`` following. More about the
+NameMapper is in section language.namemapper.
+
+Some directives are multi-line, meaning they have a matching {
+#end} tag. The lines of text between the start and end tags is the
+{ body} of the directive. Arguments on the same line as the start
+tag, in contrast, are considered part of the directive tag. More
+details are in section language.directives.syntax (Directive Syntax
+Rules).
+
+
diff --git a/www/users_guide/howItWorks.rst b/www/users_guide/howItWorks.rst
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/www/users_guide/howItWorks.rst
diff --git a/www/users_guide/index.rst b/www/users_guide/index.rst
new file mode 100644
index 0000000..93b0ce4
--- /dev/null
+++ b/www/users_guide/index.rst
@@ -0,0 +1,28 @@
+Cheetah User's Guide
+====================
+
+.. toctree::
+ :maxdepth: 2
+
+ intro.rst
+ glossary.rst
+ gettingStarted.rst
+ howItWorks.rst
+ language.rst
+ comments.rst
+ output.rst
+ inheritanceEtc.rst
+ flowControl.rst
+ errorHandling.rst
+ parserInstructions.rst
+
+ tipsAndTricks.rst
+ webware.rst
+ nonHtml.rst
+ libraries.rst
+ editors.rst
+ links.rst
+ examples.rst
+ comparisons.rst
+
+
diff --git a/www/users_guide/inheritanceEtc.rst b/www/users_guide/inheritanceEtc.rst
new file mode 100644
index 0000000..1b8136d
--- /dev/null
+++ b/www/users_guide/inheritanceEtc.rst
@@ -0,0 +1,517 @@
+Import, Inheritance, Declaration and Assignment
+===============================================
+
+(inheritanceEtc)
+
+#import and #from directives
+----------------------------
+
+(inheritanceEtc.import)
+
+Syntax:
+
+::
+
+ #import MODULE_OR_OBJECT [as NAME] [, ...]
+ #from MODULE import MODULE_OR_OBJECT [as NAME] [, ...]
+
+The {#import} and {#from} directives are used to make external
+Python modules or objects available to placeholders. The syntax is
+identical to the import syntax in Python. Imported modules are
+visible globally to all methods in the generated Python class.
+
+::
+
+ #import math
+ #import math as mathModule
+ #from math import sin, cos
+ #from math import sin as _sin
+ #import random, re
+ #from mx import DateTime # ## Part of Egenix's mx package.
+
+After the above imports, {$math}, {$mathModule}, {$sin}, {$cos} and
+{$\_sin}, {$random}, {$re} and {$DateTime} may be used in
+{$placeholders} and expressions.
+
+#extends
+--------
+
+(inheritanceEtc.extends)
+
+Syntax:
+
+::
+
+ #extends CLASS
+
+All templates are subclasses of {Cheetah.Template.Template}.
+However, it's possible for a template to subclass another template
+or a pure Python class. This is where {#extends} steps in: it
+specifies the parent class. It's equivalent to PSP's
+{"@page extends="} directive.
+
+Cheetah imports the class mentioned in an {#extends} directive
+automatically if you haven't imported it yet. The implicit
+importing works like this:
+
+::
+
+ #extends Superclass
+ ## Implicitly does '#from Superclass import Superclass'.
+
+ #extends Cheetah.Templates.SkeletonPage
+ ## Implicitly does '#from Cheetah.Templates.SkeletonPage import SkeletonPage'.
+
+If your superclass is in an unusual location or in a module named
+differently than the class, you must import it explicitly. There is
+no support for extending from a class that is not imported; e.g.,
+from a template dynamically created from a string. Since the most
+practical way to get a parent template into a module is to
+precompile it, all parent templates essentially have to be
+precompiled.
+
+There can be only one {#extends} directive in a template and it may
+list only one class. In other words, templates don't do multiple
+inheritance. This is intentional: it's too hard to initialize
+multiple base classes correctly from inside a template. However,
+you can do multiple inheritance in your pure Python classes.
+
+If your pure Python class overrides any of the standard {Template}
+methods such as {.\_\_init\_\_} or {.awake}, be sure to call the
+superclass method in your method or things will break. Examples of
+calling the superclass method are in section
+tips.callingSuperclassMethods. A list of all superclass methods is
+in section tips.allMethods.
+
+In all cases, the root superclass must be {Template}. If your
+bottommost class is a template, simply omit the {#extends} in it
+and it will automatically inherit from {Template}. { If your
+bottommost class is a pure Python class, it must inherit from
+{Template} explicitly: }
+
+::
+
+ from Cheetah.Template import Template
+ class MyPurePythonClass(Template):
+
+If you're not keen about having your Python classes inherit from
+{Template}, create a tiny glue class that inherits both from your
+class and from {Template}.
+
+Before giving any examples we'll stress that Cheetah does { not}
+dictate how you should structure your inheritance tree. As long as
+you follow the rules above, many structures are possible.
+
+Here's an example for a large web site that has not only a general
+site template, but also a template for this section of the site,
+and then a specific template-servlet for each URL. (This is the
+"inheritance approach" discussed in the Webware chapter.) Each
+template inherits from a pure Python class that contains
+methods/attributes used by the template. We'll begin with the
+bottommost superclass and end with the specific template-servlet:
+
+::
+
+ 1. SiteLogic.py (pure Python class containing methods for the site)
+ from Cheetah.Template import Template
+ class SiteLogic(Template):
+
+ 2. Site.tmpl/py (template containing the general site framework;
+ this is the template that controls the output,
+ the one that contains "<HTML><HEAD>...", the one
+ that contains text outside any #def/#block.)
+ #from SiteLogic import SiteLogic
+ #extends SiteLogic
+ #implements respond
+
+ 3. SectionLogic.py (pure Python class with helper code for the section)
+ from Site import Site
+ class SectionLogic(Site)
+
+ 4. Section.tmpl/py (template with '#def' overrides etc. for the section)
+ #from SectionLogic import SectionLogic
+ #extends SectionLogic
+
+ 5. page1Logic.py (pure Python class with helper code for the template-servlet)
+ from Section import Section
+ class indexLogic(Section):
+
+ 6. page1.tmpl/py (template-servlet for a certain page on the site)
+ #from page1Logic import page1Logic
+ #extends page1Logic
+
+A pure Python classes might also contain methods/attributes that
+aren't used by their immediate child template, but are available
+for any descendant template to use if it wishes. For instance, the
+site template might have attributes for the name and e-mail address
+of the site administrator, ready to use as $placeholders in any
+template that wants it.
+
+{ Whenever you use {#extends}, you often need {#implements} too,}
+as in step 2 above. Read the next section to understand what
+{#implements} is and when to use it.
+
+#implements
+-----------
+
+(inheritanceEtc.implements)
+
+Syntax:
+
+::
+
+ #implements METHOD
+
+You can call any {#def} or {#block} method directly and get its
+outpt. The top-level content - all the text/placeholders/directives
+outside any {#def}/{#block} - gets concatenated and wrapped in a
+"main method", by default {.respond()}. So if you call
+{.respond()}, you get the "whole template output". When Webware
+calls {.respond()}, that's what it's doing. And when you do 'print
+t' or 'str(t)' on a template instance, you're taking advantage of
+the fact that Cheetah makes {.\_\_str\_\_()} an alias for the main
+method.
+
+That's all fine and dandy, but what if your application prefers to
+call another method name rather than {.respond()}? What if it wants
+to call, say, {.send\_output()} instead? That's where {#implements}
+steps in. It lets you choose the name for the main method. Just put
+this in your template definition:
+
+::
+
+ #implements send_output
+
+When one template extends another, every template in the
+inheritance chain has its own main method. To fill the template,
+you invoke exactly one of these methods and the others are ignored.
+The method you call may be in any of the templates in the
+inheritance chain: the base template, the leaf template, or any in
+between, depending on how you structure your application. So you
+have two problems: (1) calling the right method name, and (2)
+preventing an undesired same-name subclass method from overriding
+the one you want to call.
+
+Cheetah assumes the method you will call is {.respond()} because
+that's what Webware calls. It further assumes the desired main
+method is the one in the lowest-level base template, because that
+works well with {#block} as described in the Inheritance Approach
+for building Webware servlets (section webware.inheritance), which
+was originally the principal use for Cheetah. So when you use
+{#extends}, Cheetah changes that template's main method to
+{.writeBody()} to get it out of the way and prevent it from
+overriding the base template's {.respond()}.
+
+Unfortunately this assumption breaks down if the template is used
+in other ways. For instance, you may want to use the main method in
+the highest-level leaf template, and treat the base template(s) as
+merely a library of methods/attributes. In that case, the leaf
+template needs {#implements respond} to change its main method name
+back to {.respond()} (or whatever your application desires to
+call). Likewise, if your main method is in one of the intermediate
+templates in an inheritance chain, that template needs {#implements
+respond}.
+
+The other way the assumption breaks down is if the main method {
+is} in the base template but that template extends a pure Python
+class. Cheetah sees the {#extends} and dutifully but incorrectly
+renames the method to {.writeBody()}, so you have to use
+{#implements respond} to change it back. Otherwise the dummy
+{.respond()} in {Cheetah.Template} is found, which outputs...
+nothing. { So if you're using {#extends} and get no output, the {
+first} thing you should think is,
+"Do I need to add {#implements respond} somewhere?" }
+
+#set
+----
+
+(inheritanceEtc.set)
+
+Syntax:
+
+::
+
+ #set [global] $var = EXPR
+
+{#set} is used to create and update local variables at run time.
+The expression may be any Python expression. Remember to preface
+variable names with $ unless they're part of an intermediate result
+in a list comprehension.
+
+Here are some examples:
+
+::
+
+ #set $size = $length * 1096
+ #set $buffer = $size + 1096
+ #set $area = $length * $width
+ #set $namesList = ['Moe','Larry','Curly']
+ #set $prettyCountry = $country.replace(' ', '&nbsp;')
+
+{#set} variables are useful to assign a short name to a
+{$deeply.nested.value}, to a calculation, or to a printable version
+of a value. The last example above converts any spaces in the
+'country' value into HTML non-breakable-space entities, to ensure
+the entire value appears on one line in the browser.
+
+{#set} variables are also useful in {#if} expressions, but remember
+that complex logical routines should be coded in Python, not in
+Cheetah!
+
+::
+
+ #if $size > 1500
+ #set $adj = 'large'
+ #else
+ #set $adj = 'small'
+ #end if
+
+Or Python's one-line equivalent, "A and B or C". Remember that in
+this case, B must be a true value (not None, '', 0, [] or {}).
+
+::
+
+ #set $adj = $size > 1500 and 'large' or 'small'
+
+(Note: Cheetah's one-line {#if} will not work for this, since it
+produces output rather than setting a variable.
+
+You can also use the augmented assignment operators:
+
+::
+
+ ## Increment $a by 5.
+ #set $a += 5
+
+By default, {#set} variables are not visible in method calls or
+include files unless you use the {global} attribute: {#set global
+$var = EXPRESSION}. Global variables are visible in all methods,
+nested templates and included files. Use this feature with care to
+prevent surprises.
+
+#del
+----
+
+(inheritanceEtc.del)
+
+Syntax:
+
+::
+
+ #del $var
+
+{#del} is the opposite of {#set}. It deletes a { local} variable.
+Its usage is just like Python's {del} statement:
+
+::
+
+ #del $myVar
+ #del $myVar, $myArray[5]
+
+Only local variables can be deleted. There is no directive to
+delete a {#set global} variable, a searchList variable, or any
+other type of variable.
+
+#attr
+-----
+
+(inheritanceEtc.attr)
+
+Syntax:
+
+::
+
+ #attr $var = EXPR
+
+The {#attr} directive creates class attributes in the generated
+Python class. It should be used to assign simple Python literals
+such as numbers or strings. In particular, the expression must {
+not} depend on searchList values or {#set} variables since those
+are not known at compile time.
+
+::
+
+ #attr $title = "Rob Roy"
+ #attr $author = "Sir Walter Scott"
+ #attr $version = 123.4
+
+This template or any child template can output the value thus:
+
+::
+
+ $title, by $author, version $version
+
+If you have a library of templates derived from etexts
+(http://www.gutenberg.org/), you can extract the titles and authors
+and put them in a database (assuming the templates have been
+compiled into .py template modules):
+
+#def
+----
+
+(inheritanceEtc.def)
+
+Syntax:
+
+::
+
+ #def METHOD[(ARGUMENTS)]
+ #end def
+
+Or the one-line variation:
+
+::
+
+ #def METHOD[(ARGUMENTS)] : TEXT_AND_PLACEHOLDERS
+
+The {#def} directive is used to define new methods in the generated
+Python class, or to override superclass methods. It is analogous to
+Python's {def} statement. The directive is silent, meaning it does
+not itself produce any output. However, the content of the method
+will be inserted into the output (and the directives executed)
+whenever the method is later called by a $placeholder.
+
+::
+
+ #def myMeth()
+ This is the text in my method
+ $a $b $c(123) ## these placeholder names have been defined elsewhere
+ #end def
+
+ ## and now use it...
+ $myMeth()
+
+The arglist and parentheses can be omitted:
+
+::
+
+ #def myMeth
+ This is the text in my method
+ $a $b $c(123)
+ #end def
+
+ ## and now use it...
+ $myMeth
+
+Methods can have arguments and have defaults for those arguments,
+just like in Python. Remember the {$} before variable names:
+
+::
+
+ #def myMeth($a, $b=1234)
+ This is the text in my method
+ $a - $b
+ #end def
+
+ ## and now use it...
+ $myMeth(1)
+
+The output from this last example will be:
+
+::
+
+ This is the text in my method
+ 1 - 1234
+
+There is also a single line version of the {#def} directive. {
+Unlike the multi-line directives, it uses a colon (:) to delimit
+the method signature and body}:
+
+::
+
+ #attr $adj = 'trivial'
+ #def myMeth: This is the $adj method
+ $myMeth
+
+Leading and trailing whitespace is stripped from the method. This
+is in contrast to:
+
+::
+
+ #def myMeth2
+ This is the $adj method
+ #end def
+
+where the method includes a newline after "method". If you don't
+want the newline, add {#slurp}:
+
+::
+
+ #def myMeth3
+ This is the $adj method#slurp
+ #end def
+
+Because {#def} is handled at compile time, it can appear above or
+below the placeholders that call it. And if a superclass
+placeholder calls a method that's overridden in a subclass, it's
+the subclass method that will be called.
+
+#block ... #end block
+---------------------
+
+(inheritanceEtc.block)
+
+The {#block} directive allows you to mark a section of your
+template that can be selectively reimplemented in a subclass. It is
+very useful for changing part of a template without having to
+copy-paste-and-edit the entire thing. The output from a template
+definition that uses blocks will be identical to the output from
+the same template with the {#block ... #end block} tags removed.
+
+({ Note:} don't be confused by the generic word 'block'' in this
+Guide, which means a section of code inside { any} {#TAG ... #end
+TAG} pair. Thus, an if-block, for-block, def-block, block-block
+etc. In this section we are talking only of block-blocks.)
+
+To reimplement the block, use the {#def} directive. The magical
+effect is that it appears to go back and change the output text {
+at the point the original block was defined} rather than at the
+location of the reimplementation.
+
+::
+
+ #block testBlock
+ Text in the contents
+ area of the block directive
+ #if $testIt
+ $getFoo()
+ #end if
+ #end block testBlock
+
+You can repeat the block name in the {#end block} directive or not,
+as you wish.
+
+{#block} directives can be nested to any depth.
+
+::
+
+ #block outerBlock
+ Outer block contents
+
+ #block innerBlock1
+ inner block1 contents
+ #end block innerBlock1
+
+ #block innerBlock2
+ inner block2 contents
+ #end block innerBlock2
+
+ #end block outerBlock
+
+Note that the name of the block is optional for the {#end block}
+tag.
+
+Technically, {#block} directive is equivalent to a {#def} directive
+followed immediately by a {#placeholder} for the same name. In
+fact, that's what Cheetah does. Which means you can use
+{$theBlockName} elsewhere in the template to output the block
+content again.
+
+There is a one-line {#block} syntax analagous to the one-line
+{#def}.
+
+The block must not require arguments because the implicit
+placeholder that's generated will call the block without
+arguments.
+
+
diff --git a/www/users_guide/intro.rst b/www/users_guide/intro.rst
new file mode 100644
index 0000000..b7525ea
--- /dev/null
+++ b/www/users_guide/intro.rst
@@ -0,0 +1,313 @@
+Introduction
+===============
+
+Who should read this Guide?
+---------------------------
+
+This Users' Guide provides a technical overview and reference for
+the Cheetah template system. Knowledge of Python and
+object-oriented programming is assumed. The emphasis in this Guide
+is on features useful in a wide variety of situations. Information
+on less common situations and troubleshooting tips are gradually
+being moved to the Cheetah FAQ. There is also a Cheetah Developer's
+Guide for those who want to know what goes on under the hood.
+
+What is Cheetah?
+----------------
+
+Cheetah is a Python-powered template engine and code generator. It
+may be used as a standalone utility or combined with other tools.
+Cheetah has many potential uses, but web developers looking for a
+viable alternative to ASP, JSP, PHP and PSP are expected to be its
+principle user group.
+
+Cheetah:
+
+
+- generates HTML, SGML, XML, SQL, Postscript, form email, LaTeX,
+ or any other text-based format. It has also been used to produce
+ Python, Java and PHP source code.
+
+- cleanly separates content, graphic design, and program code.
+ This leads to highly modular, flexible, and reusable site
+ architectures; faster development time; and HTML and program code
+ that is easier to understand and maintain. It is particularly well
+ suited for team efforts.
+
+- blends the power and flexibility of Python with a simple
+ template language that non-programmers can understand.
+
+- gives template writers full access in their templates to any
+ Python data structure, module, function, object, or method.
+
+- makes code reuse easy by providing an object-oriented interface
+ to templates that is accessible from Python code or other Cheetah
+ templates. One template can subclass another and selectively
+ reimplement sections of it. A compiled template **is** a Python
+ class, so it can subclass a pure Python class and vice-versa.
+
+- provides a simple yet powerful caching mechanism
+
+Like its namesake, Cheetah is fast, flexible and powerful.
+
+
+What is the philosophy behind Cheetah?
+--------------------------------------
+Cheetah's design was guided by these principles:
+
+
+- Python for the back end, Cheetah for the front end. Cheetah was
+ designed to complement Python, not replace it.
+
+- Cheetah's core syntax should be easy for non-programmers to
+ learn.
+
+- Cheetah should make code reuse easy by providing an
+ object-oriented interface to templates that is accessible from
+ Python code or other Cheetah templates.
+
+- Python objects, functions, and other data structures should be
+ fully accessible in Cheetah.
+
+- Cheetah should provide flow control and error handling. Logic
+ that belongs in the front end shouldn't be relegated to the back
+ end simply because it's complex.
+
+- It should be easy to **separate** content, graphic design, and
+ program code, but also easy to **integrate** them.
+
+ A clean separation makes it easier for a team of content writers,
+ HTML/graphic designers, and programmers to work together without
+ stepping on each other's toes and polluting each other's work. The
+ HTML framework and the content it contains are two separate things,
+ and analytical calculations (program code) is a third thing. Each
+ team member should be able to concentrate on their specialty and to
+ implement their changes without having to go through one of the
+ others (i.e., the dreaded "webmaster bottleneck").
+
+ While it should be easy to develop content, graphics and program
+ code separately, it should be easy to integrate them together into
+ a website. In particular, it should be easy:
+
+
+ - for **programmers** to create reusable components and functions
+ that are accessible and understandable to designers.
+
+ - for **designers** to mark out placeholders for content and
+ dynamic components in their templates.
+
+ - for **designers** to soft-code aspects of their design that are
+ either repeated in several places or are subject to change.
+
+ - for **designers** to reuse and extend existing templates and thus
+ minimize duplication of effort and code.
+
+ - and, of course, for **content writers** to use the templates that
+ designers have created.
+
+
+
+Why Cheetah doesn't use HTML-style tags
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Cheetah does not use HTML/XML-style tags like some other template
+languages for the following reasons: Cheetah is not limited to
+HTML, HTML-style tags are hard to distinguish from real HTML tags,
+HTML-style tags are not visible in rendered HTML when something
+goes wrong, HTML-style tags often lead to invalid HTML (e.g., ``<img
+src="<template-directive>">``), Cheetah tags are less verbose and
+easier to understand than HTML-style tags, and HTML-style tags
+aren't compatible with most WYSIWYG editors
+
+Besides being much more compact, Cheetah also has some advantages
+over languages that put information inside the HTML tags, such as
+Zope Page Templates or PHP: HTML or XML-bound languages do not work
+well with other languages, While ZPT-like syntaxes work well in
+many ways with WYSIWYG HTML editors, they also give up a
+significant advantage of those editors - concrete editing of the
+document. When logic is hidden away in (largely inaccessible) tags
+it is hard to understand a page simply by viewing it, and it is
+hard to confirm or modify that logic.
+
+Give me an example!
+-------------------
+
+Here's a very simple example that illustrates some of Cheetah's
+basic syntax:
+
+::
+
+ <HTML>
+ <HEAD><TITLE>$title</TITLE></HEAD>
+ <BODY>
+
+ <TABLE>
+ #for $client in $clients
+ <TR>
+ <TD>$client.surname, $client.firstname</TD>
+ <TD><A HREF="mailto:$client.email">$client.email</A></TD>
+ </TR>
+ #end for
+ </TABLE>
+
+ </BODY>
+ </HTML>
+
+Compare this with PSP:
+
+::
+
+ <HTML>
+ <HEAD><TITLE><%=title%></TITLE></HEAD>
+ <BODY>
+
+ <TABLE>
+ <% for client in clients: %>
+ <TR>
+ <TD><%=client['surname']%>, <%=client['firstname']%></TD>
+ <TD><A HREF="mailto:<%=client['email']%>"><%=client['email']%></A></TD>
+ </TR>
+ <%end%>
+ </TABLE>
+
+ </BODY>
+ </HTML>
+
+Section gettingStarted.tutorial has a more typical example that
+shows how to get the plug-in values **into** Cheetah, and section
+howWorks.cheetah-compile explains how to turn your template
+definition into an object-oriented Python module.
+
+Give me an example of a Webware servlet!
+----------------------------------------
+
+This example uses an HTML form to ask the user's name, then invokes
+itself again to display a **personalized** friendly greeting.
+
+::
+
+ <HTML><HEAD><TITLE>My Template-Servlet</TITLE></HEAD><BODY>
+ #set $name = $request.field('name', None)
+ #if $name
+ Hello $name
+ #else
+ <FORM ACTION="" METHOD="GET">
+ Name: <INPUT TYPE="text" NAME="name"><BR>
+ <INPUT TYPE="submit">
+ </FORM>
+ #end if
+ </BODY></HTML>
+
+To try it out for yourself on a Webware system:
+
+
+#. copy the template definition to a file **test.tmpl** in your
+ Webware servlet directory.
+
+#. Run ``cheetah compile test.tmpl``. This produces ``test.py`` (a
+ .py template module) in the same directory.
+
+#. In your web browser, go to ``test.py``, using whatever site and
+ directory is appropriate.
+
+At the first request, field 'name' will be blank (false) so the
+"#else" portion will execute and present a form. You type your name
+and press submit. The form invokes the same page. Now 'name' is
+true so the "#if" portion executes, which displays the greeting.
+The "#set" directive creates a local variable that lasts while the
+template is being filled.
+
+How mature is Cheetah?
+----------------------
+
+Cheetah is stable, production quality, post-beta code. Cheetah's
+syntax, semantics and performance have been generally stable since
+a performance overhaul in mid 2001. Most of the changes since
+October 2001 have been in response to specific requests by
+production sites, things they need that we hadn't considered.
+
+As of summer 2003, we are putting in the final touches before the
+1.0 release.
+
+
+Where can I get news?
+---------------------
+
+Cheetah releases can be obtained from the `Cheetah
+website <http://cheetahtemplate.org>`_
+
+Cheetah discussions take place on the `mailing
+list <http://lists.sourceforge.net/lists/listinfo/cheetahtemplate-discuss>`_
+
+If you encounter difficulties, or are unsure about how to do
+something, please post a detailed message to the list.
+
+How can I contribute?
+---------------------
+
+Cheetah is the work of many volunteers. If you use Cheetah please
+share your experiences, tricks, customizations, and frustrations.
+
+Bug reports and patches
+~~~~~~~~~~~~~~~~~~~~~~~
+
+If you think there is a bug in Cheetah, send a message to the
+e-mail list with the following information:
+
+
+#. a description of what you were trying to do and what happened
+
+#. all tracebacks and error output
+
+#. your version of Cheetah
+
+#. your version of Python
+
+#. your operating system
+
+#. whether you have changed anything in the Cheetah installation
+
+
+Template libraries and function libraries
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We hope to build up a framework of Template libraries (see section
+libraries.templates) to distribute with Cheetah and would
+appreciate any contributions.
+
+Test cases
+~~~~~~~~~~
+
+Cheetah is packaged with a regression testing suite that is run
+with each new release to ensure that everything is working as
+expected and that recent changes haven't broken anything. The test
+cases are in the Cheetah.Tests module. If you find a reproduceable
+bug please consider writing a test case that will pass only when
+the bug is fixed. Send any new test cases to the email list with
+the subject-line "new test case for Cheetah."
+
+Publicity
+~~~~~~~~~
+
+Help spread the word ... recommend it to others, write articles
+about it, etc.
+
+Acknowledgements
+----------------
+
+Cheetah is one of several templating frameworks that grew out of a
+'templates' thread on the Webware For Python email list. Tavis
+Rudd, Mike Orr, Chuck Esterbrook and Ian Bicking are the core
+developers.
+
+We'd like to thank the following people for contributing valuable
+advice, code and encouragement: Geoff Talvola, Jeff Johnson, Graham
+Dumpleton, Clark C. Evans, Craig Kattner, Franz Geiger, Geir
+Magnusson, Tom Schwaller, Rober Kuzelj, Jay Love, Terrel Shumway,
+Sasa Zivkov, Arkaitz Bitorika, Jeremiah Bellomy, Baruch Even, Paul
+Boddie, Stephan Diehl, Chui Tey, Michael Halle, Edmund Lian and
+Aaron Held.
+
+The Velocity, WebMacro and Smarty projects provided inspiration and
+design ideas. Cheetah has benefitted from the creativity and energy
+of their developers. Thank you.
diff --git a/www/users_guide/language.rst b/www/users_guide/language.rst
new file mode 100644
index 0000000..f387562
--- /dev/null
+++ b/www/users_guide/language.rst
@@ -0,0 +1,741 @@
+.. role:: math(raw)
+ :format: html latex
+
+Language Overview
+=================
+
+(language)
+
+Cheetah's basic syntax was inspired by the Java-based template
+engines Velocity and WebMacro. It has two types of tags: {
+$placeholders} and { #directives}. Both types are case-sensitive.
+
+Placeholder tags begin with a dollar sign ({$varName}) and are
+similar to data fields in a form letter or to the {%(key)s} fields
+on the left side of Python's {%} operator. When the template is
+filled, the placeholders are replaced with the values they refer
+to.
+
+Directive tags begin with a hash character (#) and are used for
+comments, loops, conditional blocks, includes, and all other
+advanced features. ({ Note:} you can customize the start and end
+delimeters for placeholder and directive tags, but in this Guide
+we'll assume you're using the default.)
+
+Placeholders and directives can be escaped by putting a backslash
+before them. ``\$var`` and ``\#if`` will be output as literal
+text.
+
+A placeholder or directive can span multiple physical lines,
+following the same rules as Python source code: put a backslash
+(``\``) at the end of all lines except the last line. However, if
+there's an unclosed parenthesis, bracket or brace pending, you
+don't need the backslash.
+
+::
+
+ #if $this_is_a_very_long_line and $has_lots_of_conditions \
+ and $more_conditions:
+ <H1>bla</H1>
+ #end if
+
+ #if $country in ('Argentina', 'Uruguay', 'Peru', 'Colombia',
+ 'Costa Rica', 'Venezuela', 'Mexico')
+ <H1>Hola, senorita!</H1>
+ #else
+ <H1>Hey, baby!</H1>
+ #end if
+
+Language Constructs - Summary
+-----------------------------
+
+(language.constructs)
+
+
+#. Comments and documentation strings
+
+
+ #. {## single line}
+
+ #. {#\* multi line \*#}
+
+
+#. Generation, caching and filtering of output
+
+
+ #. plain text
+
+ #. look up a value: {$placeholder}
+
+ #. evaluate an expression: {#echo} ...
+
+ #. same but discard the output: {#silent} ...
+
+ #. one-line if: {#if EXPR then EXPR else EXPR}
+
+ #. gobble the EOL: {#slurp}
+
+ #. parsed file includes: {#include} ...
+
+ #. raw file includes: {#include raw} ...
+
+ #. verbatim output of Cheetah code: {#raw} ... {#end raw}
+
+ #. cached placeholders: {$\*var}, {$\*<interval>\*var}
+
+ #. cached regions: {#cache} ... {#end cache}
+
+ #. set the output filter: {#filter} ...
+
+ #. control output indentation: {#indent} ... ({ not implemented
+ yet})
+
+
+#. Importing Python modules and objects: {#import} ..., {#from}
+ ...
+
+#. Inheritance
+
+
+ #. set the base class to inherit from: {#extends}
+
+ #. set the name of the main method to implement: {#implements}
+ ...
+
+
+#. Compile-time declaration
+
+
+ #. define class attributes: {#attr} ...
+
+ #. define class methods: {#def} ... {#end def}
+
+ #. {#block} ... {#end block} provides a simplified interface to
+ {#def} ... {#end def}
+
+
+#. Run-time assignment
+
+
+ #. local vars: {#set} ...
+
+ #. global vars: {#set global} ...
+
+ #. deleting local vars: {#del} ...
+
+
+#. Flow control
+
+
+ #. {#if} ... {#else} ... {#else if} (aka {#elif}) ... {#end if}
+
+ #. {#unless} ... {#end unless}
+
+ #. {#for} ... {#end for}
+
+ #. {#repeat} ... {#end repeat}
+
+ #. {#while} ... {#end while}
+
+ #. {#break}
+
+ #. {#continue}
+
+ #. {#pass}
+
+ #. {#stop}
+
+
+#. error/exception handling
+
+
+ #. {#assert}
+
+ #. {#raise}
+
+ #. {#try} ... {#except} ... {#else} ... {#end try}
+
+ #. {#try} ... {#finally} ... {#end try}
+
+ #. {#errorCatcher} ... set a handler for exceptions raised by
+ $placeholder calls.
+
+
+#. Instructions to the parser/compiler
+
+
+ #. {#breakpoint}
+
+ #. {#compiler-settings} ... {#end compiler-settings}
+
+
+#. Escape to pure Python code
+
+
+ #. evalute expression and print the output: {<%=} ... {%>}
+
+ #. execute code and discard output: {<%} ... {%>}
+
+
+#. Fine control over Cheetah-generated Python modules
+
+
+ #. set the source code encoding of compiled template modules:
+ {#encoding}
+
+ #. set the sh-bang line of compiled template modules: {#shBang}
+
+
+
+The use of all these constructs will be covered in the next several
+chapters.
+
+Placeholder Syntax Rules
+------------------------
+
+(language.placeholders.syntax)
+
+
+- Placeholders follow the same syntax rules as Python variables
+ except that they are preceded by {$} (the short form) or enclosed
+ in {${}} (the long form). Examples:
+
+ ::
+
+ $var
+ ${var}
+ $var2.abc['def']('gh', $subplaceholder, 2)
+ ${var2.abc['def']('gh', $subplaceholder, 2)}
+
+ We recommend {$} in simple cases, and {${}} when followed directly
+ by a letter or when Cheetah or a human template maintainer might
+ get confused about where the placeholder ends. You may alternately
+ use ``$()`` or ``$[]``, although this may confuse the (human)
+ template maintainer:
+
+ ::
+
+ $(var)
+ $[var]
+ $(var2.abc['def']('gh', $subplaceholder, 2))
+ $[var2.abc['def']('gh', $subplaceholder, 2)]
+
+ { Note:} Advanced users can change the delimiters to anything they
+ want via the {#compiler} directive.
+
+ { Note 2:} The long form can be used only with top-level
+ placeholders, not in expressions. See section
+ language.placeholders.positions for an elaboration on this.
+
+- To reiterate Python's rules, placeholders consist of one or more
+ identifiers separated by periods. Each identifier must start with a
+ letter or an underscore, and the subsequent characters must be
+ letters, digits or underscores. Any identifier may be followed by
+ arguments enclosed in ``()`` and/or keys/subscripts in ``[]``.
+
+- Identifiers are case sensitive. {$var} does not equal {$Var} or
+ {$vAr} or {$VAR}.
+
+- Arguments inside ``()`` or ``[]`` are just like in Python.
+ Strings may be quoted using any Python quoting style. Each argument
+ is an expression and may use any of Python's expression operators.
+ Variables used in argument expressions are placeholders and should
+ be prefixed with {$}. This also applies to the \*arg and \*\*kw
+ forms. However, you do { not} need the {$} with the special Python
+ constants {None}, {True} and {False}. Examples:
+
+ ::
+
+ $hex($myVar)
+ $func($arg=1234)
+ $func2($*args, $**kw)
+ $func3(3.14159, $arg2, None, True)
+ $myList[$mySubscript]
+
+- Trailing periods are ignored. Cheetah will recognize that the
+ placeholder name in {$varName.} is {varName}, and the period will
+ be left alone in the template output.
+
+- The syntax {${placeholderName, arg1="val1"}} passes arguments to
+ the output filter (see {#filter}, section output.filter. The braces
+ and comma are required in this case. It's conventional to omit the
+ {$} before the keyword arguments (i.e. {arg1}) in this case.
+
+- Cheetah ignores all dollar signs ({$}) that are not followed by
+ a letter or an underscore.
+
+
+The following are valid $placeholders:
+
+::
+
+ $a $_ $var $_var $var1 $_1var $var2_ $dict.key $list[3]
+ $object.method $object.method() $object.method
+ $nest($nest($var))
+
+These are not $placeholders but are treated as literal text:
+
+::
+
+ $@var $^var $15.50 $$
+
+Where can you use placeholders?
+-------------------------------
+
+(language.placeholders.positions)
+
+There are three places you can use placeholders: top-level
+position, expression position and LVALUE position. Each has
+slightly different syntax rules.
+
+Top-level position means interspersed in text. This is the only
+place you can use the placeholder long form: {${var}}.
+
+{ Expression position} means inside a Cheetah expression, which is
+the same as a Python expression. The placeholder names a searchList
+or other variable to be read. Expression position occurs inside ()
+and :math:`$[]$` arguments within placeholder tags (i.e., a
+placeholder inside a placeholder), and in several directive tags.
+
+{ LVALUE position} means naming a variable that will be written to.
+LVALUE is a computer science term meaning
+"the left side of an assignment statement". The first argument of
+directives {#set}, {#for}, {#def}, {#block} and {#attr} is an
+LVALUE.
+
+This stupid example shows the three positions. Top-level position
+is shown in {courier}, expression position is { italic}, and LVALUE
+position is { bold}.
+
+ #for { $count} in { $range}({ $ninetyNine}, 0, -1)
+ #set { $after} = { $count} - 1
+ {$count} bottles of beer on the wall. {$count} bottles of beer!
+ Take one down, pass it around. {$after} bottles of beer on the
+ wall.
+ #end for
+ {$hex}({ $myVar}, { $default}={ None})
+
+
+The output of course is:
+
+::
+
+ 99 bottles of beer on the wall. 99 bottles of beer!
+ Take one down, pass it around. 98 bottles of beer on the wall.
+ 98 bottles of beer on the wall. 98 bottles of beer!
+ Take one down, pass it around. 97 bottles of beer on the wall.
+ ...
+
+Are all those dollar signs really necessary?
+--------------------------------------------
+
+(language.placeholders.dollar-signs)
+
+{$} is a "smart variable prefix". When Cheetah sees {$}, it
+determines both the variable's position and whether it's a
+searchList value or a non-searchList value, and generates the
+appropriate Python code.
+
+In top-level position, the {$} is { required}. Otherwise there's
+nothing to distinguish the variable from ordinary text, and the
+variable name is output verbatim.
+
+In expression position, the {$} is { required} if the value comes
+from the searchList or a "#set global" variable, { recommended} for
+local/global/builtin variables, and { not necessary} for the
+special constants {None}, {True} and {False}. This works because
+Cheetah generates a function call for a searchList placeholder, but
+a bare variable name for a local/global/builtin variable.
+
+In LVALUE position, the {$} is { recommended}. Cheetah knows where
+an LVALUE is expected, so it can handle your variable name whether
+it has {$} or not.
+
+EXCEPTION: Do { not} use the {$} prefix for intermediate variables
+in a Python list comprehensions. This is a limitation of Cheetah's
+parser; it can't tell which variables in a list comprehension are
+the intermediate variables, so you have to help it. For example:
+
+::
+
+ #set $theRange = [x ** 2 for x in $range(10)]
+
+{$theRange} is a regular {#set} variable. {$range} is a Python
+built-in function. But {x} is a scratch variable internal to the
+list comprehension: if you type {$x}, Cheetah will miscompile it.
+
+NameMapper Syntax
+-----------------
+
+(language.namemapper)
+
+One of our core aims for Cheetah was to make it easy for
+non-programmers to use. Therefore, Cheetah uses a simplified syntax
+for mapping placeholders in Cheetah to values in Python. It's known
+as the { NameMapper syntax} and allows for non-programmers to use
+Cheetah without knowing (a) the difference between an instance and
+a dictionary, (b) what functions and methods are, and (c) what
+'self' is. A side benefit is that you can change the underlying
+data structure (e.g., instance to dictionary or vice-versa) without
+having to modify the templates.
+
+NameMapper syntax is used for all variables in Cheetah placeholders
+and directives. If desired, it can be turned off via the {Template}
+class' {'useNameMapper'} compiler setting. But it's doubtful you'd
+ever want to turn it off.
+
+Example
+~~~~~~~
+
+(language.namemapper.example)
+
+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)
+
+With Cheetah's NameMapper syntax, you can use 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 (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 version and - conceptually - far more
+accessible. With PHP or ASP, the code would be even messier than
+with 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 of this example.
+
+Dictionary Access
+~~~~~~~~~~~~~~~~~
+
+(language.namemapper.dict)
+
+NameMapper syntax allows access to dictionary items with the same
+dotted notation used to access object attributes in Python. This
+aspect of NameMapper syntax 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 works only with dictionary keys that also happen to be valid
+Python identifiers.
+
+Autocalling
+~~~~~~~~~~~
+
+(language.namemapper.autocalling)
+
+Cheetah automatically detects functions and methods in Cheetah
+$variables and calls them if the parentheses have been left off.
+Our previous example can be further simplified to:
+
+::
+
+ $customers.kerr.address
+
+As another 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:
+
+
+- When Cheetah autocalls a function/method, it calls it without
+ any arguments. Thus, the function/method must have been declared
+ without arguments (except {self} for methods) or to provide default
+ values for all arguments. If the function requires arguments, you
+ must use the {()}.
+
+- Cheetah autocalls only functions and methods. Classes and other
+ callable objects are not autocalled. The reason is that the primary
+ purpose of a function/method is to call it, whereas the primary
+ purpose of an instance is to look up its attributes or call its
+ methods, not to call the instance itself. And calling a class may
+ allocate large sums of memory uselessly or have other side effects,
+ depending on the class. For instance, consider {$myInstance.fname}.
+ Do we want to look up {fname} in the namespace of {myInstance} or
+ in the namespace of whatever {myinstance} returns? It could go
+ either way, so Cheetah follows the principle of least surprise. If
+ you { do} want to call the instance, put the {()} on, or rename the
+ {.\_\_call\_\_()} method to {.\_\_str\_\_}.
+
+- Autocalling can be disabled via Cheetah's 'useAutocalling'
+ compiler setting. You can also disable it for one placeholder by
+ using the syntax {$getVar('varName', 'default value', False)}.
+ ({.getVar()} works only with searchList values.)
+
+
+Namespace cascading and the searchList
+--------------------------------------
+
+(language.searchList)
+
+When Cheetah maps a variable name in a template to a Python value,
+it searches several namespaces in order:
+
+
+#. { Local variables:} created by {#set}, {#for}, or predefined by
+ Cheetah.
+
+#. The { searchList}, consisting of:
+
+
+ #. {#set global} variables.
+
+ #. The { searchList} containers you passed to the {Template}
+ constructor, if any.
+
+ #. The { Template instance} ("self"). This contains any attributes
+ you assigned, {#def} methods and {#block methods},
+ attributes/methods inherited via {#extends}, and other
+ attributes/methods built into {Template} or inherited by it
+ (there's a list of all these methods in section tips.allMethods).
+
+
+#. { Python globals:} created by {#import}, {#from ... import}, or
+ otherwise predefined by Cheetah.
+
+#. { Python builtins:} {None}, {max}, etc.
+
+
+The first matching name found is used.
+
+Remember, these namespaces apply only to the { first} identifier
+after the {$}. In a placeholder like {$a.b}, only 'a' is looked up
+in the searchList and other namespaces. 'b' is looked up only
+inside 'a'.
+
+A searchList container can be any Python object with attributes or
+keys: dictionaries, instances, classes or modules. If an instance
+contains both attributes and keys, its attributes are searched
+first, then its keys.
+
+Because the {Template} instance is part of the searchList, you can
+access its attributes/methods without 'self': {$myAttr}. However,
+use the 'self' if you want to make sure you're getting the
+{Template} attribute and not a same-name variable defined in a
+higher namespace: {$self.myAttr}. This works because "self" itself
+is a local variable.
+
+The final resulting value, after all lookups and function calls
+(but before the filter is applied) is called the { placeholder
+value}, no matter which namespace it was found in.
+
+{ { Note carefully:}} if you put an object 'myObject' in the
+searchList, you { cannot} look up {$myObject}! You can look up only
+the attributes/keys { inside} 'myObject'.
+
+Earlier versions of Cheetah did not allow you to override Python
+builtin names, but this was fixed in Cheetah 0.9.15.
+
+If your template will be used as a Webware servlet, do not override
+methods 'name' and 'log' in the {Template} instance or it will
+interfere with Webware's logging. However, it { is} OK to use those
+variables in a higher namespace, since Webware doesn't know about
+Cheetah namespaces.
+
+Missing Values
+--------------
+
+(language.namemapper.missing)
+
+If NameMapper can not find a Python value for a Cheetah variable
+name, it will raise the NameMapper.NotFound exception. You can use
+the {#errorCatcher} directive (section errorHandling.errorCatcher)
+or { errorCatcher} Template constructor argument (section
+howWorks.constructing) to specify an alternate behaviour. BUT BE
+AWARE THAT errorCatcher IS ONLY INTENDED FOR DEBUGGING!
+
+To provide a default value for a placeholder, write it like this:
+{$getVar('varName', 'default value')}. If you don't specify a
+default and the variable is missing, {NameMapper.NotFound} will be
+raised.
+
+Directive Syntax Rules
+----------------------
+
+(language.directives.syntax)
+
+Directive tags begin with a hash character (#) and are used for
+comments, loops, conditional blocks, includes, and all other
+advanced features. Cheetah uses a Python-like syntax inside
+directive tags and understands any valid Python expression. {
+However, unlike Python, Cheetah does not use colons (:) and
+indentation to mark off multi-line directives.} That doesn't work
+in an environment where whitespace is significant as part of the
+text. Instead, multi-line directives like {#for} have corresponding
+closing tags ({#end for}). Most directives are direct mirrors of
+Python statements.
+
+Many directives have arguments after the opening tag, which must be
+in the specified syntax for the tag. All end tags have the
+following syntax:
+
+::
+
+ #end TAG_NAME [EXPR]
+
+The expression is ignored, so it's essentially a comment.
+
+Directive closures and whitespace handling
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+(language.directives.closures) Directive tags can be closed
+explicitly with {#}, or implicitly with the end of the line if
+you're feeling lazy.
+
+::
+
+ #block testBlock #
+ Text in the body of the
+ block directive
+ #end block testBlock #
+
+is identical to:
+
+::
+
+ #block testBlock
+ Text in the body of the
+ block directive
+ #end block testBlock
+
+When a directive tag is closed explicitly, it can be followed with
+other text on the same line:
+
+::
+
+ bah, bah, #if $sheep.color == 'black'# black#end if # sheep.
+
+When a directive tag is closed implicitly with the end of the line,
+all trailing whitespace is gobbled, including the newline
+character:
+
+::
+
+ """
+ foo #set $x = 2
+ bar
+ """
+ outputs
+ """
+ foo bar
+ """
+
+ while
+ """
+ foo #set $x = 2 #
+ bar
+ """
+ outputs
+ """
+ foo
+ bar
+ """
+
+When a directive tag is closed implicitly AND there is no other
+text on the line, the ENTIRE line is gobbled up including any
+preceeding whitespace:
+
+::
+
+ """
+ foo
+ #set $x = 2
+ bar
+ """
+ outputs
+ """
+ foo
+ bar
+ """
+
+ while
+ """
+ foo
+ - #set $x = 2
+ bar
+ """
+ outputs
+ """
+ foo
+ - bar
+ """
+
+The {#slurp} directive (section output.slurp) also gobbles up
+whitespace.
+
+Spaces outside directives are output { exactly} as written. In the
+black sheep example, there's a space before "black" and another
+before "sheep". So although it's legal to put multiple directives
+on one line, it can be hard to read.
+
+::
+
+ #if $a# #echo $a + 1# #end if
+ - There's a space between each directive,
+ or two extra spaces total.
+ #if $a##echo $a + 1##end if
+ - No spaces, but you have to look closely
+ to verify none of the ``##'' are comment markers.
+ #if $a##echo $a + 1##end if ### A comment.
+ - In ``###'', the first ``#'' ends the directive,
+ the other two begin the comment. (This also shows
+ how you can add extra whitespace in the directive
+ tag without affecting the output.)
+ #if $a##echo $a + 1##end if # ## A comment.
+ - More readable, but now there's a space before the
+ comment.
+
+
diff --git a/www/users_guide/libraries.rst b/www/users_guide/libraries.rst
new file mode 100644
index 0000000..f1726f4
--- /dev/null
+++ b/www/users_guide/libraries.rst
@@ -0,0 +1,315 @@
+Batteries included: templates and other libraries
+=================================================
+
+(libraries)
+
+Cheetah comes "batteries included" with libraries of templates,
+functions, classes and other objects you can use in your own
+programs. The different types are listed alphabetically below,
+followed by a longer description of the SkeletonPage framework.
+Some of the objects are classes for specific purposes (e.g.,
+filters or error catchers), while others are standalone and can be
+used without Cheetah.
+
+If you develop any objects which are generally useful for Cheetah
+sites, please consider posting them on the wiki with an
+announcement on the mailing list so we can incorporate them into
+the standard library. That way, all Cheetah users will benefit, and
+it will encourage others to contribute their objects, which might
+include something you want.
+
+ErrorCatchers
+-------------
+
+(libraries.ErrorCatchers)
+
+Module {Cheetah.ErrorCatchers} contains error-handling classes
+suitable for the {#errorCatcher} directive. These are debugging
+tools that are not intended for use in production systems. See
+section errorHandling.errorCatcher for a description of the error
+catchers bundled with Cheetah.
+
+FileUtils
+---------
+
+(libraries.FileUtils)
+
+Module {Cheetah.FileUtils} contains generic functions and classes
+for doing bulk search-and-replace on several files, and for finding
+all the files in a directory hierarchy whose names match a glob
+pattern.
+
+Filters
+-------
+
+(libraries.Filters)
+
+Module {Filters} contains filters suitable for the {#Filter}
+directive. See section output.filter for a description of the
+filters bundled with Cheetah.
+
+SettingsManager
+---------------
+
+(libraries.SettingsManager)
+
+The {SettingsManager} class in the {Cheetah.SettingsManager} module
+is a baseclass that provides facilities for managing application
+settings. It facilitates the use of user-supplied configuration
+files to fine tune an application. A setting is a key/value pair
+that an application or component (e.g., a filter, or your own
+servlets) looks up and treats as a configuration value to modify
+its (the component's) behaviour.
+
+SettingsManager is designed to:
+
+
+- work well with nested settings dictionaries of any depth
+
+- read/write {.ini style config files} (or strings)
+
+- read settings from Python source files (or strings) so that
+ complex Python objects can be stored in the application's settings
+ dictionary. For example, you might want to store references to
+ various classes that are used by the application, and plugins to
+ the application might want to substitute one class for another.
+
+- allow sections in {.ini config files} to be extended by settings
+ in Python src files. If a section contains a setting like
+ "{importSettings=mySettings.py}", {SettingsManager} will merge all
+ the settings defined in "{mySettings.py}" with the settings for
+ that section that are defined in the {.ini config file}.
+
+- maintain the case of setting names, unlike the ConfigParser
+ module
+
+
+Cheetah uses {SettingsManager} to manage its configuration
+settings. {SettingsManager} might also be useful in your own
+applications. See the source code and docstrings in the file
+{src/SettingsManager.py} for more information.
+
+Templates
+---------
+
+(libraries.templates)
+
+Package {Cheetah.Templates} contains stock templates that you can
+either use as is, or extend by using the {#def} directive to
+redefine specific { blocks}. Currently, the only template in here
+is SkeletonPage, which is described in detail below in section
+libraries.templates.skeletonPage. (Contributed by Tavis Rudd.)
+
+Tools
+-----
+
+(libraries.Tools)
+
+Package {Cheetah.Tools} contains functions and classes contributed
+by third parties. Some are Cheetah-specific but others are generic
+and can be used standalone. None of them are imported by any other
+Cheetah component; you can delete the Tools/ directory and Cheetah
+will function fine.
+
+Some of the items in Tools/ are experimental and have been placed
+there just to see how useful they will be, and whether they attract
+enough users to make refining them worthwhile (the tools, not the
+users :).
+
+Nothing in Tools/ is guaranteed to be: (A) tested, (B) reliable,
+(C) immune from being deleted in a future Cheetah version, or (D)
+immune from backwards-incompatable changes. If you depend on
+something in Tools/ on a production system, consider making a copy
+of it outside the Cheetah/ directory so that this version won't be
+lost when you upgrade Cheetah. Also, learn enough about Python and
+about the Tool so that you can maintain it and bugfix it if
+necessary.
+
+If anything in Tools/ is found to be necessary to Cheetah's
+operation (i.e., if another Cheetah component starts importing it),
+it will be moved to the {Cheetah.Utils} package.
+
+Current Tools include:
+
+ an ambitious class useful when iterating over records of data
+ ({#for} loops), displaying one pageful of records at a time (with
+ previous/next links), and printing summary statistics about the
+ records or the current page. See {MondoReportDoc.txt} in the same
+ directory as the module. Some features are not implemented yet.
+ {MondoReportTest.py} is a test suite (and it shows there are
+ currently some errors in MondoReport, hmm). Contributed by Mike
+ Orr.
+
+ 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. You can also put a {RecursiveNull} instance at the end of
+ the searchList to convert missing values to '' rather than raising
+ a {NotFound} error or having a (less efficient) errorCatcher handle
+ it. Of course, maybe you prefer to get a {NotFound} error...
+ Contributed by Ian Bicking.
+
+ Provides navigational links to this page's parents and children.
+ The constructor takes a recursive list of (url,description) pairs
+ representing a tree of hyperlinks to every page in the site (or
+ section, or application...), and also a string containing the
+ current URL. Two methods 'menuList' and 'crumbs' return
+ output-ready HTML showing an indented menu (hierarchy tree) or
+ crumbs list (Yahoo-style bar: home > grandparent > parent >
+ currentURL). Contributed by Ian Bicking.
+
+
+Utils
+-----
+
+(libraries.Utils)
+
+Package {Cheetah.Utils} contains non-Cheetah-specific functions and
+classes that are imported by other Cheetah components. Many of
+these utils can be used standalone in other applications too.
+
+Current Utils include:
+
+ This is inherited by {Template} objects, and provides the method,
+ {.cgiImport} method (section webware.cgiImport).
+
+ A catch-all module for small functions.
+
+ Raise 'thing' if it's a subclass of Exception, otherwise return it.
+ Useful when one argument does double duty as a default value or an
+ exception to throw. Contribyted by Mike Orr.
+
+ Verifies the dictionary does not contain any keys not listed in
+ 'legalKeywords'. If it does, raise TypeError. Useful for checking
+ the keyword arguments to a function. Contributed by Mike Orr.
+
+
+ Not implemented yet, but will contain the {.uploadFile} method (or
+ three methods) to "safely" copy a form-uploaded file to a local
+ file, to a searchList variable, or return it. When finished, this
+ will be inherited by {Template}, allowing all templates to do this.
+ If you want this feature, read the docstring in the source and let
+ us know on the mailing list what you'd like this method to do.
+ Contributed by Mike Orr.
+
+ Functions to verify the type of a user-supplied function argument.
+ Contributed by Mike Orr.
+
+
+Cheetah.Templates.SkeletonPage
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+(libraries.templates.skeletonPage)
+
+A stock template class that may be useful for web developers is
+defined in the {Cheetah.Templates.SkeletonPage} module. The
+{SkeletonPage} template class is generated from the following
+Cheetah source code:
+
+::
+
+ ##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>
+
+You can redefine any of the blocks defined in this template by
+writing a new template that {#extends} SkeletonPage. (As you
+remember, using {#extends} makes your template implement the
+{.writeBody()} method instead of {.respond()} - which happens to be
+the same method SkeletonPage expects the page content to be (note
+the writeBody block in SkeletonPage).)
+
+::
+
+ #def bodyContents
+ Here's my new body. I've got some flesh on my bones now.
+ #end def bodyContents
+
+All of the $placeholders used in the {SkeletonPage} template
+definition are attributes or methods of the {SkeletonPage} class.
+You can reimplement them as you wish in your subclass. Please read
+the source code of the file {src/Templates/\_SkeletonPage.py}
+before doing so.
+
+You'll need to understand how to use the following methods of the
+{SkeletonPage} class: {$metaTags()}, {$stylesheetTags()},
+{$javascriptTags()}, and {$bodyTag()}. They take the data you
+define in various attributes and renders them into HTML tags.
+
+
+- { metaTags()} - Returns a formatted vesion of the
+ self.\_metaTags dictionary, using the formatMetaTags function from
+ {\_SkeletonPage.py}.
+
+- { stylesheetTags()} - Returns 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.
+
+- { javascriptTags()} - Returns a formatted version of the
+ {self.\_javascriptTags} and {self.\_javascriptLibs} dictionaries.
+ Each value in {self.\_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 {self.\_javascriptLibs}, but the string should be the
+ SRC filename rather than a code string.
+
+- { bodyTag()} - Returns an HTML body tag from the entries in the
+ dict {self.\_bodyTagAttribs}.
+
+
+The class also provides some convenience methods that can be used
+as $placeholders in your template definitions:
+
+
+- { 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 either of these tools are
+ available. If all your images are stored in a certain directory you
+ can reimplement this method to append that directory's path to the
+ "{src}" argument. Doing so would also insulate your template
+ definitions from changes in your directory structure.
+
+
+
diff --git a/www/users_guide/links.rst b/www/users_guide/links.rst
new file mode 100644
index 0000000..6969926
--- /dev/null
+++ b/www/users_guide/links.rst
@@ -0,0 +1,142 @@
+Useful Web Links
+================
+
+(links)
+
+See the wiki for more links. (The wiki is also updated more often
+than this chapter is.)
+
+Cheetah Links
+-------------
+
+(links.cheetah)
+
+Home Page
+ - http:www.CheetahTemplate.org/
+
+On-line Documentation
+ - http:www.CheetahTemplate.org/learn.html
+
+SourceForge Project Page
+ - http:sf.net/projects/cheetahtemplate/
+
+Mailing List Subscription Page
+ -
+ http://lists.sourceforge.net/lists/listinfo/cheetahtemplate-discuss
+
+Mailing List Archive @ Geocrawler
+ - http://www.geocrawler.com/lists/3/SourceForge/12986/0/
+
+Mailing List Archive @ Yahoo
+ - http://groups.yahoo.com/group/cheetah-archive/
+
+CVS Repository
+ - http://sourceforge.net/cvs/?group\_id=28961
+
+CVS-commits archive
+ - http://www.geocrawler.com/lists/3/SourceForge/13091/0/
+
+
+Third-party Cheetah Stuff
+-------------------------
+
+(links.thirdParty)
+
+
+- Steve Howell has written a photo viewer using Python.
+ http://mountainwebtools.com/PicViewer/install.htm
+
+
+Webware Links
+-------------
+
+(links.webware)
+
+Home Page
+ - http://webware.sf.net/
+
+On-line Documentation
+ - http://webware.sf.net/Webware/Docs/
+
+SourceForge Project Page
+ - http://sf.net/projects/webware/
+
+Mailing List Subscription Page
+ - http://lists.sourceforge.net/lists/listinfo/webware-discuss
+
+
+Python Links
+------------
+
+(links.python)
+
+Home Page
+ - http://www.python.org/
+
+On-line Documentation
+ - http://www.python.org/doc/
+
+SourceForge Project Page
+ - http://sf.net/projects/python/
+
+The Vaults of Parnassus: Python Resources
+ - http://www.vex.net/parnassus/
+
+Python Cookbook
+ - http://aspn.activestate.com/ASPN/Cookbook/Python
+
+
+Other Useful Links
+------------------
+
+(links.other)
+
+Python Database Modules and Open Source Databases
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+(links.database)
+
+Python Database Topic Guide
+ - http://python.org/topics/database/
+
+PostgreSQL Database
+ - http://www.postgresql.org/index.html
+
+MySQL Database
+ - http://www.mysql.com/
+
+A comparison of PostgreSQL and MySQL
+ - http://phpbuilder.com/columns/tim20001112.php3
+
+
+Other Template Systems
+~~~~~~~~~~~~~~~~~~~~~~
+
+(links.other.templateSystems)
+
+Chuck's "Templates" Summary Page
+ - http://webware.sf.net/Papers/Templates/
+
+
+Other Internet development frameworks
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+(links.internet)
+
+ZOPE (Z Object Publishing Environment)
+ - http://zope.org/
+
+Server Side Java
+ - http://jakarta.apache.org/
+
+PHP
+ - http://php.net/
+
+IBM Websphere
+ - http://www.ibm.com/websphere/
+
+Coldfusion and Spectra
+ - http://www.macromedia.com/
+
+
+
diff --git a/www/users_guide/nonHtml.rst b/www/users_guide/nonHtml.rst
new file mode 100644
index 0000000..19f9667
--- /dev/null
+++ b/www/users_guide/nonHtml.rst
@@ -0,0 +1,16 @@
+Non-HTML Output
+===============
+
+(nonHTML)
+
+Cheetah can also output any other text format besides HTML.
+
+Python source code
+------------------
+
+(nonHTML.python)
+
+To be written. We're in the middle of working on an autoindenter to
+make it easier to encode Python indentation in a Cheetah template.
+
+
diff --git a/www/users_guide/optikLicense.rst b/www/users_guide/optikLicense.rst
new file mode 100644
index 0000000..c428381
--- /dev/null
+++ b/www/users_guide/optikLicense.rst
@@ -0,0 +1,48 @@
+.. role:: math(raw)
+ :format: html latex
+
+Optik license
+=============
+
+(optikLicense)
+
+The optik package (Cheetah.Utils.optik) is based on Optik 1.3,
+http://optik.sourceforge.net/, © 2001 Gregory P Ward
+:math:`$<$`gward@python.net:math:`$>$`. It's unmodified from the
+original version except the {import} statements, which have been
+adjusted to make them work in this location. The following license
+applies to optik:
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ - 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.
+
+ - Neither the name of the author 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 COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "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
+ REGENTS OR CONTRIBUTORS 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.
+
+
+
diff --git a/www/users_guide/otherHtml.rst b/www/users_guide/otherHtml.rst
new file mode 100644
index 0000000..eeceaab
--- /dev/null
+++ b/www/users_guide/otherHtml.rst
@@ -0,0 +1,101 @@
+non-Webware HTML output
+=======================
+
+(otherHTML)
+
+Cheetah can be used with all types of HTML output, not just with
+Webware.
+
+Static HTML Pages
+-----------------
+
+(otherHTML.static)
+
+Some sites like Linux Gazette (http://www.linuxgazette.com/)
+require completely static pages because they are mirrored on
+servers running completely different software from the main site.
+Even dynamic sites may have one or two pages that are static for
+whatever reason, and the site administrator may wish to generate
+those pages from Cheetah templates.
+
+There's nothing special here. Just create your templates as usual.
+Then compile and fill them whenever the template definition
+changes, and fill them again whenever the placeholder values
+change. You may need an extra step to copy the .html files to their
+final location. A Makefile (chapter tips.Makefile) can help
+encapsulate these steps.
+
+CGI scripts
+-----------
+
+(otherHTML)
+
+Unlike Webware servlets, which don't have to worry about the HTTP
+headers, CGI scripts must emit their own headers. To make a
+template CGI aware, add this at the top:
+
+::
+
+ #extends Cheetah.Tools.CGITemplate
+ #implements respond
+ $cgiHeaders#slurp
+
+Or if your template is inheriting from a Python class:
+
+::
+
+ #extends MyPythonClass
+ #implements respond
+ $cgiHeaders#slurp
+
+A sample Python class:
+
+::
+
+ from Cheetah.Tools import CGITemplate
+ class MyPythonClass(CGITemplate):
+ def cgiHeadersHook(self):
+ return "Content-Type: text/html; charset=koi8-r\n\n"
+
+Compile the template as usual, put the .py template module in your
+cgi-bin directory and give it execute permission. {.cgiHeaders()}
+is a "smart" method that outputs the headers if the module is
+called as a CGI script, or outputs nothing if not. Being
+"called as a CGI script" means the environmental variable
+{REQUEST\_METHOD} exists and {self.isControlledByWebKit} is false.
+If you don't agree with that definition, override {.isCgi()} and
+provide your own.
+
+The default header is a simple ``Content-type: text/html\n\n``,
+which works with all CGI scripts. If you want to customize the
+headers (e.g., to specify the character set), override
+{.cgiHeadersHook()} and return a string containing all the headers.
+Don't forget to include the extra newline at the end of the string:
+the HTTP protocol requires this empty line to mark the end of the
+headers.
+
+To read GET/POST variables from form input, use the {.webInput()}
+method (section webware.webInput), or extract them yourself using
+Python's {cgi} module or your own function. Although {.webInput()}
+was originally written for Webware servlets, it now handles CGI
+scripts too. There are a couple behavioral differences between CGI
+scripts and Webware servlets regarding input variables:
+
+
+#. CGI scripts, using Python's {cgi} module, believe
+ {REQUEST\_METHOD} and recognize { either} GET variables { or} POST
+ variables, not both. Webware servlets, doing additional processing,
+ ignore {REQUEST\_METHOD} and recognize both, like PHP does.
+
+#. Webware servlets can ask for cookies or session variables
+ instead of GET/POST variables, by passing the argument {src='c'} or
+ {src='s'}. CGI scripts get a {RuntimeError} if they try to do
+ this.
+
+
+If you keep your .tmpl files in the same directory as your CGI
+scripts, make sure they don't have execute permission. Apache at
+least refuses to serve files in a {ScriptAlias} directory that
+don't have execute permission.
+
+
diff --git a/www/users_guide/output.rst b/www/users_guide/output.rst
new file mode 100644
index 0000000..43c80ce
--- /dev/null
+++ b/www/users_guide/output.rst
@@ -0,0 +1,468 @@
+.. role:: math(raw)
+ :format: html latex
+
+Generating, Caching and Filtering Output
+========================================
+
+(output)
+
+Output from complex expressions: #echo
+--------------------------------------
+
+(output.echo)
+
+Syntax:
+
+::
+
+ #echo EXPR
+
+The {#echo} directive is used to echo the output from expressions
+that can't be written as simple $placeholders.
+
+::
+
+ Here is my #echo ', '.join(['silly']*5) # example
+
+This produces:
+
+::
+
+ Here is my silly, silly, silly, silly, silly example.
+
+Executing expressions without output: #silent
+---------------------------------------------
+
+(output.silent)
+
+Syntax:
+
+::
+
+ #silent EXPR
+
+{#silent} is the opposite of {#echo}. It executes an expression but
+discards the output.
+
+::
+
+ #silent $myList.reverse()
+ #silent $myList.sort()
+ Here is #silent $covertOperation() # nothing
+
+If your template requires some Python code to be executed at the
+beginning; (e.g., to calculate placeholder values, access a
+database, etc), you can put it in a "doEverything" method you
+inherit, and call this method using {#silent} at the top of the
+template.
+
+One-line #if
+------------
+
+(output.oneLineIf)
+
+Syntax:
+
+::
+
+ #if EXPR1 then EXPR2 else EXPR3#
+
+The {#if} flow-control directive (section flowControl.if) has a
+one-line counterpart akin to Perl's and C's {?:} operator. If
+{EXPR1} is true, it evaluates {EXPR2} and outputs the result (just
+like {#echo EXPR2#}). Otherwise it evaluates {EXPR3} and outputs
+that result. This directive is short-circuiting, meaning the
+expression that isn't needed isn't evaluated.
+
+You MUST include both 'then' and 'else'. If this doesn't work for
+you or you don't like the style use multi-line {#if} directives
+(section flowControl.if).
+
+The trailing {#} is the normal end-of-directive character. As usual
+it may be omitted if there's nothing after the directive on the
+same line.
+
+Caching Output
+--------------
+
+(output.caching)
+
+Caching individual placeholders
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+(output.caching.placeholders)
+
+By default, the values of each $placeholder is retrieved and
+interpolated for every request. However, it's possible to cache the
+values of individual placeholders if they don't change very often,
+in order to speed up the template filling.
+
+To cache the value of a single {$placeholder}, add an asterisk
+after the $; e.g., {$\*var}. The first time the template is filled,
+{$var} is looked up. Then whenever the template is filled again,
+the cached value is used instead of doing another lookup.
+
+The {$\*} format caches "forever"; that is, as long as the template
+instance remains in memory. It's also possible to cache for a
+certain time period using the form {$\*<interval>\*variable}, where
+{<interval>} is the interval. The time interval can be specified in
+seconds (5s), minutes (15m), hours (3h), days (2d) or weeks (1.5w).
+The default is minutes.
+
+::
+
+ <HTML>
+ <HEAD><TITLE>$title</TITLE></HEAD>
+ <BODY>
+
+ $var ${var} ## dynamic - will be reinterpolated for each request
+ $*var2 $*{var2} ## static - will be interpolated only once at start-up
+ $*5*var3 $*5*{var3} ## timed refresh - will be updated every five minutes.
+
+ </BODY>
+ </HTML>
+
+Note that "every five minutes" in the example really means every
+five minutes: the variable is looked up again when the time limit
+is reached, whether the template is being filled that frequently or
+not. Keep this in mind when setting refresh times for CPU-intensive
+or I/O intensive operations.
+
+If you're using the long placeholder syntax, ``${}``, the braces go
+only around the placeholder name: ``$*.5h*{var.func('arg')}``.
+
+Sometimes it's preferable to explicitly invalidate a cached item
+whenever you say so rather than at certain time intervals. You
+can't do this with individual placeholders, but you can do it with
+cached regions, which will be described next.
+
+Caching entire regions
+~~~~~~~~~~~~~~~~~~~~~~
+
+(output.caching.regions)
+
+Syntax:
+
+::
+
+ #cache [id=EXPR] [timer=EXPR] [test=EXPR]
+ #end cache
+
+The {#cache} directive is used to cache a region of content in a
+template. The region is cached as a single unit, after placeholders
+and directives inside the region have been evaluated. If there are
+any {$\*<interval>\*var} placholders inside the cache region, they
+are refreshed only when { both} the cache region { and} the
+placeholder are simultaneously due for a refresh.
+
+Caching regions offers more flexibility than caching individual
+placeholders. You can specify the refresh interval using a
+placeholder or expression, or refresh according to other criteria
+rather than a certain time interval.
+
+{#cache} without arguments caches the region statically, the same
+way as {$\*var}. The region will not be automatically refreshed.
+
+To refresh the region at an interval, use the {timer=EXPRESSION}
+argument, equivalent to {$\*<interval>\*}. The expression should
+evaluate to a number or string that is a valid interval (e.g., 0.5,
+'3m', etc).
+
+To refresh whenever an expression is true, use {test=EXPRESSION}.
+The expression can be a method/function returning true or false, a
+boolean placeholder, several of these joined by {and} and/or {or},
+or any other expression. If the expression contains spaces, it's
+easier to read if you enclose it in {()}, but this is not
+required.
+
+To refresh whenever you say so, use {id=EXPRESSION}. Your program
+can then call {.refreshCache(ID)} whenever it wishes. This is
+useful if the cache depends on some external condition that changes
+infrequently but has just changed now.
+
+You can combine arguments by separating them with commas. For
+instance, you can specify both {id=} and {interval=}, or {id=} and
+{test=}. (You can also combine interval and test although it's not
+very useful.) However, repeating an argument is undefined.
+
+::
+
+ #cache
+ This is a static cache. It will not be refreshed.
+ $a $b $c
+ #end cache
+
+ #cache timer='30m', id='cache1'
+ #for $cust in $customers
+ $cust.name:
+ $cust.street - $cust.city
+ #end for
+ #end cache
+
+ #cache id='sidebar', test=$isDBUpdated
+ ... left sidebar HTML ...
+ #end cache
+
+ #cache id='sidebar2', test=($isDBUpdated or $someOtherCondition)
+ ... right sidebar HTML ...
+ #end cache
+
+The {#cache} directive cannot be nested.
+
+We are planning to add a {'varyBy'} keyword argument in the future
+that will allow a separate cache instances to be created for a
+variety of conditions, such as different query string parameters or
+browser types. This is inspired by ASP.net's varyByParam and
+varyByBrowser output caching keywords.
+
+#raw
+----
+
+(output.raw)
+
+Syntax:
+
+::
+
+ #raw
+ #end raw
+
+Any section of a template definition that is inside a {#raw ...
+#end raw} tag pair will be printed verbatim without any parsing of
+$placeholders or other directives. This can be very useful for
+debugging, or for Cheetah examples and tutorials.
+
+{#raw} is conceptually similar to HTML's {<PRE>} tag and LaTeX's {
+verbatim{}} tag, but unlike those tags, {#raw} does not cause the
+body to appear in a special font or typeface. It can't, because
+Cheetah doesn't know what a font is.
+
+#include
+--------
+
+(output.include)
+
+Syntax:
+
+::
+
+ #include [raw] FILENAME_EXPR
+ #include [raw] source=STRING_EXPR
+
+The {#include} directive is used to include text from outside the
+template definition. The text can come from an external file or
+from a {$placeholder} variable. When working with external files,
+Cheetah will monitor for changes to the included file and update as
+necessary.
+
+This example demonstrates its use with external files:
+
+::
+
+ #include "includeFileName.txt"
+
+The content of "includeFileName.txt" will be parsed for Cheetah
+syntax.
+
+And this example demonstrates use with {$placeholder} variables:
+
+::
+
+ #include source=$myParseText
+
+The value of {$myParseText} will be parsed for Cheetah syntax. This
+is not the same as simply placing the $placeholder tag
+"{$myParseText}" in the template definition. In the latter case,
+the value of $myParseText would not be parsed.
+
+By default, included text will be parsed for Cheetah tags. The
+argument "{raw}" can be used to suppress the parsing.
+
+::
+
+ #include raw "includeFileName.txt"
+ #include raw source=$myParseText
+
+Cheetah wraps each chunk of {#include} text inside a nested
+{Template} object. Each nested template has a copy of the main
+template's searchList. However, {#set} variables are visible across
+includes only if the defined using the {#set global} keyword.
+
+All directives must be balanced in the include file. That is, if
+you start a {#for} or {#if} block inside the include, you must end
+it in the same include. (This is unlike PHP, which allows
+unbalanced constructs in include files.)
+
+#slurp
+------
+
+(output.slurp)
+
+Syntax:
+
+::
+
+ #slurp
+
+The {#slurp} directive eats up the trailing newline on the line it
+appears in, joining the following line onto the current line.
+
+It is particularly useful in {#for} loops:
+
+::
+
+ #for $i in range(5)
+ $i #slurp
+ #end for
+
+outputs:
+
+::
+
+ 0 1 2 3 4
+
+#indent
+-------
+
+(output.indent)
+
+This directive is not implemented yet. When/if it's completed, it
+will allow you to
+
+
+#. indent your template definition in a natural way (e.g., the
+ bodies of {#if} blocks) without affecting the output
+
+#. add indentation to output lines without encoding it literally in
+ the template definition. This will make it easier to use Cheetah to
+ produce indented source code programmatically (e.g., Java or Python
+ source code).
+
+
+There is some experimental code that recognizes the {#indent}
+directive with options, but the options are purposely undocumented
+at this time. So pretend it doesn't exist. If you have a use for
+this feature and would like to see it implemented sooner rather
+than later, let us know on the mailing list.
+
+The latest specification for the future {#indent} directive is in
+the TODO file in the Cheetah source distribution.
+
+Ouput Filtering and #filter
+---------------------------
+
+(output.filter)
+
+Syntax:
+
+::
+
+ #filter FILTER_CLASS_NAME
+ #filter $PLACEHOLDER_TO_A_FILTER_INSTANCE
+ #filter None
+
+Output from $placeholders is passed through an ouput filter. The
+default filter merely returns a string representation of the
+placeholder value, unless the value is {None}, in which case the
+filter returns an empty string. Only top-level placeholders invoke
+the filter; placeholders inside expressions do not.
+
+Certain filters take optional arguments to modify their behaviour.
+To pass arguments, use the long placeholder syntax and precede each
+filter argument by a comma. By convention, filter arguments don't
+take a {$} prefix, to avoid clutter in the placeholder tag which
+already has plenty of dollar signs. For instance, the MaxLen filter
+takes an argument 'maxlen':
+
+::
+
+ ${placeholderName, maxlen=20}
+ ${functionCall($functionArg), maxlen=$myMaxLen}
+
+To change the output filter, use the {'filter'} keyword to the
+{Template} class constructor, or the {#filter} directive at runtime
+(details below). You may use {#filter} as often as you wish to
+switch between several filters, if certain {$placeholders} need one
+filter and other {$placeholders} need another.
+
+The standard filters are in the module {Cheetah.Filters}. Cheetah
+currently provides:
+
+ The default filter, which converts None to '' and everything else
+ to {str(whateverItIs)}. This is the base class for all other
+ filters, and the minimum behaviour for all filters distributed with
+ Cheetah.
+
+ Same.
+
+ Same, but truncate the value if it's longer than a certain length.
+ Use the 'maxlen' filter argument to specify the length, as in the
+ examples above. If you don't specify 'maxlen', the value will not
+ be truncated.
+
+ Output a "pageful" of a long string. After the page, output HTML
+ hyperlinks to the previous and next pages. This filter uses several
+ filter arguments and environmental variables, which have not been
+ documented yet.
+
+ Same as default, but convert HTML-sensitive characters
+ (':math:`$<$`', '&', ':math:`$>$`') to HTML entities so that the
+ browser will display them literally rather than interpreting them
+ as HTML tags. This is useful with database values or user input
+ that may contain sensitive characters. But if your values contain
+ embedded HTML tags you want to preserve, you do not want this
+ filter.
+
+ The filter argument 'also' may be used to specify additional
+ characters to escape. For instance, say you want to ensure a value
+ displays all on one line. Escape all spaces in the value with
+ '&nbsp', the non-breaking space:
+
+ ::
+
+ ${$country, also=' '}}
+
+
+To switch filters using a class object, pass the class using the {
+filter} argument to the Template constructor, or via a placeholder
+to the {#filter} directive: {#filter $myFilterClass}. The class
+must be a subclass of {Cheetah.Filters.Filter}. When passing a
+class object, the value of { filtersLib} does not matter, and it
+does not matter where the class was defined.
+
+To switch filters by name, pass the name of the class as a string
+using the { filter} argument to the Template constructor, or as a
+bare word (without quotes) to the {#filter} directive: {#filter
+TheFilter}. The class will be looked up in the { filtersLib}.
+
+The filtersLib is a module containing filter classes, by default
+{Cheetah.Filters}. All classes in the module that are subclasses of
+{Cheetah.Filters.Filter} are considered filters. If your filters
+are in another module, pass the module object as the { filtersLib}
+argument to the Template constructor.
+
+Writing a custom filter is easy: just override the {.filter}
+method.
+
+::
+
+ def filter(self, val, **kw): # Returns a string.
+
+Return the { string} that should be output for 'val'. 'val' may be
+any type. Most filters return \`' for {None}. Cheetah passes one
+keyword argument: ``kw['rawExpr']`` is the placeholder name as it
+appears in the template definition, including all subscripts and
+arguments. If you use the long placeholder syntax, any options you
+pass appear as keyword arguments. Again, the return value must be a
+string.
+
+You can always switch back to the default filter this way: {#filter
+None}. This is easy to remember because "no filter" means the
+default filter, and because None happens to be the only object the
+default filter treats specially.
+
+We are considering additional filters; see
+http://webware.colorstudy.net/twiki/bin/view/Cheetah/MoreFilters
+for the latest ideas.
+
+
diff --git a/www/users_guide/parserInstructions.rst b/www/users_guide/parserInstructions.rst
new file mode 100644
index 0000000..a85e70a
--- /dev/null
+++ b/www/users_guide/parserInstructions.rst
@@ -0,0 +1,129 @@
+Instructions to the Parser/Compiler
+===================================
+
+(parserInstructions)
+
+#breakpoint
+-----------
+
+(parserInstructions.breakpoint)
+
+Syntax:
+
+::
+
+ #breakpoint
+
+{#breakpoint} is a debugging tool that tells the parser to stop
+parsing at a specific point. All source code from that point on
+will be ignored.
+
+The difference between {#breakpoint} and {#stop} is that {#stop}
+occurs in normal templates (e.g., inside an {#if}) but
+{#breakpoint} is used only when debugging Cheetah. Another
+difference is that {#breakpoint} operates at compile time, while
+{#stop} is executed at run time while filling the template.
+
+#compiler-settings
+------------------
+
+(parserInstructions.compiler-settings)
+
+Syntax:
+
+::
+
+ #compiler-settings
+ key = value (no quotes)
+ #end compiler-settings
+
+ #compiler-settings reset
+
+The {#compiler-settings} directive overrides Cheetah's standard
+settings, changing how it parses source code and generates Python
+code. This makes it possible to change the behaviour of Cheetah's
+parser/compiler for a certain template, or within a portion of the
+template.
+
+The {reset} argument reverts to the default settings. With {reset},
+there's no end tag.
+
+Here are some examples of what you can do:
+
+::
+
+ $myVar
+ #compiler-settings
+ cheetahVarStartToken = @
+ #end compiler-settings
+ @myVar
+ #compiler-settings reset
+ $myVar
+
+::
+
+ ## normal comment
+ #compiler-settings
+ commentStartToken = //
+ #end compiler-settings
+
+ // new style of comment
+
+ #compiler-settings reset
+
+ ## back to normal comments
+
+::
+
+ #slurp
+ #compiler-settings
+ directiveStartToken = %
+ #end compiler-settings
+
+ %slurp
+ %compiler-settings reset
+
+ #slurp
+
+Here's a partial list of the settings you can change:
+
+
+#. syntax settings
+
+
+ #. cheetahVarStartToken
+
+ #. commentStartToken
+
+ #. multilineCommentStartToken
+
+ #. multilineCommentEndToken
+
+ #. directiveStartToken
+
+ #. directiveEndToken
+
+
+#. code generation settings
+
+
+ #. commentOffset
+
+ #. outputRowColComments
+
+ #. defDocStrMsg
+
+ #. useNameMapper
+
+ #. useAutocalling
+
+ #. reprShortStrConstants
+
+ #. reprNewlineThreshold
+
+
+
+The meaning of these settings and their default values will be
+documented in the future.
+
+
diff --git a/www/users_guide/tipsAndTricks.rst b/www/users_guide/tipsAndTricks.rst
new file mode 100644
index 0000000..4438990
--- /dev/null
+++ b/www/users_guide/tipsAndTricks.rst
@@ -0,0 +1,586 @@
+Tips, Tricks and Troubleshooting
+================================
+
+(tips)
+
+This chapter contains short stuff that doesn't fit anywhere else.
+
+See the Cheetah FAQ for more specialized issues and for
+troubleshooting tips. Check the wiki periodically for recent tips
+contributed by users. If you get stuck and none of these resources
+help, ask on the mailing list.
+
+Placeholder Tips
+----------------
+
+(tips.placeholder)
+
+Here's how to do certain important lookups that may not be obvious.
+For each, we show first the Cheetah expression and then the Python
+equivalent, because you can use these either in templates or in
+pure Python subclasses. The Cheetah examples use NameMapper
+shortcuts (uniform dotted notation, autocalling) as much as
+possible.
+
+To verify whether a variable exists in the searchList:
+
+::
+
+ $varExists('theVariable')
+ self.varExists('theVariable')
+
+This is useful in {#if} or {#unless} constructs to avoid a
+{#NameMapper.NotFound} error if the variable doesn't exist. For
+instance, a CGI GET parameter that is normally supplied but in this
+case the user typed the URL by hand and forgot the parameter (or
+didn't know about it). ({.hasVar} is a synonym for {.varExists}.)
+
+To look up a variable in the searchList from a Python method:
+
+::
+
+ self.getVar('theVariable')
+ self.getVar('theVariable', myDefault)
+
+This is the equivalent to {$theVariable} in the template. If the
+variable is missing, it returns the second argument, {myDefault},
+if present, or raises {NameMapper.NotFound} if there is no second
+argument. However, it usually easier to write your method so that
+all needed searchList values come in as method arguments. That way
+the caller can just use a {$placeholder} to specify the argument,
+which is less verbose than you writing a getVar call.
+
+To do a "safe" placeholder lookup that returns a default value if
+the variable is missing:
+
+::
+
+ $getVar('theVariable', None)
+ $getVar('theVariable', $myDefault)
+
+To get an environmental variable, put {os.environ} on the
+searchList as a container. Or read the envvar in Python code and
+set a placeholder variable for it.
+
+Remember that variables found earlier in the searchList override
+same-name variables located in a later searchList object. Be
+careful when adding objects containing other variables besides the
+ones you want (e.g., {os.environ}, CGI parameters). The "other"
+variables may override variables your application depends on,
+leading to hard-to-find bugs. Also, users can inadvertently or
+maliciously set an environmental variable or CGI parameter you
+didn't expect, screwing up your program. To avoid all this, know
+what your namespaces contain, and place the namespaces you have the
+most control over first. For namespaces that could contain
+user-supplied "other" variables, don't put the namespace itself in
+the searchList; instead, copy the needed variables into your own
+"safe" namespace.
+
+Diagnostic Output
+-----------------
+
+(tips.diagnostic)
+
+If you need send yourself some debugging output, you can use
+{#silent} to output it to standard error:
+
+::
+
+ #silent $sys.stderr.write("Incorrigible var is '$incorrigible'.\n")
+ #silent $sys.stderr.write("Is 'unknown' in the searchList? " +
+ $getVar("unknown", "No.") + "\n" )
+
+(Tip contributed by Greg Czajkowski.)
+
+When to use Python methods
+--------------------------
+
+(tips.pythonMethods)
+
+You always have a choice whether to code your methods as Cheetah
+{#def} methods or Python methods (the Python methods being located
+in a class your template inherits). So how do you choose?
+
+Generally, if the method consists mostly of text and placeholders,
+use a Cheetah method (a {#def} method). That's why {#def} exists,
+to take the tedium out of writing those kinds of methods. And if
+you have a couple {#if} stanzas to {#set} some variables, followed
+by a {#for} loop, no big deal. But if your method consists mostly
+of directives and only a little text, you're better off writing it
+in Python. Especially be on the watch for extensive use of {#set},
+{#echo} and {#silent} in a Cheetah method-it's a sure sign you're
+probably using the wrong language. Of course, though, you are free
+to do so if you wish.
+
+Another thing that's harder to do in Cheetah is adjacent or nested
+multiline stanzas (all those directives with an accompanying {#end}
+directive). Python uses indentation to show the beginning and end
+of nested stanzas, but Cheetah can't do that because any
+indentation shows up in the output, which may not be desired. So
+unless all those extra spaces and tabs in the output are
+acceptable, you have to keep directives flush with the left margin
+or the preceding text.
+
+The most difficult decisions come when you have conflicting goals.
+What if a method generates its output in parts (i.e., output
+concatenation), contains many searchList placeholders and lots of
+text, { and} requires lots of {#if ... #set ... #else #set ... #end
+if} stanzas. A Cheetah method would be more advantageous in some
+ways, but a Python method in others. You'll just have to choose,
+perhaps coding groups of methods all the same way. Or maybe you can
+split your method into two, one Cheetah and one Python, and have
+one method call the other. Usually this means the Cheetah method
+calling the Python method to calculate the needed values, then the
+Cheetah method produces the output. One snag you might run into
+though is that {#set} currently can set only one variable per
+statement, so if your Python method needs to return multiple values
+to your Cheetah method, you'll have to do it another way.
+
+Calling superclass methods, and why you have to
+-----------------------------------------------
+
+(tips.callingSuperclassMethods)
+
+If your template or pure Python class overrides a standard method
+or attribute of {Template} or one of its base classes, you should
+call the superclass method in your method to prevent various things
+from breaking. The most common methods to override are {.awake} and
+{.\_\_init\_\_}. {.awake} is called automatically by Webware early
+during the web transaction, so it makes a convenient place to put
+Python initialization code your template needs. You'll definitely
+want to call the superclass {.awake} because it sets up many
+wonderful attributes and methods, such as those to access the CGI
+input fields.
+
+There's nothing Cheetah-specific to calling superclass methods, but
+because it's vital, we'll recap the standard Python techniques
+here. We mention only the solution for old-style classes because
+Cheetah classes are old-style (in other Python documentation, you
+will find the technique for new-style classes, but they are not
+listed here because they cannot be used with Cheetah if you use
+dynamically-compiled templates).
+
+::
+
+ from Cheetah.Template import Template
+ class MyClass(Template):
+ def awake(self, trans):
+ Template.awake(self, trans)
+ ... great and exciting features written by me ...
+
+[ @@MO: Need to test this. .awake is in Servlet, which is a
+superclass of Template. Do we really need both imports? Can we call
+Template.awake? ]
+
+To avoid hardcoding the superclass name, you can use this function
+{callbase()}, which emulates {super()} for older versions of
+Python. It also works even {super()} does exist, so you don't have
+to change your servlets immediately when upgrading. Note that the
+argument sequence is different than {super} uses.
+
+::
+
+ ===========================================================================
+ # Place this in a module SOMEWHERE.py . Contributed by Edmund Lian.
+ class CallbaseError(AttributeError):
+ pass
+
+ def callbase(obj, base, methodname='__init__', args=(), kw={},
+ raiseIfMissing=None):
+ try: method = getattr(base, methodname)
+ except AttributeError:
+ if raiseIfMissing:
+ raise CallbaseError, methodname
+ return None
+ if args is None: args = ()
+ return method(obj, *args, **kw)
+ ===========================================================================
+ # Place this in your class that's overriding .awake (or any method).
+ from SOMEWHERE import callbase
+ class MyMixin:
+ def awake(self, trans):
+ args = (trans,)
+ callbase(self, MyMixin, 'awake', args)
+ ... everything else you want to do ...
+ ===========================================================================
+
+All methods
+-----------
+
+(tips.allMethods)
+
+Here is a list of all the standard methods and attributes that can
+be accessed from a placeholder. Some of them exist for you to call,
+others are mainly used by Cheetah internally but you can call them
+if you wish, and others are only for internal use by Cheetah or
+Webware. Do not use these method names in mixin classes
+({#extends}, section inheritanceEtc.extends) unless you intend to
+override the standard method.
+
+Variables with a star prefix ({ \*}) are frequently used in
+templates or in pure Python classes.
+
+\*{Inherited from Cheetah.Template}
+
+ Compile the template. Automatically called by {.\_\_init\_\_}.
+
+ Return the module code the compiler generated, or {None} if no
+ compilation took place.
+
+ Return the class code the compiler generated, or {None} if no
+ compilation took place.
+
+ Return a reference to the underlying search list. (a list of
+ objects). Use this to print out your searchList for debugging.
+ Modifying the returned list will affect your placeholder searches!
+
+ Return a reference to the current error catcher.
+
+ If 'cacheKey' is not {None}, refresh that item in the cache. If
+ {None}, delete all items in the cache so they will be recalculated
+ the next time they are encountered.
+
+ Break reference cycles before discarding a servlet.
+
+ Look up a variable in the searchList. Same as {$varName} but allows
+ you to specify a default value and control whether autocalling
+ occurs.
+
+ Read the named file. If used as a placeholder, inserts the file's
+ contents in the output without interpretation, like {#include raw}.
+ If used in an expression, returns the file's content (e.g., to
+ assign it to a variable).
+
+ This is what happens if you run a .py template module as a
+ standalone program.
+
+
+\*{Inherited from Cheetah.Utils.WebInputMixin}
+
+ Exception raised by {.webInput}.
+
+ Convenience method to access GET/POST variables from a Webware
+ servlet or CGI script, or Webware cookie or session variables. See
+ section webware.webInput for usage information.
+
+
+\*{Inherited from Cheetah.SettingsManager}
+
+ Get a compiler setting.
+
+ Does this compiler setting exist?
+
+ Set setting 'name' to 'value'. See {#compiler-settings}, section
+ parserInstructions.compiler-settings.
+
+ Return the underlying settings dictionary. (Warning: modifying this
+ dictionary will change Cheetah's behavior.)
+
+ Return a copy of the underlying settings dictionary.
+
+ Return a deep copy of the underlying settings dictionary. See
+ Python's {copy} module.
+
+ Update Cheetah's compiler settings from the 'newSettings'
+ dictionary. If 'merge' is true, update only the names in
+ newSettings and leave the other names alone. (The SettingsManager
+ is smart enough to update nested dictionaries one key at a time
+ rather than overwriting the entire old dictionary.) If 'merge' is
+ false, delete all existing settings so that the new ones are the
+ only settings.
+
+ Same, but pass a string of {name=value} pairs rather than a
+ dictionary, the same as you would provide in a {#compiler-settings}
+ directive, section parserInstructions.compiler-settings.
+
+ Same, but exec a Python source file and use the variables it
+ contains as the new settings. (e.g.,
+ {cheetahVarStartToken = "@"}).
+
+ Same, but get the new settings from a text file in ConfigParser
+ format (similar to Windows' \*.ini file format). See Python's
+ {ConfigParser} module.
+
+ Same, but read the open file object 'inFile' for the new settings.
+
+ Same, but read the new settings from a string in ConfigParser
+ format.
+
+ Write the current compiler settings to a file named 'path' in
+ \*.ini format.
+
+ Return a string containing the current compiler settings in \*.ini
+ format.
+
+
+\*{Inherited from Cheetah.Servlet}
+
+{ Do not override these in a subclass or assign to them as
+attributes if your template will be used as a servlet,} otherwise
+Webware will behave unpredictably. However, it { is} OK to put
+same-name variables in the searchList, because Webware does not use
+the searchList.
+
+EXCEPTION: It's OK to override { awake} and { sleep} as long as you
+call the superclass methods. (See section
+tips.callingSuperclassMethods.)
+
+ True if this template instance is part of a live transaction in a
+ running WebKit servlet.
+
+ True if Webware is installed and the template instance inherits
+ from WebKit.Servlet. If not, it inherits from
+ Cheetah.Servlet.DummyServlet.
+
+ Called by WebKit at the beginning of the web transaction.
+
+ Called by WebKit at the end of the web transaction.
+
+ Called by WebKit to produce the web transaction content. For a
+ template-servlet, this means filling the template.
+
+ Break reference cycles before deleting instance.
+
+ The filesystem pathname of the template-servlet (as opposed to the
+ URL path).
+
+ The current Webware transaction.
+
+ The current Webware application.
+
+ The current Webware response.
+
+ The current Webware request.
+
+ The current Webware session.
+
+ Call this method to insert text in the filled template output.
+
+
+Several other goodies are available to template-servlets under the
+{request} attribute, see section webware.input.
+
+{transaction}, {response}, {request} and {session} are created from
+the current transaction when WebKit calls {awake}, and don't exist
+otherwise. Calling {awake} yourself (rather than letting WebKit
+call it) will raise an exception because the {transaction} argument
+won't have the right attributes.
+
+\*{Inherited from WebKit.Servlet} These are accessible only if
+Cheetah knows Webware is installed. This listing is based on a CVS
+snapshot of Webware dated 22 September 2002, and may not include
+more recent changes.
+
+The same caveats about overriding these methods apply.
+
+ The simple name of the class. Used by Webware's logging and
+ debugging routines.
+
+ Used by Webware's logging and debugging routines.
+
+ True if the servlet can be multithreaded.
+
+ True if the servlet can be used for another transaction after the
+ current transaction is finished.
+
+ Depreciated by {.serverSidePath()}.
+
+
+Optimizing templates
+--------------------
+
+(tips.optimizing)
+
+Here are some things you can do to make your templates fill faster
+and user fewer CPU cycles. Before you put a lot of energy into
+this, however, make sure you really need to. In many situations,
+templates appear to initialize and fill instantaneously, so no
+optimization is necessary. If you do find a situation where your
+templates are filling slowly or taking too much memory or too many
+CPU cycles, we'd like to hear about it on the mailing list.
+
+Cache $placeholders whose values don't change frequently. (Section
+output.caching).
+
+Use {#set} for values that are very frequently used, especially if
+they come out of an expensive operation like a
+deeply.nested.structure or a database lookup. {#set} variables are
+set to Python local variables, which have a faster lookup time than
+Python globals or values from Cheetah's searchList.
+
+Moving variable lookups into Python code may provide a speedup in
+certain circumstances. If you're just reading {self} attributes,
+there's no reason to use NameMapper lookup ($placeholders) for
+them. NameMapper does a lot more work than simply looking up a
+{self} attribute.
+
+On the other hand, if you don't know exactly where the value will
+come from (maybe from {self}, maybe from the searchList, maybe from
+a CGI input variable, etc), it's easier to just make that an
+argument to your method, and then the template can handle all the
+NameMapper lookups for you:
+
+::
+
+ #silent $myMethod($arg1, $arg2, $arg3)
+
+Otherwise you'd have to call {self.getVar('arg1')} etc in your
+method, which is more wordy, and tedious.
+
+PSP-style tags
+--------------
+
+(tips.PSP)
+
+{<%= ... %>} and {<% ... %>} allow an escape to Python syntax
+inside the template. You do not need it to use Cheetah effectively,
+and we're hard pressed to think of a case to recommend it.
+Nevertheless, it's there in case you encounter a situation you
+can't express adequately in Cheetah syntax. For instance, to set a
+local variable to an elaborate initializer.
+
+{<%= ... %>} encloses a Python expression whose result will be
+printed in the output.
+
+{<% ... %>} encloses a Python statement or expression (or set of
+statements or expressions) that will be included as-is into the
+generated method. The statements themselves won't produce any
+output, but you can use the local function {write(EXPRESSION)} to
+produce your own output. (Actually, it's a method of a file-like
+object, but it looks like a local function.) This syntax also may
+be used to set a local variable with a complicated initializer.
+
+To access Cheetah services, you must use Python code like you would
+in an inherited Python class. For instance, use {self.getVar()} to
+look up something in the searchList.
+
+{ Warning:} { No error checking is done!} If you write:
+
+::
+
+ <% break %> ## Wrong!
+
+you'll get a {SyntaxError} when you fill the template, but that's
+what you deserve.
+
+Note that these are PSP-{ style} tags, not PSP tags. A Cheetah
+template is not a PSP document, and you can't use PSP commands in
+it.
+
+Makefiles
+---------
+
+(tips.Makefile)
+
+If your project has several templates and you get sick of typing
+"cheetah compile FILENAME.tmpl" all the time-much less remembering
+which commands to type when-and your system has the {make} command
+available, consider building a Makefile to make your life easier.
+
+Here's a simple Makefile that controls two templates,
+ErrorsTemplate and InquiryTemplate. Two external commands,
+{inquiry} and {receive}, depend on ErrorsTemplate.py. Aditionally,
+InquiryTemplate itself depends on ErrorsTemplate.
+
+::
+
+ all: inquiry receive
+
+ .PHONY: all receive inquiry printsource
+
+ printsource:
+ a2ps InquiryTemplate.tmpl ErrorsTemplate.tmpl
+
+ ErrorsTemplate.py: ErrorsTemplate.tmpl
+ cheetah compile ErrorsTemplate.tmpl
+
+ InquiryTemplate.py: InquiryTemplate.tmpl ErrorsTemplate.py
+ cheetah compile InquiryTemplate.tmpl
+
+ inquiry: InquiryTemplate.py ErrorsTemplate.py
+
+ receive: ErrorsTemplate.py
+
+Now you can type {make} anytime and it will recompile all the
+templates that have changed, while ignoring the ones that haven't.
+Or you can recompile all the templates {receive} needs by typing
+{make receive}. Or you can recompile only ErrorsTemplate by typing
+{make ErrorsTemplate}. There's also another target, "printsource":
+this sends a Postscript version of the project's source files to
+the printer. The .PHONY target is explained in the {make}
+documentation; essentially, you have it depend on every target that
+doesn't produce an output file with the same name as the target.
+
+Using Cheetah in a Multi-Threaded Application
+---------------------------------------------
+
+(tips.threads)
+
+Template classes may be shared freely between threads. However,
+template instances should not be shared unless you either:
+
+
+- Use a lock (mutex) to serialize template fills, to prevent two
+ threads from filling the template at the same time.
+
+- Avoid thread-unsafe features:
+
+
+ - Modifying searchList values or instance variables.
+
+ - Caching ({$\*var}, {#cache}, etc).
+
+ - {#set global}, {#filter}, {#errorCatcher}.
+
+
+ Any changes to these in one thread will be visible in other
+ threads, causing them to give inconsistent output.
+
+
+About the only advantage in sharing a template instance is building
+up the placeholder cache. But template instances are so low
+overhead that it probably wouldn't take perceptibly longer to let
+each thread instantiate its own template instance. Only if you're
+filling templates several times a second would the time difference
+be significant, or if some of the placeholders trigger extremely
+slow calculations (e.g., parsing a long text file each time). The
+biggest overhead in Cheetah is importing the {Template} module in
+the first place, but that has to be done only once in a
+long-running application.
+
+You can use Python's {mutex} module for the lock, or any similar
+mutex. If you have to change searchList values or instance
+variables before each fill (which is usually the case), lock the
+mutex before doing this, and unlock it only after the fill is
+complete.
+
+For Webware servlets, you're probably better off using Webware's
+servlet caching rather than Cheetah's caching. Don't override the
+servlet's {.canBeThreaded()} method unless you avoid the unsafe
+operations listed above.
+
+Using Cheetah with gettext
+--------------------------
+
+(tips.gettext)
+
+{ gettext} is a project for creating internationalized
+applications. For more details, visit
+http://docs.python.org/lib/module-gettext.html. gettext can be used
+with Cheetah to create internationalized applications, even for CJK
+character sets, but you must keep a couple things in mind:
+
+
+- xgettext is used on compiled templates, not on the templates
+ themselves.
+
+- The way the NameMapper syntax gets compiled to Python gets in
+ the way of the syntax that xgettext recognizes. Hence, a special
+ case exists for the functions {\_}, {N\_}, and {ngettext}. If you
+ need to use a different set of functions for marking strings for
+ translation, you must set the Cheetah setting {gettextTokens} to a
+ list of strings representing the names of the functions you are
+ using to mark strings for translation.
+
+
+
diff --git a/www/users_guide/webware.rst b/www/users_guide/webware.rst
new file mode 100644
index 0000000..75237ed
--- /dev/null
+++ b/www/users_guide/webware.rst
@@ -0,0 +1,598 @@
+Using Cheetah with Webware
+==========================
+
+(webware)
+
+{ Webware for Python} is a 'Python-Powered Internet Platform' that
+runs servlets in a manner similar to Java servlets. { WebKit} is
+the name of Webware's application server. For more details, please
+visit http://webware.sourceforge.net/.
+
+All comments below refer to the official version of Webware, the
+DamnSimple! offshoot at ?, and the now-abandoned
+WebwareExperimental implementation at
+http://sourceforge.net/projects/expwebware/, except where noted.
+All the implementations are 95% identical to the servlet writer:
+their differences lie in their internal structure and configuration
+files. One difference is that the executable you run to launch
+standard Webware is called {AppServer}, whereas in
+WebwareExperimental it's called {webkit}. But to servlets they're
+both "WebKit, Webware's application server", so it's one half dozen
+to the other. In this document, we generally use the term { WebKit}
+to refer to the currently-running application server.
+
+Installing Cheetah on a Webware system
+--------------------------------------
+
+(webware.installing)
+
+Install Cheetah after you have installed Webware, following the
+instructions in chapter gettingStarted.
+
+The standard Cheetah test suite ('cheetah test') does not test
+Webware features. We plan to build a test suite that can run as a
+Webware servlet, containing Webware-specific tests, but that has
+not been built yet. In the meantime, you can make a simple template
+containing something like "This is a very small template.", compile
+it, put the \*.py template module in a servlet directory, and see
+if Webware serves it up OK.
+
+{ You must not have a Webware context called "Cheetah".} If you do,
+Webware will mistake that directory for the Cheetah module
+directory, and all template-servlets will bomb out with a
+"ImportError: no module named Template". (This applies only to the
+standard Webware; WebwareExperimental does not have contexts.)
+
+If Webware complains that it cannot find your servlet, make sure
+'.tmpl' is listed in 'ExtensionsToIgnore' in your
+'Application.config' file.
+
+Containment vs Inheritance
+--------------------------
+
+(webware.background)
+
+Because Cheetah's core is flexible, there are many ways to
+integrate it with Webware servlets. There are two broad strategies:
+the { Inheritance approach} and the { Containment approach}. The
+difference is that in the Inheritance approach, your template
+object { is} the servlet, whereas in the Containment approach, the
+servlet is not a template but merely { uses} template(s) for
+portion(s) of its work.
+
+The Inheritance approach is recommended for new sites because it's
+simpler, and because it scales well for large sites with a
+site->section->subsection->servlet hierarchy. The Containment
+approach is better for existing servlets that you don't want to
+restructure. For instance, you can use the Containment approach to
+embed a discussion-forum table at the bottom of a web page.
+
+However, most people who use Cheetah extensively seem to prefer the
+Inheritance approach because even the most analytical servlet needs
+to produce { some} output, and it has to fit the site's look and
+feel { anyway}, so you may as well use a template-servlet as the
+place to put the output. Especially since it's so easy to add a
+template-servlet to a site once the framework is established. So we
+recommend you at least evaluate the effort that would be required
+to convert your site framework to template superclasses as
+described below, vs the greater flexibility and manageability it
+might give the site over the long term. You don't necessarily have
+to convert all your existing servlets right away: just build common
+site templates that are visually and behaviorally compatible with
+your specification, and use them for new servlets. Existing
+servlets can be converted later, if at all.
+
+Edmund Liam is preparing a section on a hybrid approach, in which
+the servlet is not a template, but still calls template(s) in an
+inheritance chain to produce the output. The advantage of this
+approach is that you aren't dealing with {Template} methods and
+Webware methods in the same object.
+
+The Containment Approach
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+(webware.containment)
+
+In the Containment approach, your servlet is not a template.
+Instead, it it makes its own arrangements to create and use
+template object(s) for whatever it needs. The servlet must
+explicitly call the template objects' {.respond()} (or
+{.\_\_str\_\_()}) method each time it needs to fill the template.
+This does not present the output to the user; it merely gives the
+output to the servlet. The servlet then calls its
+{#self.response().write()} method to send the output to the user.
+
+The developer has several choices for managing her templates. She
+can store the template definition in a string, file or database and
+call {Cheetah.Template.Template} manually on it. Or she can put the
+template definition in a \*.tmpl file and use { cheetah compile}
+(section howWorks.cheetah-compile) to convert it to a Python class
+in a \*.py module, and then import it into her servlet.
+
+Because template objects are not thread safe, you should not store
+one in a module variable and allow multiple servlets to fill it
+simultaneously. Instead, each servlet should instantiate its own
+template object. Template { classes}, however, are thread safe,
+since they don't change once created. So it's safe to store a
+template class in a module global variable.
+
+The Inheritance Approach
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+(webware.inheritance)
+
+In the Inheritance approach, your template object doubles as as
+Webware servlet, thus these are sometimes called {
+template-servlets}. { cheetah compile} (section
+howWorks.cheetah-compile) automatically creates modules containing
+valid Webware servlets. A servlet is a subclass of Webware's
+{WebKit.HTTPServlet} class, contained in a module with the same
+name as the servlet. WebKit uses the request URL to find the
+module, and then instantiates the servlet/template. The servlet
+must have a {.respond()} method (or {.respondToGet()},
+{.respondToPut()}, etc., but the Cheetah default is {.respond()}).
+Servlets created by {cheetah compile} meet all these requirements.
+
+(Cheetah has a Webware plugin that automatically converts a {.tmpl
+servlet file} into a {.py servlet file} when the {.tmpl servlet
+file} is requested by a browser. However, that plugin is currently
+unavailable because it's being redesigned. For now, use {cheetah
+compile} instead.)
+
+What about logic code? Cheetah promises to keep content (the
+placeholder values), graphic design (the template definition and is
+display logic), and algorithmic logic (complex calculations and
+side effects) separate. How? Where do you do form processing?
+
+The answer is that your template class can inherit from a pure
+Python class containing the analytical logic. You can either use
+the {#extends} directive in Cheetah to indicate the superclass(es),
+or write a Python {class} statement to do the same thing. See the
+template {Cheetah.Templates.SkeletonPage.tmpl} and its pure Python
+class {Cheetah.Templates.\_SkeletonPage.py} for an example of a
+template inheriting logic code. (See sections
+inheritanceEtc.extends and inheritanceEtc.implements for more
+information about {#extends} and {#implements}. They have to be
+used a certain right way.)
+
+If {#WebKit.HTTPServlet} is not available, Cheetah fakes it with a
+dummy class to satisfy the dependency. This allows servlets to be
+tested on the command line even on systems where Webware is not
+installed. This works only with servlets that don't call back into
+WebKit for information about the current web transaction, since
+there is no web transaction. Trying to access form input, for
+instance, will raise an exception because it depends on a live web
+request object, and in the dummy class the request object is
+{None}.
+
+Because Webware servlets must be valid Python modules, and
+"cheetah compile" can produce only valid module names, if you're
+converting an existing site that has .html filenames with hyphens
+(-), extra dots (.), etc, you'll have to rename them (and possibly
+use redirects).
+
+Site frameworks
+---------------
+
+(webware.siteFrameworks)
+
+Web sites are normally arranged hierarchically, with certain
+features common to every page on the site, other features common to
+certain sections or subsections, and others unique to each page.
+You can model this easily with a hierarchy of classes, with
+specific servlets inheriting from their more general superclasses.
+Again, you can do this two ways, using Cheetah's { Containment}
+approach or { Inheritance} approach.
+
+In the Inheritance approach, parents provide {#block}s and children
+override them using {#def}. Each child {#extend}s its immediate
+parent. Only the leaf servlets need to be under WebKit's document
+root directory. The superclass servlets can live anywhere in the
+filesystem that's in the Python path. (You may want to modify your
+WebKit startup script to add that library directory to your
+{PYTHONPATH} before starting WebKit.)
+
+Section libraries.templates.skeletonPage contains information on a
+stock template that simplifies defining the basic HTML structure of
+your web page templates.
+
+In the Containment approach, your hierarchy of servlets are not
+templates, but each uses one or more templates as it wishes.
+Children provide callback methods to to produce the various
+portions of the page that are their responsibility, and parents
+call those methods. Webware's {WebKit.Page} and
+{WebKit.SidebarPage} classes operate like this.
+
+Note that the two approaches are not compatible! {WebKit.Page} was
+not designed to intermix with {Cheetah.Templates.SkeletonPage}.
+Choose either one or the other, or expect to do some integration
+work.
+
+If you come up with a different strategy you think is worth noting
+in this chapter, let us know.
+
+Directory structure
+-------------------
+
+(webware.directoryStructure)
+
+Here's one way to organize your files for Webware+Cheetah.
+
+::
+
+ www/ # Web root directory.
+ site1.example.com/ # Site subdirectory.
+ apache/ # Web server document root (for non-servlets).
+ www/ # WebKit document root.
+ index.py # http://site1.example.com/
+ index.tmpl # Source for above.
+ servlet2.py # http://site1.example.com/servlet2
+ servlet2.tmpl # Source for above.
+ lib/ # Directory for helper classes.
+ Site.py # Site superclass ("#extends Site").
+ Site.tmpl # Source for above.
+ Logic.py # Logic class inherited by some template.
+ webkit.config # Configuration file (for WebwareExperimental).
+ Webware/ # Standard Webware's MakeAppWorkDir directory.
+ AppServer # Startup program (for standard Webware).
+ Configs/ # Configuration directory (for standard Webware).
+ Application.config
+ # Configuration file (for standard Webware).
+ site2.example.org/ # Another virtual host on this computer....
+
+Initializing your template-servlet with Python code
+---------------------------------------------------
+
+(webware.calculations)
+
+If you need a place to initialize variables or do calculations for
+your template-servlet, you can put it in an {.awake()} method
+because WebKit automatically calls that early when processing the
+web transaction. If you do override {.awake()}, be sure to call the
+superclass {.awake} method. You probably want to do that first so
+that you have access to the web transaction data {Servlet.awake}
+provides. You don't have to worry about whether your parent class
+has its own {.awake} method, just call it anyway, and somebody up
+the inheritance chain will respond, or at minimum {Servlet.awake}
+will respond. Section tips.callingSuperclassMethods gives examples
+of how to call a superclass method.
+
+As an alternative, you can put all your calculations in your own
+method and call it near the top of your template. ({#silent},
+section output.silent).
+
+Form processing
+---------------
+
+(webware.form)
+
+There are many ways to display and process HTML forms with Cheetah.
+But basically, all form processing involves two steps.
+
+
+#. Display the form.
+
+#. In the next web request, read the parameters the user submitted,
+ check for user errors, perform any side effects (e.g.,
+ reading/writing a database or session data) and present the user an
+ HTML response or another form.
+
+
+The second step may involve choosing between several templates to
+fill (or several servlets to redirect to), or a big
+if-elif-elif-else construct to display a different portion of the
+template depending on the situation.
+
+In the oldest web applications, step 1 and step 2 were handled by
+separate objects. Step 1 was a static HTML file, and step 2 was a
+CGI script. Frequently, a better strategy is to have a single
+servlet handle both steps. That way, the servlet has better control
+over the entire situation, and if the user submits unacceptable
+data, the servlet can redisplay the form with a "try again" error
+message at the top and and all the previous input filled in. The
+servlet can use the presence or absence of certain CGI parameters
+(e.g., the submit button, or a hidden mode field) to determine
+which step to take.
+
+One neat way to build a servlet that can handle both the form
+displaying and form processing is like this:
+
+
+#. Put your form HTML into an ordinary template-servlet. In each
+ input field, use a placeholder for the value of the {VALUE=}
+ attribue. Place another placeholder next to each field, for that
+ field's error message.
+
+#. Above the form, put a {$processFormData} method call.
+
+#. Define that method in a Python class your template {#extend}s.
+ (Or if it's a simple method, you can define it in a {#def}.) The
+ method should:
+
+
+ #. Get the form input if any.
+
+ #. If the input variable corresponding to the submit field is
+ empty, there is no form input, so we're showing the form for the
+ first time. Initialize all VALUE= variables to their default value
+ (usually ""), and all error variables to "". Return "", which will
+ be the value for {$processFormData}.
+
+ #. If the submit variable is not empty, fill the VALUE= variables
+ with the input data the user just submitted.
+
+ #. Now check the input for errors and put error messages in the
+ error placeholders.
+
+ #. If there were any user errors, return a general error message
+ string; this will be the value for {$processFormData}.
+
+ #. If there were no errors, do whatever the form's job is (e.g.,
+ update a database) and return a success message; this will be the
+ value for {$processFormData}.
+
+
+#. The top of the page will show your success/failure message (or
+ nothing the first time around), with the form below. If there are
+ errors, the user will have a chance to correct them. After a
+ successful submit, the form will appear again, so the user can
+ either review their entry, or change it and submit it again.
+ Depending on the application, this may make the servlet update the
+ same database record again, or it may generate a new record.
+
+
+{FunFormKit} is a third-party Webware package that makes it easier
+to produce forms and handle their logic. It has been successfully
+been used with Cheetah. You can download FunFormKit from
+http://colorstudy.net/software/funformkit/ and try it out for
+yourself.
+
+Form input, cookies, session variables and web server variables
+---------------------------------------------------------------
+
+(webware.input)
+
+General variable tips that also apply to servlets are in section
+tips.placeholder.
+
+To look up a CGI GET or POST parameter (with POST overriding):
+
+::
+
+ $request.field('myField')
+ self.request().field('myField')
+
+These will fail if Webware is not available, because {$request}
+(aka {self.request()} will be {None} rather than a Webware
+{WebKit.Request} object. If you plan to read a lot of CGI
+parameters, you may want to put the {.fields} method into a local
+variable for convenience:
+
+::
+
+ #set $fields = $request.fields
+ $fields.myField
+
+But remember to do complicated calculations in Python, and assign
+the results to simple variables in the searchList for display.
+These {$request} forms are useful only for occasions where you just
+need one or two simple request items that going to Python for would
+be overkill.
+
+To get a cookie or session parameter, subsitute "cookie" or
+"session" for "field" above. To get a dictionary of all CGI
+parameters, substitute "fields" (ditto for "cookies"). To verify a
+field exists, substitute "hasField" (ditto for "hasCookie").
+
+Other useful request goodies:
+
+::
+
+ ## Defined in WebKit.Request
+ $request.field('myField', 'default value')
+ $request.time ## Time this request began in Unix ticks.
+ $request.timeStamp ## Time in human-readable format ('asctime' format).
+ ## Defined in WebKit.HTTPRequest
+ $request.hasField.myField ## Is a CGI parameter defined?
+ $request.fields ## Dictionary of all CGI parameters.
+ $request.cookie.myCookie ## A cookie parameter (also .hasCookie, .cookies).
+ $request.value.myValue ## A field or cookie variable (field overrides)
+ ## (also .hasValue).
+ $request.session.mySessionVar # A session variable.
+ $request.extraURLPath ## URL path components to right of servlet, if any.
+ $request.serverDictionary ## Dict of environmental vars from web server.
+ $request.remoteUser ## Authenticated username. HTTPRequest.py source
+ ## suggests this is broken and always returns None.
+ $request.remoteAddress ## User's IP address (string).
+ $request.remoteName ## User's domain name, or IP address if none.
+ $request.urlPath ## URI of this servlet.
+ $request.urlPathDir ## URI of the directory containing this servlet.
+ $request.serverSidePath ## Absolute path of this servlet on local filesystem.
+ $request.serverURL ## URL of this servlet, without "http://" prefix,
+ ## extra path info or query string.
+ $request.serverURLDir ## URL of this servlet's directory, without "http://".
+ $log("message") ## Put a message in the Webware server log. (If you
+ ## define your own 'log' variable, it will override
+ ## this; use $self.log("message") in that case.
+
+.webInput()
+~~~~~~~~~~~
+
+(webware.webInput)
+
+From the method docstring:
+
+::
+
+ def webInput(self, names, namesMulti=(), default='', src='f',
+ defaultInt=0, defaultFloat=0.00, badInt=0, badFloat=0.00, debug=False):
+
+ This method places the specified GET/POST fields, cookies or session variables
+ into a dictionary, which is both returned and put at the beginning of the
+ searchList. It handles:
+ * single vs multiple values
+ * conversion to integer or float for specified names
+ * default values/exceptions for missing or bad values
+ * printing a snapshot of all values retrieved for debugging
+ All the 'default*' and 'bad*' arguments have "use or raise" behavior, meaning
+ that if they're a subclass of Exception, they're raised. If they're anything
+ else, that value is substituted for the missing/bad value.
+
+ The simplest usage is:
+
+ #silent $webInput(['choice'])
+ $choice
+
+ dic = self.webInput(['choice'])
+ write(dic['choice'])
+
+ Both these examples retrieves the GET/POST field 'choice' and print it. If you
+ leave off the "#silent", all the values would be printed too. But a better way
+ to preview the values is
+
+ #silent $webInput(['name'], $debug=1)
+
+ because this pretty-prints all the values inside HTML <PRE> tags.
+
+ Since we didn't specify any coversions, the value is a string. It's a "single"
+ value because we specified it in 'names' rather than 'namesMulti'. Single
+ values work like this:
+ * If one value is found, take it.
+ * If several values are found, choose one arbitrarily and ignore the rest.
+ * If no values are found, use or raise the appropriate 'default*' value.
+
+ Multi values work like this:
+ * If one value is found, put it in a list.
+ * If several values are found, leave them in a list.
+ * If no values are found, use the empty list ([]). The 'default*'
+ arguments are *not* consulted in this case.
+
+ Example: assume 'days' came from a set of checkboxes or a multiple combo box
+ on a form, and the user chose "Monday", "Tuesday" and "Thursday".
+
+ #silent $webInput([], ['days'])
+ The days you chose are: #slurp
+ #for $day in $days
+ $day #slurp
+ #end for
+
+ dic = self.webInput([], ['days'])
+ write("The days you chose are: ")
+ for day in dic['days']:
+ write(day + " ")
+
+ Both these examples print: "The days you chose are: Monday Tuesday Thursday".
+
+ By default, missing strings are replaced by "" and missing/bad numbers by zero.
+ (A "bad number" means the converter raised an exception for it, usually because
+ of non-numeric characters in the value.) This mimics Perl/PHP behavior, and
+ simplifies coding for many applications where missing/bad values *should* be
+ blank/zero. In those relatively few cases where you must distinguish between
+ ""/zero on the one hand and missing/bad on the other, change the appropriate
+ 'default*' and 'bad*' arguments to something like:
+ * None
+ * another constant value
+ * $NonNumericInputError/self.NonNumericInputError
+ * $ValueError/ValueError
+ (NonNumericInputError is defined in this class and is useful for
+ distinguishing between bad input vs a TypeError/ValueError
+ thrown for some other reason.)
+
+ Here's an example using multiple values to schedule newspaper deliveries.
+ 'checkboxes' comes from a form with checkboxes for all the days of the week.
+ The days the user previously chose are preselected. The user checks/unchecks
+ boxes as desired and presses Submit. The value of 'checkboxes' is a list of
+ checkboxes that were checked when Submit was pressed. Our task now is to
+ turn on the days the user checked, turn off the days he unchecked, and leave
+ on or off the days he didn't change.
+
+ dic = self.webInput([], ['dayCheckboxes'])
+ wantedDays = dic['dayCheckboxes'] # The days the user checked.
+ for day, on in self.getAllValues():
+ if not on and wantedDays.has_key(day):
+ self.TurnOn(day)
+ # ... Set a flag or insert a database record ...
+ elif on and not wantedDays.has_key(day):
+ self.TurnOff(day)
+ # ... Unset a flag or delete a database record ...
+
+ 'source' allows you to look up the variables from a number of different
+ sources:
+ 'f' fields (CGI GET/POST parameters)
+ 'c' cookies
+ 's' session variables
+ 'v' "values", meaning fields or cookies
+
+ In many forms, you're dealing only with strings, which is why the
+ 'default' argument is third and the numeric arguments are banished to
+ the end. But sometimes you want automatic number conversion, so that
+ you can do numeric comparisons in your templates without having to
+ write a bunch of conversion/exception handling code. Example:
+
+ #silent $webInput(['name', 'height:int'])
+ $name is $height cm tall.
+ #if $height >= 300
+ Wow, you're tall!
+ #else
+ Pshaw, you're short.
+ #end if
+
+ dic = self.webInput(['name', 'height:int'])
+ name = dic[name]
+ height = dic[height]
+ write("%s is %s cm tall." % (name, height))
+ if height > 300:
+ write("Wow, you're tall!")
+ else:
+ write("Pshaw, you're short.")
+
+ To convert a value to a number, suffix ":int" or ":float" to the name. The
+ method will search first for a "height:int" variable and then for a "height"
+ variable. (It will be called "height" in the final dictionary.) If a numeric
+ conversion fails, use or raise 'badInt' or 'badFloat'. Missing values work
+ the same way as for strings, except the default is 'defaultInt' or
+ 'defaultFloat' instead of 'default'.
+
+ If a name represents an uploaded file, the entire file will be read into
+ memory. For more sophisticated file-upload handling, leave that name out of
+ the list and do your own handling, or wait for Cheetah.Utils.UploadFileMixin.
+
+ This mixin class works only in a subclass that also inherits from
+ Webware's Servlet or HTTPServlet. Otherwise you'll get an AttributeError
+ on 'self.request'.
+
+ EXCEPTIONS: ValueError if 'source' is not one of the stated characters.
+ TypeError if a conversion suffix is not ":int" or ":float".
+
+More examples
+-------------
+
+(webware.examples)
+
+Example A - a standalone servlet
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Example B - a servlet under a site framework
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Example C - several servlets with a common template
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Other Tips
+----------
+
+(webware.otherTips)
+
+If your servlet accesses external files (e.g., via an {#include}
+directive), remember that the current directory is not necessarily
+directory the servlet is in. It's probably some other directory
+WebKit chose. To find a file relative to the servlet's directory,
+prefix the path with whatever {self.serverSidePath()} returns (from
+{Servlet.serverSidePath()}.
+
+If you don't understand how {#extends} and {#implements} work, and
+about a template's main method, read the chapter on inheritance
+(sections inheritanceEtc.extends and inheritanceEtc.implements).
+This may help you avoid buggy servlets.
+
+