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