#!/usr/local/bin/perl # # man - perl rewrite of man system # tom christiansen # # -------------------------------------------------------------------------- # begin configuration section # # this should be adequate for CONVEX systems. if you copy this script # to non-CONVEX systems, or have a particularly outre local setup, you may # wish to alter some of the defaults. # -------------------------------------------------------------------------- use DB_File; @Any_DBM_File::ISA = qw(DB_File); $PAGER = $ENV{'MANPAGER'} || $ENV{'PAGER'} || 'more'; # assume "less" pagers want -sf flags, all others must accept -s. # note: some less's prefer -r to -f. you might also add -i if supported. # $is_less = $PAGER =~ /^\S*less(\s+-\S.*)?$/; $PAGER .= $is_less ? ' -si' : ' -s'; # add -f if using "ul" # man roots to look in; you would really rather use a separate tree than # manl and mann! see %SECTIONS and $MANALT if you do. $MANPATH = &config_path; # default section precedence $MANSECT = $ENV{'MANSECT'} || 'ln16823457po'; # colons optional unless you have multi-char section names # note that HP systems want this: # $MANSECT = $ENV{'MANSECT'} || '1:1m:6:8:2:3:4:5:7'; # alternate architecture man pages in # ${MANALT}/${machine}/man(.+)/*.\11* $MANALT = $ENV{'MANALT'} || '/usr/local/man'; # default program for -t command $TROFF = $ENV{'TROFF'} || 'troff'; $NROFF = 'nroff'; $NROFF_CAN_BOLD = 0; # if nroff puts out bold as H\bH # this are used if filters are needed $TBL = 'tbl'; $NTBL = "$TBL"; # maybe you need -TX instead $NEQN = 'neqn'; $EQN = 'eqn'; $SED = 'sed'; # define this if you don't have/want UL; # without ul, you probably need COL defined unless your PAGER is very smart # you also must use col instead of ul if you've any tbl'd man pages, such # as from the X man pages or the eqnchar.7 page. $COL = 'col'; $UL = ''; # set to '' if you haven't got ul die 'need either $UL or $COL' unless $UL || $COL; # need these for .gz files or dirs $COMPRESS = 'gzip'; $ZCAT = 'zcat'; $CAT = 'cat'; # define COMPRESS_DIR if pages might have moved to manX.gz/page.X (like HPs) $COMPRESS_DIR = 1; # define COMPRESS_PAGE if pages might have moved to manX/page.X.gz (better) $COMPRESS_PAGE = 1; # Command to format man pages to be viewed on a tty or printed on a line printer $CATSET = "$NROFF -h -mandoc -"; $CATSET .= " | $COL" if $COL; # Command to typeset a man page $TYPESET = "$TROFF -mandoc"; # flags: GNU likes -i, BSD doesn't; both like -h, but BSD doesn't document it # if you don't put -i here, i'll make up for it later the hard way #$EGREP = '/usr/local/bin/egrep'; #if (-x $EGREP) { #$EGREP .= ' -i -h'; #} else { $EGREP = '/usr/bin/egrep'; unless (-x $EGREP) { $EGREP = ''; } else { $EGREP .= ' -h'; } #} # sections that have verbose aliases # if you change this, change the usage message # # if you put any of these in their own trees, comment them out and make # a link in $MANALT so people can still say 'man local foo'; for local, # cd $MANALT; ln -s . local # for the other trees (new, old, public) put either them or links # to them in $MANALT # %SECTIONS = ( 'local', 'l', 'new', 'n', 'old', 'o', 'public', 'p' ); # turn this on if you want linked (via ".so" or otherwise) man pages # to be found even if the thing they are linked to doesn't know it's # being linked to -- that is, its NAME section doesn't have reference # to it. eg, if you call a man page 'gnugrep' but it's own NAME section # just calls it grep, then you need this. usually a good idea. # $STUPID_SO = 1; # -------------------------------------------------------------------------- # end configuration section # -------------------------------------------------------------------------- # CONVEX RCS keeps CHeader; others may prefer Header ($bogus, $version) = split(/:\s*/,'$CHeader: man 0.41 91/10/28 13:48:01 $',2); chop($version); chop($version); require 'getopts.pl'; $winsize = "\0" x 8; $TIOCGWINSZ = 0x40087468; $isatty = -t STDOUT; if (ioctl(STDIN, $TIOCGWINSZ, $winsize)) { ($rows, $cols, $xpixel, $ypixel) = unpack('S4', $winsize); } else { ($rows, $cols) = (24, 80); } %options = ( 'man', 'T:m:P:M:S:fkltvwdguhaAiDKn', 'apropos', 'm:P:MvduaK', 'whatis', 'm:P:M:vduh', 'whereis', 'm:P:M:vduh' ); ($program = $0) =~ s,.*/,,; $apropos = $program eq 'apropos'; $whatis = $program eq 'whatis'; $whereis = $program eq 'whman'; $program = 'man' unless $program; &Getopts($options = $options{$program}) || &usage; if ($opt_u) { &version if $opt_v; &usage; # not reached } if ($opt_v) { &version; exit 0; } &usage if $#ARGV < 0; $MANPATH = $opt_P if $opt_P; # backwards contemptibility $MANPATH = $opt_M if $opt_M; $hard_way = $opt_h if $opt_h; if ($opt_T) { $opt_t = 1; $TYPESET =~ s/$TROFF/$opt_T/; $TROFF = $opt_T; } $MANPATH = "$MANALT/$opt_m" # want different machine type (undoc) if $machine = $opt_m; $MANSECT = $opt_S if $opt_S; # prefer our own section ordering $whatis = 1 if $opt_f; $apropos = 1 if $opt_k || $opt_K; $fromfile = 1 if $opt_l; $whereis = 1 if $opt_w; $grepman = 1 if $opt_g; $| = $debug = 1 if $opt_d; $full_index = 1 if $opt_i; $show_all = 1 if $opt_a; $stripBS = 1 if $opt_D; $query_all = $opt_A if $opt_A; $roff = $opt_t ? 'troff' : 'nroff'; # for indirect function call # maybe they said something like 'man vax ls' if ($#ARGV > 0) { local($machdir) = $MANALT . '/' . $ARGV[0]; if (-d $machdir) { $MANPATH = $machdir; $machine = shift; } } @MANPATH = split(/:/,$MANPATH); # assign priorities to the sections he cares about # the nearer the front the higher the sorting priority $secidx = 0; $delim = ($MANSECT =~ /:/) ? ':' : ' *'; for (reverse split(/$delim/, $MANSECT)) { if ($_ eq '') { warn "null section in $MANSECT\n"; next; } $MANSECT{$_} = ++$secidx; } if ($whatis) { &whatis; } elsif ($apropos) { &apropos; } elsif ($whereis) { &whereis; } elsif ($grepman) { &grepman; } else { &man; } exit $status; # -------------------------------------------------------------------------- # fill out @whatis array with all possible names of whatis files # -------------------------------------------------------------------------- sub genwhatis { local($elt,$whatis); for $elt (@MANPATH) { $whatis = "$elt/whatis"; if (-f $whatis) { push(@whatis, $whatis); } else { warn "$whatis: $!\n";# if $opt_M || $opt_P; # they asked for it } } die "$program: No whatis databases found, please run makewhatis\n" if $#whatis < 0; } # -------------------------------------------------------------------------- # run whatis (man -f) # -------------------------------------------------------------------------- sub whatis { local($target, %seeking, $section, $desc, @entries); &genwhatis; for $target (@ARGV) { $seeking{$target} = 1; } if ($hard_way) { &slow_whatis; } else { &fast_whatis; } for $target (keys %seeking) { print "$program: $target: not found.\n"; $status = 1; } } # -------------------------------------------------------------------------- # do whatis lookup against dbm file(s) # -------------------------------------------------------------------------- sub fast_whatis { local($entry, $cmd, $page, $section, $desc, @entries); for $INDEX (@whatis) { unless (-f "$INDEX.db" && dbmopen(INDEX,"$INDEX.db",0444)) { warn "$program: No dbm file $INDEX.db: $!\n" if $debug; #$status = 1; if (-f $INDEX) { local(@whatis) = ($INDEX); # dynamic scoping obfuscation &slow_whatis; } next; } else { warn "$program: dbmopened $INDEX.db"; } for $target (@ARGV) { local($ext); @entries = &quick_fetch($target,'INDEX'); next unless @entries; # $target =~ s/([^\w])/\\$1/g; for $entry (@entries) { ($cmd, $page, $section, $desc) = split(/\001/, $entry); # STUPID_SO is one that .so's that reference things that # don't know they are being referenced. STUPID_SO may cause # some peculiarities. unless ($STUPID_SO) { next unless $cmd =~ /$target/i || $cmd =~ /\.{3}/; } delete $seeking{$target}; ($ext) = $page =~ /\.([^.]*)$/; printf("%-20s - %s\n", "$cmd ($ext)", $desc); } } dbmclose(INDEX); } } # -------------------------------------------------------------------------- # do whatis lookup the hard way # -------------------------------------------------------------------------- sub slow_whatis { local($query); local($WHATIS); for (@ARGV) { s/([^\w])/\\$1/g; } $query = '^[^-]*\b?(' . join('|',@ARGV) . ')\b[^-]* -'; if ($EGREP) { if (&run("$EGREP '$query' @whatis")) { # pity can't tell which i found %seeking = (); } } else { foreach $WHATIS (@whatis) { unless (open WHATIS) { warn "can't open $WHATIS: $!\n"; next; } while () { next unless /$query/i; ($target = $+) =~ y/A-Z/a-z/; delete $seeking{$target}; print; } close WHATIS; } } } # -------------------------------------------------------------------------- # run apropos (man -k) # -------------------------------------------------------------------------- sub apropos { local($_, %seeking, $target, $query); &genwhatis; for (@ARGV) { s/(\W)/\\$1/g unless $opt_K; } if ($EGREP) { # fold case on apropos args for (@ARGV) { y/A-Z/a-z/; $seeking{$_} = 1; } $query = join('|',@ARGV); # need to fake a -i flag? unless ($EGREP =~ /-\w*i/) { local($C); local(@pat) = split(//,$query); for (@pat) { ($C = $_) =~ y/a-z/A-Z/ && ($_ = '[' . $C . $_ . ']'); } $query = join('',@pat); } if (&run("$EGREP '$query' @whatis | $PAGER")) { %seeking = (); } } else { # use perl local($code) = <<'EOF'; if ($isatty) { $pid = open(PAGER, "| $PAGER"); sleep 1; select(PAGER); } foreach $WHATIS (@whatis) { unless (open WHATIS) { warn "can't open $WHATIS: $!\n"; next; } WHATIS: while () { EOF for (@ARGV) { if ($opt_K && split(/\|/) > 1) { # speed hack $code .= "OPLOOP: {\n"; for (@_) { $code .= "\tlast OPLOOP if /$_/i;\n"; } $code .= "next WHATIS; }\n"; } else { $code .= " next WHATIS unless /$_/i;\n"; } } $code .= <<'EOF'; print; } close WHATIS; } EOF print "$code\n" if $debug; eval $code; if ($@ =~ /(.*)at \(eval\) line (\d+)/) { ($message, $line) = ($1, $2); if ((split(/\n/,$code))[$line-1] =~ /next unless/) { warn "EVAL ERROR: $@ $code" if $debug; die "$0: $message\n"; } else { die $@; } } elsif ($@) { die $@; } } close PAGER if $isatty; } # -------------------------------------------------------------------------- # print out usage message via pager and exit # -------------------------------------------------------------------------- sub usage { unless ($opt_u) { warn "usage: $program [-flags] topic ...\n"; warn " (use -u for long usage message)\n"; } else { open (PIPE, "| $PAGER"); print PIPE <; } # -------------------------------------------------------------------------- # run 'man -w' # -------------------------------------------------------------------------- sub whereis { local($target, @files); foreach $target (@ARGV) { @files = &find_files($target); if ($#files < $[) { warn "$program: $target not found\n"; $status = 1; } else { print "$target: " if $#ARGV; for (@files) { print &verify($_), " "; } print "\n"; } } } # -------------------------------------------------------------------------- # what are the file names matching this target? # -------------------------------------------------------------------------- sub find_files { local($target) = @_; local($root, $entry); local(@retlist) = (); local(@tmplist) = (); local(@entries) = (); local($tar_regx); local($found) = 0; # globals: $vars, $called_before, %dbm, $hard_way (kinda) $vars = 'dbm00'; # var for magic autoincrementation ($tar_regx = $target) =~ s/(\W)/\\$1/g; # quote meta if (!$hard_way && !$called_before++) { # generate dbm names for $root (@MANPATH) { $dbm{$root} = $vars++; # magic incr unless (-f "$root/whatis.db" && dbmopen(%$root,"$root/whatis.db",undef)) { warn "No dbm file for $root/whatis: $!\n" if $opt_M || $opt_P || $debug; #$status = 1; next; } else { warn "opened $root/whatis.db" if $debug; } $dbmopened{$root} = 1; } } for $root (@MANPATH) { local($fullname); @tmplist = (); #if ($hard_way || !$dbmopened{$root}) { if ($hard_way) { next unless -d $root; warn "slow fetch on $target in $root\n" if $debug; @tmplist = &slow_fetch($target,$root); } else { @entries = &fetch($target,$root); next if $#entries < 0; for $entry (sort @entries) { ($cmd, $page, $section, $desc) = split(/\001/, $entry); # STUPID_SO is so that .so's that reference things that # don't know they are being referenced. STUPID_SO may # cause peculiarities. unless ($STUPID_SO) { next unless $cmd =~ /$tar_regx/i || $cmd =~ /\.{3}/; } push(@tmplist, "$root/man$section/$page"); } } push(@retlist, sort bysection @tmplist); last if $#retlist >= 0 && $hard_way; } # unless (@retlist || $hard_way) { # # shameless (ab?)use of dynamic scoping # local($hard_way) = 1; # warn "recursing on find_files\n" if $debug; # return &find_files($target); # } return &trimdups(@retlist); } # -------------------------------------------------------------------------- # run a normal man command # -------------------------------------------------------------------------- sub man { local($target,$page); while (@ARGV) { undef $idx_topic; &get_section; $target = shift @ARGV; if (!$fromfile && $target =~ m!^([^/]+)/(.*)!) { if (!$isatty) { warn "$program: no tty, so no pager to prime with index\n"; $target = $1; } else { ($target, $idx_topic) = ($1, $2); } } else { undef $idx_topic; } if ($show_all) { local(@pages); local($was_defined) = defined $idx_topic; @pages = &find_files($target); if (!@pages) { &no_entry($target); next; } while ($tpage = shift @pages) { undef $idx_topic unless $was_defined; do $roff(&verify($tpage)); &prompt_RTN("to read $pages[0]") if $roff eq 'nroff' && @pages; } } else { $target = &get_page($target) unless $fromfile; do $roff($target) if $target; } &prompt_RTN("to read man page for $ARGV[0]") if $roff eq 'nroff' && @ARGV; } } # -------------------------------------------------------------------------- # find out if he wants a special section and save in $want_section # -------------------------------------------------------------------------- sub get_section { local($section) = $ARGV[0]; # interpret stty(1) as 1 stty if ($section =~ /^(\S+)\((\S*)\),?\s*$/) { shift @ARGV; unshift(@ARGV, $section = $2, $1); } $section =~ tr/A-Z/a-z/; if (defined $SECTIONS{$section}) { $want_section = $SECTIONS{$section}; shift @ARGV; } elsif (defined($MANSECT{$section}) || $section =~ /^\d\S*$/i) { $want_section = shift @ARGV; } else { return; } $want_section =~ tr/A-Z/a-z/; die "But what do you want from section $want_section?\n" if $want_section && $#ARGV < 0; } # -------------------------------------------------------------------------- # pick the first page matching his target and search orders # -------------------------------------------------------------------------- sub get_page { local($target) = @_; local(@found, @want); unless (@found = &find_files($target)) { &no_entry($target); return ''; } if (!$want_section) { @want = @found; } else {{ local($patsect); # in case it's section 3c++ ($patsect = $want_section) =~ s/(\W)/\\$1/g; # try exact match first last if @want = grep (/\.$patsect$/, @found); # otherwise how about a subsection last if @want = grep (/\.$patsect[^.]*$/, @found); # maybe it's in the wrong place (mano is notorious for this) last if @want = grep (/man$patsect[^.]*\//, @found); &no_entry($target); return ''; }} for (@want) { $_ = &verify($_) ; } $found = $want[0]; if (@want > 1 && $query_all) { local($ans, $i); select(STDERR); print "There are ", 0+@want, " manual entries available for $target:\n"; for ($i = 0; $i <= $#want; $i++) { printf "%3d\t%s\n", $i+1, $want[$i]; } { print "Which section would you like? (select 0 for all) "; ($ans = ) ? chop($ans) : ($ans = "\004"); exit if $ans eq "\004"; redo if $ans eq ''; if ($ans eq '0') { # more dynamic scope abuse local(@ARGV) = ($target); local($show_all) = 1; &man; return ''; } if (--$ans > $#want) { print "But we only have ",1+$#want, " man pages!\n"; redo; } $found = $want[$ans]; } } select(STDOUT); $found; } # -------------------------------------------------------------------------- # figure out full path name of man page, which may have been compressed # -------------------------------------------------------------------------- sub verify { local($path) = @_; local($orig) = $path; return $path if -f $path; if ($COMPRESS_PAGE) { $path .= '.gz'; return $path if -f $path; $path =~ s/.gz//; } if ($COMPRESS_DIR) { $path =~ s-(/[^/]*)$-.gz$1-; return $path if -f $path; } warn "$program: $orig has disappeared -- rerun makewhatis\n"; $status = 1; return ''; } # -------------------------------------------------------------------------- # whine about something not being found # -------------------------------------------------------------------------- sub no_entry { print STDERR "No manual entry for $_[0]"; if ($machine || $want_section) { print STDERR " in"; print STDERR " section $want_section of" if $want_section; print STDERR " the"; print STDERR " $machine" if $machine; print STDERR " manual"; } print STDERR ".\n"; $status = 1; } # -------------------------------------------------------------------------- # order by section. if the complete extension has a section # priority, use that. otherwise use the first char of extension # only. undefined priorities are lower than any defined one. # -------------------------------------------------------------------------- sub bysection { local ($e1, $e2, $p1, $p2, $s1, $s2); ($s1, $e1) = $a =~ m:.*/man([^/]+)/.*\.([^.]+)(\.gz)?$:; ($s2, $e2) = $b =~ m:.*/man([^/]+)/.*\.([^.]+)(\.gz)?$:; $e1 = $s1 if $e1 !~ /^${s1}.*/; $e2 = $s2 if $e2 !~ /^${s2}.*/; $p1 = $MANSECT{$e1} || $MANSECT{substr($e1,0,1)}; $p2 = $MANSECT{$e2} || $MANSECT{substr($e2,0,1)}; $p1 == $p2 ? $a cmp $b : $p2 <=> $p1; } # -------------------------------------------------------------------------- # see whether they want to start at a subsection, then run the command # -------------------------------------------------------------------------- sub run_topic { local($_); local($menu_rtn) = defined $idx_topic && $idx_topic eq ''; { &append_sub_topic; last if $idx_topic eq "\004"; if ($idx_topic eq '0') { $menu_rtn = 0; $idx_topic = ''; $command =~ s: '\+/[^']*'::; } $fromfile ? &reformat($command) : &run($command); if ($menu_rtn) { $idx_topic = ''; &prompt_RTN("to return to the index"); $command =~ s! '\+/.*$!!; redo; } } } # -------------------------------------------------------------------------- # run through the typesetter # -------------------------------------------------------------------------- sub troff { local ($file) = $_[0]; local ($command); local ($manroot); local ($macros); ($manroot) = $file =~ m,^(.*)/man([^\.]*)(\.gz)?/([^/]*),; $command = ((($file =~ m:\.gz:) ? $ZCAT : $CAT) . " < $file | $TYPESET"); $command =~ s,-man,$manroot/tmac.an, if -e "$manroot/tmac.an"; &insert_filters($command,$file); &run($command); } # -------------------------------------------------------------------------- # just run a regular nroff, possibly showing the index first. # -------------------------------------------------------------------------- sub nroff { local($manpage) = $_[0]; local($catpage); local($tmppage); local($command); local(@saveidx); local($manroot); local($macros); local($intmp); local(@st_cat, @st_man); die "trying to nroff a null man page" if $manpage eq ''; umask 022; if ($full_index) { &show_index($manpage); return; } if ($fromfile) { $command = (($manpage =~ m:\.gz/:) ? $ZCAT : $CAT) . " < $manpage | $CATSET"; &insert_filters($command, $manpage); } else { require 'stat.pl' unless defined &Stat; # compiled version has this already ($catpage = $manpage) =~ s,^(.*)/man([^\.]*)(\.gz)?/([^/]*)$,$1/cat$2/$4,; $manroot = $1; # Does the cat page exist? if (! -f $catpage && $COMPRESS_DIR){ # No, maybe it is compressed? if (-f "$1/cat$2.gz/$4"){ # Yes it was. $catpage = "$1/cat$2.gz/$4"; } else { # Nope, the cat file doesn't exist. # Prefer the compressed cat directory if it exists. $catpage = "$1/cat$2.gz/$4" if $catpage !~ /\.gz$/ && -d "$1/cat$2.gz"; } } @st_man = &Stat($manpage); if ($st_man[$ST_SIZE] == 0) { warn "$program: $manpage is length 0!\n"; $status = 1; return; } @st_cat = &Stat($catpage); if ($st_cat[$ST_MTIME] < $st_man[$ST_MTIME]) { $command = (($manpage =~ m:\.gz:) ? $ZCAT : $CAT) . " < $manpage | $CATSET"; $command = &insert_filters($command, $manpage); $command =~ s,-man,$manroot/tmac.an, if -e "$manroot/tmac.an"; ($catdir = $catpage) =~ s!^(.*/?cat[^/]+)/[^/]*!$1!; chdir $manroot; $tmppage = "$catpage.$$"; unless (-d $catdir && -w _ && open(tmppage, ">$tmppage") # usually EROFS && close(tmppage) ) { $catpage = $tmppage = "/tmp/man.$$"; $intmp = 1; } printf STDERR "Reformatting page. Please wait ... " if $isatty; $command .= "| $COMPRESS" if $catpage =~ /\.gz/; $command .= "> $tmppage"; $SIG{'INT'} = $SIG{'QUIT'} = $SIG{'HUP'} = $SIG{'TERM'} = 'tmp_cleanup'; $SIG{'PIPE'} = 'PLUMBER'; REFORMAT: { unless (&reformat($command)) { warn "$program: nroff of $manpage into $tmppage failed\n" unless $@; unlink $tmppage unless $debug; if (!$intmp++) { $catpage = $tmppage = "/tmp/man.$$"; warn "$program: hang on... retrying into $tmppage\n"; $command =~ s/> \S+$/> $tmppage/; $status = 0; redo REFORMAT; } else { #$status = 1; return; } }} warn "done\n" if $isatty; $intmp || rename($tmppage,$catpage) || die "couldn't rename $tmppage to $catpage: $!\n"; $SIG{'INT'} = $SIG{'QUIT'} = $SIG{'HUP'} = $SIG{'TERM'} = $SIG{'PIPE'} = 'DEFAULT'; } $command = (($catpage =~ m:\.gz:) ? $ZCAT : $CAT) . " < $catpage"; } if (-z $catpage) { unlink $catpage; die "$program: $catpage was length 0; disk full?\n"; } $command .= "| $UL" if $UL; $command .= "| $SED 's/.\b//g'" if $stripBS; if ($isatty) { $command .= "| $PAGER"; # If the pager is less, use the man page as the prompt, even if pipe if ($is_less) { # Escape all periods because less interprets them. We also # need to add an extra '\' to escape the shell intrepetation # of '\'. We also need to make a copy of $manpage, because # the substitution trashes the string and it is needed later. ($lessprompt = $manpage) =~ s/\./\\./g; $lessprompt = "$lessprompt byte %bB?s/%s .?e(END) :?pB%pB\\%.%t" if $ENV{'LESS'} =~ /M/; # he wants a long prompt $command .= " '-MPM$lessprompt'"; } } &run_topic; unlink($tmppage) if $intmp; } # -------------------------------------------------------------------------- # modify $command to prime the pager with the subsection they want # -------------------------------------------------------------------------- sub append_sub_topic { if (defined $idx_topic) {{ local($key); last if $idx_topic eq '0'; unless ($idx_topic) { $idx_topic = &pick_index; last if $idx_topic eq "\004" || $idx_topic eq '0'; } if ($idx_topic =~ m!^/!) { $command .= " '+$idx_topic'"; last; } unless ($key = &find_index($manpage, $idx_topic)) { warn "No subsection $idx_topic for $manpage\n\n"; $idx_topic = ''; redo; } $key =~ s/([!-~])/$1.$1/g unless $is_less; $command .= " '+/^[ \t]*$key'"; }} } # -------------------------------------------------------------------------- # present subsections and let user select one # -------------------------------------------------------------------------- sub pick_index { local($_); print "Valid sections for $page follow. Choose the section\n"; print "index number or string pattern. (0 for full page, RTN to quit.)\n\n"; &show_index; print "\nWhich section would you like? "; ($_ = ) ? chop : ($_ = "\004"); $_ = "\004" if 'quit' =~ /^$_/; return $_; } # -------------------------------------------------------------------------- # strip arg of extraneous cats and redirects # -------------------------------------------------------------------------- sub unshell { $_[0] =~ s/^\s*cat\s*> 8), ($? & 255) if $debug; } return ($? == 0); } # -------------------------------------------------------------------------- # check if page needs tbl or eqn, modifying command if needed # add known problems for PR directory if applicable # -------------------------------------------------------------------------- sub insert_filters { local($filters,$eqn, $tbl, $_); local(*PAGE); local($c, $PAGE) = @_; local($page,$sect, $prs, $prdir); ( $page = $PAGE ) =~ s/\.gz//; ($prdir = $page) =~ s#/[^/]*$##; $prdir =~ s#man([^/]*)$#pr$1#; $page =~ s#.*/([^/]+)$#$1#; $PAGE = "$ZCAT < $PAGE|" if $PAGE =~ /\.gz/; (open PAGE) || die ("$program: can't open $PAGE to check filters: $!\n"); warn "open $PAGE to check for filters in $_[0]\n" if $debug; while () { if (/^\.EQ/) { $_ = ; $eqn = 1 unless /\.(if|nr)/; # has eqn output not input } if (/^\.TS/) { $_ = ; $tbl = 1 unless /\.(if|nr)/; # has tbl output not input } last if $eqn && $tbl; } close PAGE; if ($roff eq 'troff') { $eqn && $_[0] =~ s/(\S+roff)/$EQN | $1/; $tbl && $_[0] =~ s/(\S+roff)/$TBL | $1/; } else { # nroff $eqn && $_[0] =~ s/(\S+roff)/$NEQN | $1/; $tbl && $_[0] =~ s/(\S+roff)/$NTBL | $1/; } ($sect) = $page =~ /\.(\d)[^.]*$/; $prs = "$prdir/$page"; if (-e $prs) { warn "found PRs for $page\n" if $debug; if ($roff eq 'nroff') { $_[0] =~ s/ - / - $prs/; } else { $_[0] .= " $prs"; } } else { print "no PRS for $page in $prs\n" if $debug; } $_[0]; } # -------------------------------------------------------------------------- # due to aliasing the dbase sometimes has the same thing twice # -------------------------------------------------------------------------- sub trimdups { local(%seen) = (); local(@retlist) = (); while ($file = shift) { push(@retlist,$file) unless $seen{$file}++; } return @retlist; } # -------------------------------------------------------------------------- # just print the version # -------------------------------------------------------------------------- sub version { warn "$program: version is \"$version\"\n" ; } # -------------------------------------------------------------------------- # create and display subsection index via pager # -------------------------------------------------------------------------- sub show_index { local($_); &load_index($_[0]); if ($#ssindex > ($rows - 4) && $isatty) { print "Hit for $#ssindex subsections via pager: "; $_ = ; local($SIG{'PIPE'}) = 'IGNORE'; if ($no_idx_file) { open (PAGER, "| $PAGER"); print PAGER @ssindex; close PAGER; } else { &run("$PAGER $idx_file"); } } else { print STDOUT @ssindex; } } # -------------------------------------------------------------------------- # find closest match on index selection in full index # -------------------------------------------------------------------------- sub find_index { local($manpage, $expr) = @_; local($_, @matches); &load_index($manpage); $expr =~ s!^/+!!; for (@ssindex) { s/^\s*\d+\s+//; s/\s+\d+\s*$//; } if ($expr > 0) { return $ssindex[$expr]; } else { $ssindex[0] = ''; if (@matches = grep (/^$expr/i, @ssindex)) { return $matches[0]; } elsif (@matches = grep (/$expr/i, @ssindex)) { return $matches[0]; } else { return ''; } } } # -------------------------------------------------------------------------- # read in subsection index into @ssindex # -------------------------------------------------------------------------- sub load_index { local($manpage) = @_; $no_idx_file = 0; &getidx($manpage) if $#saveidx < 0; @ssindex = @saveidx; die "should have have an index for $manpage" if $#saveidx < 0; } # -------------------------------------------------------------------------- # create subsection index is out of date wrt source man page # -------------------------------------------------------------------------- sub getidx { local($manpage) = @_; local($is_mh); local($_, $i, %lines, %sec, $sname, @snames); local(@retlist, $maxlen, $header, @idx , @st_man, @st_idx); # global no_idx_file, idx_file ( $idx_file = $manpage ) =~ s:/man(\w+)(\.gz)?/:/idx$1/:; $idx_file =~ s/\.gz//; require 'stat.pl' unless defined &Stat; @st_man = &Stat($manpage); @st_idx = &Stat($idx_file); if ($st_man[$ST_MTIME] < $st_idx[$ST_MTIME]) { unless (open idx_file) { warn "$program: can't open $idx_file: $!\n"; return (); } @retlist = ; close idx_file; return @saveidx = @retlist; } if (!open(manpage, $manpage =~ /\.gz/ ? "$ZCAT < $manpage|" : $manpage)) { warn "$program: can't open $manpage: $!\n"; return (); } warn "building section index\n" if $debug; ($header = "Subsections in $manpage") =~ s!/?\S*/!!; $maxlen = length($header); push(@snames, $sname = 'preamble');; # MH has these alias for sections and subsectdions if ($is_mh = $manpage =~ m:/mh/:) { %mh_sections = ( "NA", "NAME", "SY", "SYNOPSIS", "DE", "DESCRIPTION", "Fi", "FILES", "Pr", "PROFILE", "Sa", "SEE ALSO", "De", "DEFAULTS", "Co", "CONTEXT", "Hh", "HELPFUL HINTS", "Hi", "HISTORY", "Bu", "BUGS" ); $mh_expr = join('|',keys %mh_sections); } while () { if ($is_mh && /^\.($mh_expr)/) { $sname = $mh_sections{$+}; $maxlen = length($sname) if $maxlen < length($sname); push(@snames,$sname); } if (/^\.(?:s[sh]|ip)\s+(.*?)(\s*\d+)?$/i ) { $line = $_; $_ = $1; s/"//g; s/\\f([PBIR]|\(..)//g; # kill font changes s/\\s[+-]?\d+//g; # kill point changes s/\\&//g; # and \& s/\\\((ru|ul)/_/g; # xlate to '_' s/\\\((mi|hy|em)/-/g; # xlate to '-' s/\\\*\(..//g; # no troff strings s/\\//g; # kill all remaining backslashes $sname = $_; $_ = $line; $maxlen = length($sname) if $maxlen < length($sname); push(@snames,$sname); } $lines{$sname}++; } $mask = sprintf("%%2d %%-%ds %%5d\n", $maxlen + 2); $no_idx_file = $idx_file eq $manpage || !open(idx, ">$idx_file"); $line = sprintf(sprintf("Idx %%-%ds Lines\n", $maxlen + 2), $header); @retlist = ($line); for ($i = 1; $i <= $#snames; $i++) { push(@retlist, sprintf($mask, $i, $snames[$i], $lines{$snames[$i]})); } if (!$no_idx_file) { warn "storing section index in $idx_file\n" if $debug; print idx @retlist; close idx; } return @saveidx = @retlist; } # -------------------------------------------------------------------------- # interrupted -- unlink temp page # -------------------------------------------------------------------------- sub tmp_cleanup { warn "unlink $tmppage\n" if $debug; unlink $tmppage; die "Interrupted!\n"; } #-------------------------------------------------------------------------- # in case we die writing to the pipe # -------------------------------------------------------------------------- sub PLUMBER { warn "unlink $tmppage\n" if $debug; unlink $tmppage; die "Broken pipe while reformating $manpage\n" ; } # -------------------------------------------------------------------------- # print line with C\bC style emboldening # -------------------------------------------------------------------------- sub print { local($_) = @_; if (!$inbold) { print; } else { local($last); for (split(//)) { if ($last eq "\033") { print; } else { print /[!-~]/ ? $_."\b".$_ : $_; } $last = $_; } } } # -------------------------------------------------------------------------- # reformat the page with nroff, fixing up bold escapes # -------------------------------------------------------------------------- sub reformat { local($_) = @_; local($nroff, $col); local($inbold) = 0; local($status); if ($NROFF_CAN_BOLD) { return &run($_); } &unshell($_); ($nroff, $col) = m!(.*)\|\s*($COL.*)!; if ( $opt_n ) { warn "$_\n"; return 1; } warn "$nroff | (this proc) | $col\n" if $debug; open (NROFF, "$nroff |"); $colpid = open (COL, "| $col"); select(COL); while () { s/\033\+/\001/; s/\033\,/\002/; if ( /^([^\001]*)\002/ || /^([^\002]*)\001/ ) { &print($1); $inbold = !$inbold; $_ = $'; redo; } &print($_); } close NROFF; if ($?) { warn "$program: \"$nroff\" failed! status=$?" if $debug; $status++; } close COL; if ($?) { warn "$program: \"$col\" failed! status=$?" if $debug; $status++; } select(STDOUT); $status == 0; } # -------------------------------------------------------------------------- # prompt for if we're a tty and have a non-stopping pager # -------------------------------------------------------------------------- sub prompt_RTN { local($why) = $_[0] || "to continue"; return unless $isatty; unless ($is_less && $ENV{'LESS'} !~ /E/) { print "Hit $why: "; $_ = ; } } # -------------------------------------------------------------------------- # dynamically determine MANPATH (if unset) according to PATH # -------------------------------------------------------------------------- sub config_path { local($_); # for traversing $PATH local(%seen); # weed out duplicates local(*manpath); # eventual return values if (defined $ENV{'MANPATH'}) { $manpath = $ENV{'MANPATH'}; } else { for (split(/:/, $ENV{'PATH'})) { next if $_ eq '.'; next if $_ eq '..'; s![^/+]*$!man! && -d && !$seen{$_}++ && push(@manpath,$_); } $manpath = join(':', @manpath); } # $manpath; # last expr is assign to this anyway } # -------------------------------------------------------------------------- # grep through MANPATH for a pattern # -------------------------------------------------------------------------- sub grepman { local($code, $_, $dir, $root, $FILE, $found); $code = "while () {\n"; for (@ARGV) { s#/#\\/#g; $code .= < ) { unless (chdir($dir)) { warn "can't chdir to $root/$dir: $!" if $debug; next; } unless (opendir(DIR, '.')) { warn "can't opendir $root/$dir: $!" if $debug; next; } foreach $FILE ( readdir(DIR) ) { next if $FILE eq '.' || $FILE eq '..'; $path = "$root/$dir/$FILE"; if ($FILE !~ /\S\.\S/ || !-f $FILE) { print "skipping non-man file: $path\n" if $debug; next; } if ($FILE =~ /\.gz$/ || $dir =~ /\.gz$/) { $FILE = "$ZCAT $FILE|"; } print STDERR "grepping $path\n" if $debug; unless (open FILE) { warn "can't open $root/$dir/$FILE: $!"; $status++; next; } eval $code; die $@ if $@; } unless (chdir ($root)) { warn "can't return to $root: $!"; $status++; last; } } } exit ($status || !$found); }