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