Xref: utzoo alt.sources:2970 comp.lang.perl:3477 Path: utzoo!utgpu!cs.utexas.edu!uwm.edu!wuarchive!uunet!convex!usenet From: tchrist@convex.COM (Tom Christiansen) Newsgroups: alt.sources,comp.lang.perl Subject: The Answer to All Man's Problems (part 3 of 6) Keywords: man perl Message-ID: <1991Jan07.221954.10569@convex.com> Date: 7 Jan 91 22:19:54 GMT References: <1991Jan07.220902.9726@convex.com> Sender: usenet@convex.com (news access account) Reply-To: tchrist@convex.COM (Tom Christiansen) Followup-To: alt.sources.d Organization: CONVEX Software Development, Richardson, TX Lines: 1001 Nntp-Posting-Host: pixel.convex.com X } X } X printf STDERR " pathcheck returns $section\n" if $debug & 8; X $return; X} X X#--------------------------------------------------------------------------- X Xsub flush { X $| = 1; X print ''; X $| = 0; X} X Xsub has_meta { X $_[0] =~ /[[*?]/; X} X Xsub macro { X @_[0] =~ /^\\\*\(/; X} X Xsub really { X local($was,$is) = @_; X print " really in $was($is)\n"; X} X Xsub usage { X die "usage: $iam [-d debug-level] [-s sub-sections] [-p manpath] X [-x xrefpath] [pattern ...] \n"; X} X Xsub glob { X local($expr) = @_; X local(@retlist) = (); X local(*METADIR); # paranoia X X die "glob: null expr" unless $expr; # assert X X if ($expr =~ /\//) { X warn "glob: \"$expr\" has slashes, punting..."; X return <${expr}>; X } X X $expr =~ s/\*/.*/g; X $expr =~ s/\?/./g; X X unless (opendir(METADIR, '.')) { X warn "glob: can't opendir ".": $!\n"; X } else { X @retlist = sort grep(/$expr/o, grep(!/^\./, readdir(METADIR))); X closedir METADIR; X } X return @retlist; X} X Xsub dbmopen { X local($tree) = $_[0]; X # globals: %dbmopened, %warned X return 1 if $dbmopened{$tree}; X X unless (-f " X X} SHAR_EOF if test 10347 -ne "`wc -c < 'cfman'`" then echo shar: "error transmitting 'cfman'" '(should have been 10347 characters)' fi chmod 775 'cfman' fi echo shar: "extracting 'cfman.8l'" '(6219 characters)' if test -f 'cfman.8l' then echo shar: "will not over-write existing file 'cfman.8l'" else sed 's/^ X//' << \SHAR_EOF > 'cfman.8l' X.TH CFMAN 8L "" "15 November 1989" X.de Sh X.br X.PP X.ne 4 X.ti -.5i X\fB\\$1\fR X.PP X.. X.de LB \" little and bold X.ft B X.if !"\\$1"" \&\s-1\\$1\s+1 \\$2 \\$3 \\$4 \\$5 \\$6 X.ft R X.. X.de Sp X.if t .sp .5v X.if n .sp X.. X.ds lq \&"\" X.ds rq \&"\" X.if t \ X. ds lq `` X.if t \ X. ds rq '' X.de Q X\*(lq\\$1\*(rq\\$2 X.. X.Sh NAME Xcfman \- cross-reference man pages for internal consistency X.Sh SYNOPSIS X.B cfman X[ X.B \-d Xlevel X] X[ X.B \-s Xsections X] X[ X.B \-p Xmanpath X] X[ X.B \-x Xxrefpath X] X[ pattern | pathname ] ... X.br X.Sh DESCRIPTION X.I XCfman Xis a X.I perl Xprogram that checks that man page sources Xare mutually consistent in their X.LB "SEE ALSO" Xreferences. XIt will also report any X.LB ".TH" Xline that claims the Xman page is in a different place than X.I cfman Xfound it. X.PP XWhen supplied with no arguments, X.I cfman Xwill check all files (matching *.*) it finds in each man directory in Xyour colon-delimited X.LB "$MANPATH" Xenvariable if set, or in X.I /usr/man Xotherwise. It first verifies that the X.LB ".TH" Xsays Xthe man page is really where it should be, e.g. if the Xline is X.br X.in +.5i X.nf X\f(TA X\&.TH\ \ WIDGET\ \ 4 X.in -.5i X\fR X.fi X.br Xthen \fIwidget.8\fP should be the filename currently Xbeing examined. All upper-case will map to all lower-case, Xbut mixed case will be preserved for compatibility with Xthe X.LB X11 Xman pages. X.PP X.I Cfman Xthen skips ahead to the X.LB "SEE ALSO" Xsection and retrieves Xall comma-delimited entries of the general Xform \fIpagename(section)\fP. It first looks in the file X\&../man\fIsection/pagename.section\fP. If this fails Xand the current file ended in one of \fB[npl]\fP, but the X.I section Xreferenced is either X\fB1\fP or \fB8\fP, then it will check in X.I ../man8. XFailing this, X.I cfman Xchecks to see whether the referenced man page has been Xinstalled stripped of its subsection, e.g. \fIuucp\fP(1c) Xhas found its way into \fIuucp\fP(1). It then checks Xto see whether something in section \fB1\fP has been mis-installed Xin section \fB8\fP, or vice versa, or either one in section \fBl\fP Xmis-installed in the Xin section \fB8\fP and vice-versa. If all else fails, X.I cfman Xwill guess that a man page is referenced without its Xproper subsection, as in a reference to \fIrcp(1)\fP Xthat should really have been to \fIrcp(1c)\fP. If it finds Xthe misplaced man page, it reports where the reference Xthought it was and where it really was. Otherwise it Xreports the man page as missing. X.PP XThe X.LB $MANPATH Xvariable may be overridden by Xthe \fB-p\fP option. XAll checks will Xbe performed across each subtree specified in the manpath X(either from the environment of the command line), Xunless altered with the \fB-x\fP option. As a short-cut, Xthe \fIxrefpath\fP may have a leading colon to indicate Xthat it is to be concatenation of the \fImanpath\fP Xand the supplied \fIxrefpath\fP. X.PP XYou can restrict the sections checked with the \fB-s\fP Xswitch. By default, sections 1 through 8 will be examined. XThe section may be a shell metacharacter expression, Xlike X.Q ? Xor X.Q [18lpn] . X.PP XYou may restrict the individual man pages cross-referenced Xby specifying which ones you're interested in on the command Xline. These may be full pathnames, simple names like X.Q tty , Xor a shell metacharacter expression like X.Q *net . XIf Xno period occurs in the simple name, it is assumed to mean that Xthe name may have any extension. If you list specific Xman pages on the command line and X.I cfman Xfinds none matching your specification, it will report this fact. XSee the X.LB "EXAMPLES" Xsection. X.PP XMan pages that are linked by placing a \fB.so\fP directive Xon the first line will be correctly followed, and no man page Xin the same subtree. Very limited support for alternate Xman macros is provided: the X.I "\fIRand MH Message Handling System\fP" 's Xman macro set are recognized, as is Larry Wall's X.LB .Sh Xreplacement for X.LB .SH. X.Sh DIAGNOSTICS XRequires X.I perl Xto be at least version 3.0, patchlevel 1 to run. The Xprogram will abort if you try to run it with an Xearlier version of perl X.PP XFive different tracing levels can be specified with the \fB-d\fP Xoption. If any debugging is turned on, the walk through Xthe different components of the manpath are traced. XDebug values are numeric and additive, and are interpreted Xthis way: X.Sp X.in +.5i X.nf X.ne 5 X 1 Trace each man page examined X 2 Trace each cross reference examined X 4 Trace each \s-1\fB.TH\s+1\fP check X 8 Trace each file-existence test X 16 Trace each line X'in -.5i X.fi X.Sp XTracing information and other warnings are printed to X\fIstderr\fP, but normal messages about bad cross references Xare printed to \fIstdout\fP as that is \fIcfman\fP's principle Xtask. X.PP XEmbedded X.I troff Xstring macros starting \e*( cannot be resolved, and they Xwill trigger a warning message if found in the X.LB .TH Xor X.LB "SEE ALSO" Xsections. X.Sh EXAMPLES X.nf X\f(TA Xcfman # do all in $MANPATH Xcfman -p /export/exec/sun3/share/man # sun man pages Xcfman -p $HOME/man:/usr/local/mh/man:/usr/local/man:/usr/man Xcfman -p /usr/local/man -x :/usr/man # xref also in /usr/man Xcfman -s 18nlp # only these sections Xcfman '*tty*' fubar # check for *tty*.* and fubar.* Xcfman `pwd`/*.[1-8] # just check these files Xcfman -s 23 'sys*' # sys*.* files in sections 2,3 Xcfman -s 1 -p /export/exec/sun3/share/man X.fi X\fR X.PP XThe last command produced this output on my machine: X.nf X\f(TA Xbanner.1v: thinks it's in banner(1) Xfoption.1: skyversion(8) missing Xfrom.1: prmail(1) missing Xmake.1: rstat(8c) missing Xman.1: apropos(1) missing Xold-perfmon.1: missing .TH Xoldperfmon.1: missing .TH Xoldsetkeys.1: thinks it's in setkeys(1) Xorganizer.1: restore(1v) really in restore(8) Xsunview.1: traffic(1) really in traffic(1c) Xsort.1v: thinks it's in sort(1) Xsum.1v: thinks it's in sum(1) X.fi X\fR X.Sh ENVIRONMENT XThe default manpath will be taken from X.LB $MANPATH Xif set. X.Sh "SEE ALSO" Xman(1), troff(1), perl(1), man(7). X.Sh BUGS XDue to the current implentation of globbing in X.I perl, Xyou can get X.Q "Arguments too long" Xerrors. The workaround is to run X.I cfman Xfirst on X.Q [a-m]* , Xand then on X.Q [n-z]* . X.Sh AUTHOR XTom Christiansen, \s-1CONVEX\s+1 Computer Corporation. SHAR_EOF if test 6219 -ne "`wc -c < 'cfman.8l'`" then echo shar: "error transmitting 'cfman.8l'" '(should have been 6219 characters)' fi chmod 664 'cfman.8l' fi echo shar: "extracting 'makewhatis'" '(11184 characters)' if test -f 'makewhatis' then echo shar: "will not over-write existing file 'makewhatis'" else sed 's/^ X//' << \SHAR_EOF > 'makewhatis' X#!/usr/local/bin/perl X# X# makewhatis: perl rewrite for makewhatis X# author: tom christiansen X# X# Copyright 1990 Convex Computer Corporation. X# All rights reserved. X Xeval "exec /usr/bin/perl -S $0 $*" # some bozo called us with 'sh foo' X if $running_under_some_shell; # 'catman -w' likes to do this; sigh X X X&source('stat.pl'); X X($program = $0) =~ s,.*/,,; X X$UNCOMPRESS = "uncompress"; X X$MAXWHATISLEN = 300; X$MAXDATUM = 1024; # DBM is such a pain X Xumask 022; X X&source('getopts.pl'); X Xdo Getopts('ynvdP:M:') || &usage; X X$opt_P = shift if $#ARGV >= 0; X X&usage if $#ARGV > -1; X Xsub usage { die "usage: $program [-n] [-y] [-v] [[-M] manpath]\n"; } X X$nflag = $opt_n; X$yflag = $opt_y; X X$manpath = $opt_M if $opt_M; X$manpath = $opt_P if $opt_P; # backwards contemptibility X$manpath = "/usr/man" unless $manpath; X@manpath = split(/:/,$manpath); X X$| = $debug = ($opt_d || $opt_v); X X$SIG{'INT'} = 'CLEANUP'; X$SIG{'TERM'} = 'CLEANUP'; X X$SIG{'HUP'} = 'INGORE'; X Xchop($cwd = `pwd`); X X$WHATIS = "whatis"; X X# --------------------------------------------------------------------------- X# main loop X# X# chdir to each root in man path. save mtime of dbase for later compares X# with files in case of nflag or yflag. X# --------------------------------------------------------------------------- X Xforeach $root ( @manpath ) { X local($dbtime, $filecount, $entries); X X $root = "$cwd/$root" if $root !~ m:^/:; # normalize to fullpathname X chdir $root || die "can't chdir to $root: $!"; X X print "root to $root\n" if $debug; X X if ($nflag || $yflag) { X unless (&Stat('whatis.pag')) { X print "couldn't stat $root/whatis DBM file\n" if $debug; X &rebuild(0, 0) if $yflag; X next; X } X $dbtime = $st_mtime; X } X &rebuild($nflag, $yflag); X} X Xexit $status; X X# --------------------------------------------------------------------------- X# rebuild -- open a new whatis database, store all references in files in X# this root to it. if dont_touch or test_stale parms set, just X# do the checks. if test_stale, recurse on a real rebuild. X# --------------------------------------------------------------------------- X Xsub rebuild { X local($dont_touch, $test_stale) = @_; X X local(%seen); # {dev,ino} pairs of files seen X local(%so); # the .so references seen X local(@WHATIS); # whatis list X local($entries, $filecount) = (0,0); X X unless ($dont_touch || $test_stale) { X if (!open (WHATIS, "> $WHATIS.$$")) { X warn "can't open $root/$WHATIS.$$: $!\n"; X $status = 1; X return;; X } X if (!dbmopen(WHATIS, "$WHATIS.$$", 0644)) { X warn "can't dbmopen $root/$WHATIS: $!\n"; X $status = 1; X return; X } X } X X foreach $mandir ( ) { X next if $mandir =~ /man0.*/; X next if $mandir =~ /\.(old|bak)$/i; X next if $mandir =~ /~$/; X next unless -d $mandir; X X if (!chdir $mandir) { X warn "can't chdir to $root/$mandir: $!\n"; X next; X } X X ($dirext) = $mandir =~ /man(.*)$/; X $dirext =~ s/\.Z$//; X X print "subdir is $mandir\n" if $debug; X X if (!opendir(mandir,'.')) { X warn "can't opendir('$root/$mandir'): $!\n"; X next; X } X X # read each file in directory. use readdir not globbing X # because we don't want to blow up on huge directories XFILE: while ($FILE = readdir(mandir)) { X $compressed = $mandir =~ m:.*\.Z:; X next FILE if $FILE =~ /^\.{1,2}/; X X if ($FILE !~ /\S\.\S/) { X print STDERR "Skipping non-man file: $FILE\n"; X next FILE; X } X X # this will be optimized into a case statement X if ($FILE =~ /\.old$/i) { X next; X } elsif ($FILE =~ /\.bak$/i) { X next; X } elsif ($FILE =~ /\.out$/i) { X next; X } elsif ($FILE =~ /~$/) { X next; X } X X ($tmpfile = $FILE) =~ s/\.Z$//; X X ($filenam, $filext) = X $tmpfile =~ /^(\S+)\.([^.]+)$/; X X #if ($filext eq '.Z') { X #($filenam, $filext) = $filenam =~ /^(\S+)\.([^.]+)(\.Z)?$/; X #} X X if ($filext !~ /^${dirext}.*/ && $mandir ne 'mano') { X print STDERR "$FILE has a funny extension ($filext) to be in $mandir\n"; X } X X unless (&Stat($FILE)) { X warn "can't stat $root/$mandir/$FILE: $!\n"; X next FILE; X } X X if ($dont_touch || $test_stale) { X next unless $st_mtime > $dbtime; X print "$root/$mandir/$FILE newer than its dbm whatis file\n"; X closedir mandir; X chdir $root; X &rebuild(0,0) if $test_stale; X return; X } X X if ($apage = $seen{$st_dev,$st_ino}) { X printf "already saw %s, linked to %s\n", $FILE, $apage X if $debug; X &chopext($page = $FILE); X unless ($WHATIS{$page}) { X print "forgot $page\n" if $debug; X &store_indirect($page, $apage); X } X next FILE; X } X $seen{$st_dev,$st_ino} = $FILE; X X $compressed |= $FILE =~ /\.Z$/; X X if (!open(FILE, $compressed ? "$UNCOMPRESS < $FILE |" : $FILE)) { X warn "can't open $FILE: $!\n"; X next FILE; X } X X $filecount++; X print "opened $root/$mandir/$FILE\n" if $debug; X X &extract_names; X } X closedir mandir; X chdir $root || die "can't chdir back to $root: $!"; X } X X unless ($dont_touch || $test_stale) { X $, = "\n"; X print WHATIS (sort @WHATIS),''; X $, = ''; X close WHATIS || warn "can't close $WHATIS.$$: $!"; X rename ("$WHATIS.$$", $WHATIS) X || warn "can't rename $WHATIS.$$ to $WHATIS: $!"; X &check_sos(); X dbmclose(WHATIS) || warn "can't dbmclose $WHATIS: $!"; X for $ext ( 'pag', 'dir' ) { X unlink "$WHATIS.$ext"; X rename("$WHATIS.$$.$ext", "$WHATIS.$ext") X || warn "can't rename $WHATIS.$$.$ext: $!"; X } X print "$program: $root: found $entries entries in $filecount files\n"; X } X} X X X# in case we get interrupted X# Xsub CLEANUP { X print stderr "<> reading $FILE\n"; X chdir $root; X unlink "$WHATIS.$$", "$WHATIS.$$.pag", "$WHATIS.$$.dir"; X exit 1; X} X X# get next line from FILE, honoring escaped newlines X# Xsub getline { X local ($_); X X $_ = ; X { X chop; X if (/\\$/) { X chop; X $_ .= ' '; X $_ .= ; X redo; X } X } X $_; X} X Xsub extract_names { X local($_); X local($needcmdlist) = 0; X local($foundname) = 0; X local(@lines); X local($page, $page2, $indirect, $foundname, @lines, $nameline); X local($cmdlist, $ocmdlist, $tmpfile, $section); X local($prototype, $seenpage); X X unless (-T FILE) { X print STDERR "$FILE: not a text file\n"; X next; X } X X X $_ = ; # first check for leading .so reference X if (/^\.so\s+(man.+\/\S+)/) { X local($indirect, $indirect2); X $indirect = $1; X ($page) = $FILE =~ m:([^.]+)\.[^.]*$:; X ($page2) = $indirect =~ m:.*/([^/]+)$:; X ($indirect2 = $indirect) =~ s!/!.Z/!; X if (-e "../$indirect" || -e "../$indirect.Z" || -e $indirect2) { X $so{$page} = $page2; X print "$FILE: .so alias for $indirect\n" if $debug; X } else { X print STDERR "$FILE .so references non-existent $indirect\n"; X } X return; X } else { X /^\.TH\s+(\S*)\s+(\S+)/ && &doTH($1, $2); X } X XLINE: while () { X /^\.TH\s+(\S*)\s+(\S+)/ && &doTH($1, $2); X next LINE unless /^\.SH\s+"?NAME"?/i || /^\.NA\s?/; X $foundname = 1; X @lines = (); X $nameline = ''; XNAME: while ($_ = &getline()) { X last NAME if /^\.(S[hHYS])\s?/; # MH support X if ( $_ eq '.br' ) { X push(@lines, $nameline) if $nameline; X $nameline = ''; X next NAME; X } X s/^\.[IB]\b//; # Kill Bold and Italics X next if /^\./; X $nameline .= ' ' if $nameline; X $nameline .= $_; X } X X push(@lines, $nameline); X X for ( @lines ) { X next unless ord; X s/\\f([PBIR]|\(..)//g; # kill font changes X s/\\s[+-]?\d+//g; # kill point changes X s/\\&//g; # and \& X s/\\\((ru|ul)/_/g; # xlate to '_' X s/\\\((mi|hy|em)/-/g; # xlate to '-' X s/\\\*\(..//g && # no troff strings X print STDERR "trimmed troff string macro in NAME section of $FILE\n"; X s/\\//g; # kill all remaining backslashes X s/^\.\\"\s*//; # comments X if (!/\s+-+\s+/) { X # ^ otherwise L-devices would be L X printf STDERR "$FILE: no separated dash in \"%s\"\n", $_; X $needcmdlist = 1; # forgive their braindamage X s/.*-//; X $desc = $_; X } else { X ($cmdlist, $desc) = ( $`, $' ); X $cmdlist =~ s/^\s+//; X } X X # need this for two reasons: sprintf might blow up and so X # might the dbm store due to 1k limit X # X $ocmdlist = $cmdlist; # before truncation X if (length($cmdlist) > $MAXWHATISLEN) { X printf STDERR "$FILE: truncating cmdlist from %d to %d bytes for DBM's sake\n", X length($cmdlist), $MAXWHATISLEN; X $cmdlist = substr($cmdlist,0,$MAXWHATISLEN) . "..."; X } X X ($tmpfile = $FILE) =~ s/\.Z$//; X ($page, $section) = $tmpfile =~ /^(\S+)\.(\S+)$/; X $cmdlist = $page if $needcmdlist; X X $prototype = ''; $seenpage = 0; X X foreach $cmd (split(/[\s,]+/,$ocmdlist)) { X next unless $cmd; X $seenpage |= ($cmd eq $page); X if (! $prototype) { X &store_direct($cmd, $cmdlist, $tmpfile, $dirext, $desc); X $prototype = $cmd; X } else { X &store_indirect($cmd, "$prototype.$filext"); X } X } X unless ($seenpage) { X print "$FILE: forgot my own name!\n" if $debug; X if ($prototype) { X &store_indirect($page, "$prototype.$filext"); X } else { X &store_direct($page, $page, $FILE, $dirext, ''); X } X } X } X } X unless ($foundname) { X print STDERR "$FILE: no NAME lines, so has no whatis description!\n"; X ($tmpfile = $FILE) =~ s/\.Z$//; X ($page, $section) = $tmpfile =~ /^(\S+)\.(\S+)$/; X &store_direct($page, $page, $tmpfile, $dirext, 'NO DESCRIPTION'); X } X} X X# -------------------------------------------------------------------------- Xsub source { X local($file) = @_; X local($return) = 0; X X X $return = do $file; X die "couldn't parse \"$file\": $@" if $@; X die "couldn't do \"$file\": $!" unless defined $return; X warn "couldn't run \"$file\"" unless $return; X} X X Xsub chopext { X $_[0] =~ s/\.[^.]+$//; X} X Xsub check_sos { X local($key); X X foreach $key (keys %so) { X unless (defined $WHATIS{$key}) { X printf STDERR X "%s was a .so alias for %s, but %s's NAME section doesn't know it!\n", X $key, $so{$key}, $so{$key}; X &store_indirect($key, $so{$key}); X } X } X} X Xsub store_direct { X local($cmd, $list, $page, $section, $desc) = @_; X local($datum); X X push(@WHATIS,sprintf("%-20s - %s", "$list ($filext)", $desc)); X X $datum = join("\001", $list, $page, $section, $desc); X X if (defined $WHATIS{$cmd}) { X if (length($WHATIS{$cmd}) + length($datum) + 1 > $MAXDATUM) { X print STDERR "can't store $page -- would break DBM\n"; X return; X } X $WHATIS{$cmd} .= "\002"; X } X X print "storing $cmd\n" if $debug; X $WHATIS{$cmd} .= $datum; X $entries++; X} X Xsub store_indirect { X local($indirect, $real) = @_; X X print "storing $indirect as reference to $real\n" X if $debug; X X $WHATIS{$indirect} .= "\002" if $WHATIS{$indirect}; X $WHATIS{$indirect} .= $real; X $entries++; X} X Xsub doTH { X local($THname, $THext) = @_; X local($int_name, $ext_name); X X ($int_name = "$THname.$THext") =~ tr/A-Z/a-z/; X ($ext_name = "$filenam.$filext") =~ tr/A-Z/a-z/; X X if ($int_name ne $ext_name && $debug) { X print STDERR "${FILE}'s .TH thinks it's in $int_name\n"; X } X} SHAR_EOF if test 11184 -ne "`wc -c < 'makewhatis'`" then echo shar: "error transmitting 'makewhatis'" '(should have been 11184 characters)' fi chmod 755 'makewhatis' fi echo shar: "extracting 'straycats'" '(476 characters)' if test -f 'straycats' then echo shar: "will not over-write existing file 'straycats'" else sed 's/^ X//' << \SHAR_EOF > 'straycats' X#!/usr/local/bin/perl X X$manpath = shift || $ENV{'MANPATH'} || '/usr/man/'; X Xfor $root (split(/:/, $manpath)) { X X chdir($root) || die "can't chdir to $root: $!\n"; X X foreach $catdir ( ) { X opendir (catdir, $catdir) || die "can't opendir $dir: $!"; X ($mandir = $catdir) =~ s/cat/man/; X foreach $file ( readdir(catdir) ) { X next if $file eq '.' || $file eq '..'; X next if -e "$mandir/$file"; X print "no man page for $root/$catdir/$file\n"; X } X } X X} SHAR_EOF if test 476 -ne "`wc -c < 'straycats'`" then echo shar: "error transmitting 'straycats'" '(should have been 476 characters)' fi chmod 775 'straycats' fi echo shar: "extracting 'man.ms'" '(34978 characters)' if test -f 'man.ms' then echo shar: "will not over-write existing file 'man.ms'" else sed 's/^ X//' << \SHAR_EOF > 'man.ms' X.\" -------------------------------------------------------- X.de CW \" macro to begin constant-width font X\" I like TA better, but whatever looks nicest should be used X.ft TA X.. X.\" -------------------------------------------------------- X.de CE \" macro to end constant-width font X.ft R \" maybe should really be .ft P? X.. X.\" -------------------------------------------------------- X.de M \" man page reference X\\fI\\$1\\fR\\|(\\$2\)\\$3 X.. X.\" -------------------------------------------------------- X.\" Here begins a mostly successful attempt at X.\" defining a macro to output a boxed, centered X.\" figure that includes an auto-increment figure #N X.\" line using -ms macros. X.\" It doesn't actually do the centering. sigh. X.nr FN 0 1 X.de BF \" begin figure X.ds FN \\$1 X.KF X.nf X.na X.sp X.B1 X.CW X.. X.\" -------------------------------------------------------- X.de EF \" end figure X.sp .5v X.B2 X.CE X.ce X\\fBFigure \\n+(FN \\(em \\*(FN\\fR X.sp X.fi X.ad X.KE X.. X.\" -------------------------------------------------------- X.de LB \" little and bold X.ft B X.if !"\\$1"" \&\s-1\\$1\s+1 \\$2 \\$3 \\$4 \\$5 \\$6 X.ft R X.. X.\" End macro definitions X.\" -------------------------------------------------------- X.TL X\s+2The Answer to All Man's Problems\s-2 X.AU X\s+2\fITom Christiansen\fP\s-2 X.AI X\s-1CONVEX\s+1 Computer Corporation XPOB 833851 X3000 Waterview Parkway XRichardson, TX 75083-3851 X.sp X\fI{uunet,uiucdcs,sun}!convex!tchrist Xtchrist@convex.com\fP X.AB no X.ps -1 XThe \s-1UNIX\s0 on-line manual system was designed many years ago to suit Xthe needs of systems at the time, but Xdespite the growth in complexity of typical Xsystems and the need for more sophisticated software, Xfew modifications have been Xmade to it since then. XThis paper Xpresents the results of a complete rewrite of the man system. The Xthree principal goals were to effect substantial gains in Xfunctionality, extensibility, and speed. The secondary goal was to Xrewrite a basic \s-1UNIX\s0 utility in the perl programming language to Xobserve how perl affected development time, execution time, and design Xdecisions. X.PP X.ps -1 XExtensions to the original man system include storing the whatis Xdatabase in \s-1DBM\s0 format for quicker access, intelligent handling of Xentries with multiple names (via \fB.so\fP inclusion, links, or the \s-1NAME\s0 Xsection), embedded tbl and eqn directives, multiple man trees, Xextensible section naming possibilities, Xuser-definable section and sub-section search ordering, Xan indexing mechanism Xfor long man pages, Xtypesetting of man pages, Xtext-previewer support for bit mapped displays, Xautomatic validity checks on the \s-1SEE\s0 \s-1ALSO\s0 sections, Xsupport for compressed man pages to conserve disk usage, Xper-tree man macro definitions, Xand support for man pages for multiple Xarchitectures or software versions from the same host. X.ps +1 X.AE X.NH XIntroduction X.PP XThe \s-1UNIX\s0 on-line manual system was Xdesigned many years ago to suit the needs of the systems Xat the time. Since then, Xdespite Xthe growth in complexity of Xtypical systems and the need for more sophisticated software Xto support them, Xfew modifications of major significance Xhave been Xmade to the program. XThis paper describes problems inherent Xin earlier versions of the \fIman\fP program, proposes solutions Xto these problems, and outlines one implementation of these solutions. X.NH XThe Problem X.NH 2 XThe Monolithic Approach X.PP XOne of the most serious problems with the \fIman\fP program up to Xand including the \s-1BSD\s04.2 release was that Xall man pages on the entire system were expected to reside Xunder a common directory, X\fI/usr/man\fR. XThere was no Xnotion of separate sets of man pages installed on Xthe same machine in different subdirectories. XAt large installations, Xsituations commonly arise in which this Xfunctionality is desirable. XA site may wish to keep vendor-supplied man pages Xseparate from man pages that were developed locally Xor acquired from some third party. XAn individual or group may wish to maintain their own set Xof man pages. XMultiple versions of the same software package Xmight be simultaneously installed on the same machine. XA heterogeneous environment may want to be able to view man pages for Xall available architectures from any machine. XGiven the requirement that all man pages live in the Xsame directory, these scenarios are difficult to impossible to Xsupport. X.PP XThe \fIman\fP program distributed in the \s-1BSD\s04.3 release Xincluded the concept of a \s-1MANPATH\s0, Xa colon-delimited Xlist of complete man trees taken either from the user's Xenvironment or supplied on the command line. XWhile this was a vast improvement over the previous monolithic approach, Xseveral significant problems remained. XFor one thing, the program still had to use Xthe X.M access 2 Xsystem call on all possible paths to find out Xwhere the man page for a particular topic Xexisted. XWhen the user has a \s-1MANPATH\s0 containing multiple Xcomponents, the time needed for the \fIman\fP program to locate Xa man page is often noticeable, particularly when the target Xman page does not exist. X.NH 2 XHard-coded Section Names X.PP XAnother problem with the \fIman\fP program unresolved by Xthe \s-1BSD\s04.3 release Xwas that all possible sections in which a man page could Xreside were hard-coded Xinto the program. This means that while a X.B manp