From: boone@convex.cl.msu.edu (Dennis Boone)
Newsgroups: comp.infosystems.gopher
Subject: Updated gophertree (Road Map) utility
Date: 18 Aug 92 21:06:08 GMT
Organization: Michigan State University, East Lansing

The shar file appended to this message contains a revised version of
my gophertree utility.  I've fixed a few bugs, and added a few features.
See the comments for a blow-by-blow.

Usage:  gophertree [-d -bn -ln -rn] host port [path]

-d causes gophertree to list only directories
-b limits the number of items listed to n (n of each type)
-l sets page length
-r limits how many levels gophertree will descend into the tree

It should be more graceful about handling CNAMEd machines now.  (In English:
if your gopher runs on serv2.cl.msu.edu, but is also called gopher.msu.edu,
then you can now get away with pointing gophertree at the gopher... address.)
The listing will still contain the name the server returns, which probably
ISN'T the alias.

It would be nice if this beast had per-type limits on how many items it will
print.  <sigh>  If you have suggestions on (1) command line syntax, and
(2) how to handle the ellipsis at the end of a truncated section, without
(3) rudely sorting the directory, please (4) send them to me.

Warning: this thing is getting to look more and more like Farhad&Mark's
favorite hairball.  Besides, it's written in everyone's favorite write-only
language, Perl.  Not for the weak of stomach.  You've been warned.

Comments, flames, requests to drbmaint@ibm.cl.msu.edu

----------------------------------snip----------------------------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	gophertree
# This archive created: Tue Aug 18 15:43:01 1992
export PATH; PATH=/bin:$PATH
if test -f 'gophertree'
then
	echo shar: will not over-write existing file "'gophertree'"
else
cat << \SHAR_EOF > 'gophertree'
#!/usr/local/bin/perl
# gophertree v1.0
# Prints pretty indented listings of a Gopher menu tree
# Copyright (C) 1992, Trustees of Michigan State University
# 
# Modifications:
# Original author unknown
# 07/07/92 Boone      Major conversion from gopherls
# 08/14/92 Boone      Fixes: 
#                        added code to allow command line limit on recursion 
#                           depth
#                        quit indenting after 15 levels to avoid filling the
#                           title field with spaces
#                        changed to use IP address instead of hostname when
#                           checking for off-host links; this should make
#                           aliased machine names (e.g. gopher.someschool.edu)
#                           work much better
#                     Enhancements:
#                        Added option to list only directories
#                        Changed limit on number of items listed to apply to
#                           all types except directories, instead of just
#                           files; still no per-type limits though
#                        Changed command line processing to use Getopts, 
#                        allowing better option processing
# End Modifications

require "getopts.pl";

sub dokill
{
    kill 9,$child if $child;
}

sub Opengopher
{
    $sockaddr++;
    local($them,$port) = @_;	
    $them = 'localhost' unless $them;

    $AF_INET = 2;
    $SOCK_STREAM = 1;

    $SIG{'INT'} = 'dokill';

    $sockaddr = 'S n a4 x8';

    chop($hostname = `hostname`);

    ($name,$aliases,$proto) = getprotobyname('tcp');
    ($name,$aliases,$port) = getservbyname($port,'tcp')
	unless $port =~ /^\d+$/;;
    ($name,$aliases,$type,$len,$thisaddr) = gethostbyname($hostname);
    ($name,$aliases,$type,$len,$thataddr) = gethostbyname($them);

    $this = pack($sockaddr, $AF_INET, $sockaddr, $thisaddr);
    $that = pack($sockaddr, $AF_INET, $port, $thataddr);

    # Make the socket filehandle.
    socket(S, $AF_INET, $SOCK_STREAM, $proto) || die $!;

    # Give the socket an address.
    bind(S, $this) || die $!;

    # Call up the server.
    connect(S,$that) || die $!;

    # Set socket to be command buffered.
    select(S); $| = 1; select(STDOUT);

}

sub GetList 
{
	local($CurrentHost, $Port, $Path, $indent) = @_;
	local(@dirx, $Name, $Obj, $fname, $ftype, $fhost, %i, $truncated);
	&Opengopher($CurrentHost, $Port);
	print S "$Path\n";
	@dirx = <S>;
	close(S);
	$truncated = 0;
	foreach (@dirx) 
	{
		last if /^\./;
		chop; chop;
		($ObjName, $Path, $CurrentHost, $Port) = split('\t', $_);
		$Name = substr($ObjName, 1);
		$Obj = substr($ObjName, 0, 1);
		$fname = $indent . $Name;
		$ftype = "";
		$ftype = "File" if ($Obj eq "0");
		$ftype = "Dir" if ($Obj eq "1");
		$ftype = "Phone" if ($Obj eq "2");
		$ftype = "Error" if ($Obj eq "3");
		$ftype = "MacHqx" if ($Obj eq "4");
		$ftype = "PcHqx" if ($Obj eq "5");
		$ftype = "Uue" if ($Obj eq "6");
		$ftype = "Index" if ($Obj eq "7");
		$ftype = "Telnet" if ($Obj eq "8");
		$ftype = "Bin" if ($Obj eq "9");
		$ftype = "File" if ($Obj eq "R");
		$ftype = "TN3270" if ($Obj eq "T");
		$ftype = "File" if ($Obj eq "e");
		$ftype = "Ftp" if ($Obj eq "f");
		$ftype = "HTML" if ($Obj eq "h");
		$ftype = "Info" if ($Obj eq "i");
		$ftype = "Mail" if ($Obj eq "m");
		$ftype = "Sound" if ($Obj eq "s");
		$ftype = "Index" if ($Obj eq "w");
		$fhost = $CurrentHost;

		$writeme = 1;
		if ((! $opt_d) && ($Obj ne "1") && ($i{$Obj} > $breaklong))
		{
			$writeme = 0;
			$truncated = 1;
		}
		if ($Obj eq "i")		{ $writeme = 0; }
		if ($opt_d && ($Obj ne "1"))	{ $writeme = 0; }
		if ($writeme)			{ write; }

		if ($hostable{$CurrentHost} eq "")
		{
			$hostable{$CurrentHost} =
				unpack("L", (gethostbyname($CurrentHost))[4]);
		}

		if (($Obj eq "1") && 
			($hostable{$CurrentHost} eq $hostable{$firsthost}) && 
			(($Port != $firstport) || ($Path != ""))) 
		{
			$newindent = $indent;
			if ($depth < $nomoreindent)
			{
				$newindent .= $iadd;
			}
			$depth++;
			if ($depth <= $maxdepth)
			{
				&GetList($CurrentHost, $Port, 
					$Path, $newindent);
			}
			$depth--;
		}

		$i{$Obj}++;
	}
	if ($truncated) 
	{
		print "$indent    . . .\n";
		--$-;
	}
}

# **************************************************************************
# * Main
# **************************************************************************

# Parse command line

	&Getopts("b:l:dr:");
	if ($#ARGV < 1) 
	{
		print "Usage: gophertree [-d -bn -ln -rn] host port path\n";
		exit(1);
	}

	$firsthost = $CurrentHost = $ARGV[0];
	$firstport = $Port = $ARGV[1];
	$Path = "";
	if ($#ARGV == 2) 
	{
		$Path = $ARGV[2];
	}

# Initialize some variables

	($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
	$today = $mon+1 . "/" . $mday . "/" . $year;

	$^ = 'TOP';         # Report header format
	$~ = 'STDOUT';      # Report body format
	$indent = "";       # Goes in front of description to indicate treeness
	$depth = 1;         # How deep into the maze are we?
	$hostable{$firsthost} = unpack("L", (gethostbyname($firsthost))[4]);

# CHANGE--User-configurable defaults

	$breaklong = 15;    # CHANGE--Where to break long lists
	$= = 55;            # CHANGE--Lines per page
	$iadd = "  ";       # CHANGE--Amount to indent each new level
	$nomoreindent = 15; # CHANGE--How far down before we quit indenting
	$maxdepth = 999;    # CHANGE--How deep to go before "pruning" layers

# Stuff command line changes into the config variables

	if ($opt_b) { $breaklong = $opt_b; }
	if ($opt_l) { $= = $opt_l; }
	if ($opt_r) { $maxdepth = $opt_r; }

# Real work

	&GetList($CurrentHost, $Port, $Path, $indent);
	exit(0);

#
# Formats
#

format TOP =

MSU Gopher Road Map                      Host: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$firsthost
Last Update: @<<<<<<<                                               Page: @>>
$today $%

Lev Name                                       Type   Destination
--- ------------------------------------------ ------ -------------------------
.

format STDOUT =
@>> @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<
$depth $fname $ftype $fhost
.
SHAR_EOF
chmod +x 'gophertree'
fi # end of overwriting check
#	End of shell archive
exit 0
----------------------------------snip----------------------------------
--
 == Frustration is portable. ==
Newsgroups: comp.infosystems.gopher