1#!/usr/local/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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 19 20# Written by Brendan O'Dea <bod@compusol.com.au> 21# Available from ftp://ftp.gnu.org/gnu/help2man/ 22 23use 5.004; 24use strict; 25use Getopt::Long; 26use Text::Tabs qw(expand); 27use POSIX qw(strftime setlocale LC_TIME); 28 29my $this_program = 'help2man'; 30my $this_version = '1.24'; 31my $version_info = <<EOT; 32GNU $this_program $this_version 33 34Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation, Inc. 35This is free software; see the source for copying conditions. There is NO 36warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 37 38Written by Brendan O'Dea <bod\@compusol.com.au> 39EOT 40 41my $help_info = <<EOT; 42`$this_program' generates a man page out of `--help' and `--version' output. 43 44Usage: $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 55EXECUTABLE should accept `--help' and `--version' options. 56 57Report bugs to <bug-help2man\@gnu.org>. 58EOT 59 60my $section = 1; 61my ($opt_name, @opt_include, $opt_output, $opt_no_info); 62my %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. 72Getopt::Long::config('bundling'); 73GetOptions (%opt_def, 74 help => sub { print $help_info; exit }, 75 version => sub { print $version_info; exit }, 76) or die $help_info; 77 78die $help_info unless @ARGV == 1; 79 80my %include = (); 81my %append = (); 82my @include = (); # retain order given in include file 83 84# Provide replacement `quote-regex' operator for pre-5.005. 85BEGIN { 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 98while (@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. 165for my $hash (\(%include, %append)) 166{ 167 for (keys %$hash) { $hash->{$_} =~ s/\n+$/\n/ } 168} 169 170# Turn off localisation of executable's output. 171@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3; 172 173# Turn off localisation of date (for strftime). 174setlocale LC_TIME, 'C'; 175 176# Grab help and version info from executable. 177my ($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 182my $date = strftime "%B %Y", localtime; 183(my $program = $ARGV[0]) =~ s!.*/!!; 184my $package = $program; 185my $version; 186 187if ($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 210if (/^(\S+) +\(((?:GNU|Free) +[^)]+)\) +(.*)/ or 211 /^(\S+) +- *((?:GNU|Free) +\S+) +(.*)/) 212{ 213 $program = $1; 214 $package = $2; 215 $version = $3; 216} 217elsif (/^((?:GNU|Free) +)?(\S+) +(.*)/) 218{ 219 $program = $2; 220 $package = $1 ? "$1$2" : $2; 221 $version = $3; 222} 223else 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. 240my $PROGRAM = uc $program; 241 242# Extract usage clause(s) [if any] for SYNOPSIS. 243if ($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. 277my $sect = 'DESCRIPTION'; 278$_ = "$help_text\n\n$version_text"; 279 280# Normalise paragraph breaks. 281s/^\n+//; 282s/\n*$/\n/; 283s/\n\n+/\n\n/g; 284 285# Temporarily exchange leading dots, apostrophes and backslashes for 286# tokens. 287s/^\./\x80/mg; 288s/^'/\x81/mg; 289s/\\/\x82/g; 290 291# Start a new paragraph (if required) for these. 292s/([^\n])\n(Report +bugs|Email +bug +reports +to|Written +by)/$1\n\n$2/g; 293 294sub convert_option; 295 296while (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. 463unless ($opt_no_info) 464{ 465 $sect = 'SEE ALSO'; 466 $include{$sect} ||= ''; 467 $include{$sect} .= ".PP\n" if $include{$sect}; 468 $include{$sect} .= <<EOT; 469The full documentation for 470.B $program 471is maintained as a Texinfo manual. If the 472.B info 473and 474.B $program 475programs are properly installed at your site, the command 476.IP 477.B info $program 478.PP 479should give you access to the complete manual. 480EOT 481} 482 483# Output header. 484print <<EOT; 485.\\" DO NOT MODIFY THIS FILE! It was generated by $this_program $this_version. 486.TH $PROGRAM "$section" "$date" "$package $version" FSF 487EOT 488 489# Section ordering. 490my @pre = qw(NAME SYNOPSIS DESCRIPTION OPTIONS EXAMPLES); 491my @post = ('AUTHOR', 'REPORTING BUGS', 'COPYRIGHT', 'SEE ALSO'); 492my $filter = join '|', @pre, @post; 493 494# Output content. 495for (@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 513exit; 514 515# Convert option dashes to \- to stop nroff from hyphenating 'em, and 516# embolden. Option arguments get italicised. 517sub 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