# -*-perl-*-
#
# $Id: SWMan.pm,v 1.1 1999/07/30 18:46:37 mdw Exp $
#
# Display and other fiddling of manual pages
#
# (c) 1999 EBI
#

#----- Licensing notice -----------------------------------------------------
#
# This file is part of sw-tools.
#
# sw-tools is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 
# sw-tools is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with sw-tools; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

#----- Revision history -----------------------------------------------------
#
# $Log: SWMan.pm,v $
# Revision 1.1  1999/07/30 18:46:37  mdw
# New CGI script for browsing installed software and documentation.
#

#----- Package preamble -----------------------------------------------------

package SWMan;

use IO;
use POSIX;
use DirHandle;
use Exporter;

use SWConfig;
use SWCGI qw(:DEFAULT :layout);

@ISA = qw(Exporter);
@EXPORT_OK = qw(subst check);

#----- Useful functions -----------------------------------------------------

%mandb = ();

# --- @mans(SECTION)@ ---
#
# Returns a reference to a list of manual pages in the given section.

sub mans($) {
  my ($sec) = @_;
  $mandb{$sec} and return $mandb{sec};

  my $d = DirHandle->new("$C{prefix}/man/man$sec") or return undef;
  my @f;
  while (my $f = $d->read()) {
    push(@f, $f);
  }
  $mandb{$sec} = \@f;
  return \@f;
}

# --- @check(NAME, SECTION)@ ---
#
# See whether there's a manpage called NAME with section SECTION.

sub check($$) {
  my $pre = "$C{prefix}/man/man";
  my ($man, $sec) = @_;
  my $f;

  # --- Quick check for obvious things ---

  my ($base) = ($sec =~ /^(\d+)/);
  $f = "$pre$base/$man.$sec";
  -r $f and return $f; $f .= ".gz"; -r $f and return $f;

  # --- Snarf the appropriate filename list ---

  my $fs = mans($base) or return undef;
  foreach my $f (@$fs) {
    $f =~ /^$man\.$sec\w+(\.gz)?$/ and return "$C{prefix}/man/man$base/$f";
  }
  return undef;
}

# --- @subst(STRING, NAME, SECTION)@ ---
#
# If NAME(SECTION) is a manual page, return the STRING appropriately wrapped
# in an anchor element; otherwise return it unmolested.

sub subst($$$) {
  my ($s, $n, $sec) = @_;
  check($n, $sec) and
    return "<a href=\"$ref?act=man&man=$n&sec=$sec\">$s</a>";
  return "$s";
}

# --- @sections()@ ---
#
# Return a list of manual sections.

@sectionlist = ();

sub sections() {
  return @sectionlist if @sectionlist;
  my @s = ();
  my $d = DirHandle->new("$C{prefix}/man") or
    barf("couldn't open man directory: $!");
  while ($f = $d->read()) {
    next if $f !~ /^man/ || !-d "$C{prefix}/man/$f";
    push(@s, $');
  }
  return (@sectionlist = sort(@s));
}

#----- Display a quick section index ----------------------------------------

sub quickie {
  print "Quick section index:\n";
  foreach $s (sections()) {
    print "<a href=\"$ref?act=man&sec=$s\">$s</a>\n";
  }
}

#----- Display indices for manual sections ----------------------------------

sub dosection($) {
  my ($sec) = @_;
  my @m = ();

  barf("illegal section `$sec'") if $sec =~ m:/:;

  # --- Snarf the list of manual pages in this section ---

  {
    my $d = DirHandle->new("$C{prefix}/man/man$sec") or
      barf("couldn't read directory `$C{prefix}/man/man$sec': $!");
    while (my $f = $d->read()) {
      my ($man, $sec) = split(/\./, $f, 3);
      push(@m, "$man($sec)") if $sec;
    }
  }

  # --- Sort and emit the index ---

  print("<h4>Section $sec</h4>\n<table>");

  {
    my $col = 0;
    foreach my $m (sort(@m)) {
      my ($man, $sec) = $m =~ /^(.*)\((.*)\)$/;
      $col or print("<tr>\n");
      print("<td><a href=\"$ref?act=man&man=$man&sec=$sec\">$m</a>\n");
      $col = ($col + 1) % 5;
    }
  }

  print("</table>\n");
}

sub section {
  my $sec = $Q{"sec"};
  header("Index of manual section $sec");
  quickie(); print "<hr>\n";
  dosection($sec);
  print "<hr>\n"; quickie();;
  footer();
}

sub index {
  header("Manual page index");
  print("<h3>Manual page index</h3>\n");
  foreach my $s (sections()) { dosection($s); }
  footer();
}  

#----- Display a manual page ------------------------------------------------

sub man {
  my ($man, $sec) = ($Q{"man"}, $Q{"sec"});

  $sec or &index(), return;
  $man or &section(), return;

  my $file = check($man, $sec) or
    barf("no manual page $man($sec)");
  barf("illegal filename `$file'") if $file =~ m:\./:;

  # --- Read the manual page ---

  my $p = IO::Pipe->new();
  my $kid = fork();
  defined($kid) or barf("fork failed: $!");
  if ($kid == 0) {
    $p->writer();
    dup2($p->fileno(), 1);
    chdir("$C{prefix}/man");
    if ($file =~ /\.gz$/) {
      $pp = IO::Pipe->new;
      $kkid = fork();
      defined($kid) or exit(127);
      if ($kkid == 0) {
	$pp->writer();
	dup2($pp->fileno, 1);
	exec("gzip", "-dc", $file);
	exit(127);
      }
      exec("nroff", "-man");
    } else {
      exec("nroff", "-man", $file);
    }
    exit(127);
  }
  $p->reader();

  # --- Spit out the manual page now ---

  header("Manual page $Q{man}($Q{sec})");
  quickie(); print "<hr>\n";
  print "<pre>\n";
  while (my $line = $p->getline()) {
    chomp $line;

    # --- Grind through the line turning it into HTML ---

    {
      my $state = "";
      my $l = "";

      for (my $i = 0; $i < length($line); $i++) {
	my $ch = substr($line, $i, 1);
	my $nstate = "";

	# --- Sort out overstriking ---

	if (substr($line, $i + 1, 1) eq "") {
	  my ($italic, $bold) = (0, 0);
	  $ch eq "_" and $italic = 1;
	  $ch eq substr($line, $i + 2, 1) and $bold = 1;
	  $ch = substr($line, $i + 2, 1);
	  while (substr($line, $i + 1, 1) eq "") { $i += 2; }
	  if ($italic && $bold) {
	    $nstate = $state ? $state : "b";
	  } elsif ($italic) {
	    $nstate = "i";
	  } elsif ($bold) {
	    $nstate = "b";
	  }
	}
	$state ne $nstate and
	  $l .= ($state && "</$state>") . ($nstate && "<$nstate>");
	$state = $nstate;

	# --- Translate the character if it's magical ---

	$ch eq "&" and $ch = "&amp;";
	$ch eq "<" and $ch = "&lt;";
	$ch eq ">" and $ch = "&gt;";
	$l .= $ch;
      }
      $state and $l .= "</$state>";

      # --- Now find manual references in there ---
      #
      # I don't use /x regexps very often, but I think this is a good excuse.

      $l =~ s! ((?:\<[bi]\>)*)		# Leading highlighting tags
	       ([-_.\w]+)		# Various plausible manual name chars
	       ((?:\</[bi]\>)*		# Closing highlighting tags
		(?:\<[bi]\>)*		# And opening ones again
		\(			# An open parenthesis
		(?:\<[bi]\>)*)		# More opening highlights
	        (\d+\w*)		# The section number
	        ((?:\</[bi]\>)*		# Close highlights
		 \)			# Close parens
                 (?:\</[bi]\>)*)	# Finally more closing tags
	     ! subst($&, $2, $4) !egx;

      # --- And email and hypertext references too ---

      $l =~ s! ((?:\<[bi]\>)*)		# Leading highlighting
	       ((?:http|ftp)		# A protocol name
		://			# The important and obvious bit
		[^]&)\s]+		# Most characters are allowed
		[^]&).,\s\'\"])		# Don't end on punctuation
  	       ((?:\</[bi]\>)*)		# Closing tags, optional
  	     !<a href="$2">$&</a>!gx;

      $l =~ s! ((?:\<[bi]\>)*)
               ( [^\s()&;{}<>,.\`\"] [^\s()&;{}<>\`\"]* \@
                 [^\s()&;{}<>\'\"]* [^\s()&;{}<>.,\'\"])
               ((?:\</[bi]\>)*)
             !<a href="mailto:$2">$&</a>!gx;

      # --- Done! ---

      print $l, "\n";
    }
  }

  # --- Done all of that ---

  print "</pre>\n";
  $p->close();
  waitpid($kid, 0);
  barf("nroff failed (exit status $?)") if $?;
  print "<hr>\n"; quickie();;
  footer();
}

#----- Register actions -----------------------------------------------------

$main::ACT{"man"} = \&man;

#----- That's all, folks ----------------------------------------------------

1;
