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