diff options
author | R. Tyler Ballance <tyler@monkeypox.org> | 2009-12-19 15:27:36 -0800 |
---|---|---|
committer | R. Tyler Ballance <tyler@monkeypox.org> | 2009-12-19 15:27:36 -0800 |
commit | a753724d68880488a27141d1e4f174880d39e154 (patch) | |
tree | 26cdf49d72e1b967e9e072a9f7a291e971f86468 | |
parent | 7b1c2ad9f4e5830b5c88d95715014bb4044c090c (diff) | |
parent | ffa377c79b12f6b3674cba2267f186d37078d0d7 (diff) | |
download | python-cheetah-a753724d68880488a27141d1e4f174880d39e154.tar.gz |
Merge branch 'next'
96 files changed, 9798 insertions, 2139 deletions
@@ -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("<", "<") s = s.replace(">", ">") # 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' % (' '*2*indent, - self._menuCSSClass, text) - else: - return '%s<A HREF="%s%s"%s>%s</A> <BR>\n' % \ - (' '*2*indent, self._prefix, url, - self._menuCSSClass, text) - - def crumbLink(self, url, text): - if url == self._currentURL or self._prefix + url == self._currentURL: - return '<B%s>%s</B>' % (text, self._crumbCSSClass) - else: - return '<A HREF="%s%s"%s>%s</A>' % \ - (self._prefix, url, self._crumbCSSClass, text) - - def crumbSeperator(self): - return ' > ' - - def crumbTerminator(self): - return '' - - def emptyCrumb(self): - """When you are at the homepage""" - return '' - - ## internal methods - - def _menubarRecurse(self, contents, indent, stream): - if 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' % (' '*2*indent, + self._menuCSSClass, text) + else: + return '%s<A HREF="%s%s"%s>%s</A> <BR>\n' % \ + (' '*2*indent, self._prefix, url, + self._menuCSSClass, text) + + def crumbLink(self, url, text): + if url == self._currentURL or self._prefix + url == self._currentURL: + return '<B%s>%s</B>' % (text, self._crumbCSSClass) + else: + return '<A HREF="%s%s"%s>%s</A>' % \ + (self._prefix, url, self._crumbCSSClass, text) + + def crumbSeperator(self): + return ' > ' + + def crumbTerminator(self): + return '' + + def emptyCrumb(self): + """When you are at the homepage""" + return '' + + ## internal methods + + def _menubarRecurse(self, contents, indent, stream): + if isinstance(contents, tuple): + url, text = contents + rest = [] + else: + url, text = contents[0] + rest = contents[1:] + stream.write(self.menuLink(url, text, indent)) + if self._inContents(contents): + for item in rest: + self._menubarRecurse(item, indent+1, stream) + + def _inContents(self, contents): + if isinstance(contents, tuple): + return self._currentURL == contents[0] + for item in contents: + if self._inContents(item): + return True + return False ################################################## ## from the command line if __name__ == '__main__': - hierarchy = [('/', 'home'), - ('/about', 'About Us'), - [('/services', 'Services'), - [('/services/products', 'Products'), - ('/services/products/widget', 'The Widget'), - ('/services/products/wedge', 'The Wedge'), - ('/services/products/thimble', 'The Thimble'), - ], - ('/services/prices', 'Prices'), - ], - ('/contact', 'Contact Us'), - ] - - for url in ['/', '/services', '/services/products/widget', '/contact']: - print '<p>', '='*50 - print '<br> %s: <br>\n' % url - n = Hierarchy(hierarchy, url, menuCSSClass='menu', crumbCSSClass='crumb', - prefix='/here') - print n.menuList() - print '<p>', '-'*50 - print n.crumbs() + 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 + ===============<$noValue could not be found>=============== + +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! << + >> Rubber Ducky, you're the one! You make bathtime so much fun! << + >> 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: ===============<$iDontExist could not be found>=============== + +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(' ', ' ') + +{#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 + ' ', 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. + + |