
   #first Contents

                         Cheetah Developers' Guide
     _________________________________________________________________

                         Cheetah Developers' Guide

                  Mike Orr with assistance from Tavis Rudd

                              iron@mso.oz.net

Contents

     * Contents
     * 1 Introduction
          + 1.1 Who should read this Guide?
          + 1.2 Contents
     * 2 .py Template Modules
          + 2.1 An example
          + 2.2 A walk through the example
     * 3 Placeholders
          + 3.1 Simple placeholders
          + 3.2 Complex placeholders
     * 4 Caching placeholders and #cache
          + 4.1 Dynamic placeholder - no cache
          + 4.2 Static placeholder
          + 4.3 Timed-refresh placeholder
          + 4.4 Timed-refresh placeholder with braces
          + 4.5 #cache
          + 4.6 #cache with timer and id
          + 4.7 #cache with test: expression and method conditions
     * 5 Directives: Comments
          + 5.1 Docstring and header comments
     * 6 Directives: Output
          + 6.1 #echo
          + 6.2 #silent
          + 6.3 #raw
          + 6.4 #include
          + 6.5 #slurp
          + 6.6 #filter
     * 7 Directives: Import, Inheritance, Declaration and Assignment
          + 7.1 #import and #from
          + 7.2 #extends
          + 7.3 #implements
          + 7.4 #set and #set global
          + 7.5 #del
          + 7.6 #attr
          + 7.7 #def
          + 7.8 #block
          + 7.9 #settings
     * 8 Directives: Flow Control
          + 8.1 #for
          + 8.2 #repeat
          + 8.3 #while
          + 8.4 #if
          + 8.5 #unless
          + 8.6 #break and #continue
          + 8.7 #pass
          + 8.8 #stop
          + 8.9 #return
     * 9 Directives: Error Handling
          + 9.1 #try and #raise
          + 9.2 #assert
          + 9.3 #errorCatcher
     * 10 Directives: Parser Instructions
          + 10.1 #breakpoint
          + 10.2 #compiler
     * 11 Files
     * 12 Template
     * 13 The parser
     * 14 The compiler
     * 15 History of Cheetah
     * 16 Design Decisions and Tradeoffs
          + 16.1 Delimiters
          + 16.2 Late binding
          + 16.3 Caching framework
          + 16.4 Webware compatibility and the transaction framework
          + 16.5 Single inheritance
     * 17 Patching Cheetah
          + 17.1 File Requirements
          + 17.2 Testing Changes and Building Regression Tests
     * 18 Documenting Cheetah
     * A. A BNF Grammar of Cheetah
     * B. Safe Delegation
     * About this document ...

   Copyright 2002, Mike Orr. This document may be copied and modified
   under the terms of the Open Publication License
   http://www.opencontent.org/openpub/

                                1 Introduction

1.1 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.

1.2 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.


                            2 .py Template Modules

   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.


2.1 An 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.


2.2 A walk through the example

   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.


                                3 Placeholders


3.1 Simple placeholders

   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.)


3.2 Complex placeholders

   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) ] )

   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,
}

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

   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 2.1. 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  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.)


                       4 Caching placeholders and #cache


4.1 Dynamic placeholder - no cache

   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.


4.2 Static placeholder

   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 RECHARGE 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.


4.3 Timed-refresh placeholder

   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 RECHARGE 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.


4.4 Timed-refresh placeholder with 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.

   The reason this example is here is because it's a Cheetah FAQ whether
   to put the cache interval inside or outside the {}. (Also so I can
   look it up when I forget, as I frequently do.) The answer is: outside.
   The {} go around only the placeholder name and arguments. If you do it
   this way:

Timed with {}:  ${*.5m*voom}      ## Wrong!

   you get:

Timed with {}:  ${*.5m*voom}

   because ${ is not a valid placeholder, so it's treated as ordinary
   text.


4.5 #cache

   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.


4.6 #cache with timer and id

   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


4.7 #cache with test: expression and method conditions

   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 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. Since this is not implemented yet, I cannot
   provide examples here.


                            5 Directives: 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')


5.1 Docstring and header comments

   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
"""


                             6 Directives: Output


6.1 #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')


6.2 #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.


6.3 #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.


6.4 #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))


  6.4.1 #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.


  6.4.2 #include from a string or expression (eval)

   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!"


6.5 #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.


6.6 #filter

   The template:

#attr $ode = ">> Rubber Ducky, you're the one!  You make bathtime so much fun!
<<"
$ode
#filter WebSafe
$ode
#filter MaxLen
${ode, maxlen=13}
#filter None
${ode, maxlen=13}

   The output:

>> Rubber Ducky, you're the one!  You make bathtime so much fun! <<
&gt;&gt; Rubber Ducky, you're the one!  You make bathtime so much fun! &lt;&lt;
>> Rubber Duc
>> Rubber Ducky, you're the one!  You make bathtime so much fun! <<

   The WebSafe filter escapes characters that have a special meaning in
   HTML. The MaxLen filter chops off values at the specified length.
   #filter None returns to the default filter, which ignores the maxlen
   argument.

   The generated code:

 1  write(filter(VFS(SL,"ode",1))) # generated from '$ode' at line 2, col 1.
 2  write('\n')
 3  filterName = 'WebSafe'
 4  if self._filters.has_key("WebSafe"):
 5      filter = self._currentFilter = self._filters[filterName]
 6  else:
 7      filter = self._currentFilter = \
 8                  self._filters[filterName] = getattr(self._filtersLib,
                       filterName)(self).filter
 9  write(filter(VFS(SL,"ode",1))) # generated from '$ode' at line 4, col 1.
10  write('\n')
11  filterName = 'MaxLen'
12  if self._filters.has_key("MaxLen"):
13      filter = self._currentFilter = self._filters[filterName]
14  else:
15      filter = self._currentFilter = \
16                  self._filters[filterName] = getattr(self._filtersLib,
                       filterName)(self).filter
17  write(filter(VFS(SL,"ode",1), maxlen=13)) # generated from
        #'${ode, maxlen=13}' at line 6, col 1.
18  write('\n')
19  filter = self._initialFilter
20  write(filter(VFS(SL,"ode",1), maxlen=13)) # generated from
       #'${ode, maxlen=13}' at line 8, col 1.
21  write('\n')

   As we've seen many times, Cheetah wraps all placeholder lookups in a
   filter call. (This also applies to non-searchList lookups: local,
   global and builtin variables.) The filter ``function'' is actually an
   alias to the current filter object:

filter = self._currentFilter

   as set at the top of the main method. Here in lines 3-8 and 11-16 we
   see the filter being changed. Whoops, I lied. filter is not an alias
   to the filter object itself but to that object's .filter method. Line
   19 switches back to the default filter.

   In line 17 we see the maxlen argument being passed as a keyword
   argument to filter (not to VFS). In line 20 the same thing happens
   although the default filter ignores the argument.


         7 Directives: Import, Inheritance, Declaration and Assignment


7.1 #import and #from

   The template:

#import math

   This construct does not produce any output.

   The generated module, at the bottom of the import section:

import math


7.2 #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):


7.3 #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'


7.4 #set and #set global

   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 shadowing ._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.)


7.5 #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]


7.6 #attr

   The template:

#attr $namesList = ['Moe', 'Larry', 'Curly']

   In the generated class:

## GENERATED ATTRIBUTES

namesList = ['Moe', 'Larry', 'Curly']


7.7 #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.


7.8 #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.


7.9 #settings

   This directive is undocumented because it's likely to disappear in
   Cheetah 0.9.14.


                          8 Directives: Flow Control


8.1 #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(' ')


8.2 #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 i in range( 3):
    write('My bonnie lies over the ocean\n')
write('O, bring back my bonnie to me!\n')


8.3 #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


8.4 #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")


8.5 #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.


8.6 #break and #continue

   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(' - ')


8.7 #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')


8.8 #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')


8.9 #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 ""


                         9 Directives: Error Handling


9.1 #try and #raise

   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.


9.2 #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!"


9.3 #errorCatcher


  9.3.1 No error catcher

   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')


  9.3.2 Echo and BigEcho

   The template:

#errorCatcher Echo
$noValue
#errorCatcher BigEcho
$noValue

   The output:

$noValue
===============&lt;$noValue could not be found&gt;===============

   The generated code:

if self._errorCatchers.has_key("Echo"):
    self._errorCatcher = self._errorCatchers["Echo"]
else:
    self._errorCatcher = self._errorCatchers["Echo"] = ErrorCatchers.Echo(self)
write(filter(self.__errorCatcher1(localsDict=locals())))
    # generated from '$noValue' at line 2, col 1.
write('\n')
if self._errorCatchers.has_key("BigEcho"):
    self._errorCatcher = self._errorCatchers["BigEcho"]
else:
    self._errorCatcher = self._errorCatchers["BigEcho"] = \
        ErrorCatchers.BigEcho(self)
write(filter(self.__errorCatcher1(localsDict=locals())))
        # generated from '$noValue' at line 4, col 1.
write('\n')


  9.3.3 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!


                      10 Directives: Parser Instructions


10.1 #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.


10.2 #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.


                                   11 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.


                                  12 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 .)


                                 13 The parser

   How templates are compiled: a walk through Parser.py's source. (Also
   need to look at Lexer.py, but not too closely.)


                                14 The compiler

   How templates are compiled: a walk through Compiler.py .


                             15 History of Cheetah

   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 analytical 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 12 May 12, 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.

   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 onto 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 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.

   A breakthrough came in fall 2001 when Tavis figured out how to
   implement the name mapper in C. The name mapper is what gives Cheetah
   its Autocalling and Uniform Dotted Notation features. This raised
   performance sufficiently to rewrite Cheetah in a totally ``late
   binding'' manner like Python is. More about this is in the next
   chapter.

   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.


                       16 Design Decisions and Tradeoffs


16.1 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.


16.2 Late binding

   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.


16.3 Caching framework


16.4 Webware compatibility and the transaction framework


16.5 Single inheritance


                              17 Patching Cheetah

   How to commit changes to CVS or submit patches, how to run the test
   suite. Describe distutils and how the regression tests work.


17.1 File Requirements

   The codeTemplate 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).


17.2 Testing Changes and Building Regression Tests

   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:
    1. 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 writesthe 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.)
    2. 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!
    3. Run cheetah test to verify you didn't break anything. Then run
       your little test program.
    4. Repeat steps 2-3 until everything is correct.
    5. Turn your little program into a regression test as described
       below.
    6. 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.
    7. 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:

          python Test.py Run all the tests. (Equivalent to cheetah test.)

          python SyntaxAndOutput.py Run only the tests in that module.

          python SyntaxAndOutput.py CGI 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.

          python SyntaxAndOutput.py CGI Indenter Run the tests in classes
          CGI and Indenter.

          python SyntaxAndOutput.py CGI.test1 Run only test test1, which
          is a method in the CGI class.

          etc...

   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.


                            18 Documenting Cheetah

   How to build the documentation. Why LaTeX, a minimum LaTeX reference,
   etc.


                          A. A BNF Grammar of Cheetah


                              B. Safe Delegation

   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.

    1. Only give untrusted developers access to the .tmpl files.
       (Verifying what this means. Why can't trusted developers access
       them?)
    2. Disable the #attr directive and maybe the #set directive.
    3. Use Cheetah's directive validation hooks to disallow references to
       self, etc (e.g. #if $steal(self.thePrivateVar) )
    4. Implement a validator for the $placeholders and use it to disallow
       '__' in $placeholders so that tricks like $obj.__class__.__dict__
       are not possible.

                            About this document ...

   Cheetah Developers' Guide

   This document was generated using the LaTeX2HTML translator.

   LaTeX2HTML is Copyright  1993, 1994, 1995, 1996, 1997, Nikos Drakos,
   Computer Based Learning Unit, University of Leeds, and Copyright 
   1997, 1998, Ross Moore, Mathematics Department, Macquarie University,
   Sydney.

   The application of LaTeX2HTML to the Python documentation has been
   heavily tailored by Fred L. Drake, Jr. Original navigation icons were
   contributed by Christopher Petrilli.
     _________________________________________________________________

                         Cheetah Developers' Guide
     _________________________________________________________________

   Release 0.9.15a1.
