#! /usr/bin/perl package Local::MkManPages; use strict; use warnings; use File::Basename qw(basename); use File::Spec (); use Getopt::Long qw(GetOptionsFromArray); use Pod::Usage qw(pod2usage); use Pod::Simple::XHTML; __PACKAGE__->run(@ARGV); sub Releases { return (qw(1.1.1 master)); } sub Sections { return (qw(man1 man3 man5 man7)); } sub getRelease { my ( $class, $ver ) = @_; my %known = map { $_ => 1 } $class->Releases; return @_ != 2 ? %known : defined $known{$ver} ? $ver : undef; } sub run { my ( $class, @argv ) = @_; my $opt = $class->process_options(@argv); $class->cleanup( $opt->{WwwDir}, $opt->{RelVer} ); exit $class->main( $opt->{SrcDir}, $opt->{WwwDir}, $opt->{RelVer} ); } sub main { my ( $class, $srcdir, $wwwdir, $release ) = @_; foreach my $sect ( $class->Sections ) { my $dir = File::Spec->catfile( $srcdir, "doc", $sect ); opendir( my $dh, $dir ) or $class->die("opendir '$dir': $!"); while ( my $ent = readdir($dh) ) { next if $ent =~ /^\./; next if $ent !~ /\.pod$/; my $filename = File::Spec->catfile( $dir, $ent ); my $basename = basename( $ent, ".pod" ); my $title = $basename; my $out = $class->genhtml( $release, $sect, $filename, $title, $basename ); my $outfile = File::Spec->catfile( $wwwdir, "man$release", $sect, "$basename.html" ); open( my $fh, ">", $outfile ) or $class->die("Can't open $outfile: $!"); print $fh $out or $class->die("Can't print $outfile: $!"); close($fh) or $class->die("Can't close $outfile: $!"); my @altnames = $class->getnames( $filename, $basename ); foreach my $alt (@altnames) { my $target = File::Spec->catfile( $wwwdir, "man$release", $sect, "$alt.html" ); if ( ! -f $target ) { link( $outfile, $target ) or $class->die("Can't link $outfile to $target: $!"); } } } } } # Generate a manpage sub genhtml { my ( $class, $release, $section, $filename, $title, $file ) = @_; my $header = $class->htmlHeader($title); my $footer = $class->htmlFooter( $release, $section, $file ); open( my $fh, $filename ) || $class->die("Can't open $filename: $!"); my $infile = do { local $/; <$fh>; }; my $out; my $pod = Pod::Simple::XHTML->new; $pod->html_h_level(3); $pod->perldoc_url_prefix("/docs/man$release/man"); $pod->perldoc_url_postfix(".html"); $pod->man_url_prefix("/docs/man$release/man"); $pod->man_url_postfix(".html"); $pod->html_header($header); $pod->html_footer($footer); $pod->output_string( \$out ); $pod->parse_string_document($infile); return $out; } # Return all the OTHER names in a manpage sub getnames { my ( $class, $infile, $basename ) = @_; my @words = (); open( my $fh, "<", $infile ) or $class->die("Can't open $infile: $!"); { local $/ = ""; my $found = 0; while (<$fh>) { chop; s/\n/ /gm; if (/^=head1 /) { $found = 0; } elsif ($found) { if (/ - /) { s/ - .*//; s/,\s+/,/g; s/\s+,/,/g; s/^\s+//g; s/\s+$//g; s/\s/_/g; push @words, split ','; } } if (/^=head1\s*NAME\s*$/) { $found = 1; } } } return grep { $_ ne $basename } @words; } sub die { my $class = shift; $class->error(@_); exit(2); } sub error { my $class = shift; my $prog = basename($0); warn("$prog: $_\n") for @_; } # Remove all files from a manpage subtree, and leave only # the index and the section subdirs. sub cleanup { my ( $class, $wwwdir, $release ) = @_; my $dir = File::Spec->catfile( $wwwdir, "man$release" ); my $idx = File::Spec->catfile( $dir, "index.html" ); if ( !-d $dir ) { mkdir($dir) or $class->die("mkdir '$dir': $!"); } # TBD: was $class->die $class->error("No $idx") unless ( -f $idx ); foreach my $sect ( $class->Sections ) { my $sdir = File::Spec->catfile( $dir, $sect ); if ( !-d $sdir ) { mkdir($sdir) or $class->die("mkdir '$sdir': $!"); next; } opendir( my $dh, $sdir ) or $class->die("opendir '$sdir': $!"); while ( my $ent = readdir($dh) ) { next if $ent =~ /^\./; next if $ent =~ /^index.(?:html|inc)$/; my $f = File::Spec->catfile( $sdir, $ent ); unlink($f) or $class->error("Can't unlink '$f': $!"); } } } sub process_options { my ( $class, @argv ) = @_; my %opt; GetOptionsFromArray( \@argv, \%opt, "help", "man" ) or pod2usage( -verbose => 0 ); pod2usage( -verbose => 1 ) if ( $opt{help} or @argv != 3 ); pod2usage( -verbose => 2 ) if ( $opt{man} ); # my @argkeys = qw(SrcDir RelVer WwwDir); @opt{@argkeys} = @argv; # no empty values, directories must exist my @err; foreach my $key (@argkeys) { push( @err, "Invalid $key argument '$opt{$key}'" ) if ( $opt{$key} =~ /^\s*$/ ); push( @err, "Directory '$opt{$key}': $!" ) if ( $key =~ /Dir$/ and !-d $opt{$key} ); } $class->die(@err) if @err; # each source dir has a set of subdirs with documentation foreach my $sect ( $class->Sections ) { my $dir = File::Spec->catfile( $opt{SrcDir}, "doc", $sect ); push( @err, "No directory '$dir'" ) unless ( -d $dir ); } # verify release push( @err, "Unknown release '$opt{RelVer}'" ) unless ( $class->getRelease( $opt{RelVer} ) ); $class->die(@err) if @err; return \%opt; } sub htmlHeader { my ( $class, $title ) = @_; return <

$title

EOFH } # note: links could be bogus if file DNE in one of the other releases sub htmlSidebar { my ( $class, $release, $section, $file ) = @_; my $lirel = ""; foreach my $v ( grep { $release ne $_ } $class->Releases ) { $lirel .= "\n

  • $v version
  • "; } return <

    $release manpages

    EOS } sub htmlFooter { my ( $class, $release, $section, $file ) = @_; my $sidebar = $class->htmlSidebar( $release, $section, $file ); return <
    $sidebar
    EOFT } __END__ =pod =head1 NAME mk-manpages - htmlize man pages from POD for the OpenSSL website =head1 SYNOPSIS mk-manpages [options] top level directory of release , example 'OpenSSL_1_0_2-stable' version number associated with , example '1.0.2' top level directory beneath which generated html is stored, example 'web' --help display a brief help message --man display full documentation =head1 DESCRIPTION This utility is run on a web server generate the htmlized version of OpenSSL documentation from the original POD. The resultant directory structure may look something like the following (where the contents of index.html do not come from this tool): $ ls some/path/to/web man1.0.2 man1.1.0 manmaster $ ls some/path/to/web/man1.0.2 apps crypto index.html ssl $ ls some/path/to/web/man1.0.2/apps CA.pl.html asn1parse.html c_rehash.html ... =cut