diff options
author | R. Tyler Ballance <tyler@monkeypox.org> | 2009-11-16 21:09:13 -0800 |
---|---|---|
committer | R. Tyler Ballance <tyler@monkeypox.org> | 2009-11-16 21:09:13 -0800 |
commit | d9ce7916e309e2393d824e249f512d2629e5e181 (patch) | |
tree | 6b7ad5cd6292f6e017e048fbeb4551facbabd174 /docs/devel_guide_src | |
parent | e43765a679b84c52df875e9629d303e304af50a1 (diff) | |
download | python-cheetah-docs.tar.gz |
Revert "Delete the "old" docs directory to make way for fancy smancy sphinx"docs
This reverts commit 5dc95cfcd015628665d3672e56d0551943b5db6b.
Diffstat (limited to 'docs/devel_guide_src')
23 files changed, 2990 insertions, 0 deletions
diff --git a/docs/devel_guide_src/Makefile b/docs/devel_guide_src/Makefile new file mode 100755 index 0000000..9e5da96 --- /dev/null +++ b/docs/devel_guide_src/Makefile @@ -0,0 +1,38 @@ +# You must change PYTHONSRC to the path of your Python source distributon. +PYTHONSRC=/home/tavis/tmp/Python-2.2 +DOCNAME=devel_guide +MKHOWTO=$(PYTHONSRC)/Doc/tools/mkhowto +MAIN_TEX_FILE= devel_guide.tex + +all: ps pdf html htmlMultiPage text + +almost-all: ps html htmlMultiPage text + +pdf: + $(MKHOWTO) --pdf $(MAIN_TEX_FILE) + mv $(DOCNAME).pdf ../ + +ps: + $(MKHOWTO) --ps $(MAIN_TEX_FILE) + mv $(DOCNAME).ps ../ +html: + -rm -rf $(DOCNAME) + $(MKHOWTO) --html --split 1 --iconserver . $(MAIN_TEX_FILE) + -rm -rf ../$(DOCNAME)_html + mv $(DOCNAME) ../$(DOCNAME)_html + +htmlMultiPage: + -rm -rf $(DOCNAME) + $(MKHOWTO) --html --iconserver . $(MAIN_TEX_FILE) + -rm -rf ../$(DOCNAME)_html_multipage + mv $(DOCNAME) ../$(DOCNAME)_html_multipage + +text: + $(MKHOWTO) --text $(MAIN_TEX_FILE) + mv $(DOCNAME).txt ../ + +clean: + -rm -rf $(DOCNAME) + -rm -f *.aux *.l2h *~ *.log *.ind *.bkm *.how *.toc + -rm -rf ../html + diff --git a/docs/devel_guide_src/README b/docs/devel_guide_src/README new file mode 100755 index 0000000..3b45564 --- /dev/null +++ b/docs/devel_guide_src/README @@ -0,0 +1,9 @@ +To build the Cheetah documentation, you need the 'mkhowto' program from +the Python source distribution. So: + +1) Get the Python source distribution and unpack it in some directory. + +2) Edit the Cheetah documentation's Makefile and change PYTHONSRC to +point to the top-level directory of your Python source distribution. + +3) Run 'make'. diff --git a/docs/devel_guide_src/bnf.tex b/docs/devel_guide_src/bnf.tex new file mode 100755 index 0000000..aa7149c --- /dev/null +++ b/docs/devel_guide_src/bnf.tex @@ -0,0 +1,7 @@ +\section{A BNF Grammar of Cheetah} +\label{bnf} + + +% Local Variables: +% TeX-master: "devel_guide" +% End: diff --git a/docs/devel_guide_src/cache.tex b/docs/devel_guide_src/cache.tex new file mode 100755 index 0000000..043b8cf --- /dev/null +++ b/docs/devel_guide_src/cache.tex @@ -0,0 +1,365 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{Caching placeholders and \#cache} +\label{cache} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Dynamic placeholder -- no cache} +\label{cache.dynamic} + +The template: +\begin{verbatim} +Dynamic variable: $voom +\end{verbatim} + +The command line and the output: +\begin{verbatim} +% voom='Voom!' python x.py --env +Dynamic variable: Voom! +\end{verbatim} + +The generated code: +\begin{verbatim} +write('Dynamic variable: ') +write(filter(VFS(SL,"voom",1))) # generated from '$voom' at line 1, col 20. +write('\n') +\end{verbatim} + +Just what we expected, like any other dynamic placeholder. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Static placeholder} +\label{cache.static} + +The template: +\begin{verbatim} +Cached variable: $*voom +\end{verbatim} + +The command line and output: +\begin{verbatim} +% voom='Voom!' python x.py --env +Cached variable: Voom! +\end{verbatim} + +The generated code, with line numbers: +\begin{verbatim} + 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') +\end{verbatim} + +That one little star generated a whole lotta code. First, instead of an +ordinary \code{VFS} lookup (searchList) lookup, it converted the +placeholder to a lookup in the \code{.\_cacheData} dictionary. Cheetah also +generated a unique key (\code{'19760169'}) for our cached item -- this is its +cache ID. + +Second, Cheetah put a pair of if-blocks before the \code{write}. The first +(lines 3-7) determine whether the cache value is missing or out of date, and +sets local variable \code{RECACHE} true or false. +This stanza may look unnecessarily verbose -- lines 3-7 could be eliminated if +line 8 was changed to +\begin{verbatim} +if not self._cacheData.has_key('19760169'): +\end{verbatim} +-- 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 \code{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 \code{write} object into, then read +the result and stuff it into the cache. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Timed-refresh placeholder} +\label{cache.timed} + +The template: +\begin{verbatim} +Timed cache: $*.5m*voom +\end{verbatim} + +The command line and the output: +\begin{verbatim} +% voom='Voom!' python x.py --env +Timed cache: Voom! +\end{verbatim} + +The generated method's docstring: +\begin{verbatim} +""" +This is the main method generated by Cheetah +This cache will be refreshed every 30.0 seconds. +""" +\end{verbatim} + +The generated code: +\begin{verbatim} + 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') +\end{verbatim} + +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 +\code{.\_\_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 \code{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: +\begin{verbatim} +""" +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. +""" +\end{verbatim} +The docstring tells only that ``something'' will be refreshed every 60.0 +seconds, but doesn't reveal {\em which} placeholder that is. Only if you +know the relative order of the placeholders in the template can you figure +that out. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Timed-refresh placeholder with braces} +\label{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: +\begin{verbatim} +Timed with {}: $*.5m*{voom} +\end{verbatim} + +The output: +\begin{verbatim} +Timed with {}: Voom! +\end{verbatim} + +The generated code differs only in the comment. Inside the cache-refresh +if-block: +\begin{verbatim} +write(filter(VFS(SL,"voom",1))) # generated from '$*.5m*{voom}' at line 1, + #col 17. +\end{verbatim} + +If you try to do it this way: +\begin{verbatim} +Timed with {}: ${*.5m*voom} ## Wrong! +\end{verbatim} +you get: +\begin{verbatim} +Timed with {}: ${*.5m*voom} +\end{verbatim} +\verb+${+ is not a valid placeholder, so it gets treated as ordinary text. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#cache} +\label{cache.directive} + +The template: +\begin{verbatim} +#cache +This is a cached region. $voom +#end cache +\end{verbatim} + +The output: +\begin{verbatim} +This is a cached region. Voom! +\end{verbatim} + +The generated code: +\begin{verbatim} + 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 +\end{verbatim} + +This is the same as the \code{\$*voom} example, except that the plain text +around the placeholder is inside the second if-block. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#cache with timer and id} +\label{cache.directive.timer} + +The template: +\begin{verbatim} +#cache timer='.5m', id='cache1' +This is a cached region. $voom +#end cache +\end{verbatim} + +The output: +\begin{verbatim} +This is a cached region. Voom! +\end{verbatim} + +The generated code is the same as the previous example except the first +if-block: +\begin{verbatim} +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 +\end{verbatim} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#cache with test: expression and method conditions} +\label{cache.directive.test} + +The template: +\begin{verbatim} +#cache test=$isDBUpdated +This is a cached region. $voom +#end cache +\end{verbatim} + +(Analysis postponed: bug in Cheetah produces invalid Python.) + +%The output: +%\begin{verbatim} +%\end{verbatim} + +%The generated code: +%\begin{verbatim} +%\end{verbatim} + + +The template: +\begin{verbatim} +#cache id='cache1', test=($isDBUpdated or $someOtherCondition) +This is a cached region. $voom +#end cache +\end{verbatim} + +The output: +\begin{verbatim} +This is a cached region. Voom! +\end{verbatim} + +The first if-block in the generated code: +\begin{verbatim} +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 +\end{verbatim} +The second if-block is the same as in the previous example. If you leave +out the \code{()} 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: +\begin{verbatim} +#cache id='cache1', timer='30m', test=$isDBUpdated or $someOtherCondition +This is a cached region. $voom +#end cache +\end{verbatim} + +The output: +\begin{verbatim} +This is a cached region. Voom! +\end{verbatim} + +The first if-block: +\begin{verbatim} +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 +\end{verbatim} + +We are planning to add a \code{'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. + +% Local Variables: +% TeX-master: "devel_guide" +% End: diff --git a/docs/devel_guide_src/comments.tex b/docs/devel_guide_src/comments.tex new file mode 100755 index 0000000..4a7bdbd --- /dev/null +++ b/docs/devel_guide_src/comments.tex @@ -0,0 +1,100 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{Directives: Comments} +\label{comments} + +The template: + +\begin{verbatim} +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. +\end{verbatim} + +The output: + +\begin{verbatim} +Text before the comment. +Text after the comment. + +Text after the multi-line comment. + +\end{verbatim} + +The generated code: + +\begin{verbatim} + 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') +\end{verbatim} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Docstring and header comments} +\label{comments.docstring} + +The template: +\begin{verbatim} +##doc: .respond() method comment. +##doc-method: Another .respond() method comment. +##doc-class: A class comment. +##doc-module: A module comment. +##header: A header comment. +\end{verbatim} + +The output: +\begin{verbatim} + +\end{verbatim} + +The beginning of the generated \code{.respond} method: +\begin{verbatim} +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. + """ +\end{verbatim} + +The class docstring: +\begin{verbatim} +""" +A class comment. + +Autogenerated by CHEETAH: The Python-Powered Template Engine +""" +\end{verbatim} + +The top of the module: +\begin{verbatim} +#!/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 +""" +\end{verbatim} + + +% Local Variables: +% TeX-master: "devel_guide" +% End: diff --git a/docs/devel_guide_src/compiler.tex b/docs/devel_guide_src/compiler.tex new file mode 100755 index 0000000..e27aa94 --- /dev/null +++ b/docs/devel_guide_src/compiler.tex @@ -0,0 +1,8 @@ +\section{The compiler} +\label{compiler} + +How templates are compiled: a walk through Compiler.py. + +% Local Variables: +% TeX-master: "devel_guide" +% End: diff --git a/docs/devel_guide_src/design.tex b/docs/devel_guide_src/design.tex new file mode 100755 index 0000000..3008a63 --- /dev/null +++ b/docs/devel_guide_src/design.tex @@ -0,0 +1,102 @@ +\section{Design Decisions and Tradeoffs} +\label{design} + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Delimiters} +\label{design.Delimiters} + +One of the first decisions we encountered was which delimiter syntax to use. +We decided to follow Velocity's \code{\$placeholder} and \code{\#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 \verb+${longPlaceholder}+ syntax like the shells for cases +where Cheetah or you might be confused where a placeholder ends. Tavis went +ahead and made \verb+${longPlaceholder}+ and \verb+$[longPlaceholder]+ +interchangeable with it since it was trivial to implement. Finally, +the \code{\#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 \code{\$} and \code{\#}, do you?) + +The choice of comment delimiters was more arbitrary. \code{\#\#} and +\code{\#* \ldots *\#} doesn't match any language, but it's reminiscent of +Python and C while also being consistent with our ``\code{\#} is for +directives'' convention. + +We specifically chose {\em 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. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Late binding} +\label{design.lateBinding} + +One of Cheetah's unique features is the name mapper, which lets you write +\code{\$a.b} without worrying much about the type of \code{a} or \code{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 \code{a} is, whether the +the value is a function (autocall it), issue the appropriate Python incantation +to look up \code{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 \code{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 \code{a} later changes +type before a template filling? Answer: unpredictable exceptions occur. What +if \code{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 +\code{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 \code{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 \code{a} or care +whether it exists. This allows users to create \code{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. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Caching framework} +\label{design.cache} + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Webware compatibility and the transaction framework} +\label{design.webware} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Single inheritance} +\label{design.singleInheritance} + + + + + +% Local Variables: +% TeX-master: "devel_guide" +% End: diff --git a/docs/devel_guide_src/devel_guide.tex b/docs/devel_guide_src/devel_guide.tex new file mode 100755 index 0000000..1e795c4 --- /dev/null +++ b/docs/devel_guide_src/devel_guide.tex @@ -0,0 +1,55 @@ +\documentclass{howto} +\usepackage{moreverb} %% Verbatim Code Listings + +\title{Cheetah Developers' Guide} +\release{0.9.15a1} + +\author{Mike Orr with assistance from Tavis Rudd} +\authoraddress{\email{iron@mso.oz.net}} + +\begin{document} +\maketitle + + +\tableofcontents + +\copyright{Copyright 2002, Mike Orr. + This document may be copied and modified under the terms of the + {\bf Open Publication License} \url{http://www.opencontent.org/openpub/} } + + + %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + \include{introduction} + + %% How the directives affect the .py template module. + \include{pyModules} + \include{placeholders} + \include{cache} + \include{comments} + \include{output} + \include{inheritanceEtc} + \include{flowControl} + \include{errorHandling} + \include{parserInstructions} + + %% A walk through the Cheetah source. + \include{files} + \include{template} + \include{parser} + \include{compiler} + + %% Other development issues and howtos. + \include{history} + \include{design} + \include{patching} + \include{documenting} + + %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + \appendix + \include{bnf} + \include{safeDelegation} +\end{document} + +% Local Variables: +% TeX-master: "users_guide" +% End: diff --git a/docs/devel_guide_src/documenting.tex b/docs/devel_guide_src/documenting.tex new file mode 100755 index 0000000..51d5153 --- /dev/null +++ b/docs/devel_guide_src/documenting.tex @@ -0,0 +1,8 @@ +\section{Documenting Cheetah} +\label{documenting} + +How to build the documentation. Why LaTeX, a minimum LaTeX reference, etc. + +% Local Variables: +% TeX-master: "devel_guide" +% End: diff --git a/docs/devel_guide_src/errorHandling.tex b/docs/devel_guide_src/errorHandling.tex new file mode 100755 index 0000000..02079d5 --- /dev/null +++ b/docs/devel_guide_src/errorHandling.tex @@ -0,0 +1,309 @@ +\section{Directives: Error Handling} +\label{errorHandling} + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#try and \#raise} +\label{errorHandling.try} + +The template: +\begin{verbatim} +#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 +\end{verbatim} + +The output: +\begin{verbatim} +A runtime error occurred. + +Hahaha! + +You can't divide by zero, idiot! +\end{verbatim} + +The generated code: +\begin{verbatim} +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") +\end{verbatim} + +\code{\#finally} works just like in Python. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#assert} +\label{errorHandling.assert} + +The template: +\begin{verbatim} +#assert False, "You lose, buster!" +\end{verbatim} + +The output: +\begin{verbatim} +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! +\end{verbatim} + +The generated code: +\begin{verbatim} +assert False, "You lose, buster!" +\end{verbatim} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#errorCatcher} +\label{errorHandling.errorCatcher} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsubsection{No error catcher} +\label{errorHandling.errorCatcher.no} + +The template: +\begin{verbatim} +$noValue +\end{verbatim} + +The output: +\begin{verbatim} + +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 +\end{verbatim} + +The generated code: +\begin{verbatim} +write(filter(VFS(SL,"noValue",1))) # generated from '$noValue' at line 1, + # col 1. +write('\n') +\end{verbatim} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsubsection{Echo and BigEcho} +\label{errorHandling.errorCatcher.echo} + +The template: +\begin{verbatim} +#errorCatcher Echo +$noValue +#errorCatcher BigEcho +$noValue +\end{verbatim} + +The output: +\begin{verbatim} +$noValue +===============<$noValue could not be found>=============== +\end{verbatim} + +The generated code: +\begin{verbatim} +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') +\end{verbatim} + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsubsection{ListErrors} +\label{errorHandling.errorCatcher.listErrors} + +The template: +\begin{verbatim} +#import pprint +#errorCatcher ListErrors +$noValue +$anotherMissingValue.really +$pprint.pformat($errorCatcher.listErrors) +## This is really self.errorCatcher().listErrors() +\end{verbatim} + +The output: +\begin{verbatim} +$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'}] +\end{verbatim} + +The generated import: +\begin{verbatim} +import pprint +\end{verbatim} + +Then in the generated class, we have our familiar \code{.respond} method +and several new methods: +\begin{verbatim} +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)) +\end{verbatim} +\begin{verbatim} +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 "" +\end{verbatim} + +So whenever an error catcher is active, each placeholder gets wrapped in its +own method. No wonder error catchers slow down the system! + + +% Local Variables: +% TeX-master: "devel_guide" +% End: diff --git a/docs/devel_guide_src/files.tex b/docs/devel_guide_src/files.tex new file mode 100755 index 0000000..0ce45a9 --- /dev/null +++ b/docs/devel_guide_src/files.tex @@ -0,0 +1,11 @@ +\section{Files} +\label{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. + +% Local Variables: +% TeX-master: "devel_guide" +% End: diff --git a/docs/devel_guide_src/flowControl.tex b/docs/devel_guide_src/flowControl.tex new file mode 100755 index 0000000..936df67 --- /dev/null +++ b/docs/devel_guide_src/flowControl.tex @@ -0,0 +1,360 @@ +\section{Directives: Flow Control} +\label{flowControl} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#for} +\label{flowControl.for} + +The template: +\begin{verbatim} +#for $i in $range(10) +$i #slurp +#end for +\end{verbatim} + +The output: +\begin{verbatim} +0 1 2 3 4 5 6 7 8 9 +\end{verbatim} + +The generated code: +\begin{verbatim} +for i in range(10): + write(filter(i)) # generated from '$i' at line 2, col 1. + write(' ') +\end{verbatim} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#repeat} +\label{flowControl.repeat} + +The template: +\begin{verbatim} +#repeat 3 +My bonnie lies over the ocean +#end repeat +O, bring back my bonnie to me! +\end{verbatim} + +The output: +\begin{verbatim} +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! +\end{verbatim} +(OK, so the second line should be ``sea'' instead of ``ocean''.) + +The generated code: +\begin{verbatim} +for __i0 in range(3): + write('My bonnie lies over the ocean\n') +write('O, bring back my bonnie to me!\n') +\end{verbatim} + +Note that a new local variable of the form \code{\_\_i\$num} will be +used for each instance of \code{repeat} in order to permit nesting. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#while} +\label{flowControl.while} + +The template: +\begin{verbatim} +#set $alive = True +#while $alive +I am alive! +#set $alive = False +#end while +\end{verbatim} + +The output: +\begin{verbatim} +I am alive! +\end{verbatim} + +The generated code: +\begin{verbatim} +alive = True +while alive: + write('I am alive!\n') + alive = False +\end{verbatim} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#if} +\label{} + +The template: +\begin{verbatim} +#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 +\end{verbatim} + +The output: +\begin{verbatim} +It's small +\end{verbatim} + +The generated code: +\begin{verbatim} +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") +\end{verbatim} + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#unless} +\label{flowControl.unless} + +The template: +\begin{verbatim} +#set $count = 9 +#unless $count + 5 > 15 +Count is in range. +#end unless +\end{verbatim} + +The output: +\begin{verbatim} +Count is in range. +\end{verbatim} + +The generated code: +\begin{verbatim} + count = 9 + if not (count + 5 > 15): + write('Count is in range.\n') +\end{verbatim} + +{\em Note:} There is a bug in Cheetah 0.9.13. It's forgetting the +parentheses in the \code{if} expression, which could lead to it calculating +something different than it should. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#break and \#continue} +\label{flowControl.break} + +The template: +\begin{verbatim} +#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 +\end{verbatim} + +The output: +\begin{verbatim} +1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 11 - 12 - James - +\end{verbatim} + +The generated code: +\begin{verbatim} +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(' - ') +\end{verbatim} + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#pass} +\label{flowControl.pass} + +The template: +\begin{verbatim} +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. +\end{verbatim} + +The output: +\begin{verbatim} +Let's check the number. +Done checking the number. +\end{verbatim} + +The generated code: +\begin{verbatim} +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') +\end{verbatim} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#stop} +\label{flowControl.stop} + +The template: +\begin{verbatim} +A cat +#if 1 + sat on a mat + #stop + watching a rat +#end if +in a flat. +\end{verbatim} + +The output: +\begin{verbatim} +A cat + sat on a mat +\end{verbatim} + +The generated code: +\begin{verbatim} +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') +\end{verbatim} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#return} +\label{flowControl.return} + +The template: +\begin{verbatim} +1 +$test[1] +3 +#def test +1.5 +#if 1 +#return '123' +#else +99999 +#end if +#end def +\end{verbatim} + +The output: +\begin{verbatim} +1 +2 +3 +\end{verbatim} + +The generated code: +\begin{verbatim} + 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 "" +\end{verbatim} +\begin{verbatim} + 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 "" +\end{verbatim} + + +% Local Variables: +% TeX-master: "devel_guide" +% End: diff --git a/docs/devel_guide_src/history.tex b/docs/devel_guide_src/history.tex new file mode 100755 index 0000000..d5444d5 --- /dev/null +++ b/docs/devel_guide_src/history.tex @@ -0,0 +1,92 @@ +\section{History of Cheetah} +\label{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 \code{\%} 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. + +% @@MO: What influence did PSP have on Cheetah? + +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 \code{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 \code{\#for}, \code{\#if} and +\code{\#echo}. (OK, \code{\#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: \code{\#while}, \code{\#try}, \code{\#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. + +% @@MO: Write more about the contributions of Chuck, Ian and others. My +% memory is faulty so I'll have to ask them. + +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, +\url{http://www.cheetahtemplate.org/Py10.html}. + +% @@MO: Look through the changelog for important milestones to mention. + +% Local Variables: +% TeX-master: "devel_guide" +% End: diff --git a/docs/devel_guide_src/inheritanceEtc.tex b/docs/devel_guide_src/inheritanceEtc.tex new file mode 100755 index 0000000..0179555 --- /dev/null +++ b/docs/devel_guide_src/inheritanceEtc.tex @@ -0,0 +1,232 @@ +\section{Directives: Import, Inheritance, Declaration and Assignment} +\label{inheritanceEtc} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#import and \#from} +\label{inheritanceEtc.import} + +The template: +\begin{verbatim} +#import math +\end{verbatim} + +This construct does not produce any output. + +The generated module, at the bottom of the import section: +\begin{verbatim} +import math +\end{verbatim} + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#extends} +\label{inheritanceEtc.extends} + +The template: +\begin{verbatim} +#extends SomeClass +\end{verbatim} + +The generated import (skipped if \code{SomeClass} has already been +imported): +\begin{verbatim} +from SomeClass import SomeClass +\end{verbatim} + +The generated class: +\begin{verbatim} +class x(SomeClass): +\end{verbatim} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#implements} +\label{inheritanceEtc.implements} + +The template: +\begin{verbatim} +#implements doOutput +\end{verbatim} + +In the generated class, the main method is \code{.doOutput} instead of +\code{.respond}, and the attribute naming this method is: +\begin{verbatim} +_mainCheetahMethod_for_x2= 'doOutput' +\end{verbatim} + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#set and \#set global} +\label{inheritanceEtc.set} + +The template: +\begin{verbatim} +#set $namesList = ['Moe','Larry','Curly'] +$namesList +#set global $toes = ['eeny', 'meeny', 'miney', 'moe'] +$toes +\end{verbatim} + +The output: +\begin{verbatim} +['Moe', 'Larry', 'Curly'] +['eeny', 'meeny', 'miney', 'moe'] +\end{verbatim} + + +The generated code: +\begin{verbatim} +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') +\end{verbatim} + +\code{globalSetVars} is a local variable referencing \code{.\_globalSetVars}. +Writes go into it directly, but reads take advantage of the fact that +\code{.\_globalSetVars} is on the searchList. (In fact, it's the very first +namespace.) + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#del} +\label{inheritanceEtc.del} + +The template: +\begin{verbatim} +#set $a = 1 +#del $a +#set $a = 2 +#set $arr = [0, 1, 2] +#del $a, $arr[1] +\end{verbatim} + +In the generated class: +\begin{verbatim} +1 a = 1 +2 del a +3 a = 2 +4 arr = [0, 1, 2] +5 del a, arr[1] +\end{verbatim} + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#attr} +\label{inheritanceEtc.attr} + +The template: +\begin{verbatim} +#attr $namesList = ['Moe', 'Larry', 'Curly'] +\end{verbatim} + +In the generated class: +\begin{verbatim} +## GENERATED ATTRIBUTES + +namesList = ['Moe', 'Larry', 'Curly'] +\end{verbatim} + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#def} +\label{inheritanceEtc.def} + +The template: +\begin{verbatim} +#def printArg($arg) +The argument is $arg. +#end def +My method returned $printArg(5). +\end{verbatim} + +The output: +\begin{verbatim} +My method returned The argument is 5. +. +\end{verbatim} + +Hmm, not exactly what we expected. The method returns a trailing newline +because we didn't end the last line with \code{\#slurp}. So the second +period (outside the method) appears on a separate line. + +The \code{\#def} generates a method \code{.printArg} whose structure is similar +to the main method: +\begin{verbatim} +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 "" +\end{verbatim} + +When \code{.printArg} is called from a placeholder, only the arguments the user +supplied are passed. The other arguments retain their default values. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#block} +\label{inheritanceEtc.block} + +The template: +\begin{verbatim} +#block content +This page is under construction. +#end block +\end{verbatim} + +The output: +\begin{verbatim} +This page is under construction. +\end{verbatim} + +This construct generates a method \code{.content} in the same structure +as \code{.printArg} above, containing the write code: +\begin{verbatim} +write('This page is under construction.\n') +\end{verbatim} + +In the main method, the write code is: +\begin{verbatim} +self.content(trans=trans) # generated from ('content', '#block content') + # at line 1, col 1. +\end{verbatim} + +So a block placeholder implicitly passes the current transaction to the method. + + +% Local Variables: +% TeX-master: "devel_guide" +% End: diff --git a/docs/devel_guide_src/introduction.tex b/docs/devel_guide_src/introduction.tex new file mode 100755 index 0000000..3403d7e --- /dev/null +++ b/docs/devel_guide_src/introduction.tex @@ -0,0 +1,29 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{Introduction} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{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. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{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. + + +% Local Variables: +% TeX-master: "users_guide" +% End: diff --git a/docs/devel_guide_src/output.tex b/docs/devel_guide_src/output.tex new file mode 100755 index 0000000..1b714c2 --- /dev/null +++ b/docs/devel_guide_src/output.tex @@ -0,0 +1,282 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{Directives: Output} +\label{output} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#echo} +\label{output.echo} + +The template: +\begin{verbatim} +Here is my #echo ', '.join(['silly']*5) # example +\end{verbatim} + +The output: +\begin{verbatim} +Here is my silly, silly, silly, silly, silly example +\end{verbatim} + +The generated code: +\begin{verbatim} +write('Here is my ') +write(filter(', '.join(['silly']*5) )) +write(' example\n') +\end{verbatim} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#silent} +\label{output.silent} + +The template: +\begin{verbatim} +Here is my #silent ', '.join(['silly']*5) # example +\end{verbatim} + +The output: +\begin{verbatim} +Here is my example +\end{verbatim} + +The generated code: +\begin{verbatim} + write('Here is my ') + ', '.join(['silly']*5) + write(' example\n') +\end{verbatim} + +OK, it's not quite covert because that extra space gives it away, but it +almost succeeds. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#raw} +\label{output.raw} + +The template: +\begin{verbatim} +Text before raw. +#raw +Text in raw. $alligator. $croc.o['dile']. #set $a = $b + $c. +#end raw +Text after raw. +\end{verbatim} + +The output: +\begin{verbatim} +Text before raw. +Text in raw. $alligator. $croc.o['dile']. #set $a = $b + $c. +Text after raw. +\end{verbatim} + +The generated code: +\begin{verbatim} + write('''Text before raw. +Text in raw. $alligator. $croc.o['dile']. #set $a = $b + $c. +Text after raw. +''') +\end{verbatim} + +So we see that \code{\#raw} is really like a quoting mechanism. It says that +anything inside it is ordinary text, and Cheetah joins a \code{\#raw} section +with adjacent string literals rather than generating a separate \code{write} +call. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#include} +\label{output.include} + +The main template: +\begin{verbatim} +#include "y.tmpl" +\end{verbatim} + +The included template y.tmpl: +\begin{verbatim} +Let's go $voom! +\end{verbatim} + +The shell command and output: +\begin{verbatim} +% voom="VOOM" x.py --env +Let's go VOOM! +\end{verbatim} + +The generated code: +\begin{verbatim} +write(self._includeCheetahSource("y.tmpl", trans=trans, includeFrom="file", + raw=0)) +\end{verbatim} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsubsection{\#include raw} +\label{output.include.raw} + +The main template: +\begin{verbatim} +#include raw "y.tmpl" +\end{verbatim} + +The shell command and output: +\begin{verbatim} +% voom="VOOM" x.py --env +Let's go $voom! +\end{verbatim} + +The generated code: +\begin{verbatim} +write(self._includeCheetahSource("y.tmpl", trans=trans, includeFrom="fil +e", raw=1)) +\end{verbatim} + +That last argument, \code{raw}, makes the difference. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsubsection{\#include from a string or expression (eval)} +\label{output.include.expression} + +The template: +\begin{verbatim} +#attr $y = "Let's go $voom!" +#include source=$y +#include raw source=$y +#include source="Bam! Bam!" +\end{verbatim} + +The output: +\begin{verbatim} +% voom="VOOM" x.py --env +Let's go VOOM!Let's go $voom!Bam! Bam! +\end{verbatim} + +The generated code: +\begin{verbatim} +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")) +\end{verbatim} + +Later in the generated class: +\begin{verbatim} +y = "Let's go $voom!" +\end{verbatim} + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#slurp} +\label{output.slurp} + +The template: +\begin{verbatim} +#for $i in range(5) +$i +#end for +#for $i in range(5) +$i #slurp +#end for +Line after slurp. +\end{verbatim} + +The output: +\begin{verbatim} +0 +1 +2 +3 +4 +0 1 2 3 4 Line after slurp. +\end{verbatim} + +The generated code: +\begin{verbatim} +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') +\end{verbatim} + +The space after each number is because of the space before \code{\#slurp} in +the template definition. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#filter} +\label{output.filter} + +The template: +\begin{verbatim} +#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} +\end{verbatim} + +The output: +\begin{verbatim} +>> 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! << +\end{verbatim} + +The \code{WebSafe} filter escapes characters that have a special meaning in +HTML. The \code{MaxLen} filter chops off values at the specified length. +\code{\#filter None} returns to the default filter, which ignores the \code{maxlen} +argument. + +The generated code: +\begin{verbatim} + 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') +\end{verbatim} + +As we've seen many times, Cheetah wraps all placeholder lookups in a +\code{filter} call. (This also applies to non-searchList lookups: local, +global and builtin variables.) The \code{filter} ``function'' +is actually an alias to the current filter object: +\begin{verbatim} +filter = self._currentFilter +\end{verbatim} +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. \code{filter} is not an alias to +the filter object itself but to that object's \code{.filter} method. Line 19 +switches back to the default filter. + +In line 17 we see the \code{maxlen} argument being passed as a keyword +argument to \code{filter} (not to \code{VFS}). In line 20 the same thing +happens although the default filter ignores the argument. + +% Local Variables: +% TeX-master: "devel_guide" +% End: diff --git a/docs/devel_guide_src/parser.tex b/docs/devel_guide_src/parser.tex new file mode 100755 index 0000000..0198b5d --- /dev/null +++ b/docs/devel_guide_src/parser.tex @@ -0,0 +1,9 @@ +\section{The parser} +\label{parser} + +How templates are compiled: a walk through Parser.py's source. +(Also need to look at Lexer.py, but not too closely.) + +% Local Variables: +% TeX-master: "devel_guide" +% End: diff --git a/docs/devel_guide_src/parserInstructions.tex b/docs/devel_guide_src/parserInstructions.tex new file mode 100644 index 0000000..0af065f --- /dev/null +++ b/docs/devel_guide_src/parserInstructions.tex @@ -0,0 +1,61 @@ +\section{Directives: Parser Instructions} +\label{parserInstructions} + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#breakpoint} +\label{parserInstructions.breakpoint} + + +The template: +\begin{verbatim} +Text before breakpoint. +#breakpoint +Text after breakpoint. +#raise RuntimeError +\end{verbatim} + +The output: +\begin{verbatim} +Text before breakpoint. +\end{verbatim} + +The generated code: +\begin{verbatim} +write('Text before breakpoint.\n') +\end{verbatim} + +Nothing after the breakpoint was compiled. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{\#compiler} +\label{parserInstructions.compiler} + +The template: +\begin{verbatim} +// Not a comment +#compiler commentStartToken = '//' +// A comment +#compiler reset +// Not a comment +\end{verbatim} + +The output: +\begin{verbatim} +// Not a comment +// Not a comment +\end{verbatim} + +The generated code: +\begin{verbatim} +write('// Not a comment\n') +# A comment +write('// Not a comment\n') +\end{verbatim} + +So this didn't affect the generated program, it just affected how the +template definition was read. + +% Local Variables: +% TeX-master: "devel_guide" +% End: diff --git a/docs/devel_guide_src/patching.tex b/docs/devel_guide_src/patching.tex new file mode 100755 index 0000000..6049068 --- /dev/null +++ b/docs/devel_guide_src/patching.tex @@ -0,0 +1,134 @@ +\section{Patching Cheetah} +\label{patching} + +How to commit changes to CVS or submit patches, how to run the test suite. +Describe distutils and how the regression tests work. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{File Requirements} +\label{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 \code{Template} -- especially +if it has helper methods -- put it in a mixin class under \code{Cheetah.Utils} +and inherit it. + +Routines for a specific problem domain should be put under +\code{Cheetah.Tools}, so that it doesn't clutter the namespace unless the user +asks for it. + +Remember: \code{Cheetah.Utils} is for objects required by any part of Cheetah's +core. \code{Cheetah.Tools} is for completely optional objects. It should +always be possible to delete \code{Cheetah.Tools} without breaking Cheetah's +core services. + +If a core method needs to look up an attribute defined under +\code{Cheetah.Tools}, it should use \code{hasattr()} and gracefully provide a +default if the attribute does not exist (meaning the user has not imported that +subsystem). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Testing Changes and Building Regression Tests} +\label{patching.testing} + +Cheetah ships with a regression test suite. To run the built-in tests, +execute at the shell prompt: +\begin{verbatim} + cheetah test +\end{verbatim} + +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: +\begin{enumerate} +\item 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 \code{diff} on the two + outputs. (\code{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 + \url{http://gnuwin32.sourceforge.net/packages/diffutils.htm}, and MacOS + sources at \url{http://perso.wanadoo.fr/gilles.depeyrot/DevTools\_en.html}.) +\item 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 + \code{python setup.py install} before testing it. If you make it in the + installed version, do {\em not} run the installer or it will overwrite your + changes! +\item Run \code{cheetah test} to verify you didn't break anything. Then run + your little test program. +\item Repeat steps 2-3 until everything is correct. +\item Turn your little program into a regression test as described below. +\item When \code{cheetah test} runs cleanly with your regression test + included, update the \code{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, {\em be sure to} \code{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. +\item 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. +\end{enumerate} + +If you add a directory to Cheetah, you have to mention it in \code{setup.py} or +it won't be installed. + +The tests are in the \code{Cheetah.Tests} package, aka the \code{src/Tests/} +directory of your CVS sandbox. Most of the tests are in +\code{SyntaxAndOutput.py}. You can either run all the tests or choose which +to run: +\begin{description} +\item{\code{python Test.py}} + Run all the tests. (Equivalent to \code{cheetah test}.) +\item{\code{python SyntaxAndOutput.py}} + Run only the tests in that module. +\item{\code{python SyntaxAndOutput.py CGI}} + Run only the tests in the class \code{CGI} inside the module. The class + must be a direct or indirect subclass of + \code{unittest\_local\_copy.TestCase}. +\item{\code{python SyntaxAndOutput.py CGI Indenter}} + Run the tests in classes \code{CGI} and \code{Indenter}. +\item{\code{python SyntaxAndOutput.py CGI.test1}} + Run only test \code{test1}, which is a method in the \code{CGI} class. +\item{etc...} +\end{description} + +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., \code{test16}. +The method should not require any arguments except \code{self}, and should +call \code{.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 \code{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 +\code{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 \code{CGI} class) +or override \code{.searchList()} (see the \code{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 \code{unittest\_local\_copy} source, and the +\code{unittest} documentation in the Python Library Reference. Note that we +are using a hacked version of \code{unittest} to make a more convenient test +structure for Cheetah. The differences between \code{unittest\_local\_copy} +and Python's standard \code{unittest} are documented at the top of the module. + +% Local Variables: +% TeX-master: "devel_guide" +% End: diff --git a/docs/devel_guide_src/placeholders.tex b/docs/devel_guide_src/placeholders.tex new file mode 100755 index 0000000..e487d09 --- /dev/null +++ b/docs/devel_guide_src/placeholders.tex @@ -0,0 +1,478 @@ +\section{Placeholders} +\label{placeholders} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Simple placeholders} +\label{placeholders.simple} + +Let's add a few \$placeholders to our template: + +\begin{verbatim} +>>> 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 + +\end{verbatim} +\begin{verbatim} + + 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 "" +\end{verbatim} +\begin{verbatim} + 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() + +\end{verbatim} + +(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, +\code{.\_filePath} and \code{.\_fileMtime} are not updated in +\code{.\_\_init\_\_}, so they inherit the value \code{None} from +\code{Template}. Also, that if-stanza in \code{.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 +\code{write} call outputting a string literal, this module has a series of +\code{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 +\code{filter} call to apply the current output filter. (The default +output filter converts all objects to strings, and \code{None} to \code{""}.) + +Placeholders referring to a Python builtin like \code{xrange} (line 68) +generate a bare variable name. Placeholders to be looked up in the searchList +have a nested function call; e.g., +\begin{verbatim} +write(filter(VFS(SL,"what",1))) # generated from '$what' at line 1, col 8. +\end{verbatim} +\code{VFS}, remember, is a function imported from \code{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 +\code{1} rather than \code{True} because it's generated from an +\code{and} expression, and that's what Python 2.2 outputs for true \code{and} +expressions.) + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{Complex placeholders} +\label{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 +\code{Cheetah.Tests.SyntaxAndOutput.Placeholders} class. + +\begin{verbatim} +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') +\end{verbatim} + +We'll need a big program to set up the placeholder values. Here it is: + +\begin{verbatim} +#!/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] ) +\end{verbatim} + +Here's the output: + +\begin{verbatim} +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 + +\end{verbatim} + +And here -- tada! -- is the generated module. +To save space, I've included only the lines containing the \code{write} calls. +The rest of the module is the same as in the first example, chapter +\ref{pyModules.example}. I've split some of the lines to make them fit on +the page. + +\begin{verbatim} + 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. +\end{verbatim} +\begin{verbatim} +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: ') +\end{verbatim} +\begin{verbatim} +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') +\end{verbatim} + +For each placeholder lookup, the the innermost level of nesting is a \code{VFS} +call, which looks up the first (leftmost) placeholder component in the +searchList. This is wrapped by zero or more \code{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 \code{VFS} and +\code{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 (\code{None}, \code{True} and \code{False}) are +converted to \code{filter(None)}, etc. They use normal Python variable +lookup rather than \code{VFS}. (Cheetah emulates \code{True} and \code{False} +using global variables for Python < 2.2.1, when they weren't builtins yet.) + +Notice the last line is a call to \code{_} (i.e. \code{gettext}) which is used +for internationalization (see +\url{http://docs.python.org/lib/module-gettext.html}). The code is converted +normally, but an \code{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. + +% Local Variables: +% TeX-master: "devel_guide" +% End: diff --git a/docs/devel_guide_src/pyModules.tex b/docs/devel_guide_src/pyModules.tex new file mode 100755 index 0000000..2aa3236 --- /dev/null +++ b/docs/devel_guide_src/pyModules.tex @@ -0,0 +1,246 @@ +\section{.py Template Modules} +\label{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. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{An example} +\label{pyModules.example} + +Our first template follows a long noble tradition in computer tutorials. +It produces a familiar, friendly greeting. Here's the template: + +\begin{verbatim} +Hello, world! +\end{verbatim} + +... the output: + +\begin{verbatim} +Hello, world! +\end{verbatim} + +... and the .py template module cheetah-compile produced, with line +numbers added: + +% @@MO: Is it possible to print the line numbers gray instead of black? + +\begin{verbatim} + 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 """ +\end{verbatim} +\begin{verbatim} + 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 "" +\end{verbatim} +\begin{verbatim} + 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() + +\end{verbatim} + +(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. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\subsection{A walk through the example} +\label{pyModules.walk} + +Lines 20-24 are the Cheetah-specific imports. Line 33 introduces our generated +class, \code{x}, a subclass of \code{Template}. It's called x because the +source file was x.tmpl. + +Lines 40-46 are the \code{.\_\_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 \code{.\_filePath} and \code{.\_fileMtime} to the filename and +modification time (in Unix ticks) of the source .tmpl file. + +Lines 47-84 are the main method \code{.respond}, the one that fills the +template. Normally you call it without arguments, but Webware calls it with a +Webware \code{Transaction} object representing the current request. Lines +57-59 set up the \code{trans} variable. If a real or dummy transaction is +passed in, the method uses it. Otherwise (if the \code{trans} argument is +\code{None}), the method creates a \code{DummyTransaction} instance. +\code{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 \code{.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 (\url{http://www.python.org/cgi-bin/faqw.py}). +\code{VFS} and \code{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 \code{write} variable. This important variable is +discussed below. + +Lines 60-63 initialize a few more local variables. \code{SL} is the +searchList. \code{filter} is the current output filter. \code{globalSetVars} +are the variables that have been defined with \code{\#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 -- \code{\#import} for instance +generates additional \code{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 {\em re}\ compilation only. The initial compilation +happened in the \code{.\_\_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. \code{write} looks like an ordinary +function call, but remember that line 60 made it an alias to +\code{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 \code{trans} is a real Webware +transaction, \code{write} has already sent the output to Webware for handling, +so we return \code{""}. If \code{trans} is a dummy transaction, +\code{write} has been accumulating the output in a Python \code{StringIO} +object rather than sending it anywhere, so we have to return it. + +Line 83 is the end of the \code{.respond} method. + +Line 87 makes code{.\_\_str\_\_} an alias for the main method, so that you +can \code{print} it or apply \code{str} to it and it will fill the template. +Line 88 gives the name of the main method, because sometimes it's not +\code{.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. + + +% Local Variables: +% TeX-master: "devel_guide" +% End: diff --git a/docs/devel_guide_src/safeDelegation.tex b/docs/devel_guide_src/safeDelegation.tex new file mode 100755 index 0000000..dd1a8aa --- /dev/null +++ b/docs/devel_guide_src/safeDelegation.tex @@ -0,0 +1,44 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{Safe Delegation} +\label{safeDelegation} + +% @@MO: Does this really belong in the Developers' Guide or any guide? +% It's more of a wiki Wishlist item, no? Especially since nobody has +% expressed a need for it. + +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 \code{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. + +\begin{enumerate} + +\item Only give untrusted developers access to the .tmpl files. +(Verifying what this means. Why can't trusted developers access them?) + +\item Disable the \code{\#attr} directive and maybe the \code{\#set} directive. + +\item Use Cheetah's directive validation hooks to disallow +references to \code{self}, etc +(e.g. \code{\#if \$steal(self.thePrivateVar)} ) + +\item Implement a validator for the \$placeholders and use it +to disallow '\_\_' in \$placeholders so that tricks like +\code{\$obj.\_\_class\_\_.\_\_dict\_\_} are not possible. + +\end{enumerate} + + diff --git a/docs/devel_guide_src/template.tex b/docs/devel_guide_src/template.tex new file mode 100755 index 0000000..1ad21c4 --- /dev/null +++ b/docs/devel_guide_src/template.tex @@ -0,0 +1,11 @@ +\section{Template} +\label{template} + +This chapter will mainly walk through the \code{Cheetah.Template} constructor +and not at what point the template is compiled. + +(Also need to look at Transaction,py and Servlet.py.) + +% Local Variables: +% TeX-master: "devel_guide" +% End: |