Hi everyone,
While exploring for the policy to be used for converting source code to
markdown (though most of the necessary modules are implemented in
http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/src/tools/docmaker
it is found that only implementing a module named tomarkdown.py similar to
tohtml.py, it can be achieved with some minor modifications in other files
as well but in addition to that reference record can be created for which
the conversion pass has to be modified in order to make entries in record,
corresponding to reference record, secondary pass has to be made to get
reference link of implementation of these functions in `*.c' files as done
in some of libraries like http://pytorch.org/docs/stable/nn.html#
<http://pytorch.org/docs/stable/nn.html> (where source code of function is
shown/rendered on website itself) & https://keras.io/layers/core/ (wherein
for source code, a reference is made to the code repository). This record
may also be used for improvement of UI of
https://www.freetype.org/freetype2/docs/reference/ft2-index.html and as
link will be made relative to this reference table, so while deploying
online, it will make reference link on website and when accessed offline it
will make reference to offline pages.
Will this functionality will be helpful for the FreeType community? As
in the examples of libraries mentioned above are having a good UX due to
this feature.
I think this functionality will be helpful for the community for easy
reference of code and improve the UI/UX of website. Though this
functionality is not mentioned in my proposal but it will help me to
implement custom search functionality for easy accessibility which is
mentioned in my proposal.
Docmaker currently works with python2 only, I made some minor modification
so that they are compatible with python3 as well and removed some unused
imports.
Regards,
Ankit
#
# content.py
#
# Parse comment blocks to build content blocks (library file).
#
# Copyright 2002-2018 by
# David Turner.
#
# This file is part of the FreeType project, and may only be used,
# modified, and distributed under the terms of the FreeType project
# license, LICENSE.TXT. By continuing to use, modify, or distribute
# this file you indicate that you have read the license and
# understand and accept it fully.
#
# This file contains routines to parse documentation comment blocks,
# building more structured objects out of them.
#
from sources import *
from utils import *
import string, re
#
# Regular expressions to detect code sequences. `Code sequences' are simply
# code fragments embedded in '{' and '}', as demonstrated in the following
# example.
#
# {
# x = y + z;
# if ( zookoo == 2 )
# {
# foobar();
# }
# }
#
# Note that the indentation of the first opening brace and the last closing
# brace must be exactly the same. The code sequence itself should have a
# larger indentation than the surrounding braces.
#
re_code_start = re.compile( r"(\s*){\s*$" )
re_code_end = re.compile( r"(\s*)}\s*$" )
#
# A regular expression to isolate identifiers from other text. Two syntax
# forms are supported:
#
# <name>
# <name>[<id>]
#
# where both `<name>' and `<id>' consist of alphanumeric characters, `_',
# and `-'. Use `<id>' if there are multiple, valid `<name>' entries; in the
# index, `<id>' will be appended in parentheses.
#
# For example,
#
# stem_darkening[autofit]
#
# becomes `stem_darkening (autofit)' in the index.
#
re_identifier = re.compile( r"""
((?:\w|-)+
(?:\[(?:\w|-)+\])?)
""", re.VERBOSE )
#
# We collect macro names ending in `_H' (group 1), as defined in
# `freetype/config/ftheader.h'. While outputting the object data, we use
# this info together with the object's file location (group 2) to emit the
# appropriate header file macro and its associated file name before the
# object itself.
#
# Example:
#
# #define FT_FREETYPE_H <freetype.h>
#
re_header_macro = re.compile( r'^#define\s{1,}(\w{1,}_H)\s{1,}<(.*)>' )
################################################################
##
## DOC CODE CLASS
##
## 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, lines ):
self.lines = []
self.words = None
# remove margin spaces
for l in lines:
if string.strip( l[:margin] ) == "":
l = l[margin:]
self.lines.append( l )
def dump( self, prefix = "", width = 60 ):
lines = self.dump_lines( 0, width )
for l in lines:
print(prefix + l)
def dump_lines( self, margin = 0, width = 60 ):
result = []
for l in self.lines:
result.append( " " * margin + l )
return(result)
################################################################
##
## DOC PARA CLASS
##
## `Normal' text paragraphs are stored in the `DocPara' class.
##
## `self.words' contains the list of words that make up the paragraph.
##
class DocPara:
def __init__( self, lines ):
self.lines = None
self.words = []
for l in lines:
l = string.strip( l )
self.words.extend( string.split( l ) )
def dump( self, prefix = "", width = 60 ):
lines = self.dump_lines( 0, width )
for l in lines:
print(prefix + l)
def dump_lines( self, margin = 0, width = 60 ):
cur = "" # current line
col = 0 # current width
result = []
for word in self.words:
ln = len( word )
if col > 0:
ln = ln + 1
if col + ln > width:
result.append( " " * margin + cur )
cur = word
col = len( word )
else:
if col > 0:
cur = cur + " "
cur = cur + word
col = col + ln
if col > 0:
result.append( " " * margin + cur )
return result
################################################################
##
## DOC FIELD CLASS
##
## The `DocField' class stores a list containing either `DocPara' or
## `DocCode' objects. Each DocField object also has an optional `name'
## that is used when the object corresponds to a field or value definition.
##
class DocField:
def __init__( self, name, lines ):
self.name = name # can be `None' for normal paragraphs/sources
self.items = [] # list of items
mode_none = 0 # start parsing mode
mode_code = 1 # parsing code sequences
mode_para = 3 # parsing normal paragraph
margin = -1 # current code sequence indentation
cur_lines = []
# analyze the markup lines to check whether they contain paragraphs,
# code sequences, or fields definitions
#
start = 0
mode = mode_none
for l in lines:
# are we parsing a code sequence?
if mode == mode_code:
m = re_code_end.match( l )
if m and len( m.group( 1 ) ) <= margin:
# that's it, we finished the code sequence
code = DocCode( 0, cur_lines )
self.items.append( code )
margin = -1
cur_lines = []
mode = mode_none
else:
# otherwise continue the code sequence
cur_lines.append( l[margin:] )
else:
# start of code sequence?
m = re_code_start.match( l )
if m:
# save current lines
if cur_lines:
para = DocPara( cur_lines )
self.items.append( para )
cur_lines = []
# switch to code extraction mode
margin = len( m.group( 1 ) )
mode = mode_code
else:
if not string.split( l ) and cur_lines:
# if the line is empty, we end the current paragraph,
# if any
para = DocPara( cur_lines )
self.items.append( para )
cur_lines = []
else:
# otherwise, simply add the line to the current
# paragraph
cur_lines.append( l )
if mode == mode_code:
# unexpected end of code sequence
code = DocCode( margin, cur_lines )
self.items.append( code )
elif cur_lines:
para = DocPara( cur_lines )
self.items.append( para )
def dump( self, prefix = "" ):
if self.field:
print(prefix + self.field + " ::")
prefix = prefix + "----"
first = 1
for p in self.items:
if not first:
print ("")
p.dump( prefix )
first = 0
def dump_lines( self, margin = 0, width = 60 ):
result = []
nl = None
for p in self.items:
if nl:
result.append( "" )
result.extend( p.dump_lines( margin, width ) )
nl = 1
return result
#
# A regular expression to detect field definitions.
#
# Examples:
#
# foo ::
# foo.bar ::
#
re_field = re.compile( r"""
\s*
(
\w*
|
\w (\w | \.)* \w
)
\s* ::
""", re.VERBOSE )
################################################################
##
## DOC MARKUP CLASS
##
class DocMarkup:
def __init__( self, tag, lines ):
self.tag = string.lower( tag )
self.fields = []
cur_lines = []
field = None
mode = 0
for l in lines:
m = re_field.match( l )
if m:
# We detected the start of a new field definition.
# first, save the current one
if cur_lines:
f = DocField( field, cur_lines )
self.fields.append( f )
cur_lines = []
field = None
field = m.group( 1 ) # record field name
ln = len( m.group( 0 ) )
l = " " * ln + l[ln:]
cur_lines = [l]
else:
cur_lines.append( l )
if field or cur_lines:
f = DocField( field, cur_lines )
self.fields.append( f )
def get_name( self ):
try:
return self.fields[0].items[0].words[0]
except:
return None
def dump( self, margin ):
print(" " * margin + "<" + self.tag + ">")
for f in self.fields:
f.dump( " " )
print (" " * margin + "</" + self.tag + ">")
################################################################
##
## DOC CHAPTER CLASS
##
class DocChapter:
def __init__( self, block ):
self.block = block
self.sections = []
if block:
self.name = block.name
self.title = block.get_markup_words( "title" )
self.order = block.get_markup_words( "sections" )
else:
self.name = "Other"
self.title = string.split( "Miscellaneous" )
self.order = []
################################################################
##
## DOC SECTION CLASS
##
class DocSection:
def __init__( self, name = "Other" ):
self.name = name
self.blocks = {}
self.block_names = [] # ordered block names in section
self.defs = []
self.abstract = ""
self.description = ""
self.order = []
self.title = "ERROR"
self.chapter = None
def add_def( self, block ):
self.defs.append( block )
def add_block( self, block ):
self.block_names.append( block.name )
self.blocks[block.name] = block
def process( self ):
# look up one block that contains a valid section description
for block in self.defs:
title = block.get_markup_text( "title" )
if title:
self.title = title
self.abstract = block.get_markup_words( "abstract" )
self.description = block.get_markup_items( "description" )
self.order = block.get_markup_words_all( "order" )
return
def reorder( self ):
self.block_names = sort_order_list( self.block_names, self.order )
################################################################
##
## CONTENT PROCESSOR CLASS
##
class ContentProcessor:
def __init__( self ):
"""Initialize a block content processor."""
self.reset()
self.sections = {} # dictionary of documentation sections
self.section = None # current documentation section
self.chapters = [] # list of chapters
self.headers = {} # dictionary of header macros
def set_section( self, section_name ):
"""Set current section during parsing."""
if not section_name in self.sections:
section = DocSection( section_name )
self.sections[section_name] = section
self.section = section
else:
self.section = self.sections[section_name]
def add_chapter( self, block ):
chapter = DocChapter( block )
self.chapters.append( chapter )
def reset( self ):
"""Reset the content processor for a new block."""
self.markups = []
self.markup = None
self.markup_lines = []
def add_markup( self ):
"""Add a new markup section."""
if self.markup and self.markup_lines:
# get rid of last line of markup if it's empty
marks = self.markup_lines
if len( marks ) > 0 and not string.strip( marks[-1] ):
self.markup_lines = marks[:-1]
m = DocMarkup( self.markup, self.markup_lines )
self.markups.append( m )
self.markup = None
self.markup_lines = []
def process_content( self, content ):
"""Process a block content and return a list of DocMarkup objects
corresponding to it."""
markup = None
markup_lines = []
first = 1
margin = -1
in_code = 0
for line in content:
if in_code:
m = re_code_end.match( line )
if m and len( m.group( 1 ) ) <= margin:
in_code = 0
margin = -1
else:
m = re_code_start.match( line )
if m:
in_code = 1
margin = len( m.group( 1 ) )
found = None
if not in_code:
for t in re_markup_tags:
m = t.match( line )
if m:
found = string.lower( m.group( 1 ) )
prefix = len( m.group( 0 ) )
# remove markup from line
line = " " * prefix + line[prefix:]
break
# is it the start of a new markup section ?
if found:
first = 0
self.add_markup() # add current markup content
self.markup = found
if len( string.strip( line ) ) > 0:
self.markup_lines.append( line )
elif first == 0:
self.markup_lines.append( line )
self.add_markup()
return self.markups
def parse_sources( self, source_processor ):
blocks = source_processor.blocks
count = len( blocks )
for n in range( count ):
source = blocks[n]
if source.content:
# this is a documentation comment, we need to catch
# all following normal blocks in the "follow" list
#
follow = []
m = n + 1
while m < count and not blocks[m].content:
follow.append( blocks[m] )
m = m + 1
doc_block = DocBlock( source, follow, self )
def finish( self ):
# process all sections to extract their abstract, description
# and ordered list of items
#
for sec in self.sections.values():
sec.process()
# process chapters to check that all sections are correctly
# listed there
for chap in self.chapters:
for sec in chap.order:
if sec in self.sections:
section = self.sections[sec]
section.chapter = chap
section.reorder()
chap.sections.append( section )
else:
sys.stderr.write( "WARNING: chapter '" + \
chap.name + "' in " + chap.block.location() + \
" lists unknown section '" + sec + "'\n" )
# check that all sections are in a chapter
#
others = []
for sec in self.sections.values():
if not sec.chapter:
sec.reorder()
others.append( sec )
# create a new special chapter for all remaining sections
# when necessary
#
if others:
chap = DocChapter( None )
chap.sections = others
self.chapters.append( chap )
################################################################
##
## DOC BLOCK CLASS
##
class DocBlock:
def __init__( self, source, follow, processor ):
processor.reset()
self.source = source
self.code = []
self.type = "ERRTYPE"
self.name = "ERRNAME"
self.section = processor.section
self.markups = processor.process_content( source.content )
# compute block type from first markup tag
try:
self.type = self.markups[0].tag
except:
pass
# compute block name from first markup paragraph
try:
markup = self.markups[0]
para = markup.fields[0].items[0]
name = para.words[0]
m = re_identifier.match( name )
if m:
name = m.group( 1 )
self.name = name
except:
pass
if self.type == "section":
# detect new section starts
processor.set_section( self.name )
processor.section.add_def( self )
elif self.type == "chapter":
# detect new chapter
processor.add_chapter( self )
else:
processor.section.add_block( self )
# now, compute the source lines relevant to this documentation
# block. We keep normal comments in for obvious reasons (??)
source = []
for b in follow:
if b.format:
break
for l in b.lines:
# collect header macro definitions
m = re_header_macro.match( l )
if m:
processor.headers[m.group( 2 )] = m.group( 1 );
# we use "/* */" as a separator
if re_source_sep.match( l ):
break
source.append( l )
# now strip the leading and trailing empty lines from the sources
start = 0
end = len( source ) - 1
while start < end and not string.strip( source[start] ):
start = start + 1
while start < end and not string.strip( source[end] ):
end = end - 1
if start == end and not string.strip( source[start] ):
self.code = []
else:
self.code = source[start:end + 1]
def location( self ):
return self.source.location()
def get_markup( self, tag_name ):
"""Return the DocMarkup corresponding to a given tag in a block."""
for m in self.markups:
if m.tag == string.lower( tag_name ):
return m
return None
def get_markup_words( self, tag_name ):
try:
m = self.get_markup( tag_name )
return m.fields[0].items[0].words
except:
return []
def get_markup_words_all( self, tag_name ):
try:
m = self.get_markup( tag_name )
words = []
for item in m.fields[0].items:
# We honour empty lines in an `<Order>' section element by
# adding the sentinel `/empty/'. The formatter should then
# convert it to an appropriate representation in the
# `section_enter' function.
words += item.words
words.append( "/empty/" )
return words
except:
return []
def get_markup_text( self, tag_name ):
result = self.get_markup_words( tag_name )
return string.join( result )
def get_markup_items( self, tag_name ):
try:
m = self.get_markup( tag_name )
return m.fields[0].items
except:
return None
# eof
#!/usr/bin/env python
#
# DocBeauty (c) 2003, 2004, 2008 David Turner <[email protected]>
#
# This program is used to beautify the documentation comments used
# in the FreeType 2 public headers.
#
from sources import *
from content import *
from utils import *
import sys, os, string, getopt
content_processor = ContentProcessor()
def beautify_block( block ):
if block.content:
content_processor.reset()
markups = content_processor.process_content( block.content )
text = []
first = 1
for markup in markups:
text.extend( markup.beautify( first ) )
first = 0
# now beautify the documentation "borders" themselves
lines = [" /*************************************************************************"]
for l in text:
lines.append( " *" + l )
lines.append( " */" )
block.lines = lines
def usage():
print("\nDocBeauty 0.1 Usage information\n")
print(" docbeauty [options] file1 [file2 ...]\n")
print("using the following options:\n")
print(" -h : print this page")
print(" -b : backup original files with the 'orig' extension")
print("")
print(" --backup : same as -b")
def main( argv ):
"""main program loop"""
global output_dir
try:
opts, args = getopt.getopt( sys.argv[1:], \
"hb", \
["help", "backup"] )
except getopt.GetoptError:
usage()
sys.exit( 2 )
if args == []:
usage()
sys.exit( 1 )
# process options
#
output_dir = None
do_backup = None
for opt in opts:
if opt[0] in ( "-h", "--help" ):
usage()
sys.exit( 0 )
if opt[0] in ( "-b", "--backup" ):
do_backup = 1
# create context and processor
source_processor = SourceProcessor()
# retrieve the list of files to process
file_list = make_file_list( args )
for filename in file_list:
source_processor.parse_file( filename )
for block in source_processor.blocks:
beautify_block( block )
new_name = filename + ".new"
ok = None
try:
file = open( new_name, "wt" )
for block in source_processor.blocks:
for line in block.lines:
file.write( line )
file.write( "\n" )
file.close()
except:
ok = 0
# if called from the command line
#
if __name__ == '__main__':
main( sys.argv )
# eof
#!/usr/bin/env python
#
# docmaker.py
#
# Convert source code markup to HTML documentation.
#
# Copyright 2002-2018 by
# David Turner.
#
# This file is part of the FreeType project, and may only be used,
# modified, and distributed under the terms of the FreeType project
# license, LICENSE.TXT. By continuing to use, modify, or distribute
# this file you indicate that you have read the license and
# understand and accept it fully.
#
# This program is a re-write of the original DocMaker tool used to generate
# the API Reference of the FreeType font rendering engine by converting
# in-source comments into structured HTML.
#
# This new version is capable of outputting XML data as well as accepting
# more liberal formatting options. It also uses regular expression matching
# and substitution to speed up operation significantly.
#
from sources import *
from content import *
from utils import *
from formatter import *
from tohtml import *
import utils
import sys, glob, getopt
def usage():
print("\nDocMaker Usage information\n")
print(" docmaker [options] file1 [file2 ...]\n")
print("using the following options:\n")
print(" -h : print this page")
print(" -t : set project title, as in '-t \"My Project\"'")
print(" -o : set output directory, as in '-o mydir'")
print(" -p : set documentation prefix, as in '-p ft2'")
print("")
print(" --title : same as -t, as in '--title=\"My Project\"'")
print(" --output : same as -o, as in '--output=mydir'")
print(" --prefix : same as -p, as in '--prefix=ft2'")
def main(argv):
"""Main program loop."""
global output_dir
try:
opts, args = getopt.getopt(sys.argv[1:],
"ht:o:p:",
["help", "title=", "output=", "prefix="])
except getopt.GetoptError:
usage()
sys.exit(2)
if args == []:
usage()
sys.exit(1)
# process options
project_title = "Project"
project_prefix = None
output_dir = None
for opt in opts:
if opt[0] in ("-h", "--help"):
usage()
sys.exit(0)
if opt[0] in ("-t", "--title"):
project_title = opt[1]
if opt[0] in ("-o", "--output"):
utils.output_dir = opt[1]
if opt[0] in ("-p", "--prefix"):
project_prefix = opt[1]
check_output()
# create context and processor
source_processor = SourceProcessor()
content_processor = ContentProcessor()
# retrieve the list of files to process
file_list = make_file_list(args)
for filename in file_list:
source_processor.parse_file(filename)
content_processor.parse_sources(source_processor)
# process sections
content_processor.finish()
formatter = HtmlFormatter(content_processor,
project_title,
project_prefix)
formatter.toc_dump()
formatter.index_dump()
formatter.section_dump_all()
# if called from the command line
if __name__ == '__main__':
main(sys.argv)
# eof
#
# formatter.py
#
# Convert parsed content blocks to a structured document (library file).
#
# Copyright 2002-2018 by
# David Turner.
#
# This file is part of the FreeType project, and may only be used,
# modified, and distributed under the terms of the FreeType project
# license, LICENSE.TXT. By continuing to use, modify, or distribute
# this file you indicate that you have read the license and
# understand and accept it fully.
#
# This is the base Formatter class. Its purpose is to convert a content
# processor's data into specific documents (i.e., table of contents, global
# index, and individual API reference indices).
#
# You need to sub-class it to output anything sensible. For example, the
# file `tohtml.py' contains the definition of the `HtmlFormatter' sub-class
# to output HTML.
#
from sources import *
from content import *
from utils import *
################################################################
##
## FORMATTER CLASS
##
class Formatter:
def __init__( self, processor ):
self.processor = processor
self.identifiers = {}
self.chapters = processor.chapters
self.sections = processor.sections.values()
self.block_index = []
# store all blocks in a dictionary
self.blocks = []
for section in self.sections:
for block in section.blocks.values():
self.add_identifier( block.name, block )
# add enumeration values to the index, since this is useful
for markup in block.markups:
if markup.tag == 'values':
for field in markup.fields:
self.add_identifier( field.name, block )
self.block_index = self.identifiers.keys()
self.block_index.sort( key = index_key )
# also add section names to dictionary (without making them appear
# in the index)
for section in self.sections:
self.add_identifier( section.name, section )
def add_identifier( self, name, block ):
if name in self.identifiers:
# duplicate name!
sys.stderr.write( "WARNING: duplicate definition for"
+ " '" + name + "' "
+ "in " + block.location() + ", "
+ "previous definition in "
+ self.identifiers[name].location()
+ "\n" )
else:
self.identifiers[name] = block
#
# formatting the table of contents
#
def toc_enter( self ):
pass
def toc_chapter_enter( self, chapter ):
pass
def toc_section_enter( self, section ):
pass
def toc_section_exit( self, section ):
pass
def toc_chapter_exit( self, chapter ):
pass
def toc_index( self, index_filename ):
pass
def toc_exit( self ):
pass
def toc_dump( self, toc_filename = None, index_filename = None ):
output = None
if toc_filename:
output = open_output( toc_filename )
self.toc_enter()
for chap in self.processor.chapters:
self.toc_chapter_enter( chap )
for section in chap.sections:
self.toc_section_enter( section )
self.toc_section_exit( section )
self.toc_chapter_exit( chap )
self.toc_index( index_filename )
self.toc_exit()
if output:
close_output( output )
#
# formatting the index
#
def index_enter( self ):
pass
def index_name_enter( self, name ):
pass
def index_name_exit( self, name ):
pass
def index_exit( self ):
pass
def index_dump( self, index_filename = None ):
output = None
if index_filename:
output = open_output( index_filename )
self.index_enter()
for name in self.block_index:
self.index_name_enter( name )
self.index_name_exit( name )
self.index_exit()
if output:
close_output( output )
#
# formatting a section
#
def section_enter( self, section ):
pass
def block_enter( self, block ):
pass
def markup_enter( self, markup, block = None ):
pass
def field_enter( self, field, markup = None, block = None ):
pass
def field_exit( self, field, markup = None, block = None ):
pass
def markup_exit( self, markup, block = None ):
pass
def block_exit( self, block ):
pass
def section_exit( self, section ):
pass
def section_dump( self, section, section_filename = None ):
output = None
if section_filename:
output = open_output( section_filename )
self.section_enter( section )
for name in section.block_names:
skip_entry = 0
try:
block = self.identifiers[name]
# `block_names' can contain field names also,
# which we filter out
for markup in block.markups:
if markup.tag == 'values':
for field in markup.fields:
if field.name == name:
skip_entry = 1
except:
skip_entry = 1 # this happens e.g. for `/empty/' entries
if skip_entry:
continue
self.block_enter( block )
for markup in block.markups[1:]: # always ignore first markup!
self.markup_enter( markup, block )
for field in markup.fields:
self.field_enter( field, markup, block )
self.field_exit( field, markup, block )
self.markup_exit( markup, block )
self.block_exit( block )
self.section_exit( section )
if output:
close_output( output )
def section_dump_all( self ):
for section in self.sections:
self.section_dump( section )
# eof
#
# sources.py
#
# Convert source code comments to multi-line blocks (library file).
#
# Copyright 2002-2018 by
# David Turner.
#
# This file is part of the FreeType project, and may only be used,
# modified, and distributed under the terms of the FreeType project
# license, LICENSE.TXT. By continuing to use, modify, or distribute
# this file you indicate that you have read the license and
# understand and accept it fully.
#
# This library file contains definitions of classes needed to decompose C
# source code files into a series of multi-line `blocks'. There are two
# kinds of blocks.
#
# - Normal blocks, which contain source code or ordinary comments.
#
# - Documentation blocks, which have restricted formatting, and whose text
# always start with a documentation markup tag like `<Function>',
# `<Type>', etc.
#
# The routines to process the content of documentation blocks are contained
# in file `content.py'; the classes and methods found here only deal with
# text parsing and basic documentation block extraction.
#
import fileinput, re, string
################################################################
##
## SOURCE BLOCK FORMAT CLASS
##
## A simple class containing compiled regular expressions to detect
## potential documentation format block comments within C source code.
##
## The `column' pattern must contain a group to `unbox' the content of
## documentation comment blocks.
##
## Later on, paragraphs are converted to long lines, which simplifies the
## regular expressions that act upon the text.
##
class SourceBlockFormat:
def __init__( self, id, start, column, end ):
"""Create a block pattern, used to recognize special documentation
blocks."""
self.id = id
self.start = re.compile( start, re.VERBOSE )
self.column = re.compile( column, re.VERBOSE )
self.end = re.compile( end, re.VERBOSE )
#
# Format 1 documentation comment blocks.
#
# /************************************/ (at least 2 asterisks)
# /* */
# /* */
# /* */
# /************************************/ (at least 2 asterisks)
#
start = r'''
\s* # any number of whitespace
/\*{2,}/ # followed by '/' and at least two asterisks then '/'
\s*$ # probably followed by whitespace
'''
column = r'''
\s* # any number of whitespace
/\*{1} # followed by '/' and precisely one asterisk
([^*].*) # followed by anything (group 1)
\*{1}/ # followed by one asterisk and a '/'
\s*$ # probably followed by whitespace
'''
re_source_block_format1 = SourceBlockFormat( 1, start, column, start )
#
# Format 2 documentation comment blocks.
#
# /************************************ (at least 2 asterisks)
# *
# * (1 asterisk)
# *
# */ (1 or more asterisks)
#
start = r'''
\s* # any number of whitespace
/\*{2,} # followed by '/' and at least two asterisks
\s*$ # probably followed by whitespace
'''
column = r'''
\s* # any number of whitespace
\*{1}(?![*/]) # followed by precisely one asterisk not followed by `/'
(.*) # then anything (group1)
'''
end = r'''
\s* # any number of whitespace
\*+/ # followed by at least one asterisk, then '/'
'''
re_source_block_format2 = SourceBlockFormat( 2, start, column, end )
#
# The list of supported documentation block formats. We could add new ones
# quite easily.
#
re_source_block_formats = [re_source_block_format1, re_source_block_format2]
#
# The following regular expressions correspond to markup tags within the
# documentation comment blocks. They are equivalent despite their different
# syntax.
#
# A markup tag consists of letters or character `-', to be found in group 1.
#
# Notice that a markup tag _must_ begin a new paragraph.
#
re_markup_tag1 = re.compile( r'''\s*<((?:\w|-)*)>''' ) # <xxxx> format
re_markup_tag2 = re.compile( r'''\s*@((?:\w|-)*):''' ) # @xxxx: format
#
# The list of supported markup tags. We could add new ones quite easily.
#
re_markup_tags = [re_markup_tag1, re_markup_tag2]
#
# A regular expression to detect a cross reference, after markup tags have
# been stripped off.
#
# Two syntax forms are supported:
#
# @<name>
# @<name>[<id>]
#
# where both `<name>' and `<id>' consist of alphanumeric characters, `_',
# and `-'. Use `<id>' if there are multiple, valid `<name>' entries.
#
# Example: @foo[bar]
#
re_crossref = re.compile( r"""
@
(?P<name>(?:\w|-)+
(?:\[(?:\w|-)+\])?)
(?P<rest>.*)
""", re.VERBOSE )
#
# Two regular expressions to detect italic and bold markup, respectively.
# Group 1 is the markup, group 2 the rest of the line.
#
# Note that the markup is limited to words consisting of letters, digits,
# the characters `_' and `-', or an apostrophe (but not as the first
# character).
#
re_italic = re.compile( r"_((?:\w|-)(?:\w|'|-)*)_(.*)" ) # _italic_
re_bold = re.compile( r"\*((?:\w|-)(?:\w|'|-)*)\*(.*)" ) # *bold*
#
# This regular expression code to identify an URL has been taken from
#
# https://mail.python.org/pipermail/tutor/2002-September/017228.html
#
# (with slight modifications).
#
urls = r'(?:https?|telnet|gopher|file|wais|ftp)'
ltrs = r'\w'
gunk = r'/#~:.?+=&%@!\-'
punc = r'.:?\-'
any = "%(ltrs)s%(gunk)s%(punc)s" % { 'ltrs' : ltrs,
'gunk' : gunk,
'punc' : punc }
url = r"""
(
\b # start at word boundary
%(urls)s : # need resource and a colon
[%(any)s] +? # followed by one or more of any valid
# character, but be conservative and
# take only what you need to...
(?= # [look-ahead non-consumptive assertion]
[%(punc)s]* # either 0 or more punctuation
(?: # [non-grouping parentheses]
[^%(any)s] | $ # followed by a non-url char
# or end of the string
)
)
)
""" % {'urls' : urls,
'any' : any,
'punc' : punc }
re_url = re.compile( url, re.VERBOSE | re.MULTILINE )
#
# A regular expression that stops collection of comments for the current
# block.
#
re_source_sep = re.compile( r'\s*/\*\s*\*/' ) # /* */
#
# A regular expression to find possible C identifiers while outputting
# source code verbatim, covering things like `*foo' or `(bar'. Group 1 is
# the prefix, group 2 the identifier -- since we scan lines from left to
# right, sequentially splitting the source code into prefix and identifier
# is fully sufficient for our purposes.
#
re_source_crossref = re.compile( r'(\W*)(\w*)' )
#
# A regular expression that matches a list of reserved C source keywords.
#
re_source_keywords = re.compile( '''\\b ( typedef |
struct |
enum |
union |
const |
char |
int |
short |
long |
void |
signed |
unsigned |
\#include |
\#define |
\#undef |
\#if |
\#ifdef |
\#ifndef |
\#else |
\#endif ) \\b''', re.VERBOSE )
################################################################
##
## SOURCE BLOCK CLASS
##
## There are two important fields in a `SourceBlock' object.
##
## self.lines
## A list of text lines for the corresponding block.
##
## self.content
## For documentation comment blocks only, this is the block content
## that has been `unboxed' from its decoration. This is `None' for all
## other blocks (i.e., sources or ordinary comments with no starting
## markup tag)
##
class SourceBlock:
def __init__( self, processor, filename, lineno, lines ):
self.processor = processor
self.filename = filename
self.lineno = lineno
self.lines = lines[:]
self.format = processor.format
self.content = []
if self.format == None:
return
words = []
# extract comment lines
lines = []
for line0 in self.lines:
m = self.format.column.match( line0 )
if m:
lines.append( m.group( 1 ) )
# now, look for a markup tag
for l in lines:
l = string.strip( l )
if len( l ) > 0:
for tag in re_markup_tags:
if tag.match( l ):
self.content = lines
return
def location( self ):
return "(" + self.filename + ":" + repr( self.lineno ) + ")"
# debugging only -- not used in normal operations
def dump( self ):
if self.content:
print("{{{content start---")
for l in self.content:
print(l)
print("---content end}}}")
return
fmt = ""
if self.format:
fmt = repr(self.format.id) + " "
for line in self.lines:
print(line)
################################################################
##
## SOURCE PROCESSOR CLASS
##
## The `SourceProcessor' is in charge of reading a C source file and
## decomposing it into a series of different `SourceBlock' objects.
##
## A SourceBlock object consists of the following data.
##
## - A documentation comment block using one of the layouts above. Its
## exact format will be discussed later.
##
## - Normal sources lines, including comments.
##
##
class SourceProcessor:
def __init__( self ):
"""Initialize a source processor."""
self.blocks = []
self.filename = None
self.format = None
self.lines = []
def reset( self ):
"""Reset a block processor and clean up all its blocks."""
self.blocks = []
self.format = None
def parse_file( self, filename ):
"""Parse a C source file and add its blocks to the processor's
list."""
self.reset()
self.filename = filename
fileinput.close()
self.format = None
self.lineno = 0
self.lines = []
for line in fileinput.input( filename ):
# strip trailing newlines, important on Windows machines!
if line[-1] == '\012':
line = line[0:-1]
if self.format == None:
self.process_normal_line( line )
else:
if self.format.end.match( line ):
# A normal block end. Add it to `lines' and create a
# new block
self.lines.append( line )
self.add_block_lines()
elif self.format.column.match( line ):
# A normal column line. Add it to `lines'.
self.lines.append( line )
else:
# An unexpected block end. Create a new block, but
# don't process the line.
self.add_block_lines()
# we need to process the line again
self.process_normal_line( line )
# record the last lines
self.add_block_lines()
def process_normal_line( self, line ):
"""Process a normal line and check whether it is the start of a new
block."""
for f in re_source_block_formats:
if f.start.match( line ):
self.add_block_lines()
self.format = f
self.lineno = fileinput.filelineno()
self.lines.append( line )
def add_block_lines( self ):
"""Add the current accumulated lines and create a new block."""
if self.lines != []:
block = SourceBlock( self,
self.filename,
self.lineno,
self.lines )
self.blocks.append( block )
self.format = None
self.lines = []
# debugging only, not used in normal operations
def dump( self ):
"""Print all blocks in a processor."""
for b in self.blocks:
b.dump()
# eof
#
# tohtml.py
#
# A sub-class container of the `Formatter' class to produce HTML.
#
# Copyright 2002-2018 by
# David Turner.
#
# This file is part of the FreeType project, and may only be used,
# modified, and distributed under the terms of the FreeType project
# license, LICENSE.TXT. By continuing to use, modify, or distribute
# this file you indicate that you have read the license and
# understand and accept it fully.
# The parent class is contained in file `formatter.py'.
from sources import *
from content import *
from formatter import *
import time
# The following strings define the HTML header used by all generated pages.
html_header_1 = """\
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"https://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>\
"""
html_header_2 = """\
API Reference</title>
<style type="text/css">
a:link { color: #0000EF; }
a:visited { color: #51188E; }
a:hover { color: #FF0000; }
body { font-family: Verdana, Geneva, Arial, Helvetica, serif;
color: #000000;
background: #FFFFFF;
width: 87%;
margin: auto; }
div.section { width: 75%;
margin: auto; }
div.section hr { margin: 4ex 0 1ex 0; }
div.section h4 { background-color: #EEEEFF;
font-size: medium;
font-style: oblique;
font-weight: bold;
margin: 3ex 0 1.5ex 9%;
padding: 0.3ex 0 0.3ex 1%; }
div.section p { margin: 1.5ex 0 1.5ex 10%; }
div.section pre { margin: 3ex 0 3ex 9%;
background-color: #D6E8FF;
padding: 2ex 0 2ex 1%; }
div.section table.fields { width: 90%;
margin: 1.5ex 0 1.5ex 10%; }
div.section table.toc { width: 95%;
margin: 1.5ex 0 1.5ex 5%; }
div.timestamp { text-align: center;
font-size: 69%;
margin: 1.5ex 0 1.5ex 0; }
h1 { text-align: center; }
h3 { font-size: medium;
margin: 4ex 0 1.5ex 0; }
p { text-align: justify; }
pre.colored { color: blue; }
span.keyword { font-family: monospace;
text-align: left;
white-space: pre;
color: darkblue; }
table.fields td.val { font-weight: bold;
text-align: right;
width: 30%;
vertical-align: baseline;
padding: 1ex 1em 1ex 0; }
table.fields td.desc { vertical-align: baseline;
padding: 1ex 0 1ex 1em; }
table.fields td.desc p:first-child { margin: 0; }
table.fields td.desc p { margin: 1.5ex 0 0 0; }
table.index { margin: 6ex auto 6ex auto;
border: 0;
border-collapse: separate;
border-spacing: 1em 0.3ex; }
table.index tr { padding: 0; }
table.index td { padding: 0; }
table.index-toc-link { width: 100%;
border: 0;
border-spacing: 0;
margin: 1ex 0 1ex 0; }
table.index-toc-link td.left { padding: 0 0.5em 0 0.5em;
font-size: 83%;
text-align: left; }
table.index-toc-link td.middle { padding: 0 0.5em 0 0.5em;
font-size: 83%;
text-align: center; }
table.index-toc-link td.right { padding: 0 0.5em 0 0.5em;
font-size: 83%;
text-align: right; }
table.synopsis { margin: 6ex auto 6ex auto;
border: 0;
border-collapse: separate;
border-spacing: 2em 0.6ex; }
table.synopsis tr { padding: 0; }
table.synopsis td { padding: 0; }
table.toc td.link { width: 30%;
text-align: right;
vertical-align: baseline;
padding: 1ex 1em 1ex 0; }
table.toc td.desc { vertical-align: baseline;
padding: 1ex 0 1ex 1em;
text-align: left; }
table.toc td.desc p:first-child { margin: 0;
text-align: left; }
table.toc td.desc p { margin: 1.5ex 0 0 0;
text-align: left; }
</style>
</head>
<body>
"""
html_header_3l = """
<table class="index-toc-link"><tr><td class="left">[<a href="\
"""
html_header_3r = """
<table class="index-toc-link"><tr><td class="right">[<a href="\
"""
html_header_4 = """\
">Index</a>]</td><td class="right">[<a href="\
"""
html_header_5t = """\
">TOC</a>]</td></tr></table>
<h1>\
"""
html_header_5i = """\
">Index</a>]</td></tr></table>
<h1>\
"""
html_header_6 = """\
API Reference</h1>
"""
# The HTML footer used by all generated pages.
html_footer = """\
</body>
</html>\
"""
# The header and footer used for each section.
section_title_header1 = '<h1 id="'
section_title_header2 = '">'
section_title_footer = "</h1>"
# The header and footer used for code segments.
code_header = '<pre class="colored">'
code_footer = '</pre>'
# Paragraph header and footer.
para_header = "<p>"
para_footer = "</p>"
# Block header and footer.
block_header = '<div class="section">'
block_footer_start = """\
<hr>
<table class="index-toc-link"><tr><td class="left">[<a href="\
"""
block_footer_middle = """\
">Index</a>]</td>\
<td class="middle">[<a href="#">Top</a>]</td>\
<td class="right">[<a href="\
"""
block_footer_end = """\
">TOC</a>]</td></tr></table></div>
"""
# Description header/footer.
description_header = ""
description_footer = ""
# Marker header/inter/footer combination.
marker_header = "<h4>"
marker_inter = "</h4>"
marker_footer = ""
# Header location header/footer.
header_location_header = "<p>"
header_location_footer = "</p>"
# Source code extracts header/footer.
source_header = "<pre>"
source_footer = "</pre>"
# Chapter header/inter/footer.
chapter_header = """\
<div class="section">
<h2>\
"""
chapter_inter = '</h2>'
chapter_footer = '</div>'
# Index footer.
index_footer_start = """\
<hr>
<table class="index-toc-link"><tr><td class="right">[<a href="\
"""
index_footer_end = """\
">TOC</a>]</td></tr></table>
"""
# TOC footer.
toc_footer_start = """\
<hr>
<table class="index-toc-link"><tr><td class="left">[<a href="\
"""
toc_footer_end = """\
">Index</a>]</td></tr></table>
"""
# Source language keyword coloration and styling.
keyword_prefix = '<span class="keyword">'
keyword_suffix = '</span>'
section_synopsis_header = '<h2>Synopsis</h2>'
section_synopsis_footer = ''
# Translate a single line of source to HTML. This converts `<', `>', and
# `&' into `<',`>', and `&'.
#
def html_quote( line ):
result = string.replace( line, "&", "&" )
result = string.replace( result, "<", "<" )
result = string.replace( result, ">", ">" )
return result
################################################################
##
## HTML FORMATTER CLASS
##
class HtmlFormatter( Formatter ):
def __init__( self, processor, project_title, file_prefix ):
Formatter.__init__( self, processor )
global html_header_1
global html_header_2
global html_header_3l, html_header_3r
global html_header_4
global html_header_5t, html_header_5i
global html_header_6
global html_footer
if file_prefix:
file_prefix = file_prefix + "-"
else:
file_prefix = ""
self.headers = processor.headers
self.project_title = project_title
self.file_prefix = file_prefix
self.html_header = (
html_header_1 + project_title
+ html_header_2
+ html_header_3l + file_prefix + "index.html"
+ html_header_4 + file_prefix + "toc.html"
+ html_header_5t + project_title
+ html_header_6 )
self.html_index_header = (
html_header_1 + project_title
+ html_header_2
+ html_header_3r + file_prefix + "toc.html"
+ html_header_5t + project_title
+ html_header_6 )
self.html_toc_header = (
html_header_1 + project_title
+ html_header_2
+ html_header_3l + file_prefix + "index.html"
+ html_header_5i + project_title
+ html_header_6 )
self.html_footer = (
'<div class="timestamp">generated on '
+ time.asctime( time.localtime( time.time() ) )
+ "</div>" + html_footer )
self.columns = 3
def make_section_url( self, section ):
return self.file_prefix + section.name + ".html"
def make_block_url( self, block, name = None ):
if name == None:
name = block.name
try:
section_url = self.make_section_url( block.section )
except:
# we already have a section
section_url = self.make_section_url( block )
return section_url + "#" + name
def make_html_word( self, word ):
"""Analyze a simple word to detect cross-references and markup."""
# handle cross-references
m = re_crossref.match( word )
if m:
try:
name = m.group( 'name' )
rest = m.group( 'rest' )
block = self.identifiers[name]
url = self.make_block_url( block )
# display `foo[bar]' as `foo'
name = re.sub( r'\[.*\]', '', name )
# normalize url, following RFC 3986
url = string.replace( url, "[", "(" )
url = string.replace( url, "]", ")" )
try:
# for sections, display title
url = ( '‘<a href="' + url + '">'
+ block.title + '</a>’'
+ rest )
except:
url = ( '<a href="' + url + '">'
+ name + '</a>'
+ rest )
return url
except:
# we detected a cross-reference to an unknown item
sys.stderr.write( "WARNING: undefined cross reference"
+ " '" + name + "'.\n" )
return '?' + name + '?' + rest
# handle markup for italic and bold
m = re_italic.match( word )
if m:
name = m.group( 1 )
rest = m.group( 2 )
return '<i>' + name + '</i>' + rest
m = re_bold.match( word )
if m:
name = m.group( 1 )
rest = m.group( 2 )
return '<b>' + name + '</b>' + rest
return html_quote( word )
def make_html_para( self, words ):
"""Convert words of a paragraph into tagged HTML text. Also handle
cross references."""
line = ""
if words:
line = self.make_html_word( words[0] )
for word in words[1:]:
line = line + " " + self.make_html_word( word )
# handle hyperlinks
line = re_url.sub( r'<a href="\1">\1</a>', line )
# convert `...' quotations into real left and right single quotes
line = re.sub( r"(^|\W)`(.*?)'(\W|$)",
r'\1‘\2’\3',
line )
# convert tilde into non-breakable space
line = string.replace( line, "~", " " )
return para_header + line + para_footer
def make_html_code( self, lines ):
"""Convert a code sequence to HTML."""
line = code_header + '\n'
for l in lines:
line = line + html_quote( l ).rstrip() + '\n'
return line + code_footer
def make_html_items( self, items ):
"""Convert a field's content into HTML."""
lines = []
for item in items:
if item.lines:
lines.append( self.make_html_code( item.lines ) )
else:
lines.append( self.make_html_para( item.words ) )
return string.join( lines, '\n' )
def print_html_items( self, items ):
print(self.make_html_items( items ))
def print_html_field( self, field ):
if field.name:
print( '<table><tr valign="top"><td><b>'
+ field.name
+ "</b></td><td>" )
print(self.make_html_items( field.items ))
if field.name:
print("</td></tr></table>")
def html_source_quote( self, line, block_name = None ):
result = ""
while line:
m = re_source_crossref.match( line )
if m:
name = m.group( 2 )
prefix = html_quote( m.group( 1 ) )
length = len( m.group( 0 ) )
if name == block_name:
# this is the current block name, if any
result = result + prefix + '<b>' + name + '</b>'
elif re_source_keywords.match( name ):
# this is a C keyword
result = ( result + prefix
+ keyword_prefix + name + keyword_suffix )
elif name in self.identifiers:
# this is a known identifier
block = self.identifiers[name]
id = block.name
# link to a field ID if possible
try:
for markup in block.markups:
if markup.tag == 'values':
for field in markup.fields:
if field.name:
id = name
result = ( result + prefix
+ '<a href="'
+ self.make_block_url( block, id )
+ '">' + name + '</a>' )
except:
# sections don't have `markups'; however, we don't
# want references to sections here anyway
result = result + html_quote( line[:length] )
else:
result = result + html_quote( line[:length] )
line = line[length:]
else:
result = result + html_quote( line )
line = []
return result
def print_html_field_list( self, fields ):
print('<table class="fields">')
for field in fields:
print ( '<tr><td class="val" id="' + field.name + '">'
+ field.name
+ '</td><td class="desc">' )
self.print_html_items( field.items )
print("</td></tr>")
print("</table>")
def print_html_markup( self, markup ):
table_fields = []
for field in markup.fields:
if field.name:
# We begin a new series of field or value definitions. We
# record them in the `table_fields' list before outputting
# all of them as a single table.
table_fields.append( field )
else:
if table_fields:
self.print_html_field_list( table_fields )
table_fields = []
self.print_html_items( field.items )
if table_fields:
self.print_html_field_list( table_fields )
#
# formatting the index
#
def index_enter( self ):
print(self.html_index_header)
self.index_items = {}
def index_name_enter( self, name ):
block = self.identifiers[name]
url = self.make_block_url( block )
self.index_items[name] = url
def index_exit( self ):
# `block_index' already contains the sorted list of index names
count = len( self.block_index )
rows = ( count + self.columns - 1 ) // self.columns
print('<table class="index">')
for r in range( rows ):
line = "<tr>"
for c in range( self.columns ):
i = r + c * rows
if i < count:
bname = self.block_index[r + c * rows]
url = self.index_items[bname]
# display `foo[bar]' as `foo (bar)'
bname = string.replace( bname, "[", " (" )
bname = string.replace( bname, "]", ")" )
# normalize url, following RFC 3986
url = string.replace( url, "[", "(" )
url = string.replace( url, "]", ")" )
line = ( line + '<td><a href="' + url + '">'
+ bname + '</a></td>' )
else:
line = line + '<td></td>'
line = line + "</tr>"
print(line)
print("</table>")
print( index_footer_start
+ self.file_prefix + "toc.html"
+ index_footer_end )
print(self.html_footer)
self.index_items = {}
def index_dump( self, index_filename = None ):
if index_filename == None:
index_filename = self.file_prefix + "index.html"
Formatter.index_dump( self, index_filename )
#
# formatting the table of contents
#
def toc_enter( self ):
print(self.html_toc_header)
print("<h1>Table of Contents</h1>")
def toc_chapter_enter( self, chapter ):
print(chapter_header + string.join( chapter.title ) + chapter_inter)
print('<table class="toc">')
def toc_section_enter( self, section ):
print ( '<tr><td class="link">'
+ '<a href="' + self.make_section_url( section ) + '">'
+ section.title + '</a></td><td class="desc">' )
print(self.make_html_para( section.abstract ))
def toc_section_exit( self, section ):
print("</td></tr>")
def toc_chapter_exit( self, chapter ):
print("</table>")
print(chapter_footer)
def toc_index( self, index_filename ):
print( chapter_header
+ '<a href="' + index_filename + '">Global Index</a>'
+ chapter_inter + chapter_footer )
def toc_exit( self ):
print( toc_footer_start
+ self.file_prefix + "index.html"
+ toc_footer_end )
print (self.html_footer)
def toc_dump( self, toc_filename = None, index_filename = None ):
if toc_filename == None:
toc_filename = self.file_prefix + "toc.html"
if index_filename == None:
index_filename = self.file_prefix + "index.html"
Formatter.toc_dump( self, toc_filename, index_filename )
#
# formatting sections
#
def section_enter( self, section ):
print(self.html_header)
print ( section_title_header1 + section.name + section_title_header2
+ section.title
+ section_title_footer )
maxwidth = 0
for b in section.blocks.values():
if len( b.name ) > maxwidth:
maxwidth = len( b.name )
width = 70 # XXX magic number
if maxwidth > 0:
# print section synopsis
print(section_synopsis_header)
print('<table class="synopsis">')
columns = width // maxwidth
if columns < 1:
columns = 1
count = len( section.block_names )
# don't handle last entry if it is empty
if section.block_names[-1] == "/empty/":
count -= 1
rows = ( count + columns - 1 ) // columns
for r in range( rows ):
line = "<tr>"
for c in range( columns ):
i = r + c * rows
line = line + '<td>'
if i < count:
name = section.block_names[i]
if name == "/empty/":
# it can happen that a complete row is empty, and
# without a proper `filler' the browser might
# collapse the row to a much smaller height (or
# even omit it completely)
line = line + " "
else:
url = name
# display `foo[bar]' as `foo'
name = re.sub( r'\[.*\]', '', name )
# normalize url, following RFC 3986
url = string.replace( url, "[", "(" )
url = string.replace( url, "]", ")" )
line = ( line + '<a href="#' + url + '">'
+ name + '</a>' )
line = line + '</td>'
line = line + "</tr>"
print(line)
print("</table>")
print(section_synopsis_footer)
print(description_header)
print(self.make_html_items( section.description ))
print(description_footer)
def block_enter( self, block ):
print(block_header)
# place html anchor if needed
if block.name:
url = block.name
# display `foo[bar]' as `foo'
name = re.sub( r'\[.*\]', '', block.name )
# normalize url, following RFC 3986
url = string.replace( url, "[", "(" )
url = string.replace( url, "]", ")" )
print( '<h3 id="' + url + '">' + name + '</h3>' )
# dump the block C source lines now
if block.code:
header = ''
for f in self.headers.keys():
if block.source.filename.find( f ) >= 0:
header = self.headers[f] + ' (' + f + ')'
break
# if not header:
# sys.stderr.write(
# "WARNING: No header macro for"
# + " '" + block.source.filename + "'.\n" )
if header:
print ( header_location_header
+ 'Defined in ' + header + '.'
+ header_location_footer )
print(source_header)
for l in block.code:
print(self.html_source_quote( l, block.name ))
print(source_footer)
def markup_enter( self, markup, block ):
if markup.tag == "description":
print(description_header)
else:
print(marker_header + markup.tag + marker_inter)
self.print_html_markup( markup )
def markup_exit( self, markup, block ):
if markup.tag == "description":
print(description_footer)
else:
print(marker_footer)
def block_exit( self, block ):
print( block_footer_start + self.file_prefix + "index.html"
+ block_footer_middle + self.file_prefix + "toc.html"
+ block_footer_end )
def section_exit( self, section ):
print(html_footer)
def section_dump_all( self ):
for section in self.sections:
self.section_dump( section,
self.file_prefix + section.name + '.html' )
# eof
#
# utils.py
#
# Auxiliary functions for the `docmaker' tool (library file).
#
# Copyright 2002-2018 by
# David Turner.
#
# This file is part of the FreeType project, and may only be used,
# modified, and distributed under the terms of the FreeType project
# license, LICENSE.TXT. By continuing to use, modify, or distribute
# this file you indicate that you have read the license and
# understand and accept it fully.
import string, sys, os, glob, itertools
# current output directory
#
output_dir = None
# A function that generates a sorting key. We want lexicographical order
# (primary key) except that capital letters are sorted before lowercase
# ones (secondary key).
#
# The primary key is implemented by lowercasing the input. The secondary
# key is simply the original data appended, character by character. For
# example, the sort key for `FT_x' is `fFtT__xx', while the sort key for
# `ft_X' is `fftt__xX'. Since ASCII codes of uppercase letters are
# numerically smaller than the codes of lowercase letters, `fFtT__xx' gets
# sorted before `fftt__xX'.
#
def index_key( s ):
return string.join( itertools.chain( *zip( s.lower(), s ) ) )
# 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
# Divert standard output to a given project documentation file. Use
# `output_dir' to determine the filename location if necessary and save the
# old stdout handle 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 `open_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\n" )
sys.exit( 2 )
else:
output_dir = None
def file_exists( pathname ):
"""Check that a given file exists."""
result = 1
try:
file = open( pathname, "r" )
file.close()
except:
result = None
sys.stderr.write( pathname + " couldn't be accessed\n" )
return result
def make_file_list( args = None ):
"""Build a list of input files from command-line arguments."""
file_list = []
# sys.stderr.write( repr( sys.argv[1 :] ) + '\n' )
if not args:
args = sys.argv[1:]
for pathname in args:
if string.find( pathname, '*' ) >= 0:
newpath = glob.glob( pathname )
newpath.sort() # sort files -- this is important because
# of the order of files
else:
newpath = [pathname]
file_list.extend( newpath )
if len( file_list ) == 0:
file_list = None
else:
# now filter the file list to remove non-existing ones
file_list = filter( file_exists, file_list )
return file_list
# eof
_______________________________________________
Freetype-devel mailing list
[email protected]
https://lists.nongnu.org/mailman/listinfo/freetype-devel