#!/usr/bin/perl
-if ($ARGV[0] eq "-h"){
- $dir = $ARGV[1];
- $html = 1;
- shift @ARGV;
- shift @ARGV;
+use warnings;
+use strict;
+
+use Getopt::Long;
+use Pod::Usage;
+
+# Options
+my $HELP = 0;
+my $SOURCE_DIR = '';
+my $TARGET_DIR = '';
+my $WARNINGS = 0;
+
+GetOptions(
+ "help" => \$HELP,
+ "html|h=s" => \$SOURCE_DIR,
+ "target|t=s" => \$TARGET_DIR,
+ "warnings|W" => \$WARNINGS,
+) or pod2usage(1);
+
+pod2usage(0) if $HELP;
+
+exdoc();
+
+#
+# Main entry point.
+#
+sub exdoc {
+ my %templates = ();
+ my %docs = ();
+ my $stylesheet = load_stylesheet($SOURCE_DIR);
+ load_templates($SOURCE_DIR, \%templates);
+ process_source_files(\%docs);
+ merge(\%docs, \%templates, \$stylesheet);
+}
+
+#
+# Load CSS stylesheet.
+#
+sub load_stylesheet {
+ my ($dir_path) = @_;
+ my $file_path = "$dir_path/api-style.css";
+ open (my $file, '<', $file_path) or die "Could not open $file_path";
+ local $/;
+ my $contents = <$file>;
+ close $file;
+ return $contents;
+}
+
+#
+# Load HTML templates.
+#
+sub load_templates {
+ my ($dir_path, $templates) = @_;
+ opendir (my $dir, "$dir_path/sources/") or die "Could not open $dir_path";
+ while (my $file_name = readdir ($dir)) {
+ next if $file_name !~ /mono-api-.*\.html$/;
+ open (my $file, "$dir_path/sources/$file_name") or die "Could not open $file_name";
+ my $contents = '';
+ my @api = ();
+ while (<$file>) {
+ $contents .= $_;
+ if (/name="api:(.*?)"/) {
+ s/.*name="api:(\w+?)".*/$1/;
+ push @api, $_;
+ }
+ }
+ close $file;
+ $templates->{$file_name}->{contents} = $contents;
+ $templates->{$file_name}->{api} = \@api;
+ }
+ closedir $dir;
+}
+
+#
+# Extract documentation from all source files.
+#
+sub process_source_files {
+ my ($docs) = @_;
+ for my $file_path (@ARGV) {
+ process_source_file($file_path, $docs);
+ }
+}
+
+#
+# Extract documentation from a single source file.
+#
+sub process_source_file {
+ my ($file_path, $docs) = @_;
+ open (my $file, '<', $file_path) or die "Could not open $file_path";
+ while (<$file>) {
+ next if (!/\/\*\* *\n/);
+ process_function($file, $file_path, $docs);
+ }
+ close $file;
}
-if ($html){
- opendir (D, "$dir/sources/") || die "Can not open $dir";
- while ($n = readdir (D)){
- if ($n =~ /mono-api-.*\.html$/){
- open (IN, "$dir/sources/$n") || die "Can not open $n";
- $files[$filecount] = $n;
- while (<IN>){
- @files_content[$filecount] .= $_;
- if (/name="api:(.*?)"/){
- $_ =~ s/.*name="api:(\w+?)".*/\1/;
- $apis[$filecount] .= "$_";
- }
- }
- $filecount++;
- close IN;
- }
+#
+# Extract documentation from a single function.
+#
+sub process_function {
+
+ my ($file, $file_path, $docs) = @_;
+
+ my $PARAMETER_SECTION = 0;
+ my $BODY_SECTION = 1;
+ my $RETURN_SECTION = 2;
+ my $section = $PARAMETER_SECTION;
+
+ my $name = do {
+ $_ = <$file>;
+ chomp;
+ s/^ \* //;
+ s/:$//;
+ $_
+ };
+
+ # Ignore irrelevant functions, and those with the wrong doc format.
+ return if $name !~ /^mono_\w+$/;
+
+ my $deprecated;
+ my @parameters = ();
+ my $body = '';
+ my $returns = '';
+ my $prototype = '';
+
+ while (<$file>) {
+
+ # We've reached the last line in the documentation block.
+ if (/^ \*\*?\//) {
+
+ # Grab function prototype.
+ while (<$file>) {
+ $prototype .= $_;
+ last if /\{/;
+ }
+
+ # Clean up prototype.
+ $prototype = do {
+ $_ = $prototype;
+ # Strip braces and trailing whitespace.
+ s/{//;
+ s/ +$//;
+ # Turn "Type * xxx" into "Type* xxx"
+ s/^(\w+)\W+\*/$1*/;
+ $_;
+ };
+
+ # Process formatting within sections.
+ for my $parameter (@parameters) {
+ process_formatting(\$parameter->{description}, $file_path, $.);
+ }
+ process_formatting(\$returns, $file_path, $.);
+ process_formatting(\$body, $file_path, $.);
+ if (defined($deprecated)) {
+ process_formatting(\$deprecated, $file_path, $.);
+ }
+ $body =~ s/\n/ /g;
+
+ if (exists($docs->{body}->{$name})) {
+ my $origin = $docs->{origin}->{$name};
+ if ($WARNINGS) {
+ warn
+ "$file_path:$.: Redundant documentation for $name\n",
+ "$origin->{file}:$origin->{line}: Previously defined here\n";
+ }
+ }
+ $docs->{origin}->{$name} = { file => $file_path, line => $. };
+ $docs->{body}->{$name} = $body;
+ $docs->{parameters}->{$name} = \@parameters;
+ $docs->{deprecated}->{$name} = $deprecated if defined $deprecated;
+ $docs->{return}->{$name} = $returns;
+ $docs->{prototype}->{$name} = $prototype;
+ last;
+
+ }
+
+ # Strip newlines and asterisk prefix.
+ chomp;
+ s/^ +\*//;
+
+ # Replace blank lines with paragraph breaks.
+ $_ = '<p>' if /^\s*$/;
+
+ if ($section == $PARAMETER_SECTION) {
+ if (/\s*\\param +(\w+)(.*)/) {
+ # print "$file_path:$.: warning: Got parameter $1\n";
+ push @parameters, { name => $1, description => $2 };
+ } elsif (/\s*\\deprecated(.*)/) {
+ # print "$file_path:$.: warning: Got deprecated annotation\n";
+ $deprecated = $1;
+ } elsif (/\s*(\w+):(.*)/) {
+ if ($1 eq 'deprecated') {
+ warn "$file_path:$.: Old-style monodoc notation 'deprecated:' used\n"
+ if $WARNINGS;
+ $deprecated = $2;
+ } else {
+ warn "$file_path:$.: Old-style monodoc notation 'param:' used\n"
+ if $WARNINGS;
+ push @parameters, { name => $1, description => $2 };
+ }
+ } else {
+ # $body = "\t$_\n";
+ $section = $BODY_SECTION;
+ redo;
+ }
+ } elsif ($section == $BODY_SECTION) {
+ if (s/(Returns?:\s*|\\returns?\s*)//) {
+ $returns = "\t$_\n";
+ $section = $RETURN_SECTION;
+ } else {
+ $body .= "\n\t$_";
+ }
+ } elsif ($section == $RETURN_SECTION) {
+ $returns .= "\n\t$_";
+ } else {
+ die "Invalid section $section\n";
+ }
}
}
-while (<ARGV>){
- if (/\/\*\* *\n/){
- &process_doc;
- } else {
- #print "IGNORING: $_";
- }
+#
+# Substitute formatting within documentation text.
+#
+sub process_formatting {
+ my ($content, $file_path, $current_line) = @_;
+ $_ = $$content;
+
+ # Constants
+ s{NULL}{<code>NULL</code>}g;
+ s{TRUE}{<code>TRUE</code>}g;
+ s{FALSE}{<code>FALSE</code>}g;
+
+ # Parameters
+ warn "$file_path:$current_line: Old-style monodoc notation '\@param' used\n"
+ if s{@(\w+)}{<i>$1</i>}g && $WARNINGS;
+ s{\\p +(\w+)}{<i>$1</i>}g;
+
+ # Code
+ warn "$file_path:$current_line: Old-style monodoc notation '#code' used\n"
+ if s{#(\w+)}{<code>$1</code>}g && $WARNINGS;
+ warn "$file_path:$current_line: Old-style monodoc notation '`code`' used\n"
+ if s{\`((?!api:)[:.\w\*]+)\`}{<code>$1</code>}g && $WARNINGS;
+ s{\\c +(\S+(?<![.,:;]))}{<code>$1</code>}g;
+
+ $$content = $_;
}
-if ($html){
- for ($f = 0; $f < $filecount; $f++){
- $name = $files[$f];
- open (OUT, "> $dir/html/$name") || die "Can not create $dir/html/$name";
- print "Merging: $name\n";
- print OUT<<EOF;
+#
+# Merge templates with stylesheet and documentation extracted from sources.
+#
+sub merge {
+ my ($docs, $templates, $stylesheet) = @_;
+ my $last = '';
+ for my $name (keys %$templates) {
+ open (my $output_file, '>', "$TARGET_DIR/html/$name")
+ or die "Could not create $TARGET_DIR/html/$name";
+ print "Merging: $name\n";
+ print $output_file <<EOF;
<?xml version="1.0" encoding="utf-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
- <title>$name</title>
- <style type="text/css">
- h3 {
- font-size: 18px;
- padding-bottom: 4pt;
- border-bottom: 2px solid #dddddd;
- }
-
- .api {
- border: 1px solid;
- padding: 10pt;
- margin: 10pt;
- }
-
- .api-entry {
- border-bottom: none;
- font-size: 18px;
- }
-
- .prototype {
- border: 1px solid;
- background-color: #f2f2f2;
- padding: 5pt;
- margin-top: 5pt;
- margin-bottom: 5pt;
- }
-
- .header {
- border: 1px solid;
- padding: 0 0 5pt 5pt;
- margin: 10pt;
- white-space: pre;
- font-family: monospace;
- }
-
- .code {
- border: 1px solid;
- padding: 0 0 5pt 5pt;
- margin: 10pt;
- white-space: pre;
- font-family: monospace;
- }
+ <title>$name</title>
+ <style type="text/css">
+$stylesheet
</style>
</head>
<body>
+<div class="mapi-docs">
EOF
- @a = split (/\n/, $files_content[$f]);
-
- for ($ai = 0; $ai < $#a; $ai++){
- $line = $a[$ai];
-
- ($api,$caption) = $line =~ /<h4><a name=\"api:(\w+)\">(\w+)<\/a><\/h4>/;
- if ($api ne ""){
- if ($api_shown == 1){
- print OUT "</div>";
- }
- $api_shown = 1;
- $proto = $prototype{$api};
- if ($proto eq ""){
- $proto = "Prototype: $api";
- }
-
-print OUT<<EOF;
- <a name="api:$api"></a>
- <div class="api">
- <div class="api-entry">$api</div>
-
- <div class="prototype">$proto</div>
-<p>
-EOF
- &opt_print ("Parameters", $arguments{$api}, 1);
- &opt_print ("Returns", $returns{$api}, 1);
- &opt_print ("Remarks", $bodies{$api}, 0);
- print OUT "\n";
- } else {
- if ($line =~ /@API_IDX@/){
- $apis_toc = &create_toc ($apis[$f]);
- $line =~ s/\@API_IDX\@/$apis_toc/;
- }
- if ($line =~ /^<h/){
- print OUT "</div>";
- $api_shown = 0;
- }
-
- print OUT "$line\n";
- }
- }
- print OUT<<EOF;
-</body>
-</html>
+ my @a = split (/\n/, $templates->{$name}->{contents});
+ my $strike = '';
+ my $strikeextra = '';
+ my $api_shown = 0;
+ for (my $ai = 0; $ai < $#a; $ai++) {
+ my $line = $a[$ai];
+ if (my ($api, $caption) = ($line =~ /<h4><a name=\"api:(\w+)\">(\w+)<\/a><\/h4>/)) {
+ if ($api_shown == 1) {
+ print $output_file "</div> <!-- class=mapi -->\n\n";
+ if ($docs->{deprecated}->{$api}) {
+ $strike = "mapi-strike";
+ $strikeextra = "</div><br><div class='mapi-deprecated'><b>Deprecated:</b> " . $docs->{deprecated}->{$api};
+ } else {
+ $strike = "";
+ $strikeextra = "";
+ }
+ }
+ $api_shown = 1;
+ my $proto = $docs->{prototype}->{$api} // $api;
+
+ print $output_file <<EOF;
+<a name="api:$api"></a>
+<div class="mapi">
+ <div class="mapi-entry $strike"><code>$api$strikeextra</code></div>
+ <div class="mapi-height-container">
+ <div class="mapi-ptr-container"></div>
+ <div class="mapi-description">
+ <div class="mapi-ptr"></div>
+
+ <div class="mapi-declaration mapi-section">Syntax</div>
+ <div class="mapi-prototype">$proto</div>
+ <p>
EOF
- close OUT;
- system ("$ENV{runtimedir}/mono-wrapper convert.exe $dir/html/$name $dir/html/x-$name");
-
- # clean up the mess that AgilityPack does, it CDATAs our CSS
- open HACK, "$dir/html/x-$name" || die "Could not open $dir/html/x-$name";
- open HACKOUT, ">$dir/deploy/$name" || die "Could not open output";
-
- while (<HACK>){
- s/^\/\/<!\[CDATA\[//;
- s/^\/\/\]\]>\/\///;
- print HACKOUT $_;
- }
- #system ("cp.exe $dir/html/$name $dir/deploy/$name");
- }
-}
+ if (exists ($docs->{parameters}->{$api})) {
+ my $ppars = $docs->{parameters}->{$api};
+ if (@$ppars) {
+ print $output_file
+ " <div class=\"mapi-section\">Parameters</div>\n",
+ " <table class=\"mapi-parameters\"><tbody>",
+ render_parameters($ppars),
+ "</tbody></table>";
+ }
+ }
-sub process_doc {
- $doc = "";
- $func = <>;
- chop $func;
- $func =~ s/^ \* //;
- $func =~ s/:$//;
- print "Function: $func\n" if (!$html);
- $args = "";
- $inbody = 0;
- $returns = "";
- $body = "";
- $functions[$fn++] = $func;
-
- # Process arguments
- while (<>){
- if (/^ \*\*?\//){
- $body =~ s/[@#](\w+)/<i>\1<\/i>/g;
- $returns =~ s/[@#](\w+)/<i>\1<\/i>/g;
-
- $args =~ s/@(\w+)/<i>\1<\/i>/g;
- $body =~ s/\n/ /;
- $bodies{$func} = $body;
- $arguments{$func} = $args;
- $returns{$func} = $returns;
- $proto = "";
- while (<>){
- $proto .= $_;
- last if (/\{/);
- }
- $proto =~ s/{//;
- # clean it up a little, remove newlines, empty space at end
- $proto =~ s/ +$//;
- # Turn "Type * xxx" into "Type* xxx"
- $proto =~ s/^(\w+)\W+\*/\1\*/;
- $prototype{$func} = $proto;
- return;
- }
- chop;
- s/^\ \*//;
- $_ = "\n<p>" if (/^\s+$/);
-
- if ($inbody == 0){
- if (/\s*(\w+):(.*)/){
- $args .= "<dt><i>$1:</i></dt><dd>$2</dd>";
- } else {
-
- $body = "\t$_\n";
- $inbody = 1;
- }
- } elsif ($inbody == 1) {
- if (/Returns?:/){
- s/Returns?://;
- $returns = "\t$_\n";
- $inbody = 2;
- } else {
- $body .= "\n\t$_";
- }
- } else {
- $returns .= "\n\t$_";
- }
-
- }
+ opt_print ($output_file, "Return value", $docs->{return}->{$api});
+ opt_print ($output_file, "Description", $docs->{body}->{$api});
+ print $output_file " </div><!--mapi-description-->\n </div><!--height container-->\n";
+ } else {
+ if ($line =~ /\@API_IDX\@/) {
+ my $apis_toc = create_toc ($docs, $templates->{$name}->{api});
+ $line =~ s/\@API_IDX\@/$apis_toc/;
+ }
+ if ($line =~ /^<h4/) {
+ print $output_file "</div>\n";
+ $api_shown = 0;
+ }
+ if ($line =~ /`/) {
+ }
+ print $output_file "$line\n";
+ }
+ }
+ print $output_file
+ " </div>",
+ "</body>",
+ "</html>";
+ close $output_file;
+ system ("$ENV{runtimedir}/mono-wrapper convert.exe $TARGET_DIR/html/$name $TARGET_DIR/html/x-$name");
+
+ # Clean up the mess that AgilityPack makes (it CDATAs our CSS).
+ open (my $hack_input, '<', "$TARGET_DIR/html/x-$name")
+ or die "Could not open $TARGET_DIR/html/x-$name";
+ open (my $hack_output, '>', "$TARGET_DIR/deploy/$name")
+ or die "Could not open output";
+
+ my $line = 0;
+ my $doprint = 0;
+ while (<$hack_input>) {
+ print $hack_output $last if ($doprint);
+ $line++;
+ s/^\/\/<!\[CDATA\[//;
+ s/^\/\/\]\]>\/\///;
+
+ # Remove the junk <span> wrapper generated by AgilityPack.
+ if ($line==1) {
+ s/<span>//;
+ }
+ if (/<style type/) {
+ # Replace the CSS in the XHTML output with the original CSS.
+ print $hack_output $_;
+ print $hack_output $$stylesheet;
+ while (<$hack_input>) {
+ last if (/<\/style>/);
+ }
+ }
+ $last = $_;
+ $doprint = 1;
+ }
+ if (!($last =~ /span/)) {
+ print $hack_output $last;
+ }
+ # system ("cp.exe $TARGET_DIR/html/$name $TARGET_DIR/deploy/$name");
+ }
}
sub create_toc {
- my ($apis_listed) = @_;
+ my ($docs, $apis_listed) = @_;
my $type_size = 0;
my $name_size = 0;
- my $ret, $xname, $args, $line;
- $apis_toc = "";
-
-
- # Try to align things, so compute type size, method size, and arguments
- foreach $line (split /\n/, $apis_listed){
- $p = $prototype{$line};
- ($ret, $xname, $args) = $p =~ /(.*)\n(\w+)[ \t](.*)/;
- $tl = length ($ret);
- $pl = length ($xname);
+ my ($ret, $xname, $args);
+ my $apis_toc = "";
- $type_size = $tl if ($tl > $type_size);
- $name_size = $pl if ($pl > $name_size);
+ # Try to align things; compute type size, method size, and arguments.
+ foreach my $line (split /\n/, $apis_listed) {
+ if (exists ($docs->{prototype}->{$line})) {
+ my $p = $docs->{prototype}->{$line};
+ if (my ($ret, $xname, $args) = ($p =~ /(.*)\n(\w+)[ \t](.*)/)) {
+ my $tl = length ($ret);
+ my $pl = length ($xname);
+ $type_size = $tl if ($tl > $type_size);
+ $name_size = $pl if ($pl > $name_size);
+ }
+ }
}
$type_size++;
$name_size++;
- foreach $line (split /\n/, $apis_listed){
- chop;
- $p = $prototype{$line};
- ($ret, $xname, $args) = $p =~ /(.*)\n(\w+)[ \t](.*)/;
-
- $rspace = " " x ($type_size - length ($ret));
- $nspace = " " x ($name_size - length ($xname));
- $args = &format ($args, length ($ret . $rspace . $xname . $nspace), 60);
- $apis_toc .= "$ret$rspace<a href=\"\#api:$line\">$xname</a>$nspace$args\n";
+ foreach my $line (split /\n/, $apis_listed) {
+ chomp($line);
+ if (exists($docs->{prototype}->{$line})) {
+ my $p = $docs->{prototype}->{$line};
+ if (my ($ret, $xname, $args) = ($p =~ /(.*)\n(\w+)[ \t](.*)/)) {
+ $xname = $line if $xname eq "";
+ my $rspace = " " x ($type_size - length ($ret));
+ my $nspace = " " x ($name_size - length ($xname));
+ $args = wrap ($args, length ($ret . $rspace . $xname . $nspace), 60);
+ $apis_toc .= "$ret$rspace<a href=\"\#api:$line\">$xname</a>$nspace$args\n";
+ }
+ }
}
return $apis_toc;
}
-#
-# Formats the rest of the arguments in a way that will fit in N columns
-#
-sub format {
+sub wrap {
my ($args, $size, $limit) = @_;
my $sret = "";
-# return $args if ((length (args) + size) < $limit);
+ # return $args if ((length (args) + size) < $limit);
- $remain = $limit - $size;
- @sa = split /,/, $args;
- $linelen = $size;
- foreach $arg (@sa){
- if ($sret eq ""){
- $sret = $arg . ", ";
- $linelen += length ($sret);
- } else {
- if ($linelen + length ($arg) < $limit){
- $sret .= "FITS" . $arg . ", ";
- } else {
- $newline = " " x ($size) . $arg . ", ";
- $linelen = length ($newline);
- $sret .= "\n" . $newline;
- }
- }
+ my $remain = $limit - $size;
+ my @sa = split /,/, $args;
+ my $linelen = $size;
+ foreach my $arg (@sa) {
+ if ($sret eq "") {
+ $sret = $arg . ", ";
+ $linelen += length ($sret);
+ } else {
+ if ($linelen + length ($arg) < $limit) {
+ $sret .= "FITS" . $arg . ", ";
+ } else {
+ my $newline = " " x ($size) . $arg . ", ";
+ my $linelen = length ($newline);
+ $sret .= "\n" . $newline;
+ }
+ }
}
$sret =~ s/, $/;/;
return $sret;
}
+#
+# Print a section if non-empty.
+#
sub opt_print {
- my ($caption, $opttext, $quote) = @_;
-
- if ($opttext ne "" && (!($opttext =~ /^[ \t]+$/))){
- print OUT "<b>$caption</b>\n";
- if ($quote == 1){
- print OUT "<blockquote>$opttext</blockquote>\n";
- } else {
- print OUT "<p>$opttext\n";
- }
+ my ($output, $caption, $opttext) = @_;
+ if (defined($opttext) && $opttext ne '' && $opttext !~ /^[ \t]+$/) {
+ print $output
+ " <div class=\"mapi-section\">$caption</div>\n",
+ " <div>$opttext</div>\n";
+ }
+}
+
+#
+# Render parameter information as table.
+#
+sub render_parameters {
+ my ($parameters) = @_;
+ my $result = '';
+ for my $parameter (@$parameters) {
+ $result .= "<tr><td><i>$parameter->{name}</i></td><td>$parameter->{description}</td></tr>";
}
+ return $result;
}
+
+__END__
+
+=head1 NAME
+
+exdoc - Compiles API docs from Mono sources and HTML templates.
+
+=head1 SYNOPSIS
+
+ exdoc [OPTIONS] [FILE...]
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--help>
+
+Print this help message.
+
+=item B<--html> I<DIR>, B<-h> I<DIR>
+
+Use I<DIR> as the input path for HTML sources.
+
+=item B<--target> I<DIR>, B<-t> I<DIR>
+
+Use I<DIR> as the target path for output.
+
+=item B<--warnings>, B<-W>
+
+Enable warnings about documentation errors.
+
+=back
+
+=head1 DESCRIPTION
+
+Reads HTML templates and C sources, extracting documentation from the sources and splicing it into the templates.
+
+=cut