#!/usr/bin/env python
GDoc
# MODULE
# GDoc - Generic documentation tool
# PURPOSE
# GDoc is designed to extract multi-line documentation strings from
# programs or other text files, and output them formatted documentation.
# Currently, the program can produce a set of hyperlinked html files,
# a single latex document, or (for storage) xml documents.
# FORMAT
# GDoc extracts documentation "Items", which are usually header blocks
# written above functions or variables, and outputs these as formatted
# documentation. Each documentation item contains a series of sections,
# each of which contains a heading on a line by itself (e.g., "PURPOSE",
# "ARGUMENTS", etc.) followed by one or more lines of text. To provide
# an example, the source code for the GDoc module is itself formatted
# with header blocks in the required formate.
#
# Items:
# GDoc recognizes the beginnning and end of each documentation
# Item by using the Python regular expression engine to search for
# beginning and ending marker lines. Examples of such marker lines
# are show below:
#
# beginning marker: "#****c parent/name" , or
# "#****ic parent/name"
#
# ending marker "#***"
#
# Here # is an example of a comment character that is used to mark the
# beginning of a one line comment or remark in the language used in the
# source code (e.g., ! in Fortran 90). The 4 asterisks in the beginning
# marker line and the 3 asterisks in the ending marker are literal and
# are required. The "c" following the asterisks in the first beginning
# marker is an example of a single character code used to indicate the
# type of object being documented (e.g., "c" for class, "f" for function,
# etc.) in a user defined dictionary. The optional "i" character in the
# second example of a beginning marker is used to indicate that the
# item is an "internal" (or private) item. The string "name" in the above
# denotes the name of the documentation Item, which is typically the name
# of the variable, function, or other thing that is being documented.
# GDoc searches for instances of Item names to create hyperlinks in html
# documentation. The string "parent" in the above is the name of
# another documentation Item that is the parent of this one in a hierarchy:
# For example, a module may be the parent of a variable or procedure, and
# a class may be the parent of its data attributes and/or methods. These
# parent/child relationships are used to create a hierarchical tables of
# contents for each source file (in multi-file html documentation) and
# for the entire document (in both html or latex documentation).
#
# Sections:
# Each documentation item contains a list of Sections, each
# of which begins with a heading, such "NAME" or "PURPOSE", on a line
# by itself, followed by one or more lines of text. The user must
# provide supply a list of recognized Section headings in the
# configuration file, as discussed below.
#
# Configuration:
# Before parsing the source files, GDoc reads a configuration
# file. The configuration file must specify, among other things:
# i) The comment character that appears at the beginning of
# begining and ending marker lines, and at the beginning of each
# line of documentation (e.g, '#' in the above examples and in
# the source code of the GDoc module).
# ii) A list of names of the types of items to document (e.g.,
# "module", "function", "variable", etc.), and an associated
# single character codes for each. These (key : value) pairs
# are stored in a user defined dictionary, e.g.,
# ("m":"module", # "f":"function", "v":"variable" , ... ).
# iii)A list of recognized section headings (e.g., "NAME", "PURPOSE",
# etc.).
# The format of the configuration file is discussed in detail in the
# documentation for function read_config. Examples of working
# configuration files for generating the hmtl and latex documentation
# for GDoc itself are given in the html.rc and latex.rc files,
# respectively.
# USAGE
# To execute GDoc in Unix or Linux:
#
# 1) Change directory to a directory one level above the directory
# containing the source file source, and the directory in which you
# intend to create documentation. Make the documentation directory,
# if it does not already exist, and create a configuration file.
#
# 2) Start the python interpreter by typing "python" at the shell
# prompt. Within the interpreter, type "import GDoc". Then, enter
# the name of the configuration file (relative to the current
# directory) when prompted to do so. The GDoc directory must either
# be in the PYTHONPATH environment variable or a subdirectory of a
# directory in PYTHONPATH in order for python to find the module.
#
# To execute GDoc in MS Windows, one may either:
#
# 1) Start up the python interpreter and "import GDoc", as in
# unix. This requires that python search path be set so that it
# can find the GDoc directory.
#
# 2) Double click on the GDoc/GDoc.py file, which should look
# like a snake. This, however, makes the GDoc directory the
# current directory, and thus requires that the source and
# documentation directory be specified in the configuration file
# either as as absolute paths or as paths relative to the GDoc
# directory.
#
#*** -------------------------------------------------------------------
import string, re
import os, shutil
import file_util
# Defaults:
rc_filename = "GDoc.rc" # default configuration file name
com_char = "#" # default comment character
# String containing characters allowed in variable names
name_char = string.letters + string.digits + '_' + '\.'
Section
# CLASS
# Section
# PURPOSE
# Contains one Section within an Item of documentation. Each
# Section contains a heading (e.g., 'FUNCTION', 'PURPOSE', etc.)
# and the corresponding text.
# CLASS ATTRIBUTES
# types = list of recognized heading strings, e.g.,
# Section.types = ('FUNCTION','PURPOSE',..)
# ignore = list of Section types to exclude from documentation
# INSTANCE ATTRIBUTES
# heading = heading string (must be in list Section.types)
# text = list of strings, each one line with '\n' stripped
# METHODS
# __init__([heading],[text]) - constructor
# write(format,namespace) - returns string representation
# in format = 'html' or 'xml'
#*** ----------------------------------------------------------------------
class Section:
types = [] # List of allowed Section heading strings,
# e.g., 'PURPOSE', 'ARGUMENTS', 'AUTHOR', etc.
ignore = [] # List of heading strings for sections that should
# NOT be included in documentation
def __init__(self, heading='',text=[] ):
self.heading = heading
self.text = text
#end method Section.__init__
Section.write
# METHOD
# Section.write(format,namespace)
# PURPOSE
# Output one documentation Section as a string
# ARGUMENTS
# format - output format ('html' or 'xml')
# namespace - dictionary of Items accessed by name.
# Used to hyperlink html output
# RETURN
# string representation of item in chosen output format
# If format == 'html', result is hyper-linked using namespace
# If Section.type is in Section.ignore, returns blank string
#*** --------------------------------------------------------------
def write(self,format='xml',namespace=''):
if self.heading in Section.ignore:
return ''
else:
s = []
# Heading
if format == 'html' :
s.append( r"<p><strong>" )
s.append( self.heading )
s.append( r"</strong></p> <pre>" + '\n' )
elif format == 'tex' :
s.append( self.heading )
s.append( r'\begin{verbatim}' + '\n' )
elif format == 'xml' :
s.append( r'<Section name="'+self.heading+ r'">'+'\n')
# Text
for line in self.text:
if format == 'html':
s.append(link_namespace(line,namespace) + '\n' )
elif format == 'tex' :
s.append(line + '\n' )
elif format == 'xml' :
s.append( line + '\n' )
# Closing
if format == 'html' :
s.append( r"</pre>" + '\n' )
elif format == 'tex' :
s.append( r'\end{verbatim}' + '\n' )
elif format == 'xml' :
s.append( r'</Section>' + '\n' )
return string.join(s,'')
#end method Section.write
#end class Section
Item
# CLASS
# Item
# PURPOSE
# Each Item object contains the documentation for one
# module, variable, function, class, etc.
# CLASS ATTRIBUTES
# types = Dictionary of allowed Item types,
# keys are user defined one-character strings
# values are strings, e.g., FUNCTION, VARIABLE, etc.
# current = Item currently being processed
# INSTANCE ATTRIBUTES
# name = name (string)
# type = type of object (string)
# parent = parent (Item object)
# access = 'public' or 'private' (string)
# SrcFile = file from which it was extracted (SrcFile object)
# pattern = regular expression object for name
# Sections= list of Section objects
# METHODS
# __init__([attributes]) - constructor, all attributes optional
# read(lines,i,n) - extract documentation for Item from source
# write(format,namespace) - returns string representation in 'format'
# html_ref(ext,target) - returns hyper-link to self
# html_link(line,namespace) - returns line with self.names replaced
# by href links
#*** --------------------------------------------------------------------------
class Item:
types = {}
current = None
def __init__( self, name='', type='', parent='', access='public', \
SrcFile = '',pattern=''):
self.type = type # character flag for type
self.parent = parent # parent of Item
self.name = name # name of Item
self.pattern = pattern # regular expression for name
self.access = access # 'public' or 'private'
self.SrcFile = SrcFile # enclosing source file Object
self.Sections = [] # list of Section objects
self.children = [] # list of child Items
#end method Item.__init__
Item.read
# METHOD
# Item.read(lines,i,n)
# PURPOSE
# Read one documentation Item object from a list of text lines
# ARGUMENTS
# lines - list of text line strings
# i - # of current line
# n - total # of strings in lines
# RETURN
# i - current line, from which to begin reading next Item
#*** --------------------------------------------------------------
def read(self,lines,i,n):
global name_char
Item.current = self
while 1:
if not i < n :
return -1
match = Item.bgn.match(lines[i])
if match:
# Parse beginning line marker
line1 = string.strip(match.group(0))
self.type = line1[-1]
line2 = lines[i][match.end():]
line2 = re.sub(r'^(\*)*\s*','',line2)
match2 = re.match(r"^.*\/",line2)
self.parent = string.strip(match2.group(0)[:-1])
line3 = line2[match2.end():]
match3 = re.match('['+name_char+r']*',line3)
line3 = line3[:match3.end()]
self.name = string.strip(line3)
if line1[-2] == 'i' :
self.access = 'private'
else :
self.access = 'public'
self.pattern= re.compile(self.name)
self.Sections= []
a_Section = None
s=[]
s.append( r'</pre>')
s.append( r'<h2 id="' + self.name + r'" >' )
s.append( self.name )
s.append( r'</h2> <pre>' )
lines[i] = s
i = i + 1
break
else :
lines[i] = escape(lines[i],'html')
i = i + 1
while 1:
if not i < n :
raise "Error: Reached EOF searching for end marker"
match = Item.end.match(lines[i])
if match :
if a_Section :
self.Sections.append(a_Section)
lines[i] = escape(lines[i],'html')
i = i + 1
return i
else:
line1 = Item.com.sub(' ',lines[i])
line2 = string.strip(line1)
if line2 in Section.types :
it = i
if a_Section :
self.Sections.append(a_Section)
a_Section = Section(heading=line2,text=[])
elif a_Section:
if a_Section.heading == 'SOURCE':
if not i == it+1 :
a_Section.text.append(lines[i])
else:
a_Section.text.append(line1)
lines[i] = escape(lines[i],'html')
i = i + 1
else:
raise "Error: Unexpected completion of while loop "
#end method Item.read
Item.write
# METHOD
# Item.write(format,namespace,Doc_access,recursive)
# PURPOSE
# Output one documentation item as a string
# ARGUMENTS
# format - output format ('html', 'tex', or 'xml')
# namespace - dictionary of Items accessed by name.
# Used to hyperlink html output
# Doc_access- 'public' -> write only if public Items,
# 'private' -> write public or private
# RETURN
# string representation of item in chosen output format
# If format == 'html', result is hyper-linked using namespace
#*** --------------------------------------------------------------
def write(self,format='html',namespace='',Doc_access='public',recursive=''):
Item.current = self
if self.access == 'public' or Doc_access == 'private' :
s = []
# Opening
if format == 'html' :
s.append( r'<h2 id="' + self.name + r'" >' )
s.append( self.html_ref('_',"source") )
# s.append( self.parent + '/' + self.name )
s.append( r'</h2>' + '\n' )
elif format == 'tex' :
if self.depth == 1:
s.append( r'\newpage' + '\n' )
s.append( r'\section{' )
elif self.depth == 2:
s.append( r'\subsection{' )
elif self.depth == 3:
s.append( r'\subsubsection{' )
if self.depth > 0 :
Item_title = Item.types[self.type] + ' ' + self.name
s.append( escape(Item_title,'tex') )
s.append( r'}' + '\n' )
elif format == 'xml' :
s.append( r'<Item ')
s.append( r'type="' + self.type + '" ')
s.append( r'parent="' + self.parent + '" ' )
s.append( r'name="' + self.name + '" ')
s.append( r'SrcFile="'+ self.SrcFile.name + '" ')
s.append( r'>'+'\n')
# Sections
for a_Section in self.Sections :
s.append( a_Section.write(format,namespace) )
# Closing
if format == 'xml' :
s.append( r'</Item>' + '\n' )
if recursive :
for child in self.children:
s.append(child.write(format,namespace,Doc_access,recursive))
return string.join(s,'')
else:
return ''
#end method Item.write
Item.html_ref
# METHOD
# Item.html_ref([ext],[target])
# PURPOSE
# Returns string containing html element <a ... >Item.name</a>
# with an html href attribute linking to anchor of Item
# ARGUMENTS
# ext = if present, appends 'ext' string to filename in href.
# Used to distinguish html documentation and html source
# target = if present, adds target attribute to href.
# Used to create new Browswer window
#*** ---------------------------------------------------------------
def html_ref(self,ext='',target=None):
s = self.SrcFile.name + ext
if SrcFile.current :
if s == SrcFile.current.name :
s = ''
else:
s = s + '.html'
s = file_util.relative_path(SrcFile.current.name, s)
else:
s = s + '.html'
s = '"' + s + '#' + self.name +'"'
if target:
s = s + ' target="' + target + '"'
return r'<a href=' + s + r'>'+ self.name + r'</a>'
Item.html_link
# METHOD
# Item.html_link(line)
# RETURN
# string line with instances of self.name replaced by html
# <a href=..>self.name</a> hyperlinks to self. Uses method html_ref
#*** ---------------------------------------------------------------
def html_link(self,line):
name = self.name
pattern = self.pattern
match = re.search(pattern,line)
if match:
s = match.start()
e = match.end()
f = 1
if s > 0:
pre = line[:s]
if line[s-1] in name_char : f = 0
else:
pre = ''
if e < len(line) :
post = line[e:]
if line[e] in name_char : f = 0
else:
post = ''
if f:
if name == Item.current.name :
repl = r'<strong>' + name + r'</strong>'
else:
repl =self.html_ref()
else:
repl = name
line = pre + repl + self.html_link(post)
return line
#end method link
Item.set_depth
# METHOD
# Item.set_depth(depth)
# PURPOSE
# Sets depth attribute of self and all of its descendants.
#*** --------------------------------------------------------------
def set_depth(self,depth):
self.depth = depth
for child in self.children:
child.set_depth(depth+1)
Item.html_toc
# METHOD
# Item.html_toc()
# PURPOSE
# Returns string containing an html table of contents list item
# for self, and a nested list containing its children, if any.
# The method is applied recursively to children to generate a
# table of contents for the tree of Items rooted at self.
#*** -----------------------------------------------------------------
def html_toc(self):
s = []
if not self.type == 'root':
s.append(r'<li>' + Item.types[self.type] + ' ')
s.append(self.html_ref() + r'</li>' + '\n')
if self.children:
s.append( r'<ul>' + '\n' )
for child in self.children:
s.append(child.html_toc())
s.append( r'</ul>' + '\n')
return string.join(s,'')
#end Class Item
SrcFile
# CLASS
# SrcFile - documentation extracted from a source file
# CLASS ATTRIBUTES
# current = current SrcFile
# INSTANCE ATTRIBUTES
# name = SrcFile name (in format used for output file names)
# Items = list of Item objects
#*** ------------------------------------------------------------------
class SrcFile:
current = None
def __init__( self, name='' ):
self.name = re.sub(r'\.','_',name) # SrcFile name
self.Items = [] # List of Items
SrcFile.read
# METHOD
# SrcFile.read(file,namespace)
# PURPOSE
# Extract documentation Items from an entire file
# ARGUMENTS
# file - file object
# namespace - namespace dictionary to which Items are added
# RETURN
# lines - Full text of input source file, with minimal
# hyper-text anchors inserted for documented items
#*** -------------------------------------------------------------
def read(self,file,namespace=''):
SrcFile.current = self
# Read File and chomp end-of-lines
lines = file.readlines()
n = len(lines)
for i in range(n):
lines[i] = string.replace(lines[i],'\n','')
n = len(lines)
i = 0
while i < n :
an_Item = Item(SrcFile=self)
i = an_Item.read(lines,i,n)
if not i == -1 :
self.Items.append(an_Item)
namespace[an_Item.name] = an_Item
else:
break
return lines
#end method SrcFile.read
SrcFile.write
# METHOD
# SrcFile.write(format,namespace,Doc_access)
# PURPOSE
# Output documentation for instance SrcFile
# ARGUMENTS
# format - output format ('html' or 'xml')
# namespace - dictionary of Items accessed by name.
# Used to hyperlink html output
# Doc_access- 'public' -> write only if public Items,
# 'private' -> write public or private
# RETURN
# string representation of SrcFile in chosen output format
# If format == 'html', output is hyper-linked using namespace
#*** --------------------------------------------------------------
def write(self,format='xml',namespace='',Doc_access='public'):
SrcFile.current = self
s = [] # Character list, assembled by appendage
# Top Matter
s.append( header(self.name,format) )
if format == 'html' :
s.append( Document.current.index_links(self.name) )
s.append( r'<h3 align="center">TABLE OF CONTENTS</h3>'+'\n')
s.append( r'<ul>' )
for an_Item in self.Items:
if an_Item.access == 'public' or Doc_access == 'private':
if an_Item.parent == Document.current.root.name :
s.append( an_Item.html_toc() )
else:
parent = namespace[an_Item.parent]
if not parent in self.Items:
s.append( an_Item.html_toc() )
elif format == 'xml' :
s.append( r'<SrcFile name="' + self.name + '">' + '\n' )
# Body: Output Items
for an_Item in self.Items:
s.append( an_Item.write(format,namespace,Doc_access) )
# Bottom Matter
if format == 'xml' :
s.append( r'</SrcFile>' )
s.append( footer(format) )
return string.join(s,'')
# end method SrcFile.write
# end Class SrcFile
Document
# CLASS
# Document - documentation for an entire program or project
# INSTANCE ATTRIBUTES
# src_dir = path name for source directory
# doc_dir = path name for documentation directory
# filenames = list of file names of source files in src_dir
# format = format of documentation, 'html' or 'xml'
# access = 'public' (public Items only) or 'private' (private also)
# SrcFiles = list of SrcFile objects, in same order as filenames
# namespace = dictionary of Item objects accessed by name
# indices = dictionary of lists of items, used to create indices.
# Keys are type name, or "master" for master index.
# root = string identifier for "parent" of top level Items
# METHODS
# read() - read source files in self.filenames from self.src_dir
# make_indices() - make master indices and
# make_tree() - finds parents of each Item, thus creating a tree
# html_index(key,columns) - returns html table of self.indices[key]
# index_links() - return string with hyperlinks to toc and indices
# write() - write documentation to files in self.doc_dir
#*** ------------------------------------------------------------------
class Document:
current = None
def __init__(self,src_dir='',doc_dir='',filenames=[],\
format='xml',access='public'):
self.src_dir = src_dir
self.doc_dir = doc_dir
self.filenames = filenames
self.format = format
self.access = access
self.SrcFiles = []
self.namespace = {}
self.indices = {}
self.root = None
Document.read
# METHOD
# Document.read()
# PURPOSE
# Extract documentation for a multi-file Document
#*** ---------------------------------------------------------------
def read(self):
Document.current = self
os.chdir(self.src_dir)
for filename in self.filenames:
# Read a source file
print "Reading file " + filename
file = open(filename,'r')
a_SrcFile = SrcFile(name=filename)
source = a_SrcFile.read(file,self.namespace)
self.SrcFiles.append(a_SrcFile)
# If html format, echo source file as html in doc_dir
if self.format == 'html' :
htm_src = os.path.join(self.doc_dir,a_SrcFile.name+'_.html')
file = file_util.open_w(htm_src)
s=[]
s.append(header(a_SrcFile.name,'html'))
s.append(r'<pre>' + '\n')
for line in source:
s.append( string.join(line,'') + '\n' )
s.append(r'</pre>')
file.write( string.join(s,'') )
del a_SrcFile
#end Document.read
Document.make_indices
# METHOD
# Document.make_indices()
# PURPOSE
# Make dictionary of indices Document.indices
# The keys of Document.indices are Item types, and "master".
# The values are sorted lists of Items of each type, and all Items.
# USAGE
# Call after read method
#*** ---------------------------------------------------------------
def make_indices(self):
names = {}
names['master'] = []
for a_SrcFile in self.SrcFiles:
for an_Item in a_SrcFile.Items :
if an_Item.access == 'public' or self.access == 'private' :
names['master'].append(an_Item.name)
type = Item.types[an_Item.type]
if not type in names.keys() :
names[type] = []
names[type].append(an_Item.name)
for key in names.keys():
names[key].sort()
self.indices[key]=[]
for name in names[key]:
self.indices[key].append(self.namespace[name])
Document.make_tree
# METHOD
# Document.make_tree()
# PURPOSE
# Construct tree of parent and child items by constructing
# Item.children list for each Item in namespace
# USAGE
# Call after read method
#*** ---------------------------------------------------------------
def make_tree(self):
for a_SrcFile in self.SrcFiles:
for an_Item in a_SrcFile.Items :
if an_Item.access == 'public' or self.access == 'private' :
if an_Item.parent in self.namespace.keys():
parent = self.namespace[an_Item.parent]
parent.children.append(an_Item)
elif not self.root :
self.root = Item(name=an_Item.parent,type='root')
self.root.children.append(an_Item)
elif an_Item.parent == self.root.name :
self.root.children.append(an_Item)
else:
raise "Error in Document.make_tree" + '\n' \
"More than one root:" + '\n' \
+ self.root.name + ' and ' + an_Item.parent
# Set depth attributes for all Items in tree
self.root.set_depth(0)
Document.html_index
# METHOD
# Document.html_index(key,columns)
# PURPOSE
# Returns a string containing the html table containing an index
# ARGUMENTS
# key - key to index of interest in dictionary self.indices
# columns - number of columns in html table
#*** ---------------------------------------------------------------
def html_index(self,key,columns):
SrcFile.current = None
index = self.indices[key]
s = [] # Character list, assembled by appendage
s.append( header(key,'html') )
s.append( self.index_links() )
s.append( r'<h1>' + key + ' index</h1>' + '\n' )
s.append( r'<table cellspacing="6">' + '\n' )
i = 0
j = 0
k = 0
while i < len(index):
an_Item = index[i]
if j == 0:
s.append( r'<tr>' + '\n' )
s.append(r'<td class="index">'+an_Item.html_ref()+r' </td>'+'\n')
i = i + 1
j = j + 1
if j == columns :
j = 0
k = k + 1
s.append( r'</tr>' + '\n' )
if not j == 0 :
s.append( r'</tr>' + '\n' )
s.append( r"</table>" + '\n' )
s.append( footer('html') )
#s.append( r"</body>" + '\n' )
#s.append( r"</html>" )
return string.join(s,'')
Document.index_links
# METHOD
# Document.index_links()
# PURPOSE
# Returns a string containing hyper-links to indices and toc
#*** ---------------------------------------------------------------
def index_links(self, filename=''):
s=[]
s.append(r'<p>'+'\n')
ref = file_util.relative_path(filename,"toc.html")
ref = ref + "#top"
s.append(r'[<a href="'+ref+r'">' + "table of contents" + r'</a>]'+'\n')
ref = file_util.relative_path(filename,"master_index.html")
ref = ref + "#top"
s.append(r'[<a href="'+ref+r'">' + "master index" + r'</a>]'+'\n')
for key in self.indices.keys():
if not key == 'master' :
button = string.strip(key)
if button[-1] == 's' :
button = button + 'es'
else :
button = button + 's'
ref = file_util.relative_path(filename,key+"_index.html")
ref = ref + "#top"
s.append(r'[<a href="'+ref+r'">'+button+r'</a>]'+'\n')
s.append(r'</p>'+'\n')
return string.join(s,'')
Document.write
# METHOD
# Document.write()
# PURPOSE
# Output documentation for a multi-file Document
# USAGE
# Call after read, make_indices, and make_tree methods
#*** --------------------------------------------------------------
def write(self):
global GDoc_dir
Document.current = self
file_util.chdirs(self.doc_dir)
if self.format == 'html' or self.format == 'xml' :
for a_SrcFile in self.SrcFiles:
filename = a_SrcFile.name
file = file_util.open_w(filename + "." + self.format)
file.write( a_SrcFile.write\
(self.format,self.namespace,self.access) )
file.close()
del a_SrcFile
if self.format == 'html' :
# Write index files
for key in self.indices.keys():
file = file_util.open_w(key + "_index.html")
file.write(self.html_index(key,5))
file.close()
# Write toc file
file = file_util.open_w("toc.html")
file.write( header('Table of Contents','html') )
file.write(self.index_links())
file.write(r'<h3 align="center">TABLE OF CONTENTS</h3>'+'\n')
file.write(self.root.html_toc())
file.close()
if self.format == 'tex':
filename = raw_input\
("Enter filename (without .tex extension) for latex output: ")
file = file_util.open_w(filename + "." + self.format)
title = raw_input("Enter title for latex output: ")
file.write( header(title,'tex') )
file.write( self.root.write( \
self.format,self.namespace,self.access,recursive=1) )
file.write( footer('tex') )
file.close()
# Copy css stylesheet or xml dtd
if self.format == 'html' or self.format == 'xml' :
if not self.src_dir == self.doc_dir:
ext = ''
if self.format == 'html' :
ext = '.css'
elif self.format == 'xml' :
ext = '.dtd'
if ext :
src = GDoc_dir + os.sep + "GDoc" + ext
dest = self.doc_dir + os.sep + "GDoc" + ext
shutil.copy(src,dest)
#end Document.write
#end class Document
# Regular expression pattern for blank line
_blank_line = re.compile(r"^\s*$")
# CLASS
# InputFile
# INSTANCE ATTRIBUTES
# file - file object
# line - last line read from file, without newline
# METHODS
# __init__(filename)- open file filename, assign to self.file
# readline() - read and return next line, store in self.line
# next_line() - find, store, and return the next non-blank line
# read_string_dict() - read a dictionary, one key value pair per line
# read_string_list() - read a string list, one string per line
#*** ------------------------------------------------------------------
class InputFile:
def __init__(self,filename):
self.file = open(filename,'r')
# METHOD
# InputFile.readline()
# PURPOSE
# Reads and returns one line of file, saves in self.line
#*** --------------------------------------------------------------
def readline(self):
self.line = self.file.readline()
return self.line
# METHOD
# InputFile.next_line()
# PURPOSE
# Returns next non-blank line
#*** -------------------------------------------------------------
def next_line(self):
global _blank_line
self.readline()
while 1:
if _blank_line.match(self.line):
self.readline()
else:
return string.strip(self.line)
# METHOD
# InputFile.read_string_dict()
# PURPOSE
# Reads a dictionary of key and value string pairs, from a
# file containing one key / value pair per line, with strings
# delimited by blank space, until a blank line is encountered.
# Returns a dictionary of key and value strings.
#*** -------------------------------------------------------------
def read_string_dict(self):
string_dict = {}
self.line = self.next_line()
while 1:
if not _blank_line.match(self.line):
words = string.split(self.line)
key = words[0]
value = words[1]
string_dict[key] = value
self.line = self.file.readline()
else:
break
return string_dict
# METHOD
# InputFile.read_string_dict()
# PURPOSE
# Reads and a list of strings, with one string per line,
# until a blank line is returned. Returns a list of strings
# with leading and trailing white space removed.
#*** -------------------------------------------------------------
def read_string_list(self) :
string_list = []
self.line = self.next_line()
while 1:
if not _blank_line.match(self.line):
self.line = string.strip(self.line)
string_list.append(self.line)
self.line = self.file.readline()
else:
break
return string_list
# METHOD
# InputFile.read_path([root])
# PURPOSE
# Read absolute or relative path from next line of file, and
# and return a corresponding absolute path. If input path name
# is absolute, return the input. Otherwise, treat input path as
# a relative path, relative to argument root (if present) or to
# the current working directory (if root is not present).
#*** -------------------------------------------------------------
def read_path(self, root='') :
path = self.next_line()
if os.path.isabs(path):
return path
else:
if not root: root = os.getcwd()
return os.path.join(root,path)
#end Class InputFile
read_config
# FUNCTION
# read_config(rc_filename)
# PURPOSE
# Reads data from file rc_filename.
# Return a Document object with specified values for attributes:
# src_dir, doc_dir, filenames, format, and access
# Also sets values for Item and Section class attributes:
# Item.types, Section.types, Section.ignore
# FORMAT
# The configuration file consists of a series of control lines,
# most of which are followed by one or more lines containing data.
# There may be no blank lines between the control line and the
# associated data line(s), or between data lines. One or more blank
# lines separate each control line and associated data line(s) from
# the next control line. The recognized control lines and associated
# data are:
#
# 'Item_types:' - dictionary of recognized Item types and
# associated one character codes, e.g.,
# 'Section_types:' - list of recognized section headings
# 'Com_char:' - character used at beginning of item marker lines
# 'Src_dir:' - source directory path
# 'Doc_dir:' - documentation directory path
# 'Filenames:' - list of names of source files in Src_dir
# 'Format:' - format of output. Either 'html' or 'xml'
# 'Access:' - if 'private', include internal Items
# - if 'public', exclude internal Items
# 'Done' - stop reading file, return from function
#
# Lists (e.g., Section_types, and filenames) or dictionaries
# (e.g., Item_types) are listed with one item, or one key/value pair,
# per line. Directory paths may be either absolute or relative to
# current working directory. The configuration file is read as an
# infinite loop, which exits when the "Done" control line is read.
#
# For examples of the required format, see the GDoc_rc.html or
# GDoc_rc.latex file in the GDoc directory, which are used to
# generate the html and latex versions of the documentation for
# the GDoc module itself.
#*** ------------------------------------------------------------------
def read_config(rc_filename):
rc_file = InputFile(rc_filename)
top_dir = os.getcwd()
# Set defaults
format = 'html'
access = 'public'
# Loop over control lines in rc_file
while 1:
line = rc_file.next_line()
if line == 'Item_types:' :
Item.types = rc_file.read_string_dict()
type_patt = ''
for key in Item.types.keys() :
type_patt = type_patt + key
type_patt = r'[' + type_patt + r']'
elif line == 'Section_types:' :
Section.types = rc_file.read_string_list()
elif line == 'Section_ignore:' :
Section.ignore = rc_file.read_string_list()
elif line == 'Src_dir:' :
src_dir = rc_file.read_path()
print src_dir
elif line == 'Doc_dir:' :
doc_dir = rc_file.read_path()
print doc_dir
elif line == 'Filenames:' :
filenames = rc_file.read_string_list()
elif line == 'Format:':
format = rc_file.next_line()
elif line == 'Access:' :
access = rc_file.next_line()
elif line == 'Com_char:' :
com_char = rc_file.next_line()
elif line == 'Done':
# Make comment and Item begin and end marker regex's
# Item.com, Item.bgn, Item.end = make_markers(com_char,Item.types)
Item.com = r"^\s*" + com_char
Item.bgn = Item.com + r'\*\*\*\*i?' + type_patt
Item.end = Item.com + r'\*\*\*'
Item.com = re.compile(Item.com)
Item.bgn = re.compile(Item.bgn)
Item.end = re.compile(Item.end)
# Construct and return Document object
Doc = Document(src_dir,doc_dir,filenames,format,access)
return Doc
else :
raise "Error in read_config: \n " + \
"Invalid control line in configuration file:" + \
line
#end function read_config
# ------------------- Utility Functions ------------------------------ #
link_namespace
# FUNCTION
# link_namespace(line,namespace)
# ARGUMENTS
# line - a string
# namespace - dictionary of Items accessed by name
# RETURN
# string line with Item names replaced by href links
#*** -------------------------------------------------------------------
def link_namespace(line,namespace):
for name in namespace.keys():
an_Item = namespace[name]
line = an_Item.html_link(line)
return line
#end function link_namespace
escape
# FUNCTION
# escape(a_string,format)
# ARGUMENTS
# a_string - string
# format - output format. May be 'html', 'xml', or 'latex'
# RETURN
# Returns a copy of a_string appropriate for output in format,
# in which special characters are replaced by escape sequences.
#*** -----------------------------------------------------------------
def escape(a_string,format):
if format == 'html' :
a_string = a_string.replace(r'&',r'&')
a_string = a_string.replace(r'<',r'<')
a_string = a_string.replace(r'>',r'>')
elif format == 'tex' :
a_string = a_string.replace('_',r'\_')
return a_string
# FUNCTION
# header(title,format)
# ARGUMENTS
# title - title string for html or latex. Note used in xml.
# format - output format. May be 'html', 'tex', or 'xml'
# RETURN
# A string containing a standard header for output file.
#*** -----------------------------------------------------------------
def header(title='',format='html'):
if not title: title = 'GDoc File'
s=[]
if format == 'html' :
style_path = file_util.relative_path(title,'GDoc.css')
s.append( r'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">' \
+ '\n' )
s.append( r"<html>" + '\n' )
s.append( r"<head>" + '\n' )
s.append( '<link rel="stylesheet" ' + \
'href="' + style_path + '" type="text/css">' + '\n')
s.append( r"<title>" + title + r"</title>" + '\n' )
s.append( r'</head>' + '\n' )
s.append( r'<body bgcolor="#FFFFFF">' + '\n' )
s.append( r"<br />" + '\n' )
elif format == 'tex' :
s.append(r'\documentclass{article}'+'\n')
#s.append(r'\usepackage{makeidx}'+'\n')
s.append(r'\oddsidemargin 0.00 in'+'\n')
s.append(r'\evensidemargin 0.00 in'+'\n')
#s.append(r'\marginparwidth 1.00 in '+'\n')
s.append(r'\topmargin 0.00 in '+'\n')
s.append(r'\textwidth 6.50 in'+'\n')
s.append(r'\textheight 9.00 in'+'\n')
s.append(r'\setlength{\parindent}{0in}'+'\n')
s.append(r'\setlength{\parskip}{.02in}'+'\n')
#s.append(r'\pagestyle{headings}'+'\n')
#s.append(r'\makeindex'+'\n')
s.append(r'\begin{document}'+'\n')
s.append(r'\title{' + title + '}' + '\n' )
s.append(r'\maketitle'+'\n')
#s.append(r'\printindex'+'\n')
s.append(r'\tableofcontents'+'\n')
s.append(r'\newpage'+ '\n')
elif format == 'xml' :
s.append( r'<?xml version="1.0" encoding="UTF-8"?>'+'\n')
s.append( r'<!DOCTYPE SrcFile SYSTEM "GDoc.dtd" >'+'\n')
return string.join(s,'')
# FUNCTION
# footer(format)
# ARGUMENTS
# format - output format.
# PURPOSE
# Returns a string containing a standard footer for output
# files in the specified format. Outputs null string for 'xml'
#*** ----------------------------------------------------------------
def footer(format='html'):
s=[]
if format == 'html' :
s.append( r"</body>" + '\n' )
s.append( r"</html>" )
elif format == 'tex' :
s.append( r'\end{document}' )
return string.join(s,'')
find_directory
# FUNCTION
# find_directory(name)
# PURPOSE
# Searches python path and subdirectories of those in this path
# for a directory with basename == name, and returns the absolute
# path for this directory. Raises an error if the search fails.
#*** ------------------------------------------------------------
def find_directory(name):
import sys
# Search directories in python path
for dir in sys.path:
dirname, basename = os.path.split(dir)
if basename == name:
return dir
# Search subdirectories of those in python path
for dir in sys.path:
if os.path.isdir(dir):
for file in os.listdir(dir):
path = os.path.join(dir,file)
if os.path.isdir(path):
if file == name:
return path
print "Failed to find directory ", name, " in sys.path="
print sys.path
raise "Error: Search failed in function file_directory"
Main
# SCRIPT
# Main script
# SOURCE
# Find GDoc source directory
GDoc_dir = find_directory('GDoc')
# Find configuration file
rc_filename = raw_input("Enter name of rc configuration file: ")
Doc = read_config(rc_filename)
Doc.read()
Doc.make_indices()
Doc.make_tree()
Doc.write()
#*** ----------------------------------------------------------------