pyWeb
In Python, Yet Another Literate Programming Tool
Literate programming was pioneered by Knuth as a method for developing readable, understandable presentations of programs. These would present a program in a literate fashion for people to read and understand; this would be in parallel with presentation as source text for a compiler to process and both would be generated from a common source file.
One intent is to synchronize the program source with the documentation about that source. If the program and the documentation have a common origin, then the traditional gaps between intent (expressed in the documentation) and action (expressed in the working program) are significantly reduced.
Numerous tools have been developed based on Knuth's initial work. A relatively complete survey is available at sites like Literate Programming, and the OASIS XML Cover Pages: Literate Programming with SGML and XML.
The immediate predecessors to this pyWEB tool are FunnelWeb, noweb and nuweb. The ideas lifted from these other tools created the foundation for pyWEB.
There are several Python-oriented literate programming tools. These include LEO, interscript, lpy, py2html.
The FunnelWeb tool is independent of any programming language and only mildly dependent on TEX. It has 19 commands, many of which duplicate features of HTML or LATEX.
The noweb tool was written by Norman Ramsey. This tool uses a sophisticated multi-processing framework, via Unix pipes, to permit flexible manipulation of the source file to tangle and weave the programming language and documentation markup files.
The nuweb Simple Literate Programming Tool was developed by Preston Briggs (preston@tera.com). His work was supported by ARPA, through ONR grant N00014-91-J-1989. It is written in C, and very focused on producing LATEX documents. It can produce HTML, but this is clearly added after the fact. It cannot be easily extended, and is not object-oriented.
The LEO tool, is a structured GUI editor for creating source. It uses XML and noweb-style chunk management. It is more than a simple weave and tangle tool.
The interscript tool is very large and sophisticated, but doesn't gracefully tolerate HTML markup in the document. It can create a variety of markup languages from the interscript source, making it suitable for creating HTML as well as LATEX.
The lpy tool can produce very complex HTML representations of a Python program. It works by locating documentation markup embedded in Python comments and docstrings. This is called inverted literate programming.
The py2html tool does very sophisticated syntax coloring.
The following is an almost verbatim quote from Briggs' nuweb documentation, and provides an apt summary of Literate Programming.
In 1984, Knuth introduced the idea of literate programming and described a pair of tools to support the practise (Donald E. Knuth, Literate Programming, The Computer Journal 27 (1984), no. 2, 97-111.) His approach was to combine Pascal code with TEX documentation to produce a new language, WEB, that offered programmers a superior approach to programming. He wrote several programs in WEB, including weave and tangle, the programs used to support literate programming. The idea was that a programmer wrote one document, the web file, that combined documentation written in TEX (Donald E. Knuth, The TEXbook, Computers and Typesetting, 1986) with code (written in Pascal).
Running tangle on the web file would produce a complete Pascal program, ready for compilation by an ordinary Pascal compiler. The primary function of tangle is to allow the programmer to present elements of the program in any desired order, regardless of the restrictions imposed by the programming language. Thus, the programmer is free to present his program in a top-down fashion, bottom-up fashion, or whatever seems best in terms of promoting understanding and maintenance.
Running weave on the web file would produce a TEX file, ready to be processed by TEX. The resulting document included a variety of automatically generated indices and cross-references that made it much easier to navigate the code. Additionally, all of the code sections were automatically prettyprinted, resulting in a quite impressive document.
Knuth also wrote the programs for TEX and METAFONT entirely in WEB, eventually publishing them in book form. These are probably the largest programs ever published in a readable form.
pyWeb works with any programming language and any markup language. This philosophy comes from FunnelWeb, noweb and nuweb. The primary differences between pyWeb and other tools are the following.
pyWeb works with any programming language and any markup language. The initial release supports HTML completely, and LATEX approximately. The biggest gap in the LATEX support is a complete lack of understanding of the original markup in nuweb, and the very real damage done to that markup when creating pyWeb.
The following is extensively quoted from Briggs' nuweb documentation, and provides an excellent background in the advantages of the very simple approach started by nuweb and adopted by pyWeb.
The need to support arbitrary programming languages has many consequences:
Of course, we've got to have some compensation for our losses or the whole idea would be a waste. Here are the advantages I [Briggs] can see:
pyWeb supports two use cases, Tangle Source Files and Weave Documentation. These are often combined into a single request of the application that will both weave and tangle.
A user initiates this process when they have a complete .w file that contains a description of source files. These source files are described with @o commands in the .w file.
The use case is successful when the source files are produced.
Outside this use case, the user will debug those source files, possibly updating the .w file. This will lead to a need to restart this use case.
The use case is a failure when the source files cannot be produced, due to errors in the .w file. These must be corrected based on information in log messages.
The sequence is simply ./pyweb.py theFile.w.
A user initiates this process when they have a .w file that contains a description of a document to produce. The document is described by the entire .w file.
The use case is successful when the documentation file is produced.
Outside this use case, the user will edit the documentation file, possibly updating the .w file. This will lead to a need to restart this use case.
The use case is a failure when the documentation file cannot be produced, due to errors in the .w file. These must be corrected based on information in log messages.
The sequence is simply ./pyweb.py theFile.w.
A user initiates this process when they have a .w file that contains a description of a document to produce. The document is described by the entire .w file. Further, their final document should include regression test output from the source files created by the tangle operation.
The use case is successful when the documentation file is produced, including current regression test output.
Outside this use case, the user will edit the documentation file, possibly updating the .w file. This will lead to a need to restart this use case.
The use case is a failure when the documentation file cannot be produced, due to errors in the .w file. These must be corrected based on information in log messages.
The use case is a failure when the documentation file does not include current regression test output.
The sequence is as follows:
./pyweb.py -xw -pi theFile.w python theTest>aLog ./pyweb.py -xt theFile.w
The first step excludes weaving and permits errors on the @i command. The -pi option is necessary in the event that the log file does not yet exist. The second step runs the regression test, creating a log file. The third step weaves the final document, including the regression test output.
The input to pyWeb is a .w file that consists of a series of Chunks. Each Chunk is either program source code to be tangled or it is documentation to be woven. The bulk of the file is typically documentation chunks that describe the program in some human-oriented markup language like HTML or LATEX.
The pyWeb tool parses the input, and performs the tangle and weave operations. It tangles each individual output file from the program source chunks. It weaves the final documentation file from the entire sequence of chunks provided, mixing the author's original documentation with the program source.
The Major commands partition the input and define the various chunks. The Minor commands are used to control the woven and tangled output from those chunks.
There are three major commands that define the various chunks in an input file.
All material that is not explicitly in a @o or @d named chunk is implicitly collected into a sequence of anonymous document source chunks. These anonymous chunks form the backbone of the document that is woven. The anonymous chunks are never tangled into output program source files.
Note that white space (line breaks ('\n'), tabs and spaces) have no effect on the input parsing. They are completely preserved on output.
The following example has three chunks. An anonymous chunk of documentation, a named output chunk, and an anonymous chunk of documentation.
<p>Some HTML documentation that describes the following piece of the
program.</p>
@o myFile.py
@{
import math
print math.pi
@}
<p>Some more HTML documentation.</p>
There are five minor commands that cause content to be created where the command is referenced.
The named chunks (from both @o and @d commands) are assigned unique sequence numbers to simplify cross references. In LaTex it is possible to determine the page breaks and assign the sequence numbers based on the physical pages.
Chunk names and file names are case sensitive.
Chunk names can be abbreviated. A partial name can have a trailing ellipsis (...), this will be resolved to the full name. The most typical use for this is shown in the following example.
<p>Some HTML documentation.</p>
@o myFile.py
@{
@<imports of the various packages used@>
print math.pi,time.time()
@}
<p>Some notes on the packages used.</p>
@d imports...
@{
import math,time
@| math time
@}
<p>Some more HTML documentation.</p>
Named chunks are concatenated from their various pieces. This allows a named chunk to be broken into several pieces, simplifying the description. This is most often used when producing fairly complex output files.
<p>An anonymous chunk with some HTML documentation.</p>
@o myFile.py
@{
import math,time
@}
<p>Some notes on the packages used.</p>
@o myFile.py
@{
print math.pi,time.time()
@}
<p>Some more HTML documentation.</p>
Newline characters are preserved on input. Because of this the output may appear to have excessive newlines. In all of the above examples, each named chunk was defined with the following.
@{
import math,time
@}
This puts a newline character before and after the import line.
One transformation is performed when tangling output. The indentation of a chunk reference is applied to the entire chunk. This makes it simpler to prepare source for languages (like Python) where indentation is important. It also gives the author control over how the final tangled output looks.
Also, note that the myFile.py uses the @| command to show that this chunk defines the identifier aFunction.
<p>An anonymous chunk with some HTML documentation.</p>
@o myFile.py
@{
def aFunction( a, b ):
@<body of the aFunction@>
@| aFunction @}
<p>Some notes on the packages used.</p>
@d body...
@{
"""doc string"""
return a + b
@}
<p>Some more HTML documentation.</p>
The tangled output from this will look like the following. All of the newline characters are preserved, and the reference to body of the aFunction is indented to match the prevailing indent where it was referenced. In the following example, explicit line markers of ~ are provided to make the blank lines more obvious.
~
~def aFunction( a, b ):
~
~ """doc string"""
~ return a + b
~
There are two possible implementations for evaluation of a Python expression in the input.
In this implementation, we adopt the latter approach, and evaluate expressions immediately. A simple global context is created with the following variables defined.
Assuming that you have marked pyweb.py as executable, you do the following.
./pyweb.py file...
This will tangle the @o commands in each file. It will also weave the output, and create file.html.
Currently, the following command line options are accepted.
pyWeb requires Python 2.1. or newer.
Currently, input is not detabbed; Python users generally are discouraged from using tab characters in their files.
You must have Python 2.1.
Be sure to save a bootstrap copy of pyweb.py before changing pyweb.w. Should your changes to pyweb.w introduce a bug into pyweb.py, you will need a fall-back version of pyWeb that you can use in place of the one you just damaged.
This application is very directly based on (derived from?) work that preceded this, particularly the following:
Also, after using John Skaller's interscript for two large development efforts, I finally understood the feature set I really needed.
This application breaks the overall problem into the following sub-problems.
A solution to the reading and parsing problem depends on a convenient tool for breaking up the input stream and a representation for the chunks of input. Input decomposition is done with the Python Splitter pattern. The representation of the source document is done with the Composition pattern.
The Splitter pattern is widely used in text processing, and has a long legacy in a variety of languages and libraries. A Splitter decomposes a string into a sequence of strings using a split pattern. There are many variant implementations. One variant locates only a single occurence (usually the left-most); this is commonly implemented as a Find or Search string function. Another variant locates occurrences of a specific string or character, and discards the matching string or character.
The variation on Splitter that we use in this application creates each element in the resulting sequence as either (1) an instance of the split regular expression or (2) the text between split patterns. By preserving the actual split text, we can define our splitting pattern with the regular expression '@.'. This will split on any @ followed by a single character. We can then examine the instances of the split RE to locate pyWeb commands.
We could be a tad more specific and use the following as a split pattern: '@[doOifmu|(){}[\]]'. This would silently ignore unknown commands, merging them in with the surrounding text. This would leave the '@@' sequences completely alone, allowing us to replace '@@' with '@' in every text chunk.
The Composition pattern is used to build up a parse tree of instances of Chunk. This parse tree is contained in the overall Web, which is a sequence of Chunks. Each named chunk may be a sequence of Chunks with a common name.
Each chunk is composed of a sequence of instances of Command. Because of this uniform composition, the several operations (particularly weave and tangle) can be delegated to each Chunk, and in turn, delegated to each Command that composes a Chunk.
The weaving operation depends on the target document markup language. There are several approaches to this problem. One is to use a markup language unique to pyweb, and emit markup in the desired target language. Another is to use a standard markup language and use converters to transform the standard markup to the desired target markup. The problem with the second method is specifying the markup for actual source code elements in the document. These must be emitted in the proper markup language.
Since the application must transform input into a specific markup language, we opt using the Strategy pattern to encapsulate markup language details. Each alternative markup strategy is then a subclass of Weaver. This simplifies adding additional markup languages without inventing a markup language unique to pyweb. The author uses their preferred markup, and their preferred toolset to convert to other output languages.
The tangling operation produces output files. In earlier tools, some care was taken to understand the source code context for tangling, and provide a correct indentation. This required a command-line parameter to turn off indentation for languages like Fortran, where identation is not used. In pyweb, the indent of the actual @< command is used to set the indent of the material that follows. If all @< commands are presented at the left margin, no indentation will be done. This is helpful simplification, particularly for users of Python, where indentation is significant.
The standard Emitter class handles this basic indentation. A subclass can be created, if necessary, to handle more elaborate indentation rules.
The implementation is contained in a file that both defines the base classes and provides an overall main() function. The main() function uses these base classes to weave and tangle the output files.
An additional file provides a more sophisticated implementation, adding features to an HTML weave subclass.
The pyWeb base file is shown below:
"pyweb.py" (1)=
Shell Escape (4)
DOC String (3)
CVS Cruft and pyweb generator warning (5)
Imports (2)
Base Class Definitions (6)
Application Class (144)
Module Initialization of global variables (149)
Interface Functions (150)(151)
"pyweb.py" (1).
The overhead elements are described in separate sub sections as follows:
The more important elements are described in separate sections:
The following Python library modules are used by this application.
The following modules are used by specific subclasses for more specialized purposes.
Imports (2)=
import sys, os, re, time, getopt
import tempfile, filecmp
Imports (2). Used by pyweb.py (1) .
A Python __doc__ string provides a standard vehicle for documenting the module or the application program. The usual style is to provide a one-sentence summary on the first line. This is followed by more detailed usage information.
DOC String (3)=
"""pyWeb Literate Programming - tangle and weave tool.
Yet another simple literate programming tool derived from nuweb,
implemented entirely in Python.
This produces HTML (or LATEX) for any programming language.
Usage:
pyweb [-vs] [-c x] [-w format] [-t format] file.w
Options:
-v verbose output
-s silent output
-c x change the command character from '@' to x
-w format Use the given weaver for the final document.
The default is based on the input file, a leading '<'
indicates HTML, otherwise Latex.
-t format Use the given tangler to produce the output files.
-xw Exclude weaving
-xt Exclude tangling
-pi Permit include-command errors
file.w The input file, with @o, @d, @i, @[, @{, @|, @<, @f, @m, @u commands.
"""
DOC String (3). Used by pyweb.py (1) .
The shell escape is provided so that the user can define this file as executable, and launch it directly from their shell. The shell reads the first line of a file; when it finds the '#!' shell escape, the remainder of the line is taken as the path to the binary program that should be run. The shell runs this binary, providing the file as standard input.
Shell Escape (4)=
#!/usr/local/bin/python
Shell Escape (4). Used by pyweb.py (1) .
The CVS cruft is a standard way of placing CVS information into a Python module so it is preserved. See PEP (Python Enhancement Proposal) #8 for information on recommended styles.
We also sneak in the "DO NOT EDIT" warning that belongs in all generated application source files.
CVS Cruft and pyweb generator warning (5)=
__version__ = """$Revision$"""
### DO NOT EDIT THIS FILE!
### It was created by ./pyweb.py, __version__='$Revision$'.
### From source pyweb.w modified Tue Jul 23 12:05:28 2002.
### In working directory '/Users/slott/Documents/Books/Python Book/pyWeb'.
CVS Cruft and pyweb generator warning (5). Used by pyweb.py (1) .
There are three major class hierarchies that compose the base of this application. These are families of related classes that express the basic relationships among entities.
Additionally, there are several supporting classes:
Base Class Definitions (6)=
Error class - defines the errors raised (87)
Logger classes - handle logging of status messages (88)
Command class hierarchy - used to describe individual commands (74)
Chunk class hierarchy - used to describe input chunks (52)
Emitter class hierarchy - used to control output files (7)
Web class - describes the overall "web" of chunks (92)
WebReader class - parses the input file, building the Web structure (110)
Operation class hierarchy - used to describe basic operations of the application (127)
Base Class Definitions (6). Used by pyweb.py (1) .
An Emitter instance is resposible for control of an output file format. This includes the necessary file naming, opening, writing and closing operations. It also includes providing the correct markup for the file type.
There are several subclasses of the Emitter superclass, specialized for various file formats.
Emitter class hierarchy - used to control output files (7)=
Emitter superclass (8)
Weaver subclass to control documentation production (17)
LaTex subclass of Weaver (24)
HTML subclass of Weaver (33)
Tangler subclass of Emitter (43)
Tangler subclass which is make-sensitive (47)
Emitter Factory - used to generate emitter instances from parameter strings (50)(51)
Emitter class hierarchy - used to control output files (7). Used by Base Class Definitions (6) .
An Emitter instance is created to contain the various details of writing an output file. Emitters are created as follows:
Since each Emitter instance is responsible for the details of one file type, different subclasses of Emitter are used when tangling source code files (Tangler) and weaving files that include source code plus markup (Weaver). Further specialization is required when weaving HTML or LaTex.
In the case of tangling, the following algorithm is used:
In the case of weaving, the following algorithm is used:
The Emitter class is not a concrete class, and is never instantiated. It contains common features factored out of the Weaver and Tangler subclasses.
Inheriting from the Emitter class generally requires overriding one or more of the core methods: doOpen(), doClose() and doWrite(). A subclass of Tangler, might override the code writing methods: codeLine(), codeBlock() or codeFinish().
The Emitter class is an abstract superclass for all emitters. It defines the basic framework used to create and write to an output file. This class follows the Template design pattern. This design pattern directs us to factor the basic open(), close() and write() methods into three step algorithms.
def open( self ): common preparation self.do_open() #overridden by subclasses common finish-up tasks
The common preparation and common finish-up sections are generally internal housekeeping. The do_open() method would be overridden by subclasses to change the basic behavior.
The class has the following attributes:
Emitter superclass (8)=
class Emitter:
"""Emit an output file; handling indentation context."""
def __init__( self ):
self.fileName= ""
self.theFile= None
self.context= [0]
self.indent= 0
self.lastIndent= 0
self.linesWritten= 0
self.totalFiles= 0
self.totalLines= 0
Emitter core open, close and write (9)
Emitter write a block of code (13)(14)(15)
Emitter indent control: set, clear and reset (16)
Emitter superclass (8). Used by Emitter class hierarchy - used to control output files (7) .
The core open() method tracks the open files. A subclass overrides a doOpen() method to apply an OS-specific operations required to correctly name the output file, and opens the file. If any specific preamble is required by the output file format, this could be done in the doOpen() override. This kind of feature, however, is discouraged. The point of pyWeb (and its predecessors, noweb and nuweb) is to be very simple, putting complete control in the hands of the author.
The close() method closes the file. If some specific postamble is required, this can be part of a function that overrides doClose().
The write() method is the lowest-level, unadorned write. This does no some additional counting as well as moving the characters to the file. Any further processing could be added in a function that overrides doWrite().
The default implementations of the open() and close() methods do nothing, making them safe for debugging. The default write() method prints to the standard output file.
Emitter core open, close and write (9)=
def open( self, aFile ):
"""Open a file."""
self.fileName= aFile
self.doOpen( aFile )
self.linesWritten= 0
Emitter doOpen, to be overridden by subclasses (10)
def close( self ):
self.codeFinish()
self.doClose()
self.totalFiles += 1
self.totalLines += self.linesWritten
Emitter doClose, to be overridden by subclasses (11)
def write( self, text ):
self.linesWritten += text.count('\n')
self.doWrite( text )
Emitter doWrite, to be overridden by subclasses (12)
Emitter core open, close and write (9). Used by Emitter superclass (8) .
The doOpen(), doClose() and doWrite() method is overridden by the various subclasses to perform the unique operation for the subclass.
Emitter doOpen, to be overridden by subclasses (10)=
def doOpen( self, aFile ):
self.fileName= aFile
print "> creating %r" % self.fileName
Emitter doOpen, to be overridden by subclasses (10). Used by Emitter core open, close and write (9) .
Emitter doClose, to be overridden by subclasses (11)=
def doClose( self ):
print "> wrote %d lines to %s" % ( self.linesWritten, self.fileName )
Emitter doClose, to be overridden by subclasses (11). Used by Emitter core open, close and write (9) .
Emitter doWrite, to be overridden by subclasses (12)=
def doWrite( self, text ):
print text,
Emitter doWrite, to be overridden by subclasses (12). Used by Emitter core open, close and write (9) .
The codeBlock() method writes several lines of code. It calls the codeLine() method for each line of code after doing the correct indentation. Often, the last line of code is incomplete, so it is left unterminated. This last line of code also shows the indentation for any additional code to be tangled into this section.
Note that tab characters confuse the indent algorithm. Tabs are not expanded to spaces in this application. They should be expanded prior to creating a .w file.
The algorithm is as follows:
Emitter write a block of code (13)=
def codeBlock( self, text ):
"""Indented write of a block of code."""
self.indent= self.context[-1]
lines= text.split( '\n' )
for l in lines[:-1]:
self.codeLine( '%s%s\n' % (self.indent*' ',l) )
if lines[-1]:
self.codeLine( '%s%s' % (self.indent*' ',lines[-1]) )
self.lastIndent= self.indent+len(lines[-1])
Emitter write a block of code (13). Used by Emitter superclass (8) .
The codeLine() method writes a single line of source code. This is often overridden by weaver subclasses to transform source into a form acceptable by the final weave file format.
In the case of an HTML weaver, the HTML reserved characters (<, >, &, and ") must be replaced in the output of code. However, since the author's original document sections contain HTML these will not be altered.
Emitter write a block of code (14)+=
def codeLine( self, aLine ):
"""Each individual line of code; often overridden by weavers."""
self.write( aLine )
Emitter write a block of code (14). Used by Emitter superclass (8) .
The codeFinish() method finishes writing any cached lines when the emitter is closed.
Emitter write a block of code (15)+=
def codeFinish( self ):
if self.lastIndent > 0:
self.write('\n')
Emitter write a block of code (15). Used by Emitter superclass (8) .
The setIndent() method pushes the last indent on the context stack. This is used when tangling source to be sure that the included text is indented correctly with respect to the surrounding text.
The clrIndent() method discards the most recent indent from the context stack. This is used when finished tangling a source chunk. This restores the indent to the prevailing indent.
The resetIndent() method removes all indent context information.
Emitter indent control: set, clear and reset (16)=
def setIndent( self ):
self.context.append( self.lastIndent )
def clrIndent( self ):
self.context.pop()
def resetIndent( self ):
self.context= [0]
Emitter indent control: set, clear and reset (16). Used by Emitter superclass (8) .
A Weaver is an Emitter that produces markup in addition to user source document and code. The Weaver class is abstract, and a concrete subclass must provide markup in a specific language.
The Weaver subclass defines an Emitter used to weave the final documentation. This involves decorating source code to make it displayable. It also involves creating references and cross references among the various chunks.
The Weaver class adds several methods to the basic Emitter methods. These additional methods are used exclusively when weaving, never when tangling.
Weaver subclass to control documentation production (17)=
class Weaver( Emitter ):
"""Format various types of XRef's and code blocks when weaving."""
Weaver doOpen, doClose and doWrite overrides (18)
# A possible Decorator interface
Weaver document chunk begin-end (19)
Weaver code chunk begin-end (20)
Weaver file chunk begin-end (21)
Weaver reference command output (22)
Weaver cross reference output methods (23)
Weaver subclass to control documentation production (17). Used by Emitter class hierarchy - used to control output files (7) .
The default for all weavers is to create an HTML file. While not truly universally applicable, it is a common-enough operation that it might be justified in the parent class.
This close method overrides the Emitter class close() method by closing the actual file created by the open() method.
This write method overrides the Emitter class write() method by writing to the actual file created by the open() method.
Weaver doOpen, doClose and doWrite overrides (18)=
def doOpen( self, aFile ):
src, junk = os.path.splitext( aFile )
self.fileName= src + '.html'
self.theFile= open( self.fileName, "w" )
theLog.event( WeaveStartEvent, "Weaving %r" % self.fileName )
def doClose( self ):
self.theFile.close()
theLog.event( WeaveEndEvent, "Wrote %d lines to %r" %
(self.linesWritten,self.fileName) )
def doWrite( self, text ):
self.theFile.write( text )
Weaver doOpen, doClose and doWrite overrides (18). Used by Weaver subclass to control documentation production (17) .
The following functions all form part of an interface that could be removed to a separate class that is a kind of Decorator. Each weaver file format is really another of the possible decorators for woven output. This could separate the basic mechanism of weaving from the file-format issues of latex and HTML.
The docBegin() and docEnd() methods are used when weaving document text. Typically, nothing is done before emitting these kinds of chunks. However, putting a <!--line number--> comment is an example of possible additional processing.
Weaver document chunk begin-end (19)=
def docBegin( self, aChunk ):
pass
def docEnd( self, aChunk ):
pass
Weaver document chunk begin-end (19). Used by Weaver subclass to control documentation production (17) .
The codeBegin() method emits the necessary material prior to a chunk of source code, defined with the @d command. A subclass would override this to provide specific text for the intended file type.
The codeEnd() method emits the necessary material subsequent to a chunk of source code, defined with the @d command. The list of references is also provided so that links or cross references to chunks that refer to this chunk can be emitted. A subclass would override this to provide specific text for the intended file type.
Weaver code chunk begin-end (20)=
def codeBegin( self, aChunk ):
pass
def codeEnd( self, aChunk, references ):
pass
Weaver code chunk begin-end (20). Used by Weaver subclass to control documentation production (17) .
The fileBegin() method emits the necessary material prior to a chunk of source code, defined with the @o or @O command. A subclass would override this to provide specific text for the intended file type.
The fileEnd() method emits the necessary material subsequent to a chunk of source code, defined with the @o or @O command. The list of references is also provided so that links or cross references to chunks that refer to this chunk can be emitted. A subclass would override this to provide specific text for the intended file type.
Weaver file chunk begin-end (21)=
def fileBegin( self, aChunk ):
pass
def fileEnd( self, aChunk, references ):
pass
Weaver file chunk begin-end (21). Used by Weaver subclass to control documentation production (17) .
The referenceTo() method emits a reference to a chunk of source code. There reference is made with a @<...@> reference form within a @d, @o or @O chunk. The references are defined with the @d, @o or @O commands. A subclass would override this to provide specific text for the intended file type.
Weaver reference command output (22)=
def referenceTo( self, name, sequence ):
pass
Weaver reference command output (22). Used by Weaver subclass to control documentation production (17) .
The xrefHead() method puts decoration in front of cross-reference output. A subclass may override this to change the look of the final woven document.
The xrefFoot() method puts decoration after cross-reference output. A subclass may override this to change the look of the final woven document.
The xrefLine() method is used for both file and macro cross-references to show a name (either file name or macro name) and a list of chunks that reference the file or macro.
The xrefDefLine() method is used for the user identifier cross-reference. This shows a name and a list of chunks that reference or define the name. One of the chunks is identified as the defining chunk, all others are referencing chunks.
The default behavior simply writes the Python data structure used to represent cross reference information. A subclass may override this to change the look of the final woven document.
Weaver cross reference output methods (23)=
def xrefHead( self ):
pass
def xrefFoot( self ):
pass
def xrefLine( self, name, refList ):
"""File Xref and Macro Xref detail line."""
self.write( "%s: %r\n" % ( name, refList ) )
def xrefDefLine( self, name, defn, refList ):
"""User ID Xref detail line."""
self.write( "%s: %s, %r\n" % ( name, defn, refList ) )
Weaver cross reference output methods (23). Used by Weaver subclass to control documentation production (17) .
An instance of Latex can be used by the Web object to weave an output document. The instance is created outside the Web, and given to the weave() method of the Web.
w= Web() WebReader(aFile).load( w ) weave_latex= Latex() w.weave( weave_latex )
The LaTex subclass defines a Weaver that is customized to produce LaTex output of code sections and cross reference information.
Note that this implementation is incomplete, and possibly incorrect. This is a badly damaged snapshot from the nuweb original source.
LaTex subclass of Weaver (24)=
class Latex( Weaver ):
"""Latex formatting for XRef's and code blocks when weaving."""
LaTex doOpen override, close and write are the same as Weaver (25)
LaTex code chunk begin (26)
LaTex code chunk end (27)
LaTex file output begin (28)
LaTex file output end (29)
LaTex references summary at the end of a chunk (30)
LaTex write a line of code (31)
LaTex reference to a chunk (32)
LaTex subclass of Weaver (24). Used by Emitter class hierarchy - used to control output files (7) .
The LaTex open() method opens a .tex file by replacing the source file's suffix with ".tex" and opening the resulting file.
LaTex doOpen override, close and write are the same as Weaver (25)=
def doOpen( self, aFile ):
src, junk = os.path.splitext( aFile )
self.fileName= src + '.tex'
self.theFile= open( self.fileName, "w" )
theLog.event( WeaveStartEvent, "Weaving %r" % self.fileName )
LaTex doOpen override, close and write are the same as Weaver (25). Used by LaTex subclass of Weaver (24) .
The LaTex codeBegin() method writes the header prior to a chunk of source code.
LaTex code chunk begin (26)=
def codeBegin( self, aChunk ):
self.resetIndent()
self.write("\\begin{flushleft} \\small")
if not aChunk.big_definition: # defined with 'O' instead of 'o'
self.write("\\begin{minipage}{\\linewidth}")
self.write( " \\label{scrap%d}" % aChunk.seq )
self.write( '\\verb@"%s"@~{\\footnotesize ' % aChunk.name )
self.write( "\\NWtarget{nuweb%s}{%s}$\\equiv$"
% (aChunk.name,aChunk.seq) )
self.write( "\\vspace{-1ex}\n\\begin{list}{}{} \\item" )
LaTex code chunk begin (26). Used by LaTex subclass of Weaver (24) .
The LaTex codeEnd() method writes the trailer subsequent to a chunk of source code. This calls the LaTex references() method to write a reference to the chunk that invokes this chunk.
LaTex code chunk end (27)=
def codeEnd( self, aChunk, references ):
self.write("{\\NWsep}\n\\end{list}")
self.references( references )
if not aChunk.big_definition: # defined with 'O' instead of 'o'
self.write("\\end{minipage}\\\\[4ex]")
self.write("\\end{flushleft}")
LaTex code chunk end (27). Used by LaTex subclass of Weaver (24) .
The LaTex codeBegin() method writes the header prior to a the creation of a tangled file.
LaTex file output begin (28)=
def fileBegin( self, aChunk ):
self.resetIndent()
self.write("\\begin{flushleft} \\small")
if not aChunk.big_definition: # defined with 'O' instead of 'o'
self.write("\\begin{minipage}{\\linewidth}")
self.write( " \\label{scrap%d}" % aChunk.seq )
self.write( '\\verb@"%s"@~{\\footnotesize ' % aChunk.name )
self.write( "\\NWtarget{nuweb%s}{%s}$\\equiv$"% (aChunk.name,aChunk.seq) )
self.write( "\\vspace{-1ex}\n\\begin{list}{}{} \\item" )
LaTex file output begin (28). Used by LaTex subclass of Weaver (24) .
The LaTex codeEnd() method writes the trailer subsequent to a tangled file. This calls the LaTex references() method to write a reference to the chunk that invokes this chunk.
LaTex file output end (29)=
def fileEnd( self, aChunk, references ):
self.write("{\\NWsep}\n\\end{list}")
self.references( references )
if not aChunk.big_definition: # defined with 'O' instead of 'o'
self.write("\\end{minipage}\\\\[4ex]")
self.write("\\end{flushleft}")
LaTex file output end (29). Used by LaTex subclass of Weaver (24) .
The references() method writes a list of references after a chunk of code.
LaTex references summary at the end of a chunk (30)=
def references( self, references ):
if references:
self.write("\\vspace{-1ex}")
self.write("\\footnotesize\\addtolength{\\baselineskip}{-1ex}")
self.write("\\begin{list}{}{\\setlength{\\itemsep}{-\\parsep}")
self.write("\\setlength{\\itemindent}{-\\leftmargin}}")
for n,s in references:
self.write("\\item \\NWtxtFileDefBy\\ %s (%s)" % (n,s) )
self.write("\\end{list}")
else:
self.write("\\vspace{-2ex}")
LaTex references summary at the end of a chunk (30). Used by LaTex subclass of Weaver (24) .
The codeLine() method writes a single line of code to the weaver, providing the necessary LaTex markup.
LaTex write a line of code (31)=
def codeLine( self, aLine ):
"""Each individual line of code with LaTex decoration."""
self.write( '\\mbox{}\\verb@%s@\\\\\n' % aLine.rstrip() )
LaTex write a line of code (31). Used by LaTex subclass of Weaver (24) .
The referenceTo() method writes a reference to another chunk of code. It uses write directly as to follow the current indentation on the current line of code.
LaTex reference to a chunk (32)=
def referenceTo( self, name, sequence ):
self.write( "\\NWlink{nuweb%s}{%s}$\\equiv$"% (name,sequence) )
LaTex reference to a chunk (32). Used by LaTex subclass of Weaver (24) .
An instance of HTML can be used by the Web object to weave an output document. The instance is created outside the Web, and given to the weave() method of the Web.
w= Web() WebReader(aFile).load( w ) weave_html= HTML() w.weave( weave_html )
The HTML subclass defines a Weaver that is customized to produce HTML output of code sections and cross reference information.
All HTML chunks are identified by anchor names of the form pywebn. Each n is the unique chunk number, in sequential order.
HTML subclass of Weaver (33)=
class HTML( Weaver ):
"""HTML formatting for XRef's and code blocks when weaving."""
HTML code chunk begin (34)
HTML code chunk end (35)
HTML output file begin (36)
HTML output file end (37)
HTML references summary at the end of a chunk (38)
HTML write a line of code (39)
HTML reference to a chunk (40)
HTML simple cross reference markup (41)
HTML subclass of Weaver (33). Used by Emitter class hierarchy - used to control output files (7) .
The codeBegin() method starts a chunk of code, defined with @d, providing a label and HTML tags necessary to set the code off visually.
HTML code chunk begin (34)=
def codeBegin( self, aChunk ):
self.resetIndent()
self.write( '\n<a name="pyweb%s"></a>\n' % ( aChunk.seq ) )
self.write( '<!--line number %s-->' % (aChunk.lineNumber()) )
self.write( '<p><em>%s</em> (%s) %s</p>\n'
% (aChunk.fullName,aChunk.seq,aChunk.firstSecond) )
self.write( "<pre><code>\n" )
HTML code chunk begin (34). Used by HTML subclass of Weaver (33) .
The codeEnd() method ends a chunk of code, providing a HTML tags necessary to finish the code block visually. This calls the references method to write the list of chunks that reference this chunk.
HTML code chunk end (35)=
def codeEnd( self, aChunk, references ):
self.write( "\n</code></pre>\n" )
self.write( '<p>◊ <em>%s</em> (%s).' % (aChunk.fullName,aChunk.seq) )
self.references( references )
self.write( "</p>\n" )
HTML code chunk end (35). Used by HTML subclass of Weaver (33) .
The fileBegin() method starts a chunk of code, defined with @o or @O, providing a label and HTML tags necessary to set the code off visually.
HTML output file begin (36)=
def fileBegin( self, aChunk ):
self.resetIndent()
self.write( '\n<a name="pyweb%s"></a>\n' % ( aChunk.seq ) )
self.write( '<!--line number %s-->' % (aChunk.lineNumber()) )
self.write( '<p><tt>"%s"</tt> (%s) %s</p>\n'
% (aChunk.fullName,aChunk.seq,aChunk.firstSecond) )
self.write( "<pre><code>\n" )
HTML output file begin (36). Used by HTML subclass of Weaver (33) .
The fileEnd() method ends a chunk of code, providing a HTML tags necessary to finish the code block visually. This calls the references method to write the list of chunks that reference this chunk.
HTML output file end (37)=
def fileEnd( self, aChunk, references ):
self.write( "\n</code></pre>\n" )
self.write( '<p>◊ <tt>"%s"</tt> (%s).' % (aChunk.fullName,aChunk.seq) )
self.references( references )
self.write( "</p>\n" )
HTML output file end (37). Used by HTML subclass of Weaver (33) .
The references() method writes the list of chunks that refer to this chunk.
HTML references summary at the end of a chunk (38)=
def references( self, references ):
if references:
self.write( " Used by ")
for n,s in references:
self.write( '<a href="#pyweb%s"><em>%s</em> (%s)</a> ' % ( s,n,s ) )
self.write( "." )
HTML references summary at the end of a chunk (38). Used by HTML subclass of Weaver (33) .
The codeLine() method writes an individual line of code for HTML purposes. This encodes the four basic HTML entities (<, >, &, ") to prevent code from being interpreted as HTML.
The htmlClean() method does the basic HTML entity replacement. This is factored out of the basic codeLine() method so that subclasses can use this method, also.
HTML write a line of code (39)=
def htmlClean( self, text ):
"""Replace basic HTML entities."""
clean= text.replace( "&", "&" ).replace( '"', """ )
clean= clean.replace( "<", "<" ).replace( ">", ">" )
return clean
def codeLine( self, aLine ):
"""Each individual line of code with HTML cleanup."""
self.write( self.htmlClean(aLine) )
HTML write a line of code (39). Used by HTML subclass of Weaver (33) .
The referenceTo() method writes a reference to another chunk. It uses the direct write() method so that the reference is indented properly with the surrounding source code.
HTML reference to a chunk (40)=
def referenceTo( self, aName, seq ):
"""Weave a reference to a chunk."""
# Provide name to get a full reference.
# Omit name to get a short reference.
if aName:
self.write( '<a href="#pyweb%s">→<em>%s</em> (%s)</a> '
% ( seq, aName, seq ) )
else:
self.write( '<a href="#pyweb%s">(%s)</a> '
% ( seq, seq ) )
HTML reference to a chunk (40). Used by HTML subclass of Weaver (33) .
The xrefHead() method writes the heading for any of the cross reference blocks created by @f, @m, or @u. In this implementation, the cross references are simply unordered lists.
The xrefFoot() method writes the footing for any of the cross reference blocks created by @f, @m, or @u. In this implementation, the cross references are simply unordered lists.
The xrefLine() method writes a line for the file or macro cross reference blocks created by @f or @m. In this implementation, the cross references are simply unordered lists.
HTML simple cross reference markup (41)=
def xrefHead( self ):
self.write( "<dl>\n" )
def xrefFoot( self ):
self.write( "</dl>\n" )
def xrefLine( self, name, refList ):
self.write( "<dt>%s:</dt><dd>" % name )
for r in refList:
self.write( '<a href="#pyweb%s">%s</a> ' % (r,r) )
self.write( "</dd>\n" )
HTML write user id cross reference line (42)
HTML simple cross reference markup (41). Used by HTML subclass of Weaver (33) .
The xrefDefLine() method writes a line for the user identifier cross reference blocks created by @u. In this implementation, the cross references are simply unordered lists. The defining instance is included in the correct order with the other instances, but is bold and marked with a bullet ().
HTML write user id cross reference line (42)=
def xrefDefLine( self, name, defn, refList ):
self.write( "<dt>%s:</dt><dd>" % name )
allPlaces= refList+[defn]
allPlaces.sort()
for r in allPlaces:
if r == defn:
self.write( '<a href="#pyweb%s"><b>•%s</b></a> '
% (r,r) )
else:
self.write( '<a href="#pyweb%s">%s</a> ' % (r,r) )
self.write( "</dd>\n" )
HTML write user id cross reference line (42). Used by HTML simple cross reference markup (41) .
The Tangler class is concrete, and can tangle source files. An instance of Tangler is given to the Web class tangle() method.
w= Web() WebReader( aFile ).load( w ) t= Tangler() w.tangle( t )
The Tangler subclass defines an Emitter used to tangle the various program source files. The superclass is used to simply emit correctly indented source code and do very little else that could corrupt or alter the output.
Language-specific subclasses could be used to provide additional decoration. For example, inserting #line directives showing the line number in the original source file.
Tangler subclass of Emitter (43)=
class Tangler( Emitter ):
"""Tangle output files."""
Tangler doOpen, doClose and doWrite overrides (44)
Tangler code chunk begin (45)
Tangler code chunk end (46)
Tangler subclass of Emitter (43). Used by Emitter class hierarchy - used to control output files (7) .
The default for all tanglers is to create the named file.
This doClose() method overrides the Emitter class doClose() method by closing the actual file created by open.
This doWrite() method overrides the Emitter class doWrite() method by writing to the actual file created by open.
Tangler doOpen, doClose and doWrite overrides (44)=
def doOpen( self, aFile ):
self.fileName= aFile
self.theFile= open( aFile, "w" )
theLog.event( TangleStartEvent, "Tangling %r" % aFile )
def doClose( self ):
self.theFile.close()
theLog.event( TangleEndEvent, "Wrote %d lines to %r"
% (self.linesWritten,self.fileName) )
def doWrite( self, text ):
self.theFile.write( text )
Tangler doOpen, doClose and doWrite overrides (44). Used by Tangler subclass of Emitter (43) .
The codeBegin() method starts emitting a new chunk of code. It does this by setting the Tangler's indent to the prevailing indent at the start of the @< reference command.
Tangler code chunk begin (45)=
def codeBegin( self, aChunk ):
self.setIndent()
Tangler code chunk begin (45). Used by Tangler subclass of Emitter (43) .
The codeEnd() method ends emitting a new chunk of code. It does this by resetting the Tangler's indent to the previous setting.
Tangler code chunk end (46)=
def codeEnd( self, aChunk ):
self.clrIndent()
Tangler code chunk end (46). Used by Tangler subclass of Emitter (43) .
The TanglerMake class is can tangle source files. An instance of TanglerMake is given to the Web class tangle() method.
w= Web() WebReader( aFile ).load( w ) t= TanglerMake() w.tangle( t )
The TanglerMake subclass makes the Tangler used to tangle the various program source files more make-friendly. This subclass of Tangler does not touch an output file where there is no change. This is helpful when pyWeb's output is sent to make. Using TanglerMake assures that only files with real changes are rewritten, minimizing recompilation of an application for changes to the associated documentation.
Tangler subclass which is make-sensitive (47)=
class TanglerMake( Tangler ):
"""Tangle output files, leaving files untouched if there are no changes."""
def __init__( self ):
Tangler.__init__( self )
self.tempname= None
TanglerMake doOpen override, using a temporary file (48)
TanglerMake doClose override, comparing temporary to original (49)
Tangler subclass which is make-sensitive (47). Used by Emitter class hierarchy - used to control output files (7) .
A TanglerMake creates a temporary file to collect the tangled output. When this file is completed, we can compare it with the original file in this directory, avoiding a "touch" if the new file is the same as the original.
TanglerMake doOpen override, using a temporary file (48)=
def doOpen( self, aFile ):
self.tempname= tempfile.mktemp()
self.theFile= open( self.tempname, "w" )
theLog.event( TangleStartEvent, "Tangling %r" % aFile )
TanglerMake doOpen override, using a temporary file (48). Used by Tangler subclass which is make-sensitive (47) .
If there is a previous file: compare the temporary file and the previous file. If there was previous file or the files are different: rename temporary to replace previous; else: unlink temporary and discard it. This preserves the original (with the original date and time) if nothing has changed.
TanglerMake doClose override, comparing temporary to original (49)=
def doClose( self ):
self.theFile.close()
try:
same= filecmp.cmp( self.tempname, self.fileName )
except OSError,e:
same= 0
if same:
theLog.event( SummaryEvent, "No change to %r" % (self.fileName) )
os.remove( self.tempname )
else:
# note the Windows requires the original file name be removed first
try:
os.remove( self.fileName )
except OSError,e:
pass
os.rename( self.tempname, self.fileName )
theLog.event( TangleEndEvent, "Wrote %d lines to %r"
% (self.linesWritten,self.fileName) )
TanglerMake doClose override, comparing temporary to original (49). Used by Tangler subclass which is make-sensitive (47) .
We use the Factory Method design pattern to permit extending the Emitter class hierarchy. Any application that imports this basic pyWeb module can define appropriate new subclasses, provide a subclass of this EmitterFactory, and use the existing main program.
import pyweb class MyHTMLWeaver( HTML ): ... (overrides to various methods) ... class MyEmitterFactory( EmitterFactory ): def mkEmitter( self, name ): """Make an Emitter - try superclass first, then locally defined.""" s= pyweb.EmitterFactory.mkEmitter( self, name ) if s: return s if name.lower() == 'myhtmlweaver': return MyHTMLWeaver() return None if __name__ == "__main__": pyweb.main( MyEmitterFactory(), sys.argv )
We use a Chain of Command-like design for the mkEmitter() method. A subclass first uses the parent class mkEmitter() to see if the name is recognized. If it is not, then the subclass can match the added class names against the argument.
To emphasize the implementation, we provide an EmitterFactory superclass that creates the abstract superclasses of Weaver and Tangler. We subclass this to create a more useful EmitterFactory that creates any of the instances in this base pyWeb module.
The EmitterFactorySuper is a superclass that only recognizes the basic Weaver and Tangler emitters. This must be subclassed to recognize the more useful emitters.
Emitter Factory - used to generate emitter instances from parameter strings (50)=
class EmitterFactorySuper:
def mkEmitter( self, name ):
if name.lower() == 'weaver': return Weaver()
elif name.lower() == 'tangler': return Tangler()
return None
Emitter Factory - used to generate emitter instances from parameter strings (50). Used by Emitter class hierarchy - used to control output files (7) .
The EmitterFactory class is a subclass of EmitterFactorySuper that recognizes all of the various emitters defined in this module. It also shows how a subclass would be constructed.
Emitter Factory - used to generate emitter instances from parameter strings (51)+=
class EmitterFactory( EmitterFactorySuper ):
def mkEmitter( self, name ):
"""Make an Emitter - try superclass first, then locally defined."""
s= EmitterFactorySuper.mkEmitter( self, name )
if s: return s
if name.lower() == 'html': return Weaver()
elif name.lower() == 'latex': return Latex()
elif name.lower() == 'tanglermake': return TanglerMake()
return None
Emitter Factory - used to generate emitter instances from parameter strings (51). Used by Emitter class hierarchy - used to control output files (7) .
A Chunk is a piece of the input file. It is a collection of Command instances. A chunk can be woven or tangled to create output.
The two most important methods are the weave() and tangle() methods. These visit the commands of this chunk, producing the required output file.
Additional methods (startswith(), searchForRE() and usedBy()) are used to examine the text of the Command instances within the chunk.
A Chunk instance is created by the WebReader as the input file is parsed. Each Chunk instance has one or more pieces of the original input text. This text can be program source, a reference command, or the documentation source.
Chunk class hierarchy - used to describe input chunks (52)=
Chunk class (53)
NamedChunk class (63)
OutputChunk class (68)
NamedDocumentChunk class (71)
Chunk class hierarchy - used to describe input chunks (52). Used by Base Class Definitions (6) .
The Chunk class is both the superclass for this hierarchy and the implementation for anonymous chunks. An anonymous chunk is always documentation in the target markup language. No transformation is ever done on anonymous chunks.
A NamedChunk is a chunk created with a @d command. This is a chunk of source programming language, bracketed with @{ and @}.
An OutputChunk is a named chunk created with a @o or @O command. This must be a chunk of source programming language, bracketed with @{ and @}.
A NamedDocumentChunk is a named chunk created with a @d command. This is a chunk of documentation in the target markup language, bracketed with @[ and @].
An instance of the Chunk class has a life that includes four important events: creation, cross-reference, weave and tangle.
A Chunk is created by a WebReader, and associated with a Web. There are several web append methods, depending on the exact subclass of Chunk. The WebReader calls the chunk's webAdd() method select the correct method for appending and indexing the chunk. Individual instances of Command are appended to the chunk. The basic outline for creating a Chunk instance is as follows:
w= Web() c= Chunk() c.webAdd( w ) c.append( ...some Command... ) c.append( ...some Command... )
Before weaving or tangling, a cross reference is created for all user identifiers in all of the Chunk instances. This is done by: (1) visit each Chunk and call the getUserIDRefs() method to gather all identifiers; (2) for each identifier, visit each Chunk and call the searchForRE() method to find uses of the identifier.
ident= [] for c in the Web's named chunk list: ident.extend( c.getUserIDRefs() ) for i in ident: pattern= re.compile('\W%s\W' % i) for c in the Web's named chunk list: c.searchForRE( pattern, self )
A Chunk is woven or tangled by the Web. The basic outline for weaving is as follows. The tangling operation is essentially the same.
for c in the Web's chunk list: c.weave( aWeaver )
The Chunk class contains the overall definitions for all of the various specialized subclasses. In particular, it contains the append(), appendChar() and appendText() methods used by all of the various Chunk subclasses.
When a @@ construct is located in the input stream, the stream contains three text tokens: material before the @@, the @@, and the material after the @@. These three tokens are reassembled into a single block of text. This reassembly is accomplished by changing the chunk's state so that the next TextCommand is appended onto the previous TextCommand.
There are two operating states for instances of this class. The state change is accomplished on a call to the appendChar() method, and alters the behavior of the appendText() method. The appendText() method either:
Each subclass of Chunk has a particular type of text that it will process. Anonymous chunks only handle document text. The NamedChunk subclass that handles program source will override this method to create a different command type. The makeContent() method creates the appropriate Command instance for this Chunk subclass.
The weave() method of an anonymous Chunk uses the weaver's docBegin() and docEnd() methods to insert text that is source markup. Other subclasses will override this to use different Weaver methods for different kinds of text.
The Chunk constructor initializes the following instance variables:
Chunk class (53)=
class Chunk:
"""Anonymous piece of input file: will be output through the weaver only."""
# construction and insertion into the web
def __init__( self ):
self.commands= [ ]
self.lastCommand= None
self.big_definition= None
self.xref= None
self.firstSecond= None
self.name= ''
self.seq= None
def __str__( self ):
return "\n".join( map( str, self.commands ) )
Chunk append a command (54)
Chunk append a character (55)
Chunk append text (56)
Chunk add to the web (57)
def makeContent( self, text, lineNumber=0 ):
return TextCommand( text, lineNumber )
Chunk examination: starts with, matches pattern, references (58)
Chunk weave (61)
Chunk tangle (62)
Chunk class (53). Used by Chunk class hierarchy - used to describe input chunks (52) .
The append() method simply appends a Command instance to this chunk.
Chunk append a command (54)=
def append( self, command ):
"""Add another Command to this chunk."""
self.commands.append( command )
Chunk append a command (54). Used by Chunk class (53) .
When an @@ construct is located, the appendChar() method:
Chunk append a character (55)=
def appendChar( self, text, lineNumber=0 ):
"""Append a single character to the most recent TextCommand."""
if len(self.commands)==0 or not isinstance(self.commands[-1],TextCommand):
self.commands.append( self.makeContent("",lineNumber) )
self.commands[-1].text += text
self.lastCommand= self.commands[-1]
Chunk append a character (55). Used by Chunk class (53) .
The appendText() method appends a TextCommand to this chunk, or it appends it to the most recent TextCommand. This condition is defined by the appendChar() method.
Chunk append text (56)=
def appendText( self, text, lineNumber=0 ):
"""Add another TextCommand to this chunk or concatenate to the most recent TextCommand."""
if self.lastCommand:
assert len(self.commands)>=1 and isinstance(self.commands[-1],TextCommand)
self.commands[-1].text += text
self.lastCommand= None
else:
self.commands.append( self.makeContent(text,lineNumber) )
Chunk append text (56). Used by Chunk class (53) .
The webAdd() method adds this chunk to the given document web. Each subclass of the Chunk class must override this to be sure that the various Chunk subclasses are indexed properly. The Chunk class uses the add() method of the Web class to append an anonymous, unindexed chunk.
Chunk add to the web (57)=
def webAdd( self, web ):
"""Add self to a Web as anonymous chunk."""
web.add( self )
Chunk add to the web (57). Used by Chunk class (53) .
The startsWith() method examines a the first Command instance this Chunk instance to see if it starts with the given prefix string.
The lineNumber() method returns the line number of the first Command in this chunk. This provides some context for where the chunk occurs in the original input file.
A NamedChunk instance may define one or more identifiers. This parent class provides a dummy version of the getUserIDRefs method. The NamedChunk subclass overrides this to provide actual results. By providing this at the superclass level, the Web can easily gather identifiers without knowing the actual subclass of Chunk.
The searchForRE() method examines each Command instance to see if it matches with the given regular expression. If so, this can be reported to the Web instance and accumulated as part of a cross reference for this Chunk.
The usedBy() method visits each Command instance; a Command instance calls the Web class setUsage() method to report the references from this Chunk to other Chunks. This set of references can be reversed to identify the chunks that refer to this chunk.
Chunk examination: starts with, matches pattern, references (58)=
def startswith( self, prefix ):
"""Examine the first command's starting text."""
return len(self.commands) >= 1 and self.commands[0].startswith( prefix )
def searchForRE( self, rePat, aWeb ):
"""Visit each command, applying the pattern."""
Chunk search for user identifiers done by iteration through each command (59)
def usedBy( self, aWeb ):
"""Update web's used-by xref."""
Chunk usedBy update done by iteration through each command (60)
def lineNumber( self ):
"""Return the first command's line number or None."""
return len(self.commands) >= 1 and self.commands[0].lineNumber
def getUserIDRefs( self ):
return []
Chunk examination: starts with, matches pattern, references (58). Used by Chunk class (53) .
The chunk search in the searchForRE() method parallels weaving and tangling a Chunk. The operation is delegated to each Command instance within the Chunk instance.
Chunk search for user identifiers done by iteration through each command (59)=
for c in self.commands:
if c.searchForRE( rePat, aWeb ):
return self
return None
Chunk search for user identifiers done by iteration through each command (59). Used by Chunk examination: starts with, matches pattern, references (58) .
The usedBy() update visits each Command instance. It calls the Command class usedBy() method, passing in the overall Web instance and this Chunk instance. This allows the Command to generate a reference from this Chunk to another Chunk, and notify the Web instance of this reference. The Command, if it is a ReferenceCommand, will also update the Chunk instance refCount attribute.
Note that an exception may be raised by this operation if a referenced Chunk does not actually exist. If a reference Command does raise an error, we append this Chunk information and reraise the error with the additional context information.
Chunk usedBy update done by iteration through each command (60)=
try:
for t in self.commands:
t.usedBy( aWeb, self )
except Error,e:
raise Error,e.args+(self,)
Chunk usedBy update done by iteration through each command (60). Used by Chunk examination: starts with, matches pattern, references (58) .
The weave() method weaves this chunk into the final document as follows:
Note that an exception may be raised by this operation if a referenced Chunk does not actually exist. If a reference Command does raise an error, we append this Chunk information and reraise the error with the additional context information.
Chunk weave (61)=
def weave( self, aWeb, aWeaver ):
"""Create the nicely formatted document from an anonymous chunk."""
aWeaver.docBegin( self )
try:
for t in self.commands:
t.weave( aWeb, aWeaver )
except Error, e:
raise Error,e.args+(self,)
aWeaver.docEnd( self )
def weaveReferenceTo( self, aWeb, aWeaver ):
"""Create a reference to this chunk -- except for anonymous chunks."""
raise Exception( "Cannot reference an anonymous chunk.""")
def weaveShortReferenceTo( self, aWeb, aWeaver ):
"""Create a short reference to this chunk -- except for anonymous chunks."""
raise Exception( "Cannot reference an anonymous chunk.""")
Chunk weave (61). Used by Chunk class (53) .
Anonymous chunks cannot be tangled. Any attempt indicates a serious problem with this program or the input file.
Chunk tangle (62)=
def tangle( self, aWeb, aTangler ):
"""Create source code -- except anonymous chunks should not be tangled"""
raise Error( 'Cannot tangle an anonymous chunk', self )
Chunk tangle (62). Used by Chunk class (53) .
A NamedChunk is created and used almost identically to an anonymous Chunk. The most significant difference is that a name is provided when the NamedChunk is created. This name is used by the Web to organize the chunks.
A NamedChunk is created with a @d, @o or @O command. A NamedChunk contains programming language source when the brackets are @{ and @}. A separate subclass of NamedDocumentChunk is used when the brackets are @[ and @].
A NamedChunk can be both tangled into the output program files, and woven into the output document file.
The weave() method of a NamedChunk uses the Weaver's codeBegin() and codeEnd() methods to insert text that is program source and requires additional markup to make it stand out from documentation. Other subclasses can override this to use different Weaver methods for different kinds of text.
This class introduces some additional attributes.
NamedChunk class (63)=
class NamedChunk( Chunk ):
"""Named piece of input file: will be output as both tangler and weaver."""
def __init__( self, name ):
Chunk.__init__( self )
self.name= name
self.seq= None
self.fullName= None
self.xref= []
self.refCount= 0
def __str__( self ):
return "%r: %s" % ( self.name, Chunk.__str__(self) )
def makeContent( self, text, lineNumber=0 ):
return CodeCommand( text, lineNumber )
NamedChunk user identifiers set and get (64)
NamedChunk add to the web (65)
NamedChunk weave (66)
NamedChunk tangle (67)
NamedChunk class (63). Used by Chunk class hierarchy - used to describe input chunks (52) .
The setUserIDRefs() method accepts a list of user identifiers that are associated with this chunk. These are provided after the @| separator in a @d named chunk. These are used by the @u cross reference generator.
NamedChunk user identifiers set and get (64)=
def setUserIDRefs( self, text ):
"""Save xref variable names."""
self.xref= text.split()
def getUserIDRefs( self ):
return self.xref
NamedChunk user identifiers set and get (64). Used by NamedChunk class (63) .
The webAdd() method adds this chunk to the given document Web instance. Each class of Chunk must override this to be sure that the various Chunk classes are indexed properly. This class uses the addNamed() method of the Web class to append a named chunk.
NamedChunk add to the web (65)=
def webAdd( self, web ):
"""Add self to a Web as named chunk, update xrefs."""
web.addNamed( self )
NamedChunk add to the web (65). Used by NamedChunk class (63) .
The weave() method weaves this chunk into the final document as follows:
The weaveRefenceTo() method weaves a reference to a chunk using both name and sequence number. The weaveShortReferenceTo() method weaves a reference to a chunk using only the sequence number. These references are created by ReferenceCommand instances within a chunk being woven.
If a ReferenceCommand does raise an error during weaving, we append this Chunk information and reraise the error with the additional context information.
NamedChunk weave (66)=
def weave( self, aWeb, aWeaver ):
"""Create the nicely formatted document from a chunk of code."""
# format as <pre> in a different-colored box
self.fullName= aWeb.fullNameFor( self.name )
aWeaver.codeBegin( self )
for t in self.commands:
try:
t.weave( aWeb, aWeaver )
except Error,e:
raise Error,e.args+(self,)
aWeaver.codeEnd( self, aWeb.chunkReferencedBy( self.seq ) )
def weaveReferenceTo( self, aWeb, aWeaver ):
"""Create a reference to this chunk."""
self.fullName= aWeb.fullNameFor( self.name )
aWeaver.referenceTo( self.fullName, self.seq )
def weaveShortReferenceTo( self, aWeb, aWeaver ):
"""Create a shortened reference to this chunk."""
aWeaver.referenceTo( None, self.seq )
NamedChunk weave (66). Used by NamedChunk class (63) .
The tangle() method tangles this chunk into the final document as follows:
If a ReferenceCommand does raise an error during tangling, we append this Chunk information and reraise the error with the additional context information.
NamedChunk tangle (67)=
def tangle( self, aWeb, aTangler ):
"""Create source code."""
# use aWeb to resolve @<namedChunk@>
# format as correctly indented source text
aTangler.codeBegin( self )
for t in self.commands:
try:
t.tangle( aWeb, aTangler )
except Error,e:
raise Error,e.args+(self,)
aTangler.codeEnd( self )
NamedChunk tangle (67). Used by NamedChunk class (63) .
A OutputChunk is created and used identically to a NamedChunk. The difference between this class and the parent class is the decoration of the markup when weaving.
The OutputChunk class is a subclass of NamedChunk that handles file output chunks defined with @o or @O. These are woven slightly differently, to allow for a presentation of the file chunks that is different from the presentation of the other named chunks.
The weave() method of a OutputChunk uses the Weaver's fileBegin() and fileEnd() methods to insert text that is program source and requires additional markup to make it stand out from documentation. Other subclasses could override this to use different Weaver methods for different kinds of text.
All other methods, including the tangle method are identical to NamedChunk.
OutputChunk class (68)=
class OutputChunk( NamedChunk ):
"""Named piece of input file, defines an output tangle."""
OutputChunk add to the web (69)
OutputChunk weave (70)
OutputChunk class (68). Used by Chunk class hierarchy - used to describe input chunks (52) .
The webAdd() method adds this chunk to the given document Web. Each class of Chunk must override this to be sure that the various Chunk classes are indexed properly. This class uses the addOutput() method of the Web class to append a file output chunk.
OutputChunk add to the web (69)=
def webAdd( self, web ):
"""Add self to a Web as output chunk, update xrefs."""
web.addOutput( self )
OutputChunk add to the web (69). Used by OutputChunk class (68) .
The weave() method weaves this chunk into the final document as follows:
These chunks of documentation are never tangled. Any attempt is an error.
If a ReferenceCommand does raise an error during weaving, we append this Chunk information and reraise the error with the additional context information.
OutputChunk weave (70)=
def weave( self, aWeb, aWeaver ):
"""Create the nicely formatted document from a chunk of code."""
# format as <pre> in a different-colored box
self.fullName= aWeb.fullNameFor( self.name )
aWeaver.fileBegin( self )
try:
for t in self.commands:
t.weave( aWeb, aWeaver )
except Error,e:
raise Error,e.args+(self,)
aWeaver.fileEnd( self, aWeb.chunkReferencedBy( self.seq ) )
OutputChunk weave (70). Used by OutputChunk class (68) .
A NamedDocumentChunk is created and used identically to a NamedChunk. The difference between this class and the parent class is that this chunk is only woven when referenced. The original definition is silently skipped.
The NamedDocumentChunk class is a subclass of NamedChunk that handles named chunks defined with @d and the @[...@] delimiters. These are woven slightly differently, since they are document source, not programming language source.
We're not as interested in the cross reference of named document chunks. They can be used multiple times or never. They are often referenced by anonymous chunks. While this chunk subclass participates in this data gathering, it is ignored for reporting purposes.
All other methods, including the tangle method are identical to NamedChunk.
NamedDocumentChunk class (71)=
class NamedDocumentChunk( NamedChunk ):
"""Named piece of input file with document source, defines an output tangle."""
def makeContent( self, text, lineNumber=0 ):
return TextCommand( text, lineNumber )
NamedDocumentChunk weave (72)
NamedDocumentChunk tangle (73)
NamedDocumentChunk class (71). Used by Chunk class hierarchy - used to describe input chunks (52) .
The weave() method quietly ignores this chunk in the document. A named document chunk is only included when it is referenced during weaving of another chunk (usually an anonymous document chunk).
The weaveReferenceTo() method inserts the content of this chunk into the output document. This is done in response to a ReferenceCommand in another chunk. The weaveShortReferenceTo() method calls the weaveReferenceTo() to insert the entire chunk.
NamedDocumentChunk weave (72)=
def weave( self, aWeb, aWeaver ):
"""Ignore this when producing the document."""
pass
def weaveReferenceTo( self, aWeb, aWeaver ):
"""On a reference to this chunk, expand the body in place."""
try:
for t in self.commands:
t.weave( aWeb, aWeaver )
except Error,e:
raise Error,e.args+(self,)
def weaveShortReferenceTo( self, aWeb, aWeaver ):
"""On a reference to this chunk, expand the body in place."""
self.weaveReferenceTo( aWeb, aWeaver )
NamedDocumentChunk weave (72). Used by NamedDocumentChunk class (71) .
NamedDocumentChunk tangle (73)=
def tangle( self, aWeb, aTangler ):
"""Raise an exception on an attempt to tangle."""
raise Error( "Cannot tangle a chunk defined with @[.""" )
NamedDocumentChunk tangle (73). Used by NamedDocumentChunk class (71) .
The input stream is broken into individual commands, based on the various @x strings in the file. There are several subclasses of Command, each used to describe a different command or block of text in the input.
All instances of the Command class are created by a WebReader instance. In this case, a WebReader can be thought of as a factory for Command instances. Each Command instance is appended to the sequence of commands that belong to a Chunk. A chunk may be as small as a single command, or a long sequence of commands.
Each command instance responds to methods to examine the content, gather cross reference information and tangle a file or weave the final document.
Command class hierarchy - used to describe individual commands (74)=
Command superclass (75)
TextCommand class to contain a document text block (76)
CodeCommand class to contain a program source code block (77)
XrefCommand superclass for all cross-reference commands (78)
FileXrefCommand class for an output file cross-reference (79)
MacroXrefCommand class for a named chunk cross-reference (80)
UserIdXrefCommand class for a user identifier cross-reference (81)
ReferenceCommand class for chunk references (82)
Command class hierarchy - used to describe individual commands (74). Used by Base Class Definitions (6) .
A Command is created by the WebReader, and attached to a Chunk. The Command participates in cross reference creation, weaving and tangling.
The Command superclass is abstract, and has default methods factored out of the various subclasses. When a subclass is created, it will override some of the methods provided in this superclass.
class MyNewCommand( Command ): ... overrides for various methods ...
Additionally, a subclass of WebReader must be defined to parse the new command syntax. The main process() function must also be updated to use this new subclass of WebReader.
The Command superclass provides the parent class definition for all of the various command types. The most common command is a block of text, which is woven or tangled. The next most common command is a reference to a chunk, which is woven as a mark-up reference, but tangled as an expansion of the source code.
The attributes of a Command instance includes the line number on which the command began, in lineNumber.
Command superclass (75)=
class Command:
"""A Command is the lowest level of granularity in the input stream."""
def __init__( self, fromLine=0 ):
self.lineNumber= fromLine
def __str__( self ):
return "at %r" % self.lineNumber
def startswith( self, prefix ):
return None
def searchForRE( self, rePat, aWeb ):
return None
def usedBy( self, aWeb, aChunk ):
pass
def weave( self, aWeb, aWeaver ):
pass
def tangle( self, aWeb, aTangler ):
pass
Command superclass (75). Used by Command class hierarchy - used to describe individual commands (74) .
A TextCommand is created by a Chunk or a NamedDocumentChunk when a WebReader calls the chunk's appendChar() or appendText() method. This Command participates in cross reference creation, weaving and tangling. When it is created, the source line number is provided so that this text can be tied back to the source document.
An instance of the TextCommand class is a block of document text. It can originate in an anonymous block or a named chunk delimited with @[ and @].
This subclass provides a concrete implementation for all of the methods. Since text is the author's original markup language, it is emitted directly to the weaver or tangler.
TextCommand class to contain a document text block (76)=
class TextCommand( Command ):
"""A piece of document source text."""
def __init__( self, text, fromLine=0 ):
Command.__init__( self, fromLine )
self.text= text
def __str__( self ):
return "at %r: %r..." % (self.lineNumber,self.text[:32])
def startswith( self, prefix ):
return self.text.startswith( prefix )
def searchForRE( self, rePat, aWeb ):
return rePat.search( self.text )
def weave( self, aWeb, aWeaver ):
aWeaver.write( self.text )
def tangle( self, aWeb, aTangler ):
aTangler.write( self.text )
TextCommand class to contain a document text block (76). Used by Command class hierarchy - used to describe individual commands (74) .
A CodeCommand is created by a NamedChunk when a WebReader calls the appendText() or appendChar() method. The Command participates in cross reference creation, weaving and tangling. When it is created, the source line number is provided so that this text can be tied back to the source document.
An instance of the CodeCommand class is a block of program source code text. It can originate in a named chunk (@d) with a @{ and @} delimiter. Or it can be a file output chunk (@o, @O).
It uses the codeBlock() methods of a Weaver or Tangler. The weaver will insert appropriate markup for this code. The tangler will assure that the prevailing indentation is maintained.
CodeCommand class to contain a program source code block (77)=
class CodeCommand( TextCommand ):
"""A piece of program source code."""
def weave( self, aWeb, aWeaver ):
aWeaver.codeBlock( self.text )
def tangle( self, aWeb, aTangler ):
aTangler.codeBlock( self.text )
CodeCommand class to contain a program source code block (77). Used by Command class hierarchy - used to describe individual commands (74) .
An XrefCommand is created by the WebReader when any of the @f, @m, @u commands are found in the input stream. The Command is then appended to the current Chunk being built by the WebReader.
The XrefCommand superclass defines any common features of the various cross-reference commands (@f, @m, @u).
The formatXref() method creates the body of a cross-reference by the following algorithm:
If this command winds up in a tangle operation, that use is illegal. An exception is raised and processing stops.
XrefCommand superclass for all cross-reference commands (78)=
class XrefCommand( Command ):
"""Any of the Xref-goes-here commands in the input."""
def __str__( self ):
return "at %r: cross reference" % (self.lineNumber)
def formatXref( self, xref, aWeaver ):
aWeaver.xrefHead()
xk= xref.keys()
xk.sort()
for n in xk:
aWeaver.xrefLine( n, xref[n] )
aWeaver.xrefFoot()
def tangle( self, aWeb, aTangler ):
raise Error('Illegal tangling of a cross reference command.')
XrefCommand superclass for all cross-reference commands (78). Used by Command class hierarchy - used to describe individual commands (74) .
A FileXrefCommand is created by the WebReader when the @f command is found in the input stream. The Command is then appended to the current Chunk being built by the WebReader.
The FileXrefCommand class weave method gets the file cross reference from the overall web instance, and uses the formatXref() method of the XrefCommand superclass for format this result.
FileXrefCommand class for an output file cross-reference (79)=
class FileXrefCommand( XrefCommand ):
"""A FileXref command."""
def weave( self, aWeb, aWeaver ):
"""Weave a File Xref from @o commands."""
self.formatXref( aWeb.fileXref(), aWeaver )
FileXrefCommand class for an output file cross-reference (79). Used by Command class hierarchy - used to describe individual commands (74) .
A MacroXrefCommand is created by the WebReader when the @m command is found in the input stream. The Command is then appended to the current Chunk being built by the WebReader.
The MacroXrefCommand class weave method gets the named chunk (macro) cross reference from the overall web instance, and uses the formatXref() method of the XrefCommand superclass method for format this result.
MacroXrefCommand class for a named chunk cross-reference (80)=
class MacroXrefCommand( XrefCommand ):
"""A MacroXref command."""
def weave( self, aWeb, aWeaver ):
"""Weave the Macro Xref from @d commands."""
self.formatXref( aWeb.chunkXref(), aWeaver )
MacroXrefCommand class for a named chunk cross-reference (80). Used by Command class hierarchy - used to describe individual commands (74) .
A MacroXrefCommand is created by the WebReader when the @u command is found in the input stream. The Command is then appended to the current Chunk being built by the WebReader.
The UserIdXrefCommand class weave method gets the user identifier cross reference information from the overall web instance. It then formats this line using the following algorithm, which is similar to the algorithm in the XrefCommand superclass.
UserIdXrefCommand class for a user identifier cross-reference (81)=
class UserIdXrefCommand( XrefCommand ):
"""A UserIdXref command."""
def weave( self, aWeb, aWeaver ):
"""Weave a user identifier Xref from @d commands."""
ux= aWeb.userNamesXref()
aWeaver.xrefHead()
un= ux.keys()
un.sort()
for u in un:
defn, refList= ux[u]
aWeaver.xrefDefLine( u, defn, refList )
aWeaver.xrefFoot()
UserIdXrefCommand class for a user identifier cross-reference (81). Used by Command class hierarchy - used to describe individual commands (74) .
A ReferenceCommand instance is created by a WebReader when a @<name@> construct in is found in the input stream. This is attached to the current Chunk being built by the WebReader.
During a weave, this creates a markup reference to another NamedChunk. During tangle, this actually includes the NamedChunk at this point in the tangled output file.
The constructor creates several attributes of an instance of a ReferenceCommand.
ReferenceCommand class for chunk references (82)=
class ReferenceCommand( Command ):
"""A reference to a named chunk, via @<name@>."""
def __init__( self, refTo, fromLine=0 ):
Command.__init__( self, fromLine )
self.refTo= refTo
self.fullname= None
self.sequenceList= None
def __str__( self ):
return "at %r: reference to chunk %r" % (self.lineNumber,self.refTo)
ReferenceCommand resolve this chunk name if it was abbreviated (83)
ReferenceCommand refers to chunk (84)
ReferenceCommand weave a reference to a chunk (85)
ReferenceCommand tangle a referenced chunk (86)
ReferenceCommand class for chunk references (82). Used by Command class hierarchy - used to describe individual commands (74) .
The resolve() method queries the overall Web instance for the full name and sequence number for this chunk reference. This is used by the Weaver class referenceTo() method to write the markup reference to the chunk.
ReferenceCommand resolve this chunk name if it was abbreviated (83)=
def resolve( self, aWeb ):
"""Reference to actual location of this chunk"""
self.fullName, self.sequenceList = aWeb.chunkReference( self.refTo )
ReferenceCommand resolve this chunk name if it was abbreviated (83). Used by ReferenceCommand class for chunk references (82) .
The usedBy() method is a request that is delegated by a Chunk; it resolves the reference and calls the setUsage() method of the overall Web instance to indicate that the parent chunk refers to the named chunk. This also updates the reference count for the named chunk.
ReferenceCommand refers to chunk (84)=
def usedBy( self, aWeb, aChunk ):
self.resolve( aWeb )
aWeb.setUsage( aChunk, self.fullName )
ReferenceCommand refers to chunk (84). Used by ReferenceCommand class for chunk references (82) .
The weave() method inserts a markup reference to a named chunk. It uses the Weaver class referenceTo() method to format this appropriately for the document type being woven.
ReferenceCommand weave a reference to a chunk (85)=
def weave( self, aWeb, aWeaver ):
"""Create the nicely formatted reference to a chunk of code."""
self.resolve( aWeb )
aWeb.weaveChunk( self.fullName, aWeaver )
ReferenceCommand weave a reference to a chunk (85). Used by ReferenceCommand class for chunk references (82) .
The tangle() method inserts the resolved chunk in this place. When a chunk is tangled, it sets the indent, inserts the chunk and resets the indent.
ReferenceCommand tangle a referenced chunk (86)=
def tangle( self, aWeb, aTangler ):
"""Create source code."""
self.resolve( aWeb )
aWeb.tangleChunk( self.fullName, aTangler )
ReferenceCommand tangle a referenced chunk (86). Used by ReferenceCommand class for chunk references (82) .
An Error is raised whenever processing cannot continue. Since it is a subclass of Exception, it takes an arbitrary number of arguments. The first should be the basic message text. Subsequent arguments provide additional details. We will try to be sure that all of our internal exceptions reference a specific chunk, if possible. This means either including the chunk as an argument, or catching the exception and appending the current chunk to the exception's arguments.
The Python raise statement takes an instance of Error and passes it to the enclosing try/except statement for processing.
The typical creation is as follows:
raise Error("No full name for %r" % chunk.name, chunk)
A typical exception-handling suite might look like this:
try: ...something that may raise an Error or Exception... except Error,e: print e.args # this is our internal Error except Exception,w: print w.args # this is some other Python Exception
The Error class is a subclass of Exception used to differentiate application-specific exceptions from other Python exceptions. It does no additional processing, but merely creates a distinct class to facilitate writing except statements.
Error class - defines the errors raised (87)=
class Error( Exception ): pass
Error class - defines the errors raised (87). Used by Base Class Definitions (6) .
A single global variable theLog has an instance of the Logger. This instance must be global to this entire module. It is created at the module scope. See the Module Initialization section, below.
theLog= Logger(standard)
Important application events are defined as subclasses of Event. By default, these classes behave somewhat like exceptions. They are constructed with an arbitrary list of values as their arguments. The intent is to name and package the arguments, so there are no methods to override.
class MyEvent( Event ): pass
When a log message needs to be written, the event() method of the logger actually creates the subclass of Event with the desired arguments. It also attaches a LogActivity object to the Event, and calls the Event's log() method.
theLog.event( MyEvent, "arg1", arg2, etc... )
The global logger instance can be configured to apply certain logging strategy methods to each Event instance that is created. The default strategies are LogReport, LogDebug and LogDiscard. These are applied by the event() method after the Event instance is constructed. The LogReport strategy writes a summary to stdout; the LogDebug strategy writes a very detailed line to stdout; the LogDiscard strategy silently ignores the event.
An instance of the Logger class provides global context information for all debugging activity. The most important service is the event() method; this method creates and then activates the given log event.
The Logger class event() method constructs an Event instance. The function accepts a sequence of arguments. The first argument must be an Event class. The remaining arguments are arguments given to the Event class constructor.
The LogActivity instance determines what is done with this class of event. Two of the built-in LogActivity classes are LogReport and LogDiscard. An instance of LogReport will report the event. An instance of LogDiscard will silently discard the event.