#!/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 -p -t -d= [ ... ]\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 ) && ($_ = )) { 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*)$/ && ($_ = )) {$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 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 .= $_; $_ = ; $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 .= $_; $_ = ; $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 ((($valid, $token) = &matchKW("extern\\s*\\\"C\\\"")) && $valid) { &matchRBracket; while (!&matchLBracket) { &parseDeclaration( '' ) || die "Unmatched brace! line = $linenumber\n"; } &matchSemi; } # 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 () { $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 () { last if (/END/); # if we found a code entry. for (;;) { &expandTabs( $_ ); if ($literal) { # Check for beginning of code block. if (s/^(.*)\<\$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 (/^\ $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' }; }