#!/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*$/) { $_ = '
' 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+)}{$1}g;
s{\\a +(\w+)}{$1}g;
s{\\e +(\w+)}{$1}g;
s{\\em +(\w+)}{$1}g;
# Constants
s{NULL}{NULL
}g;
s{TRUE}{TRUE
}g;
s{FALSE}{FALSE
}g;
# Parameters
warn "$file_path:$current_line: Old-style monodoc notation '\@param' used\n"
if s{@(\w+)}{$1}g && $WARNINGS;
s{\\p +(\w+)}{$1}g;
# Code
warn "$file_path:$current_line: Old-style monodoc notation '#code' used\n"
if s{#(\w+)}{$1
}g && $WARNINGS;
warn "$file_path:$current_line: Old-style monodoc notation '`code`' used\n"
if s{\`((?!api:)[:.\w\*]+)\`}{$1
}g && $WARNINGS;
s{\\c +(\S+(?$1}g;
s{\\code}{
}g;
s{\\endcode}{
}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 <$api$strikeextra
EOF if (exists ($docs->{parameters}->{$api})) { my $ppars = $docs->{parameters}->{$api}; if (@$ppars) { print $output_file "