1239462Sdim#!/usr/bin/perl -w 2218885Sdim 3218885Sdim# Generate a short man page from --help and --version output. 4218885Sdim# Copyright � 1997, 1998, 1999, 2000 Free Software Foundation, Inc. 5218885Sdim 6218885Sdim# This program is free software; you can redistribute it and/or modify 7218885Sdim# it under the terms of the GNU General Public License as published by 8218885Sdim# the Free Software Foundation; either version 2, or (at your option) 9249423Sdim# any later version. 10249423Sdim 11249423Sdim# This program is distributed in the hope that it will be useful, 12249423Sdim# but WITHOUT ANY WARRANTY; without even the implied warranty of 13249423Sdim# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14249423Sdim# GNU General Public License for more details. 15249423Sdim 16249423Sdim# You should have received a copy of the GNU General Public License 17249423Sdim# along with this program; if not, write to the Free Software Foundation, 18249423Sdim# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 19249423Sdim 20249423Sdim# Written by Brendan O'Dea <bod@compusol.com.au> 21249423Sdim# Available from ftp://ftp.gnu.org/gnu/help2man/ 22249423Sdim 23218885Sdimuse 5.004; 24218885Sdimuse strict; 25249423Sdimuse Getopt::Long; 26249423Sdimuse Text::Tabs qw(expand); 27218885Sdimuse POSIX qw(strftime setlocale LC_TIME); 28261991Sdim 29261991Sdimmy $this_program = 'help2man'; 30249423Sdimmy $this_version = '1.24'; 31261991Sdimmy $version_info = <<EOT; 32249423SdimGNU $this_program $this_version 33218885Sdim 34276479SdimCopyright (C) 1997, 1998, 1999, 2000 Free Software Foundation, Inc. 35218885SdimThis is free software; see the source for copying conditions. There is NO 36218885Sdimwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 37261991Sdim 38261991SdimWritten by Brendan O'Dea <bod\@compusol.com.au> 39218885SdimEOT 40218885Sdim 41218885Sdimmy $help_info = <<EOT; 42249423Sdim`$this_program' generates a man page out of `--help' and `--version' output. 43249423Sdim 44249423SdimUsage: $this_program [OPTION]... EXECUTABLE 45249423Sdim 46280031Sdim -n, --name=STRING use `STRING' as the description for the NAME paragraph 47280031Sdim -s, --section=SECTION use `SECTION' as the section for the man page 48249423Sdim -i, --include=FILE include material from `FILE' 49249423Sdim -I, --opt-include=FILE include material from `FILE' if it exists 50249423Sdim -o, --output=FILE send output to `FILE' 51249423Sdim -N, --no-info suppress pointer to Texinfo manual 52249423Sdim --help print this help, then exit 53249423Sdim --version print version number, then exit 54249423Sdim 55249423SdimEXECUTABLE should accept `--help' and `--version' options. 56249423Sdim 57249423SdimReport bugs to <bug-help2man\@gnu.org>. 58249423SdimEOT 59249423Sdim 60249423Sdimmy $section = 1; 61249423Sdimmy ($opt_name, @opt_include, $opt_output, $opt_no_info); 62249423Sdimmy %opt_def = ( 63249423Sdim 'n|name=s' => \$opt_name, 64249423Sdim 's|section=s' => \$section, 65249423Sdim 'i|include=s' => sub { push @opt_include, [ pop, 1 ] }, 66249423Sdim 'I|opt-include=s' => sub { push @opt_include, [ pop, 0 ] }, 67249423Sdim 'o|output=s' => \$opt_output, 68249423Sdim 'N|no-info' => \$opt_no_info, 69249423Sdim); 70249423Sdim 71249423Sdim# Parse options. 72261991SdimGetopt::Long::config('bundling'); 73261991SdimGetOptions (%opt_def, 74261991Sdim help => sub { print $help_info; exit }, 75261991Sdim version => sub { print $version_info; exit }, 76276479Sdim) or die $help_info; 77276479Sdim 78276479Sdimdie $help_info unless @ARGV == 1; 79276479Sdim 80276479Sdimmy %include = (); 81276479Sdimmy %append = (); 82276479Sdimmy @include = (); # retain order given in include file 83261991Sdim 84261991Sdim# Provide replacement `quote-regex' operator for pre-5.005. 85261991SdimBEGIN { eval q(sub qr { '' =~ $_[0]; $_[0] }) if $] < 5.005 } 86276479Sdim 87261991Sdim# Process include file (if given). Format is: 88261991Sdim# 89261991Sdim# [section name] 90261991Sdim# verbatim text 91280031Sdim# 92280031Sdim# or 93280031Sdim# 94280031Sdim# /pattern/ 95280031Sdim# verbatim text 96280031Sdim# 97280031Sdim 98280031Sdimwhile (@opt_include) 99280031Sdim{ 100280031Sdim my ($inc, $required) = @{shift @opt_include}; 101280031Sdim 102280031Sdim next unless -f $inc or $required; 103280031Sdim die "$this_program: can't open `$inc' ($!)\n" 104280031Sdim unless open INC, $inc; 105280031Sdim 106249423Sdim my $key; 107249423Sdim my $hash = \%include; 108249423Sdim 109249423Sdim while (<INC>) 110249423Sdim { 111249423Sdim # [section] 112249423Sdim if (/^\[([^]]+)\]/) 113249423Sdim { 114249423Sdim $key = uc $1; 115249423Sdim $key =~ s/^\s+//; 116249423Sdim $key =~ s/\s+$//; 117249423Sdim $hash = \%include; 118249423Sdim push @include, $key unless $include{$key}; 119249423Sdim next; 120249423Sdim } 121249423Sdim 122249423Sdim # /pattern/ 123249423Sdim if (m!^/(.*)/([ims]*)!) 124249423Sdim { 125249423Sdim my $pat = $2 ? "(?$2)$1" : $1; 126249423Sdim 127249423Sdim # Check pattern. 128249423Sdim eval { $key = qr($pat) }; 129249423Sdim if ($@) 130249423Sdim { 131249423Sdim $@ =~ s/ at .*? line \d.*//; 132249423Sdim die "$inc:$.:$@"; 133249423Sdim } 134249423Sdim 135249423Sdim $hash = \%append; 136249423Sdim next; 137249423Sdim } 138249423Sdim 139249423Sdim # Check for options before the first section--anything else is 140249423Sdim # silently ignored, allowing the first for comments and 141249423Sdim # revision info. 142249423Sdim unless ($key) 143249423Sdim { 144249423Sdim # handle options 145249423Sdim if (/^-/) 146249423Sdim { 147249423Sdim local @ARGV = split; 148249423Sdim GetOptions %opt_def; 149249423Sdim } 150249423Sdim 151249423Sdim next; 152249423Sdim } 153249423Sdim 154261991Sdim $hash->{$key} ||= ''; 155261991Sdim $hash->{$key} .= $_; 156261991Sdim } 157261991Sdim 158261991Sdim close INC; 159261991Sdim 160249423Sdim die "$this_program: no valid information found in `$inc'\n" 161249423Sdim unless $key; 162249423Sdim} 163249423Sdim 164249423Sdim# Compress trailing blank lines. 165249423Sdimfor my $hash (\(%include, %append)) 166249423Sdim{ 167249423Sdim for (keys %$hash) { $hash->{$_} =~ s/\n+$/\n/ } 168249423Sdim} 169249423Sdim 170249423Sdim# Turn off localisation of executable's ouput. 171249423Sdim@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3; 172249423Sdim 173249423Sdim# Turn off localisation of date (for strftime). 174249423Sdimsetlocale LC_TIME, 'C'; 175249423Sdim 176249423Sdim# Grab help and version info from executable. 177249423Sdimmy ($help_text, $version_text) = map { 178249423Sdim join '', map { s/ +$//; expand $_ } `$ARGV[0] --$_ 2>/dev/null` 179249423Sdim or die "$this_program: can't get `--$_' info from $ARGV[0]\n" 180249423Sdim} qw(help version); 181249423Sdim 182249423Sdimmy $date = strftime "%B %Y", localtime; 183249423Sdim(my $program = $ARGV[0]) =~ s!.*/!!; 184249423Sdimmy $package = $program; 185249423Sdimmy $version; 186249423Sdim 187218885Sdimif ($opt_output) 188218885Sdim{ 189218885Sdim unlink $opt_output 190218885Sdim 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" GNU 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