Home | History | Annotate | Line # | Download | only in man
      1 #!/usr/bin/perl -w
      2 
      3 # Generate a short man page from --help and --version output.
      4 # Copyright  1997, 1998, 1999, 2000 Free Software Foundation, Inc.
      5 
      6 # This program is free software; you can redistribute it and/or modify
      7 # it under the terms of the GNU General Public License as published by
      8 # the Free Software Foundation; either version 2, or (at your option)
      9 # any later version.
     10 
     11 # This program is distributed in the hope that it will be useful,
     12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14 # GNU General Public License for more details.
     15 
     16 # You should have received a copy of the GNU General Public License
     17 # along with this program; if not, write to the Free Software Foundation,
     18 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
     19 
     20 # Written by Brendan O'Dea <bod (at] compusol.com.au>
     21 # Available from ftp://ftp.gnu.org/gnu/help2man/
     22 
     23 use 5.004;
     24 use strict;
     25 use Getopt::Long;
     26 use Text::Tabs qw(expand);
     27 use POSIX qw(strftime setlocale LC_TIME);
     28 
     29 my $this_program = 'help2man';
     30 my $this_version = '1.24';
     31 my $version_info = <<EOT;
     32 GNU $this_program $this_version
     33 
     34 Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation, Inc.
     35 This is free software; see the source for copying conditions.  There is NO
     36 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
     37 
     38 Written by Brendan O'Dea <bod\@compusol.com.au>
     39 EOT
     40 
     41 my $help_info = <<EOT;
     42 `$this_program' generates a man page out of `--help' and `--version' output.
     43 
     44 Usage: $this_program [OPTION]... EXECUTABLE
     45 
     46  -n, --name=STRING       use `STRING' as the description for the NAME paragraph
     47  -s, --section=SECTION   use `SECTION' as the section for the man page
     48  -i, --include=FILE      include material from `FILE'
     49  -I, --opt-include=FILE  include material from `FILE' if it exists
     50  -o, --output=FILE       send output to `FILE'
     51  -N, --no-info           suppress pointer to Texinfo manual
     52      --help              print this help, then exit
     53      --version           print version number, then exit
     54 
     55 EXECUTABLE should accept `--help' and `--version' options.
     56 
     57 Report bugs to <bug-help2man\@gnu.org>.
     58 EOT
     59 
     60 my $section = 1;
     61 my ($opt_name, @opt_include, $opt_output, $opt_no_info);
     62 my %opt_def = (
     63     'n|name=s'		=> \$opt_name,
     64     's|section=s'	=> \$section,
     65     'i|include=s'	=> sub { push @opt_include, [ pop, 1 ] },
     66     'I|opt-include=s'	=> sub { push @opt_include, [ pop, 0 ] },
     67     'o|output=s'	=> \$opt_output,
     68     'N|no-info'		=> \$opt_no_info,
     69 );
     70 
     71 # Parse options.
     72 Getopt::Long::config('bundling');
     73 GetOptions (%opt_def,
     74     help    => sub { print $help_info; exit },
     75     version => sub { print $version_info; exit },
     76 ) or die $help_info;
     77 
     78 die $help_info unless @ARGV == 1;
     79 
     80 my %include = ();
     81 my %append = ();
     82 my @include = (); # retain order given in include file
     83 
     84 # Provide replacement `quote-regex' operator for pre-5.005.
     85 BEGIN { eval q(sub qr { '' =~ $_[0]; $_[0] }) if $] < 5.005 }
     86 
     87 # Process include file (if given).  Format is:
     88 #
     89 #   [section name]
     90 #   verbatim text
     91 #
     92 # or
     93 #
     94 #   /pattern/
     95 #   verbatim text
     96 #
     97 
     98 while (@opt_include)
     99 {
    100     my ($inc, $required) = @{shift @opt_include};
    101 
    102     next unless -f $inc or $required;
    103     die "$this_program: can't open `$inc' ($!)\n"
    104 	unless open INC, $inc;
    105 
    106     my $key;
    107     my $hash = \%include;
    108 
    109     while (<INC>)
    110     {
    111 	# [section]
    112 	if (/^\[([^]]+)\]/)
    113 	{
    114 	    $key = uc $1;
    115 	    $key =~ s/^\s+//;
    116 	    $key =~ s/\s+$//;
    117 	    $hash = \%include;
    118 	    push @include, $key unless $include{$key};
    119 	    next;
    120 	}
    121 
    122 	# /pattern/
    123 	if (m!^/(.*)/([ims]*)!)
    124 	{
    125 	    my $pat = $2 ? "(?$2)$1" : $1;
    126 
    127 	    # Check pattern.
    128 	    eval { $key = qr($pat) };
    129 	    if ($@)
    130 	    {
    131 		$@ =~ s/ at .*? line \d.*//;
    132 		die "$inc:$.:$@";
    133 	    }
    134 
    135 	    $hash = \%append;
    136 	    next;
    137 	}
    138 
    139 	# Check for options before the first section--anything else is
    140 	# silently ignored, allowing the first for comments and
    141 	# revision info.
    142 	unless ($key)
    143 	{
    144 	    # handle options
    145 	    if (/^-/)
    146 	    {
    147 		local @ARGV = split;
    148 		GetOptions %opt_def;
    149 	    }
    150 
    151 	    next;
    152 	}
    153 
    154 	$hash->{$key} ||= '';
    155 	$hash->{$key} .= $_;
    156     }
    157 
    158     close INC;
    159 
    160     die "$this_program: no valid information found in `$inc'\n"
    161 	unless $key;
    162 }
    163 
    164 # Compress trailing blank lines.
    165 for my $hash (\(%include, %append))
    166 {
    167     for (keys %$hash) { $hash->{$_} =~ s/\n+$/\n/ }
    168 }
    169 
    170 # Turn off localisation of executable's ouput.
    171 @ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
    172 
    173 # Turn off localisation of date (for strftime).
    174 setlocale LC_TIME, 'C';
    175 
    176 # Grab help and version info from executable.
    177 my ($help_text, $version_text) = map {
    178     join '', map { s/ +$//; expand $_ } `$ARGV[0] --$_ 2>/dev/null`
    179 	or die "$this_program: can't get `--$_' info from $ARGV[0]\n"
    180 } qw(help version);
    181 
    182 my $date = strftime "%B %Y", localtime;
    183 (my $program = $ARGV[0]) =~ s!.*/!!;
    184 my $package = $program;
    185 my $version;
    186 
    187 if ($opt_output)
    188 {
    189     unlink $opt_output
    190 	or die "$this_program: can't unlink $opt_output ($!)\n"
    191 	if -e $opt_output;
    192 
    193     open STDOUT, ">$opt_output"
    194 	or die "$this_program: can't create $opt_output ($!)\n";
    195 }
    196 
    197 # The first line of the --version information is assumed to be in one
    198 # of the following formats:
    199 #
    200 #   <version>
    201 #   <program> <version>
    202 #   {GNU,Free} <program> <version>
    203 #   <program> ({GNU,Free} <package>) <version>
    204 #   <program> - {GNU,Free} <package> <version>
    205 #
    206 # and seperated from any copyright/author details by a blank line.
    207 
    208 ($_, $version_text) = split /\n+/, $version_text, 2;
    209 
    210 if (/^(\S+) +\(((?:GNU|Free) +[^)]+)\) +(.*)/ or
    211     /^(\S+) +- *((?:GNU|Free) +\S+) +(.*)/)
    212 {
    213     $program = $1;
    214     $package = $2;
    215     $version = $3;
    216 }
    217 elsif (/^((?:GNU|Free) +)?(\S+) +(.*)/)
    218 {
    219     $program = $2;
    220     $package = $1 ? "$1$2" : $2;
    221     $version = $3;
    222 }
    223 else
    224 {
    225     $version = $_;
    226 }
    227 
    228 $program =~ s!.*/!!;
    229 
    230 # No info for `info' itself.
    231 $opt_no_info = 1 if $program eq 'info';
    232 
    233 # --name overrides --include contents.
    234 $include{NAME} = "$program \\- $opt_name\n" if $opt_name;
    235 
    236 # Default (useless) NAME paragraph.
    237 $include{NAME} ||= "$program \\- manual page for $program $version\n";
    238 
    239 # Man pages traditionally have the page title in caps.
    240 my $PROGRAM = uc $program;
    241 
    242 # Extract usage clause(s) [if any] for SYNOPSIS.
    243 if ($help_text =~ s/^Usage:( +(\S+))(.*)((?:\n(?: {6}\1| *or: +\S).*)*)//m)
    244 {
    245     my @syn = $2 . $3;
    246 
    247     if ($_ = $4)
    248     {
    249 	s/^\n//;
    250 	for (split /\n/) { s/^ *(or: +)?//; push @syn, $_ }
    251     }
    252 
    253     my $synopsis = '';
    254     for (@syn)
    255     {
    256 	$synopsis .= ".br\n" if $synopsis;
    257 	s!^\S*/!!;
    258 	s/^(\S+) *//;
    259 	$synopsis .= ".B $1\n";
    260 	s/\s+$//;
    261 	s/(([][]|\.\.+)+)/\\fR$1\\fI/g;
    262 	s/^/\\fI/ unless s/^\\fR//;
    263 	$_ .= '\fR';
    264 	s/(\\fI)( *)/$2$1/g;
    265 	s/\\fI\\fR//g;
    266 	s/^\\fR//;
    267 	s/\\fI$//;
    268 	s/^\./\\&./;
    269 
    270 	$synopsis .= "$_\n";
    271     }
    272 
    273     $include{SYNOPSIS} ||= $synopsis;
    274 }
    275 
    276 # Process text, initial section is DESCRIPTION.
    277 my $sect = 'DESCRIPTION';
    278 $_ = "$help_text\n\n$version_text";
    279 
    280 # Normalise paragraph breaks.
    281 s/^\n+//;
    282 s/\n*$/\n/;
    283 s/\n\n+/\n\n/g;
    284 
    285 # Temporarily exchange leading dots, apostrophes and backslashes for
    286 # tokens.
    287 s/^\./\x80/mg;
    288 s/^'/\x81/mg;
    289 s/\\/\x82/g;
    290 
    291 # Start a new paragraph (if required) for these.
    292 s/([^\n])\n(Report +bugs|Email +bug +reports +to|Written +by)/$1\n\n$2/g;
    293 
    294 sub convert_option;
    295 
    296 while (length)
    297 {
    298     # Convert some standard paragraph names.
    299     if (s/^(Options|Examples): *\n//)
    300     {
    301 	$sect = uc $1;
    302 	next;
    303     }
    304 
    305     # Copyright section
    306     if (/^Copyright +[(\xa9]/)
    307     {
    308 	$sect = 'COPYRIGHT';
    309 	$include{$sect} ||= '';
    310 	$include{$sect} .= ".PP\n" if $include{$sect};
    311 
    312 	my $copy;
    313 	($copy, $_) = split /\n\n/, $_, 2;
    314 
    315 	for ($copy)
    316 	{
    317 	    # Add back newline
    318 	    s/\n*$/\n/;
    319 
    320 	    # Convert iso9959-1 copyright symbol or (c) to nroff
    321 	    # character.
    322 	    s/^Copyright +(?:\xa9|\([Cc]\))/Copyright \\(co/mg;
    323 
    324 	    # Insert line breaks before additional copyright messages
    325 	    # and the disclaimer.
    326 	    s/(.)\n(Copyright |This +is +free +software)/$1\n.br\n$2/g;
    327 
    328 	    # Join hyphenated lines.
    329 	    s/([A-Za-z])-\n */$1/g;
    330 	}
    331 
    332 	$include{$sect} .= $copy;
    333 	$_ ||= '';
    334 	next;
    335     }
    336 
    337     # Catch bug report text.
    338     if (/^(Report +bugs|Email +bug +reports +to) /)
    339     {
    340 	$sect = 'REPORTING BUGS';
    341     }
    342 
    343     # Author section.
    344     elsif (/^Written +by/)
    345     {
    346 	$sect = 'AUTHOR';
    347     }
    348 
    349     # Examples, indicated by an indented leading $, % or > are
    350     # rendered in a constant width font.
    351     if (/^( +)([\$\%>] )\S/)
    352     {
    353 	my $indent = $1;
    354 	my $prefix = $2;
    355 	my $break = '.IP';
    356 	$include{$sect} ||= '';
    357 	while (s/^$indent\Q$prefix\E(\S.*)\n*//)
    358 	{
    359 	    $include{$sect} .= "$break\n\\f(CW$prefix$1\\fR\n";
    360 	    $break = '.br';
    361 	}
    362 
    363 	next;
    364     }
    365 
    366     my $matched = '';
    367     $include{$sect} ||= '';
    368 
    369     # Sub-sections have a trailing colon and the second line indented.
    370     if (s/^(\S.*:) *\n / /)
    371     {
    372 	$matched .= $& if %append;
    373 	$include{$sect} .= qq(.SS "$1"\n);
    374     }
    375 
    376     my $indent = 0;
    377     my $content = '';
    378 
    379     # Option with description.
    380     if (s/^( {1,10}([+-]\S.*?))(?:(  +)|\n( {20,}))(\S.*)\n//)
    381     {
    382 	$matched .= $& if %append;
    383 	$indent = length ($4 || "$1$3");
    384 	$content = ".TP\n\x83$2\n\x83$5\n";
    385 	unless ($4)
    386 	{
    387 	    # Indent may be different on second line.
    388 	    $indent = length $& if /^ {20,}/;
    389 	}
    390     }
    391 
    392     # Option without description.
    393     elsif (s/^ {1,10}([+-]\S.*)\n//)
    394     {
    395 	$matched .= $& if %append;
    396 	$content = ".HP\n\x83$1\n";
    397 	$indent = 80; # not continued
    398     }
    399 
    400     # Indented paragraph with tag.
    401     elsif (s/^( +(\S.*?)  +)(\S.*)\n//)
    402     {
    403 	$matched .= $& if %append;
    404 	$indent = length $1;
    405 	$content = ".TP\n\x83$2\n\x83$3\n";
    406     }
    407 
    408     # Indented paragraph.
    409     elsif (s/^( +)(\S.*)\n//)
    410     {
    411 	$matched .= $& if %append;
    412 	$indent = length $1;
    413 	$content = ".IP\n\x83$2\n";
    414     }
    415 
    416     # Left justified paragraph.
    417     else
    418     {
    419 	s/(.*)\n//;
    420 	$matched .= $& if %append;
    421 	$content = ".PP\n" if $include{$sect};
    422 	$content .= "$1\n";
    423     }
    424 
    425     # Append continuations.
    426     while (s/^ {$indent}(\S.*)\n//)
    427     {
    428 	$matched .= $& if %append;
    429 	$content .= "\x83$1\n"
    430     }
    431 
    432     # Move to next paragraph.
    433     s/^\n+//;
    434 
    435     for ($content)
    436     {
    437 	# Leading dot and apostrophe protection.
    438 	s/\x83\./\x80/g;
    439 	s/\x83'/\x81/g;
    440 	s/\x83//g;
    441 
    442 	# Convert options.
    443 	s/(^| )(-[][\w=-]+)/$1 . convert_option $2/mge;
    444     }
    445 
    446     # Check if matched paragraph contains /pat/.
    447     if (%append)
    448     {
    449 	for my $pat (keys %append)
    450 	{
    451 	    if ($matched =~ $pat)
    452 	    {
    453 		$content .= ".PP\n" unless $append{$pat} =~ /^\./;
    454 		$content .= $append{$pat};
    455 	    }
    456 	}
    457     }
    458 
    459     $include{$sect} .= $content;
    460 }
    461 
    462 # Refer to the real documentation.
    463 unless ($opt_no_info)
    464 {
    465     $sect = 'SEE ALSO';
    466     $include{$sect} ||= '';
    467     $include{$sect} .= ".PP\n" if $include{$sect};
    468     $include{$sect} .= <<EOT;
    469 The full documentation for
    470 .B $program
    471 is maintained as a Texinfo manual.  If the
    472 .B info
    473 and
    474 .B $program
    475 programs are properly installed at your site, the command
    476 .IP
    477 .B info $program
    478 .PP
    479 should give you access to the complete manual.
    480 EOT
    481 }
    482 
    483 # Output header.
    484 print <<EOT;
    485 .\\" DO NOT MODIFY THIS FILE!  It was generated by $this_program $this_version.
    486 .TH $PROGRAM "$section" "$date" "$package $version" GNU
    487 EOT
    488 
    489 # Section ordering.
    490 my @pre = qw(NAME SYNOPSIS DESCRIPTION OPTIONS EXAMPLES);
    491 my @post = ('AUTHOR', 'REPORTING BUGS', 'COPYRIGHT', 'SEE ALSO');
    492 my $filter = join '|', @pre, @post;
    493 
    494 # Output content.
    495 for (@pre, (grep ! /^($filter)$/o, @include), @post)
    496 {
    497     if ($include{$_})
    498     {
    499 	my $quote = /\W/ ? '"' : '';
    500 	print ".SH $quote$_$quote\n";
    501 
    502 	for ($include{$_})
    503 	{
    504 	    # Replace leading dot, apostrophe and backslash tokens.
    505 	    s/\x80/\\&./g;
    506 	    s/\x81/\\&'/g;
    507 	    s/\x82/\\e/g;
    508 	    print;
    509 	}
    510     }
    511 }
    512 
    513 exit;
    514 
    515 # Convert option dashes to \- to stop nroff from hyphenating 'em, and
    516 # embolden.  Option arguments get italicised.
    517 sub convert_option
    518 {
    519     local $_ = '\fB' . shift;
    520 
    521     s/-/\\-/g;
    522     unless (s/\[=(.*)\]$/\\fR[=\\fI$1\\fR]/)
    523     {
    524 	s/=(.)/\\fR=\\fI$1/;
    525 	s/ (.)/ \\fI$1/;
    526 	$_ .= '\fR';
    527     }
    528 
    529     $_;
    530 }
    531