1#!/usr/bin/perl -w 2 3# Generate a short man page from --help and --version output. 4# Copyright (C) 1997-2004, 2008-2010 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 3 of the License, or 9# (at your option) 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, see <http://www.gnu.org/licenses/>. 18 19# Written by Brendan O'Dea <bod@debian.org> 20# Available from ftp://ftp.gnu.org/gnu/help2man/ 21 22use 5.005; 23use strict; 24use Getopt::Long; 25use Text::Tabs qw(expand); 26use POSIX qw(strftime setlocale LC_ALL); 27use locale; 28 29my $this_program = 'help2man'; 30my $this_version = '1.35'; 31 32my $have_gettext; 33BEGIN { 34 eval { 35 require Locale::gettext; 36 Locale::gettext->import (qw(gettext textdomain)); 37 $have_gettext = 1; 38 }; 39 40 unless ($have_gettext) 41 { 42 *gettext = sub { $_[0] }; 43 *textdomain = sub {}; 44 } 45} 46 47sub _ { gettext @_ } 48sub N_ { $_[0] } 49 50textdomain $this_program; 51{ 52 my ($user_locale) = grep defined && length, 53 (map $ENV{$_}, qw(LANGUAGE LC_ALL LC_MESSAGES LANG)), 'C'; 54 55 sub kark # die with message formatted in the invoking user's locale 56 { 57 setlocale LC_ALL, $user_locale; 58 my $fmt = gettext shift; 59 die +(sprintf $fmt, @_), "\n"; 60 } 61} 62 63my $version_info = sprintf _(<<'EOT'), $this_program, $this_version; 64GNU %s %s 65 66Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004 Free Software 67Foundation, Inc. 68This is free software; see the source for copying conditions. There is NO 69warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 70 71Written by Brendan O'Dea <bod@debian.org> 72EOT 73 74my $help_info = sprintf _(<<'EOT'), $this_program, $this_program; 75`%s' generates a man page out of `--help' and `--version' output. 76 77Usage: %s [OPTION]... EXECUTABLE 78 79 -n, --name=STRING description for the NAME paragraph 80 -s, --section=SECTION section number for manual page (1, 6, 8) 81 -m, --manual=TEXT name of manual (User Commands, ...) 82 -S, --source=TEXT source of program (FSF, Debian, ...) 83 -L, --locale=STRING select locale (default "C") 84 -i, --include=FILE include material from `FILE' 85 -I, --opt-include=FILE include material from `FILE' if it exists 86 -o, --output=FILE send output to `FILE' 87 -p, --info-page=TEXT name of Texinfo manual 88 -N, --no-info suppress pointer to Texinfo manual 89 --help print this help, then exit 90 --version print version number, then exit 91 92EXECUTABLE should accept `--help' and `--version' options although 93alternatives may be specified using: 94 95 -h, --help-option=STRING help option string 96 -v, --version-option=STRING version option string 97 98Report bugs to <bug-help2man@gnu.org>. 99EOT 100 101my $section = 1; 102my $manual = ''; 103my $source = ''; 104my $locale = 'C'; 105my $help_option = '--help'; 106my $version_option = '--version'; 107my ($opt_name, @opt_include, $opt_output, $opt_info, $opt_no_info); 108 109my %opt_def = ( 110 'n|name=s' => \$opt_name, 111 's|section=s' => \$section, 112 'm|manual=s' => \$manual, 113 'S|source=s' => \$source, 114 'L|locale=s' => \$locale, 115 'i|include=s' => sub { push @opt_include, [ pop, 1 ] }, 116 'I|opt-include=s' => sub { push @opt_include, [ pop, 0 ] }, 117 'o|output=s' => \$opt_output, 118 'p|info-page=s' => \$opt_info, 119 'N|no-info' => \$opt_no_info, 120 'h|help-option=s' => \$help_option, 121 'v|version-option=s' => \$version_option, 122); 123 124# Parse options. 125Getopt::Long::config('bundling'); 126GetOptions (%opt_def, 127 help => sub { print $help_info; exit }, 128 version => sub { print $version_info; exit }, 129) or die $help_info; 130 131die $help_info unless @ARGV == 1; 132 133die "$this_program: no locale support (Locale::gettext required)\n" 134 unless $locale eq 'C' or $have_gettext; 135 136# Set localization of date and executable's output. 137delete @ENV{qw(LANGUAGE LC_MESSAGES LANG)}; 138setlocale LC_ALL, $ENV{LC_ALL} = $locale; 139 140my %include = (); 141my %append = (); 142my @include = (); # retain order given in include file 143 144# Process include file (if given). Format is: 145# 146# [section name] 147# verbatim text 148# 149# or 150# 151# /pattern/ 152# verbatim text 153# 154 155while (@opt_include) 156{ 157 my ($inc, $required) = @{shift @opt_include}; 158 159 next unless -f $inc or $required; 160 kark N_("%s: can't open `%s' (%s)"), $this_program, $inc, $! 161 unless open INC, $inc; 162 163 my $key; 164 my $hash = \%include; 165 166 while (<INC>) 167 { 168 # [section] 169 if (/^\[([^]]+)\]/) 170 { 171 $key = uc $1; 172 $key =~ s/^\s+//; 173 $key =~ s/\s+$//; 174 $hash = \%include; 175 push @include, $key unless $include{$key}; 176 next; 177 } 178 179 # /pattern/ 180 if (m!^/(.*)/([ims]*)!) 181 { 182 my $pat = $2 ? "(?$2)$1" : $1; 183 184 # Check pattern. 185 eval { $key = qr($pat) }; 186 if ($@) 187 { 188 $@ =~ s/ at .*? line \d.*//; 189 die "$inc:$.:$@"; 190 } 191 192 $hash = \%append; 193 next; 194 } 195 196 # Check for options before the first section--anything else is 197 # silently ignored, allowing the first for comments and 198 # revision info. 199 unless ($key) 200 { 201 # handle options 202 if (/^-/) 203 { 204 local @ARGV = split; 205 GetOptions %opt_def; 206 } 207 208 next; 209 } 210 211 $hash->{$key} ||= ''; 212 $hash->{$key} .= $_; 213 } 214 215 close INC; 216 217 kark N_("%s: no valid information found in `%s'"), $this_program, $inc 218 unless $key; 219} 220 221# Compress trailing blank lines. 222for my $hash (\(%include, %append)) 223{ 224 for (keys %$hash) { $hash->{$_} =~ s/\n+$/\n/ } 225} 226 227# Grab help and version info from executable. 228my ($help_text, $version_text) = map { 229 join '', map { s/ +$//; expand $_ } `$ARGV[0] $_ 2>/dev/null` 230 or kark N_("%s: can't get `%s' info from %s"), $this_program, 231 $_, $ARGV[0] 232} $help_option, $version_option; 233 234my $date = strftime "%B %Y", localtime; 235(my $program = $ARGV[0]) =~ s!.*/!!; 236my $package = $program; 237my $version; 238 239if ($opt_output) 240{ 241 unlink $opt_output or kark N_("%s: can't unlink %s (%s)"), 242 $this_program, $opt_output, $! if -e $opt_output; 243 244 open STDOUT, ">$opt_output" 245 or kark N_("%s: can't create %s (%s)"), $this_program, $opt_output, $!; 246} 247 248# The first line of the --version information is assumed to be in one 249# of the following formats: 250# 251# <version> 252# <program> <version> 253# {GNU,Free} <program> <version> 254# <program> ({GNU,Free} <package>) <version> 255# <program> - {GNU,Free} <package> <version> 256# 257# and seperated from any copyright/author details by a blank line. 258 259($_, $version_text) = split /\n+/, $version_text, 2; 260 261if (/^(\S+) +\(((?:GNU|Free) +[^)]+)\) +(.*)/ or 262 /^(\S+) +- *((?:GNU|Free) +\S+) +(.*)/) 263{ 264 $program = $1; 265 $package = $2; 266 $version = $3; 267} 268elsif (/^((?:GNU|Free) +)?(\S+) +(.*)/) 269{ 270 $program = $2; 271 $package = $1 ? "$1$2" : $2; 272 $version = $3; 273} 274else 275{ 276 $version = $_; 277} 278 279$program =~ s!.*/!!; 280 281# No info for `info' itself. 282$opt_no_info = 1 if $program eq 'info'; 283 284for ($include{_('NAME')}) 285{ 286 if ($opt_name) # --name overrides --include contents. 287 { 288 $_ = "$program \\- $opt_name\n"; 289 } 290 elsif ($_) # Use first name given as $program 291 { 292 $program = $1 if /^([^\s,]+)(?:,?\s*[^\s,\\-]+)*\s+\\?-/; 293 } 294 else # Set a default (useless) NAME paragraph. 295 { 296 $_ = sprintf _("%s \\- manual page for %s %s") . "\n", $program, 297 $program, $version; 298 } 299} 300 301# Man pages traditionally have the page title in caps. 302my $PROGRAM = uc $program; 303 304# Set default page head/footers 305$source ||= "$program $version"; 306unless ($manual) 307{ 308 for ($section) 309 { 310 if (/^(1[Mm]|8)/) { $manual = _('System Administration Utilities') } 311 elsif (/^6/) { $manual = _('Games') } 312 else { $manual = _('User Commands') } 313 } 314} 315 316# Extract usage clause(s) [if any] for SYNOPSIS. 317my $PAT_USAGE = _('Usage'); 318my $PAT_USAGE_CONT = _('or'); 319if ($help_text =~ s/^($PAT_USAGE):( +(\S+))(.*)((?:\n(?: {6}\1| *($PAT_USAGE_CONT): +\S).*)*)//om) 320{ 321 my @syn = $3 . $4; 322 323 if ($_ = $5) 324 { 325 s/^\n//; 326 for (split /\n/) { s/^ *(($PAT_USAGE_CONT): +)?//o; push @syn, $_ } 327 } 328 329 my $synopsis = ''; 330 for (@syn) 331 { 332 $synopsis .= ".br\n" if $synopsis; 333 s!^\S*/!!; 334 s/^(\S+) *//; 335 $synopsis .= ".B $1\n"; 336 s/\s+$//; 337 s/(([][]|\.\.+)+)/\\fR$1\\fI/g; 338 s/^/\\fI/ unless s/^\\fR//; 339 $_ .= '\fR'; 340 s/(\\fI)( *)/$2$1/g; 341 s/\\fI\\fR//g; 342 s/^\\fR//; 343 s/\\fI$//; 344 s/^\./\\&./; 345 346 $synopsis .= "$_\n"; 347 } 348 349 $include{_('SYNOPSIS')} ||= $synopsis; 350} 351 352# Process text, initial section is DESCRIPTION. 353my $sect = _('DESCRIPTION'); 354$_ = "$help_text\n\n$version_text"; 355 356# Normalise paragraph breaks. 357s/^\n+//; 358s/\n*$/\n/; 359s/\n\n+/\n\n/g; 360 361# Join hyphenated lines. 362s/([A-Za-z])-\n *([A-Za-z])/$1$2/g; 363 364# Temporarily exchange leading dots, apostrophes and backslashes for 365# tokens. 366s/^\./\x80/mg; 367s/^'/\x81/mg; 368s/\\/\x82/g; 369 370my $PAT_BUGS = _('Report +(?:\w+ +)?bugs|Email +bug +reports +to'); 371my $PAT_AUTHOR = _('Written +by'); 372my $PAT_OPTIONS = _('Options'); 373my $PAT_EXAMPLES = _('Examples'); 374my $PAT_FREE_SOFTWARE = _('This +is +free +software'); 375my $PAT_INFO = _('For +complete +documentation'); 376 377# Start a new paragraph (if required) for these. 378s/([^\n])\n($PAT_BUGS|$PAT_AUTHOR)/$1\n\n$2/og; 379 380sub convert_option; 381 382while (length) 383{ 384 # Convert some standard paragraph names. 385 if (s/^($PAT_OPTIONS): *\n//o) 386 { 387 $sect = _('OPTIONS'); 388 next; 389 } 390 elsif (s/^($PAT_EXAMPLES): *\n//o) 391 { 392 $sect = _('EXAMPLES'); 393 next; 394 } 395 # Skip any texinfo reference as that's handled separately 396 if (s/($PAT_INFO).*\n//o) 397 { 398 next; 399 } 400 401 # Copyright section 402 if (/^Copyright +[(\xa9]/) 403 { 404 $sect = _('COPYRIGHT'); 405 $include{$sect} ||= ''; 406 $include{$sect} .= ".PP\n" if $include{$sect}; 407 408 my $copy; 409 ($copy, $_) = split /\n\n/, $_, 2; 410 411 for ($copy) 412 { 413 # Add back newline 414 s/\n*$/\n/; 415 416 # Convert iso9959-1 copyright symbol or (c) to nroff 417 # character. 418 s/^Copyright +(?:\xa9|\([Cc]\))/Copyright \\(co/mg; 419 420 # Insert line breaks before additional copyright messages 421 # and the disclaimer. 422 s/(.)\n(Copyright |$PAT_FREE_SOFTWARE)/$1\n.br\n$2/og; 423 } 424 425 $include{$sect} .= $copy; 426 $_ ||= ''; 427 next; 428 } 429 430 # Catch bug report text. 431 if (/^($PAT_BUGS) /o) 432 { 433 $sect = _('REPORTING BUGS'); 434 } 435 436 # Author section. 437 elsif (/^($PAT_AUTHOR)/o) 438 { 439 $sect = _('AUTHOR'); 440 } 441 442 # Examples, indicated by an indented leading $, % or > are 443 # rendered in a constant width font. 444 if (/^( +)([\$\%>] )\S/) 445 { 446 my $indent = $1; 447 my $prefix = $2; 448 my $break = '.IP'; 449 $include{$sect} ||= ''; 450 while (s/^$indent\Q$prefix\E(\S.*)\n*//) 451 { 452 $include{$sect} .= "$break\n\\f(CW$prefix$1\\fR\n"; 453 $break = '.br'; 454 } 455 456 next; 457 } 458 459 my $matched = ''; 460 $include{$sect} ||= ''; 461 462 # Sub-sections have a trailing colon and the second line indented. 463 if (s/^(\S.*:) *\n / /) 464 { 465 $matched .= $& if %append; 466 $include{$sect} .= qq(.SS "$1"\n); 467 } 468 469 my $indent = 0; 470 my $content = ''; 471 472 # Option with description. 473 if (s/^( {1,10}([+-]\S.*?))(?:( +(?!-))|\n( {20,}))(\S.*)\n//) 474 { 475 $matched .= $& if %append; 476 $indent = length ($4 || "$1$3"); 477 $content = ".TP\n\x84$2\n\x84$5\n"; 478 unless ($4) 479 { 480 # Indent may be different on second line. 481 $indent = length $& if /^ {20,}/; 482 } 483 } 484 485 # Option without description. 486 elsif (s/^ {1,10}([+-]\S.*)\n//) 487 { 488 $matched .= $& if %append; 489 $content = ".HP\n\x84$1\n"; 490 $indent = 80; # not continued 491 } 492 493 # Indented paragraph with tag. 494 elsif (s/^( +(\S.*?) +)(\S.*)\n//) 495 { 496 $matched .= $& if %append; 497 $indent = length $1; 498 $content = ".TP\n\x84$2\n\x84$3\n"; 499 } 500 501 # Indented paragraph. 502 elsif (s/^( +)(\S.*)\n//) 503 { 504 $matched .= $& if %append; 505 $indent = length $1; 506 $content = ".IP\n\x84$2\n"; 507 } 508 509 # Left justified paragraph. 510 else 511 { 512 s/(.*)\n//; 513 $matched .= $& if %append; 514 $content = ".PP\n" if $include{$sect}; 515 $content .= "$1\n"; 516 } 517 518 # Append continuations. 519 while ($indent ? s/^ {$indent}(\S.*)\n// : s/^(\S.*)\n//) 520 { 521 $matched .= $& if %append; 522 $content .= "\x84$1\n" 523 } 524 525 # Move to next paragraph. 526 s/^\n+//; 527 528 for ($content) 529 { 530 # Leading dot and apostrophe protection. 531 s/\x84\./\x80/g; 532 s/\x84'/\x81/g; 533 s/\x84//g; 534 535 # Convert options. 536 s/(^| |\()(-[][\w=-]+)/$1 . convert_option $2/mge; 537 538 # Escape remaining hyphens 539 s/-/\x83/g; 540 } 541 542 # Check if matched paragraph contains /pat/. 543 if (%append) 544 { 545 for my $pat (keys %append) 546 { 547 if ($matched =~ $pat) 548 { 549 $content .= ".PP\n" unless $append{$pat} =~ /^\./; 550 $content .= $append{$pat}; 551 } 552 } 553 } 554 555 $include{$sect} .= $content; 556} 557 558# Refer to the real documentation. 559unless ($opt_no_info) 560{ 561 my $info_page = $opt_info || $program; 562 563 $sect = _('SEE ALSO'); 564 $include{$sect} ||= ''; 565 $include{$sect} .= ".PP\n" if $include{$sect}; 566 $include{$sect} .= sprintf _(<<'EOT'), $program, $program, $info_page; 567The full documentation for 568.B %s 569is maintained as a Texinfo manual. If the 570.B info 571and 572.B %s 573programs are properly installed at your site, the command 574.IP 575.B info coreutils \(aq%s invocation\(aq 576.PP 577should give you access to the complete manual. 578EOT 579} 580 581# Output header. 582print <<EOT; 583.\\" DO NOT MODIFY THIS FILE! It was generated by $this_program $this_version. 584.TH $PROGRAM "$section" "$date" "$source" "$manual" 585EOT 586 587# Section ordering. 588my @pre = (_('NAME'), _('SYNOPSIS'), _('DESCRIPTION'), _('OPTIONS'), 589 _('EXAMPLES')); 590 591my @post = (_('AUTHOR'), _('REPORTING BUGS'), _('COPYRIGHT'), _('SEE ALSO')); 592my $filter = join '|', @pre, @post; 593 594# Output content. 595for my $sect (@pre, (grep ! /^($filter)$/o, @include), @post) 596{ 597 if ($include{$sect}) 598 { 599 my $lsect = gettext $sect; 600 my $quote = $lsect =~ /\W/ ? '"' : ''; 601 print ".SH $quote$lsect$quote\n"; 602 603 for ($include{$sect}) 604 { 605 # Replace leading dot, apostrophe, backslash and hyphen 606 # tokens. 607 s/\x80/\\&./g; 608 s/\x81/\\&'/g; 609 s/\x82/\\e/g; 610 s/\x83/\\-/g; 611 612 # Convert some latin1 chars to troff equivalents 613 s/\xa0/\\ /g; # non-breaking space 614 615 $sect eq 'REPORTING BUGS' 616 and s/\n(.)/\n.br\n$1/g; 617 618 print; 619 } 620 } 621} 622 623close STDOUT or kark N_("%s: error writing to %s (%s)"), $this_program, 624 $opt_output || 'stdout', $!; 625 626exit; 627 628# Convert option dashes to \- to stop nroff from hyphenating 'em, and 629# embolden. Option arguments get italicised. 630sub convert_option 631{ 632 local $_ = '\fB' . shift; 633 634 s/-/\x83/g; 635 unless (s/\[=(.*)\]$/\\fR[=\\fI$1\\fR]/) 636 { 637 s/=(.)/\\fR=\\fI$1/; 638 s/ (.)/ \\fI$1/; 639 $_ .= '\fR'; 640 } 641 642 $_; 643} 644