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