#!/usr/bin/env python
#
# DocMaker 0.1 (c) 2000-2001 David Turner <david@freetype.org>
#
# DocMaker is a very simple program used to generate the API Reference
# of programs by extracting comments from source files, and generating
# the equivalent HTML documentation.
#
# DocMaker is very similar to other tools like Doxygen, with the
# following differences:
#
# - It is written in Python (so it is slow, but easy to maintain and
# improve).
#
# - The comment syntax used by DocMaker is simpler and makes for
# clearer comments.
#
# Of course, it doesn't have all the goodies of most similar tools,
# (e.g. C++ class hierarchies), but hey, it is only 2000 lines of
# Python.
#
# DocMaker is mainly used to generate the API references of several
# FreeType packages.
#
# - David
#
import fileinput, sys, os, time, string, glob, getopt
# The Project's title. This can be overridden from the command line with
# the options "-t" or "--title".
#
project_title = "Project"
# The project's filename prefix. This can be set from the command line with
# the options "-p" or "--prefix"
#
project_prefix = ""
# The project's documentation output directory. This can be set from the
# command line with the options "-o" or "--output".
#
output_dir = None
# The following defines the HTML header used by all generated pages.
#
html_header_1 = """\
<html>
<header>
<title>"""
html_header_2= """ API Reference</title>
<basefont face="Verdana,Geneva,Arial,Helvetica">
<style content="text/css">
P { text-align=justify }
H1 { text-align=center }
LI { text-align=justify }
</style>
</header>
<body text=#000000
bgcolor=#FFFFFF
link=#0000EF
vlink=#51188E
alink=#FF0000>
<center><h1>"""
html_header_3=""" API Reference</h1></center>
"""
# This is recomputed later when the project title changes.
#
html_header = html_header_1 + project_title + html_header_2 + project_title + html_header_3
# The HTML footer used by all generated pages.
#
html_footer = """\
</body>
</html>"""
# The header and footer used for each section.
#
section_title_header = "<center><h1>"
section_title_footer = "</h1></center>"
# The header and footer used for code segments.
#
code_header = "<font color=blue><pre>"
code_footer = "</pre></font>"
# Paragraph header and footer.
#
para_header = "<p>"
para_footer = "</p>"
# Block header and footer.
#
block_header = "<center><table width=75%><tr><td>"
block_footer = "</td></tr></table><hr width=75%></center>"
# Description header/footer.
#
description_header = "<center><table width=87%><tr><td>"
description_footer = "</td></tr></table></center><br>"
# Marker header/inter/footer combination.
#
marker_header = "<center><table width=87% cellpadding=5><tr bgcolor=#EEEEFF><td><em><b>"
marker_inter = "</b></em></td></tr><tr><td>"
marker_footer = "</td></tr></table></center>"
# Source code extracts header/footer.
#
source_header = "<center><table width=87%><tr bgcolor=#D6E8FF width=100%><td><pre>"
source_footer = "</pre></table></center><br>"
# Chapter header/inter/footer.
#
chapter_header = "<center><table width=75%><tr><td><h2>"
chapter_inter = "</h2><ul>"
chapter_footer = "</ul></td></tr></table></center>"
current_section = None
# This function is used to sort the index. It is a simple lexicographical
# sort, except that it places capital letters before lowercase ones.
#
def index_sort( s1, s2 ):
if not s1:
return -1
if not s2:
return 1
l1 = len( s1 )
l2 = len( s2 )
m1 = string.lower( s1 )
m2 = string.lower( s2 )
for i in range( l1 ):
if i >= l2 or m1[i] > m2[i]:
return 1
if m1[i] < m2[i]:
return -1
if s1[i] < s2[i]:
return -1
if s1[i] > s2[i]:
return 1
if l2 > l1:
return -1
return 0
# Sort input_list, placing the elements of order_list in front.
#
def sort_order_list( input_list, order_list ):
new_list = order_list[:]
for id in input_list:
if not id in order_list:
new_list.append( id )
return new_list
# Translate a single line of source to HTML. This will convert
# a "<" into "<.", ">" into ">.", etc.
#
def html_quote( line ):
result = string.replace( line, "&", "&" )
result = string.replace( result, "<", "<" )
result = string.replace( result, ">", ">" )
return result
# same as 'html_quote', but ignores left and right brackets
#
def html_quote0( line ):
return string.replace( line, "&", "&" )
# Open the standard output to a given project documentation file. Use
# "output_dir" to determine the filename location if necessary and save the
# old stdout in a tuple that is returned by this function.
#
def open_output( filename ):
global output_dir
if output_dir and output_dir != "":
filename = output_dir + os.sep + filename
old_stdout = sys.stdout
new_file = open( filename, "w" )
sys.stdout = new_file
return ( new_file, old_stdout )
# Close the output that was returned by "close_output".
#
def close_output( output ):
output[0].close()
sys.stdout = output[1]
# Check output directory.
#
def check_output( ):
global output_dir
if output_dir:
if output_dir != "":
if not os.path.isdir( output_dir ):
sys.stderr.write( "argument" + " '" + output_dir + "' " +
"is not a valid directory" )
sys.exit( 2 )
else:
output_dir = None
def compute_time_html( ):
global html_footer
time_string = time.asctime( time.localtime( time.time() ) )
html_footer = "<p><center><font size=""-2"">generated on " + time_string + "</font></p></center>" + html_footer
# The FreeType 2 reference is extracted from the source files. These
# contain various comment blocks that follow one of the following formats:
#
# /**************************
# *
# * FORMAT1
# *
# *
# *
# *
# *************************/
#
# /**************************/
# /* */
# /* FORMAT2 */
# /* */
# /* */
# /* */
# /* */
#
# /**************************/
# /* */
# /* FORMAT3 */
# /* */
# /* */
# /* */
# /* */
# /**************************/
#
# Each block contains a list of markers; each one can be followed by
# some arbitrary text or a list of fields. Here an example:
#
# <Struct>
# MyStruct
#
# <Description>
# this structure holds some data
#
# <Fields>
# x :: horizontal coordinate
# y :: vertical coordinate
#
#
# This example defines three markers: 'Struct', 'Description' & 'Fields'.
# The first two markers contain arbitrary text, while the last one contains
# a list of fields.
#
# Each field is simply of the format: WORD :: TEXT...
#
# Note that typically each comment block is followed by some source code
# declaration that may need to be kept in the reference.
#
# Note that markers can alternatively be written as "@MARKER:" instead of
# "<MARKER>". All marker identifiers are converted to lower case during
# parsing in order to simply sorting.
#
# We associate with each block the following source lines that do not begin
# with a comment. For example, the following:
#
# /**********************************
# *
# * <mytag> blabla
# *
# */
#
# bla_bla_bla
# bilip_bilip
#
# /* - this comment acts as a separator - */
#
# blo_blo_blo
#
#
# will only keep the first two lines of sources with
# the "blabla" block.
#
# However, the comment will be kept, with following source lines if it
# contains a starting '#' or '@' as in:
#
# /*@.....*/
# /*#.....*/
# /* @.....*/
# /* #.....*/
#
#############################################################################
#
# The DocCode class is used to store source code lines.
#
# 'self.lines' contains a set of source code lines that will be dumped as
# HTML in a <PRE> tag.
#
# The object is filled line by line by the parser; it strips the leading
# "margin" space from each input line before storing it in 'self.lines'.
#
class DocCode:
def __init__( self, margin = 0 ):
self.lines = []
self.margin = margin
def add( self, line ):
# remove margin whitespace
#
if string.strip( line[: self.margin] ) == "":
line = line[self.margin :]
self.lines.append( line )
def dump( self ):
for line in self.lines:
print "--" + line
print ""
def get_identifier( self ):
# this function should never be called
#
return "UNKNOWN_CODE_IDENTIFIER!"
def dump_html( self, identifiers = None ):
# clean the last empty lines
#
l = len( self.lines ) - 1
while l > 0 and string.strip( self.lines[l - 1] ) == "":
l = l - 1
# The code footer should be directly appended to the last code
# line to avoid an additional blank line.
#
print code_header,
for line in self.lines[0 : l+1]:
print '\n' + html_quote(line),
print code_footer,
#############################################################################
#
# The DocParagraph is used to store text paragraphs.
# 'self.words' is simply a list of words for the paragraph.
#
# The paragraph is filled line by line by the parser.
#
class DocParagraph:
def __init__( self ):
self.words = []
def add( self, line ):
# Get rid of unwanted spaces in the paragraph.
#
# The following two lines are the same as
#
# self.words.extend( string.split( line ) )
#
# but older Python versions don't have the `extend' attribute.
#
last = len( self.words )
self.words[last : last] = string.split( line )
# This function is used to retrieve the first word of a given
# paragraph.
#
def get_identifier( self ):
if self.words:
return self.words[0]
# should never happen
#
return "UNKNOWN_PARA_IDENTIFIER!"
def get_words( self ):
return self.words[:]
def dump( self, identifiers = None ):
max_width = 50
cursor = 0
line = ""
extra = None
alphanum = string.lowercase + string.uppercase + string.digits + '_'
for word in self.words:
# process cross references if needed
#
if identifiers and word and word[0] == '@':
word = word[1 :]
# we need to find non-alphanumeric characters
#
l = len( word )
i = 0
while i < l and word[i] in alphanum:
i = i + 1
if i < l:
extra = word[i :]
word = word[0 : i]
block = identifiers.get( word )
if block:
word = '<a href="' + block.html_address() + '">' + word + '</a>'
else:
word = '?' + word
if cursor + len( word ) + 1 > max_width:
print html_quote0(line)
cursor = 0
line = ""
line = line + word
if not extra:
line = line + " "
cursor = cursor + len( word ) + 1
# Handle trailing periods, commas, etc. at the end of cross
# references.
#
if extra:
if cursor + len( extra ) + 1 > max_width:
print html_quote0(line)
cursor = 0
line = ""
line = line + extra + " "
cursor = cursor + len( extra ) + 1
extra = None
if cursor > 0:
print html_quote0(line)
# print "