diff options
author | Ryan Bloom <rbb@apache.org> | 2000-07-23 18:11:14 +0000 |
---|---|---|
committer | Ryan Bloom <rbb@apache.org> | 2000-07-23 18:11:14 +0000 |
commit | 5e613cc1c662f16fe22a6710aaa517770f05aa02 (patch) | |
tree | 5ed19a9b80e2c1316de8ce5e495ec06f4849b9d5 /helpers | |
parent | be294131562f36585dccec3959338b64d357da9f (diff) | |
download | apr-5e613cc1c662f16fe22a6710aaa517770f05aa02.tar.gz |
Add scandoc and the default template to APR's helpers directory.
git-svn-id: https://svn.apache.org/repos/asf/apr/apr/trunk@60431 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'helpers')
-rw-r--r-- | helpers/default.pl | 488 | ||||
-rwxr-xr-x | helpers/scandoc | 1325 |
2 files changed, 1813 insertions, 0 deletions
diff --git a/helpers/default.pl b/helpers/default.pl new file mode 100644 index 000000000..3cd9eb50a --- /dev/null +++ b/helpers/default.pl @@ -0,0 +1,488 @@ +<< +# Scandoc template file. +# +# This is an example set of templates that is designed to create several +# different kinds of index files. It generates a "master index" which intended +# for use with a frames browser; A "package index" which is the root page of +# the index, and then "package files" containing documentation for all of the +# classes within a single package. + +###################################################################### + +## For quick and superficial customization, +## simply change these variables + +$project_name = '[My Project]'; +$company_logo = '<img src="../images/ScanDocBig.jpg">'; # change this to an image tag. +$copyright = '© 2000 [My Name]'; +$image_directory = "../images/"; +$bullet1_image = $image_directory . "ball1.gif"; +$bullet2_image = $image_directory . "ball2.gif"; +$bgcolor1 = "#FFFFFF"; +$bgcolor2 = "#FFFFFF"; + +###################################################################### + +## Begin generating frame index file. + +file "index.html"; +>><html> + <head> + <meta http-equiv="Content-Type" content="text/html; iso-8859-1"> + <title>$project_name</title> + </head> + <frameset cols="190,0%"> + <frame src="master.html" name="Master Index" noresize> + <frame src="packages.html" name="Documentation"> + <noframes> + <body bgcolor="$bgcolor2" stylesrc="index.html"> + <p>Some Documentation</p> + </body> + </noframes> + </frameset> +</html> +<< + +###################################################################### + +## Begin generating master index file (left-hand frame). + +file "master.html"; +>><html> + <head> + <title>Master Index</title> + </head> + <body bgcolor="$bgcolor1" text=#0000ff link=#0020ff vlink=#0020ff> + <center><img src="${image_directory}ScanDocSmall.jpg" border="0" /></center> + <p> + <a href="packages.html" target="Documentation">Master Index</a> + </p> + <p> + <font size="2"> + <nobr> +<< + +## For each package, generate an index entry. + +foreach $p (packages()) { + >><a href="$(p.url)" target="Documentation"><b>$(p.name)</b></a><br> + <dir> + << + foreach $e ($p->classes()) { + >><li><a href="$(e.url)" target="Documentation">$(e.fullname)</a> + << + } + foreach $e ($p->globals()) { + >><li><a href="$(e.url)" target="Documentation">$(e.fullname)</a> + << + } + >></dir><< +} + +>> + <a href="to-do.html" target="Documentation"><b>To-Do List</b></a><br> + </nobr> + </font> + </p> + </body> +</html> +<< + +###################################################################### + +## Begin generating package index file + +file "packages.html"; +>><html> + <head> + <title>$project_name -- Packages</title> + </head> + <body bgcolor="$bgcolor2"> + + <center>$company_logo + <h1>Documentation for $project_name</h1> + </center> + <h2>Package List</h2> +<< + +## For each package, generate an index entry. + +foreach $p (packages()) { + >><a href = "$(p.url)">$(p.name)</a><br> + << +} + +>> + <p> + <hr size=4> + $copyright<br> + Generated by <a href="$scandocURL"><b>ScanDoc $majorVersion.$minorVersion</b></a><br> + Last Updated: $date<br> + </body> +</html> + +<< + +###################################################################### + +## Generate "To-do list" + +file "to-do.html"; +>><html> + <head> + <title>$project_name -- To-Do list</title> + </head> + <body bgcolor="$bgcolor2"> + + $company_logo + + <h1>To-do list for $project_name</h1> +<< + +if (&todolistFiles()) { + >><hr size=4><p> + << + foreach $f (&todolistFiles()) { + my @m = &todolistEntries( $f ); + if ($f =~ /([^\/]+)$/) { $f = $1; } + >><b>$f:</b><ul> + << + foreach $text (@m) { + if ($text) { + print "<li>", &processDescription( $text ), "\n"; + } + } + >></ul> + << + } +} + +>> + <hr size=4> + $copyright<br> + Generated by <a href="$scandocURL"><b>ScanDoc $majorVersion.$minorVersion</b></a><br> + Last Updated: $date<br> + </body> +</html> +<< + +###################################################################### + +## Generate individual files for each package. + +my $p; +foreach $p (packages()) { + file $p->name() . ".html"; + >><html> + <head> + <title>$project_name -- $(p.name)</title> + </head> + <body bgcolor="$bgcolor2"> + <center> + <font size=6><b>$project_name</b></font> + <hr size=4><p> + </center> + + <h2>Package Name: $(p.name)</h2> + <b> +<< + +## Generate class and member index at the top of the file. + +foreach $c ($p->classes()) { + >><h3><img src="$bullet1_image" width=18 height=17 align=texttop> + <a href="$(c.url)">$(c.fullname)</h3></a> + <ul> + << + foreach $m ($c->members()) { + >><li><a href="$(m.url)">$(m.longname)</a> + << + } + >></ul> + << +} + +>> +</b> +<< + +## Generate detailed class documentation +foreach $c ($p->classes()) { + ## Output searchable keyword list + if ($c->keywords()) { + print "<!-- ", $c->keywords(), " -->\n"; + } + + >><hr size="4"> + <a name="$(c.anchor)"></a> + <h1>$(c.fullname)</h1> + <table bgcolor="ffffff" border="0" cellspacing="4"> + <tr> + <th align=center colspan=2> + </th> + </tr> + << + + # Output author tag + if ($c->author()) { + >><tr><th width=20% align=right>Author:</th><< + >><td>$(c.author)</td></tr><< + } + + # Output package version + if ($c->version()) { + >><tr><th width=20% align=right>Version:</th><< + >><td>$(c.version)</td></tr><< + } + + # Output Source file + if ($c->sourcefile()) { + >><tr><th width=20% align=right>Source:</th><< + >><td>$(c.sourcefile)</td></tr><< + } + + # Output base class list + if ($c->baseclasses()) { + >><tr><th width=20% align=right>Base classes:</th> + <td><< + my @t = (); + foreach $b ($c->baseclasses()) { + my $name = $b->name(); + if ($url = $b->url()) { + push @t, "<a href=\"$url\">$name</a>"; + } + else { push @t, $name; } + } + print join( ', ', @t ); + >></td></tr> + << + } + + # Output subclasses list + if ($c->subclasses()) { + >><tr><th width=20% align=right>Subclasses:</th> + <td><< + my @t = (); + foreach $s ($c->subclasses()) { + my $name = $s->name(); + if ($url = $s->url()) { + push @t, "<a href=\"$url\">$name</a>"; + } + else { push @t, $name; } + } + print join( ', ', @t ); + >></td></tr><< + } + + # Output main class description + >></tr> + </table> + <p> + << + print &processDescription( $c->description() ); + + # Output "see also" information + if ($c->seealso()) { + >><p><dt><b>See Also</b><dd> + << + my @r = (); + foreach $a ($c->seealso()) { + my $name = $a->name(); + if ($url = $a->url()) { + push @r, "<a href=\"$url\">$name</a>"; + } + else { push @r, $name; } + } + print join( ',', @r ); + >><p> + << + } + + # Output class member index + if ($c->members()) { + print "<h2>Member Index</h2>\n"; + print "<ul>"; + foreach $m ($c->members()) { + >><li><a href="$(m.url)">$(m.fullname)</a> + << + } + >></ul><< + } + + # Output class member variable documentation + if ($c->membervars()) { + print "<h2>Class Variables</h2>\n"; + print "<blockquote>\n"; + foreach $m ($c->membervars()) { &variable( $m ); } + print "</blockquote>\n"; + } + + # Output class member function documentation + if ($c->memberfuncs()) { + print "<h2>Class Methods</h2>\n"; + print "<blockquote>\n"; + foreach $m ($c->memberfuncs()) { &function( $m ); } + print "</blockquote>\n"; + } +} + +# Output global variables +if ($p->globalvars()) { + >><h2>Global Variables</h2> + <blockquote> + << + foreach $m ($p->globalvars()) { &variable( $m ); } + print "</blockquote>\n"; +} + +# Output global functions +if ($p->globalfuncs()) { + >><h2>Global Functions</h2> + <blockquote> + << + foreach $m ($p->globalfuncs()) { &function( $m ); } + print "</blockquote>\n"; +} + +>> + <hr size=4> + $copyright<br> + Generated by <a href="$scandocURL"><b>ScanDoc $majorVersion.$minorVersion</b></a><br> + Last Updated: $date<br> + </body> +</html> +<< +} # end of foreach (packages) loop + +###################################################################### + +## Subroutine to generate documentation for a member function or global function + +sub function { + local ($f) = @_; + + if ($f->keywords()) { + >><!-- $(f.keywords) --> + << + } + >> + <a name="$(f.anchor)"></a> + <dl> + <dt> + <b><img src="$bullet2_image" width=19 height=17 align=texttop>$(f.fullname);</b> + <dd> + << + print &processDescription( $f->description() ); + >> + <p><dl> + << + if ($f->params()) { + >> + <dt><b>Parameters</b><dd> + <table width="85%"> + << + foreach $a ($f->params()) { + >><tr valign=top><th align=right> + $(a.name)</th><td><< + print &processDescription( $a->description() ); + >></td></tr> + << + } + >></table> + << + } + + if ($f->returnValue()) { + >><dt><b>Return Value</b> + <dd><< + print &processDescription( $f->returnValue() ); + >><p><< + } + + if ($f->exceptions()) { + >><dt><b>Exceptions</b><dd> + <table width=85%><tr><td colspan=2><hr size=3></td></tr> + << + foreach $a ($f->exceptions()) { + >><tr valign=top><th align=right> + $(a.name)</th><td><< + print &processDescription( $a->description() ); + >></td></tr> + << + } + >><tr><td colspan=2><hr size=3></td></tr></table> + << + } + + if ($f->seealso()) { + >><dt><b>See Also</b><dd> + << + my @r = (); + foreach $a ($f->seealso()) { + my $name = $a->name(); + if ($url = $a->url()) { + push @r, "<a href=\"$url\">$name</a>"; + } + else { push @r, $name; } + } + print join( ',', @r ); + >><p><< + } + >></dl></dl> + << +} + +###################################################################### + +## Subroutine to generate documentation for a member variable or global variable. + +sub variable { + local ($v) = @_; + + if ($v->keywords()) { + print "<!-- $(v.keywords) -->"; + } + + >> + <a name="$(v.name)"></a> + <dl><dt> + <b><img src="$bullet2_image" width=19 height=17 align=texttop>$(v.fullname);</b> + <dd> + <<print &processDescription( $v->description() );>> + <p><dl> + << + if ($v->seealso()) { + >><dt><b>See Also</b><dd> + << + $comma = 0; + foreach $a ($v->seealso()) { + if ($comma) { print ","; } + $comma = 1; + >><a href="$(a.url)">$(a.name)</a> + << + } + >><p> + << + } + >></dl></dl> + << +} + +###################################################################### + +sub processDescription { + local ($_) = @_; + + s/^\s+//; # Remove whitespace from beginning + s/\s+$/\n/; # Remove whitespace from end + s/\n\n/<p>\n/g; # Replace multiple CR's with paragraph markers + s:\@heading(.*)\n:<p><h2>$1</h2>:; # Handle heading text + + # Handle embedded image tags + s:\@caution:<p><img src=\"${image_directory}/caution.gif\" align=left>:; + s:\@warning:<p><img src=\"${image_directory}/warning.gif\" align=left>:; + s:\@bug:<p><img src=\"${image_directory}/bug.gif\">:; + s:\@tip:<p><img src=\"${image_directory}/tip.gif\">:; + + return $_; +} diff --git a/helpers/scandoc b/helpers/scandoc new file mode 100755 index 000000000..5a0ebb492 --- /dev/null +++ b/helpers/scandoc @@ -0,0 +1,1325 @@ +#!/usr/bin/perl +# +# ScanDoc - Version 0.12, A C/C++ Embedded Documentation Analyser +# ---------------------------------------------------------------- +# +# Distributed under the "Artistic License". See the file +# "COPYING" that accompanies the ScanDoc distribution. +# +# See http://scandoc.sourceforge.net/ for more information and +# complete documentation. +# +# (c) 1997 - 2000 Talin and others. + +require "ctime.pl"; +require "getopts.pl"; + +# 1 = on (verbose); 0 = off +$debug = 0; + +# Get the current date +$date = &ctime(time); + +# Set the default tab size +$tabSize = 4; + +$minorVersion = 12; +$majorVersion = 0; +$scandocURL = "http://scandoc.sourceforge.net/"; + +# Set up default templates +&Getopts( 'i:d:p:t:' ); + +if ($#ARGV < 0) { + die "Usage: -i <doc-template> -p <output-path> -t<tabsize> -d<sym>=<value> [ <input-files> ... ]\n"; +} + +# Read the template +if (!defined $opt_i) { + $opt_i = "default.pl"; +} +&readTemplate( $opt_i ); + +# Set the destination path. +$destPath = ""; +$destPath = $opt_p if (defined($opt_p)); + +# Set the tab size. +$tabSize = $opt_t if (defined($opt_t)); + +# Handle defines +if ($opt_d) { + foreach $def (split( /,/, $opt_d )) { + if ($def =~ /\s*(\w*)\=(.*)/) { + $${1} = $2; + } + else { + $${1} = 1; + } + } +} + +# For each input filename, parse it +while ($srcfile = shift(@ARGV)) { + + $linenumber = 0; + open( FILE, $srcfile ) || die "Can't open file $srcfile\n"; + print STDERR "Reading \"$srcfile\"\n"; + + $docTag = 'description'; + $docEmpty = 1; + $packageName = '.general'; + $author = ''; + $version = ''; + $class = 0; + $_ = ''; + + while (&parseDeclaration( '' )) {} +} + +# Collate subclasses and associate with class record. +foreach $className (keys %subclasses) { + my $class = &classRecord( $className ); + + if ($class) { + my @subs = (); + # print STDERR "$className ", join( ',', @{$subclasses{ $className }} ), "\n"; + foreach $subName ($subclasses{ $className }) { + if (&classRecord( $subName )) { + push @subs, $subName; + } + $class->{ 'subs' } = @subs; + } + } +} + +# Turn packages into objects. Special case for "default" package. +foreach $pkg (keys %packages) +{ + # print STDERR $pkg, "\n"; + bless $packages{ $pkg }, PackageRecord; + if ($pkg eq '.general') { + $packages{ $pkg }{ 'name' } = "General"; + } + else { + $packages{ $pkg }{ 'name' } = $pkg; + } + # print STDERR $packages{ $pkg }->Classes(), "\n"; +} + +# Execute template file +# print STDERR $docTemplate; # For debugging +eval $docTemplate; +print STDERR $@; + +exit; + +# ======================= Subroutines ================================ + +# Read a line of input, and remove blank lines and preprocessor directives. +sub rdln { + my ($skip_next_line) = 0; + if (defined ($_)) { + my ($previous_line) = $_; + while ( (/^(\s*|\#.*)$/ || $skip_next_line ) && ($_ = <FILE>)) { + if ($previous_line =~ m/\\\s*/) { $skip_next_line = 1; } + else { $skip_next_line = 0; } + $previous_line = $_; + $linenumber++; + if ($debug) { print STDERR "(0:$srcfile) $linenumber.\n"; } + } + } +} + +# Don't skip "#" +sub rdln2 { + if (defined ($_)) { + while (/^(\s*)$/ && ($_ = <FILE>)) {$linenumber++; if ($debug) { print STDERR "(0:$srcfile) $linenumber.\n"; } } + } +} + +# Remove comments from current line +sub removeComment { + s|//.*||; +} + +# parsing functions +sub matchKW { &rdln; return (s/^\s*($_[0])//, $1) if defined ($_); return (0, 0); } +#sub matchStruct { &rdln; return (s/^\s*(struct|class)//, $1) if defined ($_); return (0, 0); } +#sub matchPermission { &rdln; return (s/^\s*(public|private|protected)// && $1) if defined ($_); return (0,0); } +sub matchID { &rdln; return (s/^\s*([A-Za-z_]\w*)//, $1) if defined ($_); return (0,0); } +sub matchColon { &rdln; return (s/^\s*\://) if defined ($_); return 0; } +sub matchComma { &rdln; return (s/^\s*\,//) if defined ($_); return 0; } +sub matchSemi { &rdln; return (s/^\s*\;//) if defined ($_); return 0; } +sub matchRBracket { &rdln; return (s/^\s*\{//) if defined ($_); return 0; } +sub matchLBracket { &rdln; return (s/^\s*\}//) if defined ($_); return 0; } +sub matchRParen { &rdln; return (s/^\s*\(//) if defined ($_); return 0; } +sub matchLParen { &rdln; return (s/^\s*\)//) if defined ($_); return 0; } +sub matchRAngle { &rdln; return (s/^\s*\<//) if defined ($_); return 0; } +sub matchLAngle { &rdln; return (s/^\s*\>//) if defined ($_); return 0; } +sub matchDecl { &rdln; return (s/^(\s*[\s\w\*\[\]\~\&\n\:]+)//, $1) if defined ($_); return (0, 0); } +sub matchOper { &rdln; return (s/^\s*([\~\&\^\>\<\=\!\%\*\+\-\/\|\w]*)// && $1) if defined ($_); return 0; } +sub matchFuncOper { &rdln; return (s/^\s*(\(\))// && $1) if defined ($_); return 0; } +sub matchAny { &rdln; return (s/^\s*(\S+)//, $1) if defined ($_); return (0, 0); } +sub matchChar { &rdln; return (s/^(.)//, $1) if defined ($_); return (0, 0); } +sub matchChar2 { &rdln2; return (s/^(.)//, $1) if defined ($_); return (0, 0); } +sub matchString { &rdln; return (s/^\"(([^\\\"]|(\\.)))*\"//, $1) if defined ($_); return (0, 0); } + +# Skip to next semicolon +sub skipToSemi { + + while (!&matchSemi) { + + &rdln; + s|//.*||; # Eat comments + if (&matchLBracket) { + &skipBody; + next; + } + last if !s/^\s*([^\s\{\;]+)//; + # print STDERR "$1 "; + } +} + +# Skip function body +sub skipBody { + local( $nest ); + + $nest = 1; + +# for (;;) { +# if (&matchRBracket) { $nest++; } +# elsif (&matchLBracket) { +# $nest--; +# last if !$nest; +# } +# else { +# last if ((($valid,) = &matchKW( "[^\{\}]")) && !$valid); +# } +# } +} + +# Skip a string. (multiline) +sub skipString { + local( $char, $lastchar); + $lastchar = "\""; + + for (;;) { + ($valid, $char) = &matchChar2; + if (($char eq "\"") && ($lastchar ne "\\")) { last; } + if ($lastchar eq "\\") { $lastchar = " "; } + else { $lastchar = $char; } + } +} + + +# Skip everything in parenthesis. +sub skipParenBody { + local( $nest ); + + $nest = 1; + + for (;;) { + if (&matchRParen) { $nest++; } + elsif (&matchLParen) { + $nest--; + last if !$nest; + } + else { + last if ((($valid,) = &matchKW( "[^\(\)]")) && !$valid); + } + } +} + +# Parse (*name) syntax +sub parseParenPointer { + if (s/^(\s*\(\s*\*)//) { + $decl .= $1; + $nest = 1; + + for (;;) { + # Preserve spaces, eliminate in-line comments + &removeComment; + while (s/^(\s+)//) { $decl .= $1; &rdln; } + + if (&matchRParen) { $nest++; $decl .= "("; } + elsif (&matchLParen) { + $decl .= ")"; + $nest--; + last if !$nest; + } + elsif ((($valid, $d) = &matchKW( "[^\(\)]*")) && $valid) { $decl .= $d; } + else { last; } + } + + # Just in case there are array braces afterwards. + while ((($valid, $d) = &matchDecl) && $valid) { $decl .= $d; } + } +} + +# Parse template arguments +sub matchAngleArgs { + + if (&matchRAngle) { + local ($args, $nest); + + $args = "<"; + $nest = 1; + + for (;;) { + if (&matchRAngle) { $nest++; $args .= "<"; } + elsif (&matchLAngle) { + $nest--; + $args .= ">"; + last if !$nest; + } + elsif ((($valid, $d) = &matchChar) && $valid) { $args .= $d; } + else { last; } + } + return $args; + } + else { return ''; } +} + +# convert tabs to spaces +sub expandTabs { + local ($text) = @_; + local ($n); + + while (($n = index($text,"\t")) >= 0) { + substr($text, $n, 1) = " " x ($tabSize-($n % $tabSize)); + } + + return $text; +} + +# Process a line of text from a "special" comment +sub handleCommentLine { + local ($_) = @_; + + if ($docEmpty) { + # Eliminate blank lines at the head of the doc. + return if (/^\s*$/); + } + + # First, expand tabs. + $_ = &expandTabs( $_ ); + + # Remove gratuitous \s*\s (james) + s/(^|\n)\s*\*\s/$1/g; + + # If it's one of the standard tags + if (s/^\s*\@(see|package|version|author|param|return|result|exception|keywords|deffunc|defvar|heading|todo)\s*//) { + my $tag = $1; + $tag = 'return' if ($tag eq 'result'); + + # for param and exception, split the param name and the text + # seperate them with tabs. + if ($tag eq "param" || $tag eq "exception") { + s/^\s*(\w+)\s*(.*)/\t$1\t$2/; + } + elsif ($tag eq "heading") { + # 'heading' is processed by the template, if at all. + $_ = "\@heading\t$_"; + $tag = "description"; + } + elsif ($tag eq 'todo') { + if ($todolist{ $srcfile } ne '') { + $todolist{ $srcfile } .= "\n"; + } + } + + # If it's @deffunc or @defvar + if ($tag =~ /def(.*)/) { + + $type = $1; + + # @deffunc and @defvar force a comment to be written out as if there was a + # declaration. + # Designed for use with macros and other constructs I can't parse. + + if (/(\S+)\s+(.*)$/) { + $name = $1; + $decl = $2; + $dbname = &uniqueName( "$baseScope$name" ); + + my $entry = { 'type' => $type, + 'name' => $name, + 'longname'=> $name, + 'fullname'=> "$name$decl", + 'scopename'=>"$baseScope$name", + 'uname' => $dbname, + 'decl' => $decl, + 'package' => $packageName }; + + bless $entry, MemberRecord; + + if ($class) { + $entry->{ 'class' } = "$context"; + $class->{ 'members' }{ $dbname } = $entry; + } + else { + $packages{ $packageName }{ 'globals' }{ $dbname } = $entry; + } + $docTag = 'description'; + &dumpComments( $entry ); + return; + } + } + elsif ($tag eq 'package') { + s/^\s*//; + s/\s*$//; + $packageName = $_; + $docTag = 'description'; + return; + } + elsif ($tag eq 'author') { + $author = $_; + $docTag = 'description'; + return; + } + elsif ($tag eq 'version') { + $version = $_; + $docTag = 'description'; + return; + } + + $docTag = $tag; + } + elsif (/^\s*@\w+/) { + # any other line that begins with an @ should be inserted into the main + # description for later expansion. + $docTag = 'description'; + } + + # "To-do" lists are handled specially, and not associated with a class. + if ($docTag eq 'todo') { + $todolist{ $srcfile } .= $_; + return; + } + + # Append to current doc tag, regardless of whether it's a new line + # or a continuation. Also mark this doc as non-empty. + $docTags{ $docTag } .= $_; + $docEmpty = 0; + + # @see doesn't persist. + if ($docTag eq 'see') { $docTag = 'description'; } + + # print STDERR ":$_"; +} + +# Clear doc tag information at end of class or file +sub clearComments { + + $docTag = 'description'; + $docEmpty = 1; + %docTags = (); +} + +# Add doc tag information to current documented item +sub dumpComments { + local ($hashref) = @_; + + if ($docEmpty == 0) { + + if ($author ne '') { $hashref->{ 'author' } = $author; } + if ($version ne '') { $hashref->{ 'version' } = $version; } + $hashref->{ 'sourcefile' } = $srcfile; + + # Store the tags for this documentation into the global doc symbol table + foreach $key (keys %docTags) { + my $data = $docTags{ $key }; + + $data =~ s/\s*$//; + + $hashref->{ $key } = $data; + } + } + + &clearComments(); +} + +# Generate a unique name from the given name. +sub uniqueName { + local ($name) = @_; + + # Duplicate doc entries need to be distinguished, so give them a different label. + while ($docs{ $name }) { + if ($name =~ /-(\d+)$/) { + $name = $` . "-" . ($1 + 1); + } + else { $name .= "-2"; } + } + + $docs{ $name } = 1; + return $name; +} + +# Get the current class record. +sub classRecord { + local ($className) = @_; + local ($pkg) = $classToPackage{ $className }; + + if ($pkg) { + return $packages{ $pkg }{ 'classes' }{ $className }; + } + return 0; +} + +# Parse a declaration in the file +sub parseDeclaration { + + local ($context) = @_; + local ($baseScope) = ''; + local ($decl); + my ($token); + + if ($context) { $baseScope = $context . "::"; } + + &rdln; + + if (!defined ($_)) { return 0; } + + if (s|^\s*//\*\s+||) { + # Special C++ comment + &handleCommentLine( $' ); + $_ = ''; &rdln; + } + elsif (s|^\s*//||) { + # Ordinary C++ comment + $_ = ''; + &rdln; + } + elsif (s|^\s*\/\*\*\s+||) { + # Special C comments + + s/\={3,}|\-{3,}|\*{3,}//; # Eliminate banner strips + $text = ''; + $docTag = 'description'; + + # Special comment + while (!/\*\//) { &handleCommentLine( $_ ); $text .= $_; $_ = <FILE>; $linenumber++; if ($debug) { print STDERR "(1) $linenumber\n."; }} + s/\={3,}|\-{3,}|\*{3,}//; # Eliminate banner strips + /\*\//; + &handleCommentLine( $` ); + $text.= $`; $_ = $'; + } + elsif (s|^\s*\/\*||) { + # Ordinary C comment + $text = ""; + + while (!/\*\//) { $text .= $_; $_ = <FILE>; $linenumber++; if ($debug) { print STDERR "(2) $linenumber\n."; }} + /\*\//; + $text.= $`; $_ = $'; + } + elsif ((($valid, $tag) = &matchKW( "template")) && $valid) { + # Template definition + $args = &matchAngleArgs; + &rdln; + + ##$tmplParams = $args; JAMES + $result = &parseDeclaration( $context ); + ##$tmplParams = ''; JAMES + return $result; + } + elsif ((($valid, $tag) = &matchKW("class|struct")) && $valid) { + # Class or structure definition + local ($className,$class); + + if ((($valid, $className) = &matchID) && $valid) { + + return 1 if (&matchSemi); # Only a struct tag + + # A class instance + if ((($valid,)=&matchID) && $valid) { + &matchSemi; + return 1; + } + + my $fullName = "$baseScope$className"; ##$tmplParams"; JAMES + # print STDERR "CLASS $fullName\n"; + + my @bases = (); + + if (&matchColon) { + + for (;;) { + my $p; + &matchKW( "virtual" ); + $perm = "private"; + if ((($valid, $p) = &matchKW( "public|private|protected" )) && $valid) { $perm = $p; } + &matchKW( "virtual" ); + + last if !( (($valid, $base) = &matchID) && $valid ); + + push @bases, $base; + push @{ $subclasses{ $base } }, $fullName; + # print STDERR " : $perm $base\n"; + last if !&matchComma; + } + } + + # print STDERR "\n"; + # print STDERR "parsing class $fullName\n"; + + if ($docEmpty == 0) { + $class = { 'type' => $tag, + 'name' => $fullName, + 'longname'=> "$tag $className", + 'fullname'=> "$tag $className", + 'scopename'=> "$tag $fullName", + 'uname' => $fullName, + 'bases' => \@bases, + 'package' => $packageName, + 'members' => {} }; + + # print STDERR "$className: @bases\n"; + + bless $class, ClassRecord; + + print STDERR " parsing class $fullName\n"; + # $classToPackage{ $className } = $packageName; + $classToPackage{ $fullName } = $packageName; + # $classList{ $className } = $class; + $classList{ $fullName } = $class; + $packages{ $packageName }{ 'classes' }{ $fullName } = $class; + &dumpComments( $class ); + } + + if (&matchRBracket) { + local ($perm) = ("private"); + + while (!&matchLBracket) { + my $p; + if ((($valid, $p) = &matchKW( "public\:|private\:|protected\:" )) && $valid) { + $perm = $p; + } + else { + &parseDeclaration( $fullName ) + || die "Unmatched brace! line = $linenumber\n"; + } + } + + &matchSemi; + } + + &clearComments; + } + } + elsif ( ((($valid,)=&matchKW( "enum")) && $valid) || ((($valid,)=&matchKW( "typedef" )) && $valid)) { + &skipToSemi; + } + elsif ((($valid,)=&matchKW( "friend\s*class" )) && $valid) { + &skipToSemi; + } + # elsif ($kw = &matchID) { + # $type = "$kw "; + # + # if ($kw =~/virtual|static|const|volatile/) { + # $type .= &typ; + # } + # } + elsif ((($valid, $decl) = &matchDecl) && $valid) { + my ($instanceClass) = ""; + + # print STDERR "DECLARATION=$decl, REST=$_, baseScope=$baseScope\n"; + + return 1 if ($decl =~ /^\s*$/); + + if (!($class)) { + if ($decl =~ s/(\S*\s*)(\S+)\:\:(\S+)\s*$/$1$3/) { + $instanceClass = $2; + } + } + + # Eliminate in-line comments + &removeComment; + + # Check for multi-line declaration + while ((($valid, $d) = &matchDecl) && $valid) { $decl .= $d; } + + # Handle template args, but don't let operator overloading confuse us! + $tempArgs = ''; + if (!($decl =~ /\boperator\b/) && ($tempArgs = &matchAngleArgs)) { + $tempArgs = $decl . $tempArgs; + $decl = ''; + while ((($valid, $d) = &matchDecl) && $valid) { $decl .= $d; } + } + + # Look for (*name) syntax + &parseParenPointer; + + # Special handling for operator... syntax + $oper = ""; + if ($decl =~ s/\boperator\b(.*)/operator/) { + $oper = $1; + $oper .= &matchOper; + # If, after all that there's no opers, then try a () operator + if (!($oper =~ /\S/)) { $oper .= &matchFuncOper; } + } + + ($type,$mod,$decl) = $decl =~ /([\s\w]*)([\s\*\&]+\s?)(\~?\w+(\[.*\])*)/; + + $type = $tempArgs . $type; + $decl .= $oper; + + if ($mod =~ /\s/) { $type .= $mod; $mod = ""; } + + for (;;) { + + # print STDERR "Looping: $type/$mod/$decl\n"; + + if (&matchRParen) { + $nest = 1; + $args = ""; + + for (;;) { + # print STDERR "Argloop $_\n"; + + # Process argument lists. + + # Preserve spaces, eliminate in-line comments + # REM: Change this to save inline comments and automatically + # generate @param clauses + s|//.*||; + while (s/^(\s+)//) { $args .= " "; &rdln; } + + if (&matchRParen) { $nest++; $args .= "("; } + elsif (&matchLParen) { + $nest--; + last if !$nest; + $args .= ")"; + } + elsif ((($valid, $d) = &matchKW( "[\,\=\.\:\-]" )) && $valid) { $args .= $d; } + elsif ((($valid, $d) = &matchDecl) && $valid) { $args .= $d; } + elsif ((($valid, $d) = &matchAngleArgs) && $valid) { $args .= $d; } + elsif ((($valid, $d) = &matchString) && $valid) { $args .= "\"$d\""; } + else { last; } + } + + # print STDERR "$type$mod$baseScope$decl($args);\n"; + + &matchKW( "const" ); + + # Search for any text within the name field + # if ($docTag && $decl =~ /\W*(~?\w*).*/) + if ($docEmpty == 0) { + $type =~ s/^\s+//; + $mod =~ s/\&/\&/g; + $args =~ s/\&/\&/g; + $args =~ s/\s+/ /g; + $dbname = &uniqueName( "$baseScope$decl" ); + + my $entry = { 'type' => 'func', + 'name' => $decl, + 'longname'=> "$decl()", + 'fullname'=> "$type$mod$decl($args)", + 'scopename'=>"$type$mod$baseScope$decl($args)", + 'uname' => $dbname, + 'decl' => "$type$mod$decl($args)", + 'package' => $packageName }; + + bless $entry, MemberRecord; + + if ($class) { + $entry->{ 'class' } = "$context"; + $class->{ 'members' }{ $dbname } = $entry; + } + elsif ($instanceClass) { + $class = &classRecord ($instanceClass); + if (!($class)) { + print STDERR "WARNING: Skipping \"$instanceClass\:\:$decl\". Class \"$instanceClass\" not declared ($linenumber).\n"; + } else { + $entry->{ 'class' } = "$instanceClass"; + $class->{ 'members' }{ $dbname } = $entry; + $class = 0; + } + } + else { + $packages{ $packageName }{ 'globals' }{ $dbname } = $entry; + } + &dumpComments( $entry ); + } + else { &clearComments; } + + s|//.*||; + + # Constructor super-call syntax + if (&matchColon) { + + # Skip over it. + for (;;) { + &rdln; + last if /^\s*(\{|\;)/; + last if !((($valid,)=&matchAny) && $valid); + } + } + + last if &matchSemi; + if (&matchRBracket) { &skipBody; last; } + last if !&matchComma; + last if !((($valid, $decl) = &matchDecl) && $valid); + + # Look for (*name) syntax + &parseParenPointer; + + $decl =~ s/^\s*//; + $oper = ""; + if ($decl =~ /\boperator\b/) { + $decl =~ s/\boperator\b(.*)/operator/; + $oper = $1 . &matchOper; + } + ($mod,$d) = $decl =~ /^\s*([\*\&]*)\s*(\~?\w+(\[.*\])*)/; + $decl .= $oper; + $decl = $d if $d ne ""; + } + else { + s|//.*||; + + $final = 0; + + if ((($valid,)=&matchKW( "\=" )) && $valid) { + for (;;) { + + if (&matchRBracket) { + &skipBody; + $final = 1; + last; + } + + if (&matchSemi) { + $final = 1; + last; + } + + # var = new ... (...) + if ((($valid,)=&matchKW("new")) && $valid) { + &matchKW("[A-Za-z_0-9 ]*"); + if (&matchRParen) { + &skipParenBody; + } + } + + # var = (.....) ... + if (&matchRParen) { + &skipParenBody; + } + + # var = ... * ... + &matchKW ("[\/\*\-\+]*"); + + # var = "..." + if ((($valid,) = &matchKW ("[\"]")) && $valid) { + &skipString; + } + #&matchString; + + last if /^\s*,/; + #last if !((($valid,)=&matchAny) && $valid); + last if !((($valid,)=&matchKW("[A-Za-z_0-9 \-]*")) && $valid); + if (&matchSemi) { + $final = 1; + last; + } + } + } + + s|//.*||; + + # void ~*&foo[]; + # void foo[]; + # void far*foo[]; + # print STDERR "Decl: $type$mod$baseScope$decl;\n"; + + # Search for any text within the name field + if ($docEmpty == 0 && ($decl =~ /\W*(~?\w*).*/)) + { + $mod =~ s/\&/\&/g; + $name = $decl; + + $dbname = &uniqueName( "$baseScope$1" ); + + my $entry = { 'type' => 'var', + 'name' => $1, + 'longname' => "$name", + 'fullname' => "$type$mod$decl", + 'scopename'=> "$baseScope$type$mod$decl", + 'uname' => $dbname, + 'decl' => "$type$mod$decl", + 'package' => $packageName }; + + bless $entry, MemberRecord; + + if ($class) { + $entry->{ 'class' } = "$context"; + $class->{ 'members' }{ $dbname } = $entry; + } + else { + $packages{ $packageName }{ 'globals' }{ $dbname } = $entry; + } + &dumpComments( $entry ); + } + else { &clearComments; } + + last if $final; + last if &matchSemi; + last if !&matchComma; + last if !((($valid, $decl) = &matchDecl) && $valid); + + # Look for (*name) syntax + &parseParenPointer; + + $decl =~ s/^\s*//; + ($mod,$d) = $decl =~ /^\s*([\*\&]*)(\~?\w+(\[.*\])*)/; + $decl = $d if $d ne ""; + } + } + } + elsif ($context ne "" && /^\s*\}/) { + # print STDERR "Popping!\n"; + return 1; + } + elsif (&matchRBracket) { + &skipBody; + } + elsif ((($valid, $token) = &matchAny) && $valid) { + # Comment in for debugging + # print STDERR "token: $token \n"; + } + else { return 0; } + + return 1; +} + +# read a file into a string ( filename, default-value ) +sub readFile { + local ( $filename, $result ) = @_; + + if ($filename && open( FILE, $filename )) { + $result = ""; + while (<FILE>) { $result .= $_; } + close( FILE ); + } + return $result; +} + +# Read the entire document template and translate into PERL code. +sub readTemplate { + local ( $filename ) = @_; + $docTemplate = ''; + $indent = ''; + $literal = 1; # We're in literal mode. + + if (!-e $filename) { + if (-e "./templates/$filename") { $filename = "./templates/$filename"; } + elsif (-e "../templates/$filename") { $filename = "../templates/$filename"; } + else { die "Could not find template '$filename'.\n"; } + } + + open( FILE, $filename ) || die "Error opening '$filename'.\n"; + while (<FILE>) { + last if (/END/); + + # if we found a code entry. + for (;;) { + &expandTabs( $_ ); + if ($literal) { + # Check for beginning of code block. + if (s/^(.*)\<\<//) { + $line = $1; + if (substr( $line, 0, length( $indent ) ) eq $indent) { + substr( $line, 0, length( $indent ) ) = ''; + } + + if ($line ne '') { + $line =~ s/\"/\\\"/g; + $line =~ s/\$\((\w+)\.(\w+)\)/\" \. \$$1->$2() \. \"/g; + $docTemplate .= "${indent}print\"$line\";"; + } + # else { $docTemplate .= "\n"; } + $literal = 0; + } + else { + if (substr( $_, 0, length( $indent ) ) eq $indent) { + substr( $_, 0, length( $indent ) ) = ""; + } + chop; + s/\"/\\\"/g; + s/\$\((\w+)\.(\w+)\)/\" \. \$$1->$2() \. \"/g; + $_ = $indent . "print \"" . $_ . "\\n\";\n"; + last; + } + } + else { + # Check for beginning of literal block. + if (s/^(\s*)\>\>//) { + $indent = $1; + $literal = 1; + } + elsif (s/^(\s*)(.*)\>\>//) { + $docTemplate .= "$indent$2"; + $literal = 1; + } + else { + last; + } + } + } + + $docTemplate .= $_; + } + close( FILE ); + # print $docTemplate; +} + +# Functions intended to be called from doc template file. + +# Open a new output file +sub file { + my $mfile = $_[ 0 ]; + + open( STDOUT, ">$destPath$mfile" ) || die "Error writing to '$mfile'\n"; +} + +# return list of package objects +sub packages { + my ($p, @r); + @r = (); + + foreach $p (sort keys %packages) { + push @r, $packages{ $p }; + } + return @r; +} + +# return list of source files which have to-do lists +sub todolistFiles { + my ($p, @r); + @r = (); + + foreach $p (sort keys %todolist) { + push @r, $p; + } + return @r; +} + +# return list of tab-delimited to-do-list texts. +sub todolistEntries { + local $_ = $todolist{ $_[0] }; + s/^\s+//; # Remove whitespace from beginning + s/\s+$/\n/; # Remove whitespace from end + return split( /\n/, $_ ); +} + +# Convert package name to URL. +sub packageURL { + my $p = $_[0]; + + if ($p eq 'General') { $p = '.general'; } + if ($p eq '') { $p = '.general'; } + + if (ref $packages{ $p }) { + return $packages{ $p }->url(); + } + return 0; +} + +# Get the see-also list for an object +sub seealsoList { + my $self = shift; + my ($see, $name, $url, $p, @r); + @r = (); + + if (defined ($self->{ 'see' })) { + foreach $_ (split(/\n/,$self->{ 'see' })) { + + if (/^\<a\s+href/) { # if already an HREF. + $name = $_; + $url = 0; + } + elsif (/([^\#]*)\#(.*)/) { # If a package name is present + $url = &packageURL( $1 ) . '#' . $2; + $name = $2; + } + else { + $name = $_; + $url = "#$_"; + + # This doesn't appear to do anything - so I commented it. (james) + # Look up the package in the index and use it to construct the html filename. + #if (/^([^\:]*)\:\:(.*)/) { + # $className = ($1 eq '') ? '' : $classToPackage{ $1 }; + # $p = $packageToFile{ $className }; + # if ($p ne '') { + # $url = &packageURL( $1 ) . '#' . $_; + # } + #} + } + + $url =~ s/^\:*//; # Remove leading colons from name + $url =~ s/::/-/g; # Replace :: with dash + + my $entry = { 'name' => $name, + 'url' => $url }; + + bless $entry, DocReference; + + push @r, $entry; + } + } + return @r; +} + +# Class for parsed package +package PackageRecord; + +sub classes { + my $self = shift; + my $classes = $self->{ 'classes' }; + return map $classes->{ $_ }, (sort keys %$classes); +} + +sub globals { + my $self = shift; + my $globals = $self->{ 'globals' }; + return map $globals->{ $_ }, (sort keys %$globals); +} + +sub globalvars { + my $self = shift; + my $globals = $self->{ 'globals' }; + my ($p, @r); + @r = (); + + foreach $p (sort keys %$globals) { + my $m = $globals->{ $p }; + if ($m->{ 'type' } ne 'func') { push @r, $m; } + } + return @r; +} + +sub globalfuncs { + my $self = shift; + my $globals = $self->{ 'globals' }; + my ($p, @r); + @r = (); + + foreach $p (sort keys %$globals) { + my $m = $globals->{ $p }; + if ($m->{ 'type' } eq 'func') { push @r, $m; } + } + return @r; +} + +sub name { + my $self = shift; + return $self->{ 'name' }; +} + +sub url { + my $self = shift; + return "default-pkg.html" if ($self->{ 'name' } eq '.general'); + return $self->{ 'name' } . '.html'; +} + +sub anchor { + my $self = shift; + my $url = $self->{ 'name' }; + return $url; +} + +# Class for parsed class +package ClassRecord; + +sub keywords { return ${$_[0]}{ 'keywords' }; } +sub author { return ${$_[0]}{ 'author' }; } +sub version { return ${$_[0]}{ 'version' }; } +sub name { return ${$_[0]}{ 'name' }; } +sub longname { return ${$_[0]}{ 'longname' }; } +sub fullname { return ${$_[0]}{ 'fullname' }; } +sub scopename { return ${$_[0]}{ 'scopename' }; } +sub sourcefile { return ${$_[0]}{ 'sourcefile' }; } +#sub description { return &::processDescription( ${$_[0]}{ 'description' } ); } +sub description { return ${$_[0]}{ 'description' }; } +sub seealso { &::seealsoList( $_[0] ); } + +sub url { + my $self = shift; + return 0 unless $self->{ 'package' }; + my $pname = ::packageURL( $self->{ 'package' } ); + my $url = $self->{ 'uname' }; + $url =~ s/::/-/g; + return "$pname#$url"; +} + +sub anchor { + my $self = shift; + my $url = $self->{ 'uname' }; + $url =~ s/::/-/g; + return $url; +} + +sub members { + my $self = shift; + my $members = $self->{ 'members' }; + my ($p, @r); + @r = (); + + foreach $p (sort keys %$members) { + push @r, $members->{ $p }; + } + return @r; +} + +sub membervars { + my $self = shift; + my $members = $self->{ 'members' }; + my ($p, @r); + @r = (); + + foreach $p (sort keys %$members) { + my $m = $members->{ $p }; + if ($m->{ 'type' } ne 'func') { push @r, $m; } + } + return @r; +} + +sub memberfuncs { + my $self = shift; + my $members = $self->{ 'members' }; + my ($p, @r); + @r = (); + + foreach $p (sort keys %$members) { + my $m = $members->{ $p }; + if ($m->{ 'type' } eq 'func') { push @r, $m; } + } + return @r; +} + +sub baseclasses { + my $self = shift; + my $bases = $self->{ 'bases' }; + my ($p, $class, @r); + @r = (); + + foreach $p (@$bases) { + + unless ($class = $::classList{ $p }) { + # It's one we don't know about, so just make something up + $class = { 'name' => $p, + 'longname'=> "class $p", + 'fullname'=> "class $p", + 'scopename'=>"class $p", + 'uname' => $p, + 'members' => {} }; + + if ($::classToPackage{ $p }) { + $class->{ 'package' } = $::classToPackage{ $p }; + } + + bless $class, ClassRecord; + } + push @r, $class; + } + return @r; +} + +sub subclasses { + my $self = shift; + my $subs; + my ($p, $class, @r); + @r = (); + + if (defined ($self->{ 'subs' })) { + $subs = $self->{ 'subs' }; + foreach $p (sort @$subs) { + $class = $::classList{ $p }; + push @r, $class; + } + } + return @r; +} + +# Class for parsed class member or global +package MemberRecord; + +sub type { return ${$_[0]}{ 'type' }; } +sub keywords { return ${$_[0]}{ 'keywords' }; } +sub author { return ${$_[0]}{ 'author' }; } +sub version { return ${$_[0]}{ 'version' }; } +sub name { return ${$_[0]}{ 'name' }; } +sub longname { return ${$_[0]}{ 'longname' }; } +sub fullname { return ${$_[0]}{ 'fullname' }; } +sub scopename { return ${$_[0]}{ 'scopename' }; } +sub returnValue { return ${$_[0]}{ 'return' }; } +sub sourcefile { return ${$_[0]}{ 'sourcefile' }; } +sub description { return ${$_[0]}{ 'description' }; } +sub seealso { &::seealsoList( $_[0] ); } + +sub url { + my $self = shift; + return 0 unless $self->{ 'package' }; + my $pname = ::packageURL( $self->{ 'package' } ); + my $url = $self->{ 'uname' }; + $url =~ s/::/-/g; + return "$pname#$url"; +} + +sub anchor { + my $self = shift; + my $url = $self->{ 'uname' }; + $url =~ s/::/-/g; + $url; +} + +sub params { + my $self = shift; + my $params = $self->{ 'param' }; + my @r; + @r = (); + + return 0 unless ($params); + + my @paramList = split( /\t/, $params ); + + for ($i = 1; $i < $#paramList; $i += 2) { + my $entry = { 'name' => $paramList[ $i ], + 'description' => $paramList[ $i + 1 ] }; + + bless $entry, ArgRecord; + + push @r, $entry; + } + return @r; +} + +sub exceptions { + my $self = shift; + my $params = $self->{ 'exception' }; + my @r; + @r = (); + + return 0 unless ($params); + + my @paramList = split( /\t/, $params ); + + for ($i = 1; $i < $#paramList; $i += 2) { + my $entry = { 'name' => $paramList[ $i ], + 'description' => $paramList[ $i + 1 ] }; + + bless $entry, ArgRecord; + + push @r, $entry; + } + return @r; +} + +package ArgRecord; +sub name { return ${$_[0]}{ 'name' }; } +sub description { return ${$_[0]}{ 'description' }; } + +package DocReference; +sub name { return ${$_[0]}{ 'name' }; } +sub url { return ${$_[0]}{ 'url' }; } |