From 9cad7ff740962a8be9db82281f62af4d28759911 Mon Sep 17 00:00:00 2001 From: Jon Purdy Date: Wed, 22 Feb 2017 18:27:54 -0800 Subject: [PATCH] [exdoc] Refactor. Avoid globals, use proper data structures instead of strings, and produce some warnings for documentation errors. --- docs/exdoc | 517 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 329 insertions(+), 188 deletions(-) diff --git a/docs/exdoc b/docs/exdoc index 76da62004af..64aa85fdf44 100644 --- a/docs/exdoc +++ b/docs/exdoc @@ -3,84 +3,255 @@ use warnings; use strict; -my $sourcedir = ''; -my $dir = ''; -my $html = 0; -my $css = ''; -my @files = (); -my $filecount = 0; -my @files_content = (); -my @apis = (); -my %deprecated = (); -my %prototype = (); -my %arguments = (); -my %returns = (); -my %bodies = (); - -if ($ARGV[0] eq "-h") { - $sourcedir = $ARGV[1]; - $dir = $sourcedir; - $html = 1; - shift @ARGV; - shift @ARGV; +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; } -open (FILE, "$dir/api-style.css" || die "Did not find $dir/api-style.css"); -while () { - $css = $css . $_; + +# +# Extract documentation from all source files. +# +sub process_source_files { + my ($docs) = @_; + for my $file_path (@ARGV) { + process_source_file($file_path, $docs); + } } -if ($ARGV[0] eq "-t") { - $dir = $ARGV[1]; - shift @ARGV; +# +# 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, "$sourcedir/sources/") || die "Can not open $dir"; - while (my $n = readdir (D)) { - if ($n =~ /mono-api-.*\.html$/) { - open (IN, "$sourcedir/sources/$n") || die "Can not open $n"; - $files[$filecount] = $n; - $files_content[$filecount] = ''; - while () { - $files_content[$filecount] .= $_; - if (/name="api:(.*?)"/) { - $_ =~ s/.*name="api:(\w+?)".*/$1/; - $apis[$filecount] .= "$_"; +# +# 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}); + } + process_formatting(\$returns); + process_formatting(\$body); + $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"; } } - $filecount++; - close IN; + $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. + $_ = '

' if /^\s*$/; + + if ($section == $PARAMETER_SECTION) { + if (/\s*(\w+):(.*)/) { + if ($1 eq 'deprecated') { + $deprecated = $2; + } else { + push @parameters, { name => $1, description => $2 }; + } + } else { + $body = "\t$_\n"; + $section = $BODY_SECTION; + } + } elsif ($section == $BODY_SECTION) { + if (/Returns?:/) { + s/Returns?://; + $returns = "\t$_\n"; + $section = $RETURN_SECTION; + } else { + $body .= "\n\t$_"; + } + } elsif ($section == $RETURN_SECTION) { + $returns .= "\n\t$_"; + } else { + die "Invalid section $section\n"; } } } -while () { - if (/\/\*\* *\n/) { - &process_doc; - } else { - #print "IGNORING: $_"; - } +# +# Substitute formatting within documentation text. +# +sub process_formatting { + my ($content) = @_; + $_ = $$content; + + # Constants + s{NULL}{NULL}g; + s{TRUE}{TRUE}g; + s{FALSE}{FALSE}g; + + # Parameters + s{@(\w+)}{$1}g; + + # Code + s{#(\w+)}{$1}g; + s{\`([:.\w\*]+)\`}{$1}g; + + $$content = $_; } -if ($html) { +# +# Merge templates with stylesheet and documentation extracted from sources. +# +sub merge { + my ($docs, $templates, $stylesheet) = @_; my $last = ''; - for (my $f = 0; $f < $filecount; $f++) { - my $name = $files[$f]; - open (OUT, "> $dir/html/$name") || die "Can not create $dir/html/$name"; + 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 OUT < $name

EOF - my @a = split (/\n/, $files_content[$f]); + my @a = split (/\n/, $templates->{$name}->{contents}); my $strike = ''; my $strikeextra = ''; my $api_shown = 0; @@ -88,19 +259,19 @@ EOF my $line = $a[$ai]; if (my ($api, $caption) = ($line =~ /

(\w+)<\/a><\/h4>/)) { if ($api_shown == 1) { - print OUT "

\n\n"; - if ($deprecated{$api}) { + print $output_file " \n\n"; + if ($docs->{deprecated}->{$api}) { $strike = "mapi-strike"; - $strikeextra = "
Deprecated: " . $deprecated{$api}; + $strikeextra = "

Deprecated: " . $docs->{deprecated}->{$api}; } else { $strike = ""; $strikeextra = ""; } } $api_shown = 1; - my $proto = $prototype{$api} // $api; + my $proto = $docs->{prototype}->{$api} // $api; - print OUT <
$api$strikeextra
@@ -113,61 +284,64 @@ EOF
$proto

EOF - if (exists ($arguments{$api})) { - my $ppars = $arguments{$api}; - if ($ppars !~ /^[ \t]+$/) { - print OUT "

Parameters
\n"; - print OUT " ".${arguments{$api}}."
"; + if (exists ($docs->{parameters}->{$api})) { + my $ppars = $docs->{parameters}->{$api}; + if (@$ppars) { + print $output_file + "
Parameters
\n", + " ", + render_parameters($ppars), + "
"; } } - &opt_print ("Return value", $returns{$api}, 0); - &opt_print ("Description", $bodies{$api}, 0); - print OUT "
\n
\n"; + opt_print ($output_file, "Return value", $docs->{return}->{$api}); + opt_print ($output_file, "Description", $docs->{body}->{$api}); + print $output_file " \n \n"; } else { if ($line =~ /\@API_IDX\@/) { - my $apis_toc = &create_toc ($apis[$f]); + my $apis_toc = create_toc ($docs, $templates->{$name}->{api}); $line =~ s/\@API_IDX\@/$apis_toc/; } if ($line =~ /^

\n"; + print $output_file "\n"; $api_shown = 0; } if ($line =~ /`/) { } - print OUT "$line\n"; + print $output_file "$line\n"; } } - print OUT < - - -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"; + print $output_file + " ", + "", + ""; + 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 () { - print HACKOUT $last if ($doprint); + while (<$hack_input>) { + print $hack_output $last if ($doprint); $line++; s/^\/\/\/\///; - # Remove the junk wrapper generated by AgilityPack + # Remove the junk wrapper generated by AgilityPack. if ($line==1) { s///; } if (/