MD to HTML: allow extra header stuff
[openssl-web.git] / bin / mk-manpages
1 #! /usr/bin/perl
2
3 package Local::MkManPages;
4
5 use strict;
6 use warnings;
7 use File::Basename qw(basename);
8 use File::Spec ();
9 use Getopt::Long qw(GetOptionsFromArray);
10 use Pod::Usage qw(pod2usage);
11 use Pod::Simple::XHTML;
12
13 __PACKAGE__->run(@ARGV);
14
15 sub Releases    { return (qw(master 1.1.1 1.1.0 1.0.2)) }
16 sub Dirs        { return (qw(apps crypto ssl
17                              man1 man3 man5 man7)) }
18 sub Dir2Section { return ( apps => "man1", crypto => "man3", ssl => "man3" ) }
19
20 sub getRelease {
21     my ( $class, $ver ) = @_;
22     my %known = map { $_ => 1 } $class->Releases;
23     return @_ != 2 ? %known : defined $known{$ver} ? $ver : undef;
24 }
25
26 sub run {
27     my ( $class, @argv ) = @_;
28     my $opt = $class->process_options(@argv);
29     $class->cleanup( $opt->{WwwDir}, $opt->{RelVer} );
30     exit $class->main( $opt->{SrcDir}, $opt->{WwwDir}, $opt->{RelVer} );
31 }
32
33 sub main {
34     my ( $class, $srcdir, $wwwdir, $release ) = @_;
35
36     foreach my $subdir ( $class->Dirs ) {
37         my $dir = File::Spec->catfile( $srcdir, $subdir );
38         if ( opendir( my $dh, $dir ) ) {
39             while ( my $ent = readdir($dh) ) {
40                 next if $ent =~ /^\./;
41                 next if $ent !~ /\.pod$/;
42
43                 my $origbase = basename( $ent, ".pod" );
44                 my $title = $origbase;
45                 my $tmp_sect = { $class->Dir2Section }->{$subdir} // $subdir;
46                 (my $tmp_sectnum = $tmp_sect) =~ s|^man||;
47                 # In addition to what getdata() gives us, we add a few
48                 # defaults of our own:
49                 #
50                 #       release => "..."        # 
51                 #       subdir  => "..."        # The original subdir
52                 #       sect    => "..."        # Output section subdir
53                 #       sectnum => n            # Default section number
54                 #
55                 my %data = (
56                     subdir   => $subdir,
57                     sect     => $tmp_sect,
58                     sectnum  => $tmp_sectnum,
59                     $class->getdata( File::Spec->catfile ( $dir, $ent ) )
60                 );
61                 # These are for display
62                 my $podfile = File::Spec->catfile( $subdir, $ent );
63                 my $incfile = File::Spec->catfile( $data{sect},
64                                                    "$origbase.inc" );
65                 # These are files we're actually manipulating
66                 my $inpod = File::Spec->catfile( $srcdir, $podfile );
67                 my $outinc = File::Spec->catfile( $wwwdir, $incfile );
68
69                 # Get main HTML output
70                 my $out = $class->geninc( $release, $inpod, %data );
71                 open( my $fh, ">", $outinc )
72                     or $class->die("Can't open $outinc: $!");
73                 print $fh $out or $class->die("Can't print $outinc: $!");
74                 close($fh) or $class->die("Can't close $outinc: $!");
75
76                 foreach my $htmlname (@{$data{names}}) {
77                     my $htmlfile = File::Spec->catdir( $data{sect},
78                                                        "$htmlname.html" );
79                     my $outhtml = File::Spec->catfile( $wwwdir, $htmlfile );
80                     $out = $class->genhtml( $release, $title, $origbase,
81                                             $htmlname, %data );
82                     open( $fh, ">", $outhtml )
83                         or $class->die("Can't open $outhtml: $!");
84                     print $fh $out or $class->die("Can't print $outhtml: $!");
85                     close($fh) or $class->die("Can't close $outhtml: $!");
86                 }
87             }
88         }
89     }
90 }
91
92 # Generate manpag HTML wrapper
93 sub genhtml {
94     my ( $class, $release, $title, $origbase, $htmlbase, %data ) = @_;
95     return <<EOH;
96 <!DOCTYPE html>
97 <html lang="en">
98 <!-- OSSL: original subdir: $data{subdir} -->
99 <!-- OSSL: subdir: $data{sect} -->
100 <!-- OSSL: section: $data{sectnum} -->
101 <!-- OSSL: description: $data{description} -->
102 <!--#include virtual="/inc/head.shtml" -->
103 <body>
104 <!--#include virtual="/inc/banner.shtml" -->
105   <div id="main">
106     <div id="content">
107       <div class="blog-index">
108         <article>
109           <header><h2>$title</h2></header>
110           <div class="entry-content">
111             <p>
112 <!--#include virtual="$origbase.inc" -->
113             </p>
114           </div>
115           <footer>
116             You are here: <a href="/">Home</a>
117             : <a href="/docs">Docs</a>
118             : <a href="/docs/manpages.html">Manpages</a>
119             : <a href="/docs/man$release/">$release</a>
120             : <a href="/docs/man$release/$data{sect}">$data{sect}</a>
121             : <a href="/docs/man$release/$data{sect}/$htmlbase.html">$htmlbase</a>
122             <br/><a href="/sitemap.txt">Sitemap</a>
123           </footer>
124         </article>
125       </div>
126       <aside class="sidebar">
127         <section>
128           <h1><a href="/docs/man$release/">$release manpages</a></h1>
129           <ul>
130             <li><a href="../man1">Commands</a></li>
131             <li><a href="../man3">Libraries</a></li>
132             <li><a href="../man5">File Formats</a></li>
133             <li><a href="../man7">Overviews</a></li>
134           </ul>
135         </section>
136 <!--#include virtual="$htmlbase.cross" -->
137       </aside>
138     </div>
139   </div>
140 <!--#include virtual="/inc/footer.shtml" -->
141 </body>
142 </html>
143 EOH
144 }
145
146 # Generate manpage content
147 sub geninc {
148     my ( $class, $release, $filename, %data ) = @_;
149
150     open( my $fh, $filename ) or $class->die("Can't open $filename: $!");
151     my $infile = do { local $/; <$fh>; };
152     close( $fh );
153
154     # L<asdf...|qwer...> ==> L<qwer>
155     $infile =~ s/L<[^|>]*\|([^>]+)>/L<$1>/g;
156
157     # L<asdf(x)> --> L<asdf>
158     $infile =~ s/L<([^>]+)\(\d\)>/L<$1>/g;
159
160     my $out;
161     my $pod = Pod::Simple::XHTML->new;
162     $pod->html_h_level(3);
163     $pod->perldoc_url_prefix("/docs/man$release/$data{sect}/");
164     $pod->perldoc_url_postfix(".html");
165     $pod->man_url_prefix("/docs/man$release/$data{sect}/");
166     $pod->man_url_postfix(".html");
167     $pod->html_header('');
168     $pod->html_footer('');
169     $pod->output_string( \$out );
170     $pod->parse_string_document($infile);
171     return $out;
172 }
173
174 # Return diverse data from a manpage if available, currently:
175 # {
176 #    names       => [ ... ]             # list of all OTHER names
177 #    description => "text"              # the short description from NAME
178 #    section     => n                   # the section number
179 # }
180 sub getdata {
181     my ( $class, $infile ) = @_;
182     my %data = ();
183     open( my $fh, "<", $infile ) or $class->die("Can't open $infile: $!");
184     {
185         local $/ = "";
186         my $foundname = 0;
187         while (<$fh>) {
188             chop;
189             s/\n/ /gm;
190             if (/^=for comment openssl_manual_section:\s*(\d+)/) {
191                 $data{sectnum} = "$1";
192                 $data{sect} = "man$1";
193             }
194             elsif (/^=head1\s/) {
195                 $foundname = 0;
196             }
197             elsif ($foundname) {
198                 if (/ - /) {
199                     $data{description} = $';
200                     $_ = $`;
201                     s/,\s+/,/g;
202                     s/\s+,/,/g;
203                     s/^\s+//g;
204                     s/\s+$//g;
205                     s/\s/_/g;
206                     push @{$data{names}}, split ',';
207                 }
208             }
209             if (/^=head1\s+NAME\s*$/) {
210                 $foundname = 1;
211             }
212         }
213     }
214     return %data;
215 }
216
217 sub die {
218     my $class = shift;
219     $class->error(@_);
220     exit(2);
221 }
222
223 sub error {
224     my $class = shift;
225     my $prog  = basename($0);
226     warn("$prog: $_\n") for @_;
227 }
228
229 # Remove all files from a manpage subtree, and leave only
230 # the index and the section subdirs.
231 sub cleanup {
232     my ( $class, $wwwdir, $release ) = @_;
233     my $idx = File::Spec->catfile( $wwwdir, "index.html" );
234
235     if ( !-d $wwwdir ) {
236         mkdir($wwwdir) or $class->die("mkdir '$wwwdir': $!");
237     }
238
239     # TBD: was $class->die
240     $class->error("No $idx") unless ( -f $idx );
241     foreach my $subdir ( $class->Dirs ) {
242         my $realsubdir = { $class->Dir2Section }->{$subdir} // $subdir;
243         my $realsdir = File::Spec->catfile( $wwwdir, $realsubdir );
244         if ( !-d $realsdir ) {
245             mkdir($realsdir) or $class->die("mkdir '$realsdir': $!");
246             next;
247         }
248
249         my $sdir = File::Spec->catfile( $wwwdir, $subdir );
250         if ( -d $sdir ) {
251             opendir( my $dh, $sdir ) or $class->die("opendir '$sdir': $!");
252             while ( my $ent = readdir($dh) ) {
253                 next if $ent =~ /^\./;
254                 next if $realsdir eq $sdir && $ent =~ /^index.(?:html|inc)$/;
255                 my $f = File::Spec->catfile( $sdir, $ent );
256                 unlink($f) or $class->error("Can't unlink '$f': $!");
257             }
258             closedir( $dh );
259
260             if ( $realsubdir ne $subdir ) {
261                 rmdir $sdir;
262             }
263         }
264     }
265 }
266
267 sub process_options {
268     my ( $class, @argv ) = @_;
269     my %opt;
270
271     GetOptionsFromArray( \@argv, \%opt, "help", "man" )
272       or pod2usage( -verbose => 0 );
273
274     pod2usage( -verbose => 1 ) if ( $opt{help} or @argv != 3 );
275     pod2usage( -verbose => 2 ) if ( $opt{man} );
276
277     # <src/dir> <rel.ver> <www/dir>
278     my @argkeys = qw(SrcDir RelVer WwwDir);
279     @opt{@argkeys} = @argv;
280
281     # no empty values, directories must exist
282     my @err;
283     foreach my $key (@argkeys) {
284         push( @err, "Invalid $key argument '$opt{$key}'" )
285           if ( $opt{$key} =~ /^\s*$/ );
286         push( @err, "Directory '$opt{$key}': $!" )
287           if ( $key =~ /Dir$/ and !-d $opt{$key} );
288     }
289     $class->die(@err) if @err;
290
291     # each source dir has a set of subdirs with documentation
292     my @found_dirs = ();
293     my $docdir = File::Spec->catfile( $opt{SrcDir} );
294     foreach my $subdir ( $class->Dirs ) {
295         my $dir = File::Spec->catfile( $docdir, $subdir );
296         push @found_dirs, $dir if -d $dir;
297     }
298     push( @err, "No documentation directories in $docdir" )
299         unless ( @found_dirs );
300
301     # verify release
302     push( @err, "Unknown release '$opt{RelVer}'" )
303       unless ( $class->getRelease( $opt{RelVer} ) );
304     $class->die(@err) if @err;
305
306     return \%opt;
307 }
308
309 __END__
310
311 =pod
312
313 =head1 NAME
314
315 mk-manpages - htmlize man pages from POD for the OpenSSL website
316
317 =head1 SYNOPSIS
318
319 mk-manpages [options] <SrcDir> <RelVer> <WwwDir>
320
321   <SrcDir>   doc directory of release <RelVer>, example 'OpenSSL_1_0_2-stable/doc'
322   <RelVer>   version number associated with <SrcDir>, example '1.0.2'
323   <WwwDir>   top level directory beneath which generated html is stored, example 'web'
324
325     --help    display a brief help message
326     --man     display full documentation
327
328 =head1 DESCRIPTION
329
330 This utility is run on a web server generate the htmlized version of
331 OpenSSL documentation from the original POD.  The resultant directory
332 structure may look something like the following (where the contents of
333 index.html do not come from this tool):
334
335  $ ls some/path/to/web
336  man1.0.2    man1.1.0    manmaster
337  $ ls some/path/to/web/man1.0.2
338  apps        crypto      index.html  ssl
339  $ ls some/path/to/web/man1.0.2/apps
340  CA.pl.html
341  asn1parse.html
342  c_rehash.html
343  ...
344
345 =cut