e46a49ecf1
Former-commit-id: d0813289fa2d35e1f8ed77530acb4fb1df441bc0
519 lines
15 KiB
Perl
519 lines
15 KiB
Perl
#!/usr/bin/perl
|
|
|
|
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;
|
|
}
|
|
|
|
#
|
|
# 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 = '';
|
|
my $codeblock = 'false';
|
|
|
|
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, $.);
|
|
}
|
|
|
|
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/^ +\*//;
|
|
|
|
if (/\s*\\code$/) {
|
|
$codeblock = 'true';
|
|
} elsif (/\s*\\endcode$/) {
|
|
$codeblock = 'false';
|
|
}
|
|
|
|
# Replace blank lines with paragraph breaks if we're not in a code block.
|
|
if (/^\s*$/) {
|
|
$_ = '<p>' if $codeblock eq 'false';
|
|
}
|
|
|
|
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$_";
|
|
}
|
|
} elsif ($section == $RETURN_SECTION) {
|
|
$returns .= "\n\t$_";
|
|
} else {
|
|
die "Invalid section $section\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# Substitute formatting within documentation text.
|
|
#
|
|
sub process_formatting {
|
|
my ($content, $file_path, $current_line) = @_;
|
|
$_ = $$content;
|
|
|
|
# General formatting
|
|
s{\\b +(\w+)}{<strong>$1</strong>}g;
|
|
s{\\a +(\w+)}{<i>$1</i>}g;
|
|
s{\\e +(\w+)}{<i>$1</i>}g;
|
|
s{\\em +(\w+)}{<i>$1</i>}g;
|
|
|
|
# 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;
|
|
s{\\code}{<pre><code class="mapi-codeblock">}g;
|
|
s{\\endcode}{</code></pre>}g;
|
|
|
|
$$content = $_;
|
|
}
|
|
|
|
#
|
|
# 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">
|
|
$stylesheet
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="mapi-docs">
|
|
EOF
|
|
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
|
|
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>";
|
|
}
|
|
}
|
|
|
|
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 ($docs, $apis_listed) = @_;
|
|
my $type_size = 0;
|
|
my $name_size = 0;
|
|
my ($ret, $xname, $args);
|
|
my $apis_toc = "";
|
|
|
|
# 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 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;
|
|
}
|
|
|
|
sub wrap {
|
|
my ($args, $size, $limit) = @_;
|
|
my $sret = "";
|
|
|
|
# return $args if ((length (args) + size) < $limit);
|
|
|
|
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 ($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
|