v06i045: Help programs (help), Part1/2

sources-request at mirror.UUCP sources-request at mirror.UUCP
Thu Jul 10 03:52:49 AEST 1986


Submitted by: talcott!seismo!wucs!nz (Neal Ziring)
Mod.sources: Volume 6, Issue 45
Archive-name: help/Part1

[  This first part contains program and documentation; the next
   part contains a small documentation tree.  --r$ ]

#! /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:
#	Makefile
#	README
#	help.1
#	help.h
#	help.c
# This archive created: Tue Jun  3 08:20:04 1986
# By:	Neal Ziring (Washington U. Engineering Computer Lab)
export PATH; PATH=/bin:$PATH
if test -f 'Makefile'
then
	echo shar: will not over-write existing file "'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
#
#  help Makefile, 1.1	7/27/84
#
CFLAGS= -O
SRCS= help.h help.c
DESTDIR=

help:	help.o 
	${CC} ${CFLAGS} help.o -o help

install: help
	install -s -c help $(DESTDIR)/usr/local/bin

help.o: help.h /usr/include/sys/file.h /usr/include/sys/param.h

ci:
	ci -l ${SRCS}

clean:  
	rm -f *.o help

SHAR_EOF
fi # end of overwriting check
if test -f 'README'
then
	echo shar: will not over-write existing file "'README'"
else
cat << \SHAR_EOF > 'README'

 Help is a facility for climbing a tree structure.

 Help comes in two types: a help topic with subtopics, or a help topic
 without any subtopics.  

	Topics that have no subtopics reside in the directory they are
	a subtopic of, with filenames `topicname.HLP'

	Topics that have subtopics of their own reside in directories
	which are subdirectories of the help topic of which they are
	subtopics, and have filenames `.HLP'  Thier subtopics have the
	filenames `topicname.HLP'.  A directory for a subtopic just
	has the subtopic's name as its directory name.

	For instance, if you had a help topic "ls" with some subtopics,
	/usr/help/ls would be a directory, containing the files

		/usr/help/ls/.HLP	main help text
		/usr/help/ls/.MANUAL	shell script for accessing man page
		/usr/help/ls/options.HLP	help for ls options
		/usr/help/ls/output.HLP		help for ls output format

	If you had a simple help for the who command, "who", it would comprise
	the following files at the top level:

		/usr/help/who.HLP	help text for who(1)
		/usr/help/who.MANUAL	script for accessing who(1) man page

To make help, cd to the directory, and type: 

	% make help

or

	% make install

if you are ambitious.  The source for help is one C module, help.c, with a 
header file, help.h.

Help uses the more(1) program to read help texts.  Make sure that more
resides on the path specified by the VIEWPROGRAM defined symbol in help.h.

SHAR_EOF
fi # end of overwriting check
if test -f 'help.1'
then
	echo shar: will not over-write existing file "'help.1'"
else
cat << \SHAR_EOF > 'help.1'
.TH HELP 1 "29 November 1984"
.UC 4
.SH NAME
help \- user-friendly documentation reader
.SH SYNOPSIS
.B help
[ -dlqCn [ \fIoption-args\fP ] ]\ \ \ [ \fItopic-path\fP ]
.br
.SH DESCRIPTION
.I Help
is a documentation reader based roughly on the VMS(tm) help facility.
Documentation texts are arranged in a tree, and the user reads them with
.I more(1).
.I Help
allows documentation to be treated as an n-tree, the
structure of the help tree is the structure of the directory tree in
which the help files reside.  Further, 
.I help
supports a user-translucent cross-reference facility.
.PP
The help program is invoked from the shell as shown below.
.PP
	help  [ -dlqCn [option-args] ] topic sub-topic sub-sub-topic ...
.PP
.I Topic
may be any top-level help file name.  A 
.I topic-path
is a path of topics and their subtopics, down into the tree.
Topic and subtopic names may be abbreviated, but no wild-cards
are recognized.
If any switches are present they all must follow a single dash,
and be the first option on the command line.
.PP
The options are:
.TP 5
.B d
Next argument is the help root directory.  Default is /usr/help.
.TP 5
.B l
Use list-only mode.  Only the list of subtopics for the command-line
help path is printed.  Interactive mode is not used.
.TP 5
.B q
Use text-only mode.  Only the text of the help path is printed.
Interactive
mode is not used.
.TP 5
.B C
Forces multi-column output of subtopic lists.
This is the default for interactive mode.
.TP 5
.B n
Force one-topic-per-line output of the subtopics lists.
This is the default for list mode.
.PP
The help interactive mode asks the user what help he would like next.
The prompt looks like this:
.PP
Help Topic?
.PP
or like this:
.PP
vi commands deletion subtopic?
.PP

Where "vi commands deletion" is the 
.I "topic-path"
followed to reach this prompt.
The path is printed for each prompt, and as a header for each help
text.
.PP
The following commands are meaningful in to the "topic?" prompt:
.TP 5
.B ?
Print current help and subtopic-list again.
.TP 5
.B ".<topic-name>"
Do a UNIX Programmer's Manual lookup (if possible).  If other characters
follow the dot, they are taken as the filename for the .MANUAL file.
(e.g. command '.who' looks for file who.MANUAL)
.TP 5
.B "Return <CR>"
Exit this level of help.  If at top level, goes back to calling
program.
.TP 5
.B "<subtopic-name>"
Read the documentation for this subtopic, or all subtopics for which
this is a legal abbreviation.  Wildcards are not permitted, but a
short abbreviation matches everything longer.
.TP 5
.B "#"
List the available subtopics again.  This lists the subtopics of this
node, and re-prompts the user.  The help text is NOT re-printed.
.TP 5
.B "$<topicname>"
List some information on the files that make up this topic.  Files
that match the abbreviation of `topicname' are looked up with 
.I file(1).
.PP
On 4.2BSD Unix systems, 
.I help
supports a limited form of Twenex-style name-completion.
The ESCape character invokes completion of a topic name, the ^D character
produces an alphabetized list of possible completions.
Completion is called ``limited'' because only a unique abbreviation
can be filled out with the ESCape key (unlike the Twenex \fIcsh\fP).
.sp 1
.PP
.B "HELP FILES"
.PP
Help files are the text and directories of the 
.I help
documentation tree.
There are four kinds of help files: topic directory, help file,
subtopic file, and manual file.
.PP
A topic directory is a directory with the name of the subtopic it
describes (e.g.  /usr/help/ls/options is a topic directory about the
topic of options for 
.I ls(1)).
.PP
A help file is a file of formatted text with the name ".HLP".  It
resides in the topic directory for which it is the description text.
.PP
A subtopic file has the name  "subtopic-name.HLP".  It contains the
text for a subtopic which does not itself have any subtopics.
.PP
A manual file contains the commands to be given to 
.I sh(1)
when the user gives the lookup manual command (.).
This file has name <topic>.MANUAL.  Therefore the manual command
script for the current node is just ".MANUAL".
.br
A manual file resides in the same topic directory as the help text 
whose corresponding
manual it accesses.  
The manual may be a shell script or a binary file, but it must be marked
executable by all users.
.PP
A help
.I "cross-reference file"
has the extension ".XREF".  A cross-reference is not listed on the 
subtopics list, even though it resides in the same directories as the
other help files.
There are two kinds of cross reference files; 
.I direct
references and
.I apology
reference.  An apology reference is just some text, apologizing for the 
lack of documentation on a particular topic.
A direct reference is a one-line file, the first character of which must
be the flag '@'.  The rest of the line is a sequence of words, separated by
spaces, specifing the cross-reference help path.  For instance, the file
/usr/help/lpr.XREF might contain the text:
.PP
\ @ printing lpr
.LP
Therefore, lpr.XREF is a direct cross-reference file.
.sp 1
.PP
The usual way of calling help is to just type 
.sp 1
%
.I help
.PP
in the shell.  This brings up help interactive mode at the top level
of topics.  The user can always abort with ctrl/D.
.SH AUTHOR
Neal Ziring with George Robbert, Washington University in St. Louis.
.SH FILES
/usr/local/bin/help
.br
/usr/help*
.br
/usr/ucb/more
.br
/bin/sh
.SH "SEE ALSO"
more(1), man(1), sh(1), system(2), access(2)
.SH DIAGNOSTICS
Error messages are printed whenever a request is made to get
documentation that does not exists, or is not accessible.  
.PP
Signals are not trapped.
.SH BUGS
The file naming conventions are somewhat strict.
.PP
No wildcards allowed in topic names.  (Could have made them reg. exp.
but that would be confusing to novices.)
.PP
Terminal type is not checked, the display is assumed to be at least 76
columns wide.
.PP
There is no way for the user to specify another program for viewing
files ("more -d" is always used.)  This is coded into the header file.
.PP
The algorithm used for selecting amoung ambiguous help paths is
non-obvious, and can be confusing when too-short abbreviations are used.

SHAR_EOF
fi # end of overwriting check
if test -f 'help.h'
then
	echo shar: will not over-write existing file "'help.h'"
else
cat << \SHAR_EOF > 'help.h'

	/*
         *	help.h: header file for the VMS-help emulator
	 */

#include <stdio.h>
#include <ctype.h>
#define BSD
/* #define USG */
#ifdef USG
#include <sys/types.h>
#endif
#include <sys/file.h>
#include <sys/param.h>
#include <sys/dir.h>
#ifdef BSD
#include <sys/ioctl.h>
#include <sys/signal.h>
#endif


#define iswhite(_c)       (_c ==' ' || _c =='	' || _c =='\n' || _c =='\r')
#define PROMPT " Topic? "
#define SUBPROMPT "subtopic? "
#define HELPEX	".HLP"				/* help extension */
#define	MANEX	".MANUAL"			/* manual filename*/
#define MAN_SUBEX "/.MANUAL"                    /* other man filename */
#define XREFEX  ".XREF"                         /* cross reference */
#define INFOHEAD "file "
#define INFOTAIL " | sed 's/^/     /'"

#define LF '\n'
#define RET '\r'
#define ESC '\033'

#define	MAXLINELEN	128
#define	MAXNAMELEN	 24		/* max length of NAME of topix */
#define	MAXNAMES	256		/* maximum number of subtopics */
#define COLUMNSPACE	  3
#define TERMWID		 78
#define HELPDIR "/usr/help"
#define VIEWPROGRAM  "/usr/ucb/more"    /* program to look at text */
#define VIEWPROGOPTS1 "-d"
#define VIEWPROGOPTS2 "-18"
#define SHELLPROG    "/bin/sh"		/* program to execute text */
#define SHELLOPTS    "-c"

char 	progname[MAXNAMELEN];
char	olddir[MAXNAMELEN + 68];
char	newdir[MAXNAMELEN + 68];
char	*helpdir;
char	**environ;	/* environment, for forks */
char    gbl_ppt[MAXLINELEN];

int	dumb_flag;
int	list_flag;
int	col_flag;
int	frst_flag;

char	*helpcmds[] =
	{ "Return - Exit this level of help",
	  "*	  - Print this message",
	  "?	  - Reprint this help and its subtopics",
	  "#	  - Reprint just subtopics of this help",
	  ".	  - Look at current manual page, if any",
	  "$<topic>	- Get information on topic files",
	  ".<topic>	- Look at topic manual page, if any",
	  "<topic> 	- Look at help for subtopic `topic'" ,
	  NULL
	};

#ifdef BSD
struct tchars  *init_tchars = NULL;
struct tchars  *here_tchars = NULL;

#define Set_Tc_Init(FD) \
        ((init_tchars==NULL)?(0):(ioctl(FD,TIOCSETC,init_tchars)))
#define Set_Tc_Here(FD) \
        ((here_tchars==NULL)?(0):(ioctl(FD,TIOCSETC,here_tchars)))

int is_tty = 0;
char *read_resp();

#define ESC_COMP       1
#define CTRLD_COMP    -1
#define SPEC_CHARS  ".$?*#"

char gbl_match[MAXNAMELEN];

#else
#define Set_Tc_Init  1
#define Set_Tc_Here  0
#endif
SHAR_EOF
fi # end of overwriting check
if test -f 'help.c'
then
	echo shar: will not over-write existing file "'help.c'"
else
cat << \SHAR_EOF > 'help.c'
#include "help.h"
#define MAXMATCHES	24

 /* ************************************************************
  * 			H   E   L   P    !!
  *		     =========================
  *
  * 	This program emulates the VMS help facility in the UNIX
  *  environment.  The main routine HELP1 looks up help and 
  *  subtopics for help.  The help texts for various topix 
  *  are tree-structured.  A directory is defined as a "help"
  *  directory, it has four types of files in it:
  *		main help text: 	.HLP
  *		manual page name:	.MANUAL
  *		subtopic texts:		<topicname>.HLP
  *		subtopic directories:	<topicname>
  *             subtopic cross-refs:    <topicname>.XREF
  *
  *	Subtopic names must start with an alphanumeric
  *  character.  Preferably all subtopics will start with
  *  lowercase letters.
  *
  *	The routine help1 is recursive, it descends the	tree
  *  structure.
  */

  main(argc, argv)
  	int argc;
	char *argv[];
  {
	int i,j,k;
	int longlen;
	char yesno[10];
	char *opts;
		
	strcpy(progname,argv[0]);
	helpdir = NULL;
	getwd(olddir);
	dumb_flag = 0;
	list_flag = 0;
	frst_flag = 1;
	col_flag  = 1;

	argc--;
	if (*(*++argv) == '-') {		/* must be options */
		for (opts = *argv; *opts != '\0'; opts++) {
			switch(*opts) {
			   case '-' :	
			   	break;

			   case 'd' :	
			   	if (argc > 1) {
					helpdir = *++argv;
					argc--;
				}
				break;

			   case 'l' :
			   	list_flag = 1;
				col_flag = 0;
				break;

			   case 'q' :
			   	dumb_flag = 1;
				break;

			   case 'C' :
			   	col_flag = 1;
				break;

			   case 'n' :
			   	col_flag = 0;

			   default:
			        fprintf(stderr,"%s: %c: bad option.\n",
					progname,*opts);
				break;
			}
		}
		argc--; argv++;
	}

	if (helpdir == NULL) helpdir = HELPDIR;

	if (chdir(helpdir) < 0) {
		fprintf(stderr,"%s: %s: help directory not found.\n",
			progname,helpdir);
		exit(1);
	}

#ifdef BSD
	init_funky();
#endif
	if (argc >= 1) {		/* treat vector as a help path */
#ifdef DEBUG
	    fprintf(stderr," help path vector, argc=%d, *argv=%s.\n",
		    argc,*argv);
#endif
	    help1( "", argv, 0, 0, 0);

	}
	else	help1( "", NULL, 0, 0, 0);

	exit(0);
 }




 /* **************************************************************
  * printhelp: given a string, pop .HLP on the end and do more(1).
  *
  *	This routine sends a help file to more(1).  A string
  *  is passed in, which is the name of a help.  If the string
  *  is nil, then just use the name HLP.
  *	The return value is -1 if no help file is accessible, 
  *  0 if the more(1) command was called okay with fkoff();
  */

  printhelp(hs, path)
  	char *hs;
  {
	char filename[MAXNAMELEN], comm[MAXNAMELEN + 20];

	if (hs == NULL) strcpy(filename,HELPEX);
	else if (strlen(hs) < 1) strcpy(filename, HELPEX);
	else {
		strcpy(filename, hs);
		strcat(filename,HELPEX);
	}

	if ( access(filename, R_OK) < 0 ) {
		printf("\n Sorry, no help text for %s.\n",
			(hs==NULL)?"this topic":hs );
		return(-1);
	}

	if (path != NULL) printf("\n HELP: %s\n",path);

	fkoff(VIEWPROGRAM,VIEWPROGOPTS1,VIEWPROGOPTS2,filename,NULL);

	return(0);
  }



/* *************************************************************
 * printtopix: print the topics available in this directory
 *		in a nice format.
 *
 *	This routine does a directory of help options, 
 *  and prints them out in a manner similar to that of ls(1).
 *  All filenames which start with anything other than numbers 
 *  or letters are not kept.  Extensions are stripped off.
 *	
 *	The number of subtopics found is returned, along with
 *  a pointer to a null-terminated vector of string pointers.
 *  If the mode is non-zero, it means just use the strings stored
 *  in topix.  If mode==0, then re-allocate space and re-check
 *  the directory.  If mode
 */

 printtopix( topix, mode, supress)
 	char *topix[];
	int mode, supress;
{
	int i,j,k,l,xrefs = 0;
	int  namewidth, totalnames;
	char *malloc(), *nbuf;
	char *thisname, *index();
	char *s1, *s2, **nxtname, *nxcnt;
	struct direct *readdir(), *filedat;
	DIR *dirp, *opendir();

	if ( mode ) {
		for(nxtname=topix, i=0; *nxtname++ != NULL; i++ );
		totalnames = i;
		goto inputtopix;
	}

	/* if mode is zero, then allocate space and search the directory */
	nbuf = malloc( MAXNAMES * MAXNAMELEN );
	nxcnt=nbuf;
	dirp = opendir(".");
	if (dirp == NULL) {
		fprintf(stderr,"%s: Cannot open help directory.\n",progname);
		return(-1);
	}

	for(nxtname=topix, i=0; i < (MAXNAMES-1) ; ) {
		filedat = readdir(dirp);
		if (filedat == NULL) break;
		thisname = filedat->d_name;
		if ( !(isalnum(*thisname)) )    /* if not in [0-9A-Za-z] */
				continue;	/* do the next one.      */
		*nxcnt = '\0';
		if ( (s1 = index(thisname,'.')) != NULL) {
			if (strcmp(s1, HELPEX) == 0) *s1 = '\0';
			else if ( strcmp(s1, MANEX) == 0) continue;
			else if ( strcmp(s1, XREFEX) == 0) {
			    /* mark xref with at sign in first char */
			    strcpy(nxcnt,"@");
			}
		}
		if (strlen(thisname) >= MAXNAMELEN - 1)
			*(thisname+MAXNAMELEN-1) = '\0';
		/* copy in data from this loop */
		if (*nxcnt == '\0') strcpy(nxcnt,thisname);
		else strcat(nxcnt,thisname);
		*nxtname++ = nxcnt; 
		/* update pointers for next loop */
		nxcnt += strlen(thisname) + ((*nxcnt=='@')?(2):(1));
		i++;
	}
	*nxtname++ = NULL;
	totalnames = i;
	closedir(dirp);

	if (totalnames == 0) return(0);
	
	/* sort the names in ascending order with exchange algorithm */
	for(i=0; i < totalnames-1; i++)
		for(j=i+1; j <totalnames; j++)
			if (strcmp(topix[i],topix[j]) > 0) {
				thisname = topix[i];
				topix[i] = topix[j];
				topix[j] = thisname;
			}
	    

 inputtopix:
	if (supress) return(totalnames);
	else {
	    if (col_flag) printf("\n Subtopics:\n");
	    printlist(topix, totalnames);
	    return(totalnames);
	}

}



 /* ****************************************************************
  * help1: descend recursive help tree and provide some
  *	     user services.
  *
  *	This routine is the heart of the new UNIX help facility.
  *  It climbs recursively around an n-tree of documentation files
  *  and directories.  The routine printhelp() outputs a file of
  *  help text, the routine printtopix prints out all subtopics.
  *
  *	This routine can operate in interactive mode, or not.  The
  *  basic cycle of operation is:
  *		if (not list-only-mode) print help.
  *		if (not quiet-mode)	print list.
  *		if (not interactive)	return.
  *		print prompt and do commands.
  *		return.
  *
  *	There are a number of commands available in the
  *  interactive mode, they are:
  *
  *		blank line:	up recursive level.
  *		subtopic name:	recurse to subtopic.
  *		?:		list topics again.
  *		*:		get list of commands
  *		$:		get info on topix
  *		^D:		quit help program.
  *		.:		man page (if any).
  *		#		list topics again
  *		
  */

  help1(ppt,svec,skip,xref,comp_flag)
  	char *ppt;
	char *svec[];
	short  skip;
	short  xref;       /* non-zero if doing xref */
	short  comp_flag;  /* non-zero if doing completions */
{                             /* this routine is too big, eh? */
	int i,j,k, err;
	int no_subs;
	char answer[MAXLINELEN];
	char fullpath[MAXNAMELEN + 29];
	char yesno[10];
	char *topix[MAXNAMES], *mx[MAXMATCHES];
	int  matchv();
	char *index(), *getenv();
	char *s1, *s2, *s3, *rest;
	char *wvec[MAXMATCHES];
	char cmdbuf[90];


	if (comp_flag) {
	    printtopix( topix, 0, 1);
	    if ( (k = matchv( *svec, topix, mx)) == 0) return(-1);
	    /* if there is more than one match, and we are not at end, flub */
	    if ( svec[1] != NULL  &&  k > 1 ) return(-1);
	    /* if we are not at end, go down one level */
	    if ( svec[1] != NULL ) {
		if ( chdir( mx[0] ) < 0) return(-1);
		else {
		    k = help1(ppt, svec+1, 0, 0, comp_flag);
		    chdir("..");
		    return(k);
		}
	    }
	    /* if we are at end, then do appropriate action on comp_flag */
	    else {
		strcpy(gbl_match, mx[0]);
		if ( comp_flag == CTRLD_COMP ) {
		    fputs("\n",stdout);
		    printlist(mx,k);
		}
	    }
	    return(0);
	}

	if ( svec != NULL  && *svec != NULL) {
	    printtopix( topix, 0, 1);
	    if ( (k = matchv(*svec, topix, mx)) == 0) {
		printf(" sorry, no direct help path to %s %s\n",ppt,*svec);
		if ( (k = xref_matchv(*svec, topix, mx)) == 0) {
		    printf("    no cross-references, either.\n\n");
		    if (strlen(ppt) == 0) help1(ppt,NULL,1,0, 0);
		    return(-1);
		}
		else {
		    do_xref(ppt,*svec,mx);
		    if (strlen(ppt) == 0) help1(ppt,NULL,1,0, 0);
		    return(-1);
		}
	    }

	    for(i = 0, j = 0, err = -1; mx[i] != NULL; ) {
		if (i > 0)
		  j = takeit(" Next help path: %s %s\nTry it?",ppt,mx[i]);
		if (j == 1) { i++; continue; }
		if (j < 0)  break;
		strcpy(fullpath,ppt);
		strcat(fullpath," ");
		strcat(fullpath,mx[i]);

		if ( chdir(mx[i]) < 0) {
		    if ( *(svec + 1) != NULL ) {
			i++;
			continue;
		    }
		    if (list_flag) 
			return(-1);
		    else
			printhelp( mx[i],fullpath);
		    printf("\n");
		    if (!dumb_flag) help1(ppt,NULL,1,0, 0);
		    err = 0;
		}
		else {
		    if ( help1(fullpath,(svec+1),0,0, 0) >= 0) {
			err = 0;
			chdir("..");
			printf("\n");
			if (!dumb_flag) help1(ppt,NULL,1,xref, 0);
		    }
		    else chdir("..");
		}
		i++;
	    }
	    return(err);
	}

	if ( !list_flag && !skip) 
	    if (printhelp(NULL, ppt) < 0) {
		return(-1);
	    }

	if ( !list_flag && !dumb_flag && ( access(MANEX, R_OK) >= 0) && !skip)
		printf("\n	Manual page IS available.\n");

	if ( !dumb_flag ) {
	    if ( printtopix( topix, 0, skip) <= 0 ) {
		no_subs = 1;
	    }
	    else no_subs= 0;
	}

	if ( dumb_flag  ||  list_flag ) return(0);

	while (xref == 0) {
		if (frst_flag) {
			printf(" (type '*' for commands)\n");
			frst_flag = 0;
		}


		/* now, prompt and wait for user response */

		if ( strlen(ppt) < 1 ) sprintf(gbl_ppt," Help %s",PROMPT);
		else		 sprintf(gbl_ppt,"  %s %s",ppt,SUBPROMPT);
		fputs(gbl_ppt,stdout);
#ifndef BSD
		if ( fgets(answer, MAXLINELEN-1, stdin) == NULL ) {
			printf("\nbye...\n");
			exit(1);
		}
#else
		if (read_gets(stdin->_file, answer, MAXLINELEN - 1, 1) < 0) {
		        printf("\nbye...\n");
			exit(1);
		}
#endif

		/* first remove any leading blanks or tabs */
		for(s1=answer; *s1 == ' ' || *s1 == '	'; s1++);

		/* chop off all of answer after first word. */
		s2 = index(s1,' ');
		if (s2 == NULL)  s2 = index(s1,'	');
		if (s2 == NULL)  s2 = index(s1,'\n');
		if (s2)
		  { *s2 = '\0'; rest = ++s2; }
		else {
		    rest = s2 = s1 + strlen(s1);
		}
		makewvec(rest,wvec,MAXMATCHES);

		if ( strlen(s1) == 0 )		/*  on blank line, */
			break;			/* pop up one level*/

		switch (*s1) {
	    	    case '?':			/* ?: print stuff again */
		    	printhelp(NULL, ppt);
			if ( access(MANEX, R_OK) >= 0 )
			    printf("\n	Manual page IS available.\n");
	    		if (!no_subs) printtopix(topix, 1,0);
			else	printf("\n Sorry, no subtopics.\n\n");
			break;

		    case '#':
			if (!no_subs) printtopix( topix, 1,0);
			else    printf("\n Sorry, no subtopics.\n\n");
			break;

		    case '*':			/* *: list commands */
			printf("\n");
		        for(i=0; helpcmds[i] != NULL; i++) {
				printf("	%s\n",helpcmds[i]);
			}
			printf("\n");
			break;

		    case '$':			/* $: find out about files */
		    	s2 = s1 + 1;
			if (no_subs) {
				printf("\n Sorry, no subtopics.\n\n");
		    		break;
			}
			strcpy(cmdbuf, INFOHEAD);
			strcat(cmdbuf,s2);
			strcat(cmdbuf,"* ");
			strcat(cmdbuf, INFOTAIL);
			printf("\n File information: \n");
			system(cmdbuf);
			printf("\n");
			break;
		        

	    	    case '.':			/* .: do manpage if any */
		    	s2 = s1 + 1;
			if (no_subs) {
				printf("\n Sorry, no subtopics.\n\n");
		    		break;
			}
			if ( *s2 == '\0' ) {
				mx[0] = "";  mx[1] = NULL; k = 1;
			}
			else if ( (k = matchv( s2, topix, mx)) == 0 ) {
			   printf("\n Sorry, no topics match %s\n",s2);
			   printf(" (list cmnds with '*', topix with '#')\n");
			}
			for( i=0; i < k; i++ ) {
				strcpy(cmdbuf,mx[i]);
				strcat(cmdbuf,MANEX);
				if (access(cmdbuf, R_OK|X_OK ) < 0) {
				        strcpy(cmdbuf,mx[i]);
					strcat(cmdbuf,MAN_SUBEX);
					if (access(cmdbuf,R_OK|X_OK) < 0) {
					    printf("\n Sorry, %s for %s.\n\n",
					      "No manual reference available",
					      (strlen(s2)==0)?ppt:mx[i]);
					    continue;
					}
				}
				if (i > 0) {
 				     k=
				     takeit(" Next man page: %s.\n Take it? ",
					mx[i]);
				     if (k ==  1) continue;
				     if (k == -1) break;
				}
				printf(" ...doing %s\n\n",cmdbuf);
				fkoff(SHELLPROG,SHELLOPTS,cmdbuf,NULL);
					/* source contents of .MANUAL file */
			}
			break;


		    default:			/* must be a topic spec */
		        if ( no_subs )  {
				printf("\n Sorry, no subtopics.\n\n");
		    		break;
			}
			if ( (k = matchv( s1, topix, mx)) == 0 ) {
			   printf("\n Sorry, no direct help for  %s\n",s1);
			   if ( (k = xref_matchv(s1,topix,mx)) == 0) {
			       printf(" no cross-references to  %s\n",s1);
			       printf(
				" (list cmnds with '*', topics with '#')\n");
			       break;	/* leave switch */
			   }
			   else {
			       do_xref(ppt,s1,mx);
			       break;   /* leave switch */
			   }
			}

			/* step thru sub-topics that match src */
		        for(i=0; i < k; i++) {
			    s3 = mx[i];

			    if (i > 0) {
			        j=takeit("\nNext %s subtopic: %s\nTake it?",
					 ppt,s3);
				if (j ==  1) continue;
				if (j == -1) break;
			    }

			    if ( chdir(s3) >= 0 ) {  /* directory  subtopic */
			    	strcpy(fullpath,ppt);
			    	strcat(fullpath," ");
			    	strcat(fullpath,s3);
			    	help1(fullpath,wvec,0,0, 0);	/* recurse */
				if ( strcmp(getwd(newdir),helpdir) )
					chdir("..");
			    }			/* else text subtopic */
			    else  {
				strcpy(fullpath, ppt);
				strcat(fullpath, " ");
				strcat(fullpath, s3);
				printhelp(s3, fullpath);
			    }
			}

		}	/* end of switch */

	}	/* end of while(1) */

	if (!no_subs && (*topix != NULL) ) free( *topix );

	return(0);

  }	/* end of help1 */


/* ****************************************************************
 * takeit: ask user whether to take next topic, return t or f
 *
 *	This routine takes a message, with format, and asks the 
 *  user whether he wants to do the action, not do the action,
 *  or quit the cycle of actions.
 *	y - return  0
 *	n - return  1
 *	q - return -1
 *	? - tell what y, n, and q do.
 *      * - tell what y, n, and q do.
 *     <CR> - carriage return
 *
 *	Naturally, case is insignificant.
 */

 takeit(fmt,m1,m2,m3)
   char *fmt, *m1, *m2, *m3;
 {

	char ans[40];
	int  done, ret;
	char *fgets();

	for(done = 0; !done; ) {
		if (fmt != NULL) printf(fmt, m1, m2, m3);
		printf(" [ynq] ");
		if (fgets(ans, 39, stdin) == NULL) exit(1);  /* abort */
		isupper(*ans)?(*ans=tolower(*ans)):0;
		if (*ans == 'n') {
			done = 1;
			ret = 1;
		}
		else if (*ans == 'y' || *ans == '\n') {
			done = 1;
			ret = 0;
		}
		else if (*ans == 'q') {
			done = 1;
			ret = -1;
		}
		else printf("  Answer y to get next subtopic, n to skip, q to quit.\n");
	}
	return(ret);
 }


 /* ***************************************************************
  * matchv: return all matches of a string in a vector.
  *
  *	This routine accepts a string and a vector of strings. 
  *  The string is supposed to be an abbreviation of one or more
  *  strings in the vector.  The full versions of the strings are
  *  placed in a another vector (which is in a static area) and
  *  a pointer to that vector returned.  Note that the input
  *  vector of pointers must have NULL as its terminating element.
  *  The output vector will also terminate with NULL.
  *	NOTE: The vector returned contains pointers into the input
  *  vector.  Do not, therefore, mess with the contents of the output
  *  vector.
  *
  *  If NULL is returned, nothing matched, or there was some other
  *  error.
  */

  matchv( src, vec, mx)
    char *src;
    char *vec[];
    char *mx[];
  {
	char *m[MAXMATCHES];
	char  *s1, *s2;
	int i,j, slen;

	if ( (slen = strlen(src)) == 0 ) return(NULL);  

	for(i=0, j=0; vec[i] != NULL && j < MAXMATCHES; i++) {
	    if  ( strcmp(src,vec[i]) == 0 ) {   /* exact match! */
		m[0] = vec[i];  j = 1;
		break;
	    }
	    else if ( strncmp(src,vec[i],slen) == 0   && 
		     strlen(vec[i]) >= slen )
		   		m[j++] = vec[i];
	}
	m[j] = NULL;
	for(i=0; i <= j; mx[i] = m[i], i++);
	return(j);
  }

 /* *****************************************************************
  * fkoff: fork a process and return the exit status of the process.
  *
  *    This routine takes a command line separated into words, and
  *  uses vfork(2) and execve(2) to quickly run the program.
  *
  *  This is a simplified version of the fkoff() routine from dcon(8).
  *  Here, a program name and up to four arguments may be passed in.
  *  If one of them is null, FINE, but the fifth 
  *
  */

  fkoff(prg,arg1,arg2,arg3,arg4)
    char *prg, *arg1, *arg2, *arg3, *arg4;

  {
	char *command, *malloc(), *argvec[6];
	int pid, stat, i;

	for(i=0; i < 6; argvec[i++] = NULL);
	argvec[0] = prg;
	argvec[1] = arg1;
	argvec[2] = arg2;
	argvec[3] = arg3;
	argvec[4] = arg4;
	if (argvec[0] == NULL) return(-1);
	command = malloc( strlen(argvec[0]) + 1);
	strcpy(command,argvec[0]);

	Set_Tc_Init( (stdin->_file) ) ;
#ifdef USG
	pid = fork();
#else
	pid = vfork();    /* fork 2 copies of us */
#endif
	if (pid < 0 )  {
	        fflush(stdout);
		perror("help: fork");
		Set_Tc_Here( (stdin->_file) ) ; 
		return(0);
	}
	else if (pid == 0)  { /* we are child, execve the program. */
   		pid = execve(command,argvec,environ);
		_exit(1);   
	}
	else {          /* we are parent, wait for child */
		pid = wait(&stat);
		Set_Tc_Here( (stdin->_file) ) ;
		if (pid == -1) return(pid);
		stat = stat / 0400;   /* get hi byte of status */
		return(stat);
        }
  }

/* **************************************************************
 * makewvec: make a vector of words, up to N of them
 *
 *    This routine uses index to simply parse a string
 *  made up of (possibly) several blank-separated words.
 *  The words are stored into the slots of a vector, as pointers
 *  into the original string.  Therefore, the original string
 *  is destroyed.
 */
 makewvec(sstr, rvec, veccnt)
     char *sstr;
     char *rvec[];
     int veccnt;
{
    int mcnt = 0;
    int done = 0;
    int i,j;
    char *s1, *s2, *index();

    if (strlen(sstr) == 0) {
	rvec[0] = NULL;
    }
    else {
	/* skip leading whitespace */
	for(s1=sstr; iswhite(*s1); s1++);
	for(mcnt = 0, done = 0; !done; mcnt++) {
	    s2 = index(s1,' ');
	    if (s2 == NULL) s2 = index(s1,'	');
	    if (s2 == NULL) s2 = index(s1,'\n');
	    if (s2 != NULL) *s2 = '\0';
	    rvec[mcnt] = s1;
	    if (mcnt + 1 >= veccnt) break;
	    if (s2 == NULL) done = 1;
	    else {
		/* skip more white space */
		for(s1 = s2+1; iswhite(*s1); s1++);
		if (*s1 == '\0') done = 1;
	    }
	}
	rvec[mcnt] = NULL;
    }

    return(mcnt);
}


 /* ***************************************************************
  * xref_matchv: return all cross-ref matches for a topic
  *
  *	This routine accepts a string and a vector of strings. 
  *  The string is supposed to be a referal to one or more
  *  cross-reference file names in the vector.  The matches are
  *  placed in a another vector (which is in a static area) and
  *  a pointer to that vector returned.  Note that the input
  *  vector of pointers must have NULL as its terminating element.
  *  The output vector will also terminate with NULL.
  *	NOTE: The vector returned contains pointers into the input
  *  vector.  Do not, therefore, mess with the contents of the output
  *  vector.  
  *     NOTE: cross-ref file names in the topix vector begin with 
  *  the flag char '@' and still have their extension of .XREF.
  *
  *  If 0 is returned, nothing matched, or there was some other error.
  */

  xref_matchv( src, vec, mx)
    char *src;
    char *vec[];
    char *mx[];
  {
	char *m[MAXMATCHES];
	char  *s1, *s2;
	char  one_ref[MAXNAMELEN];
	int i,j, slen;

	if ( (slen = strlen(src)) == 0 ) return(NULL);  

	for(i=0, j=0; vec[i] != NULL && j < MAXMATCHES; i++) {
	    if ( *vec[i] != '@' ) break;
	    for(s1 = vec[i], s2 = one_ref; 
		*s1 != '\0' && *s1 != '.';
		*s2++ = *s1++);
	    *s2 = '\0';
	    if ( strcmp(src,one_ref) == 0) {    /* exact match! */
		m[0] = vec[i]; j = 1;
		break;
	    }
	    else if
	      (strncmp(src,(vec[i] + 1),slen) == 0 && strlen(vec[i]) >= slen)
		      m[j++] = vec[i];
        }
	m[j] = NULL;
	for(i=0; i <= j; mx[i] = m[i], i++);
	return(j);
  }


/* **************************************************************
 * do_xref: given a vector of cross-reference filenames, do them
 *
 *     This routine follows a set of cross references.  
 *   Technically, there are two kinds of cross-reference files, 
 *   distinguished by the first character of their contents.
 *   If the first character is an at sign (@) the file is a 
 *   ``direct'' cross-reference and the rest of the line starting
 *   with the @ sign is a vector of words that give an ABSOLUTE
 *   help path.  If the first char is not @ then the file is an
 *   ``apologetic'' cross-reference, and the text in it is a 
 *   lame-brained excuse for why the topic in question is not
 *   documented in help.
 *
 *      For each filename in the vector, the following actions 
 *   are performed.
 *
 *        1. check that the file exists and is readable, by
 *           opening it for reading.
 *        2. If this is not the first xref, ask the user if
 *           he wants to look at this one, if so, proceed to 
 *        3. Read the first line of the file and check it.
 *               a. if first char is not '@', do a more(1)
 *                  on the file with fkoff.
 *               b. otherwise, read the first line, and
 *                  use makewvec to parse it into words.
 *                  pass these words to a new invokation
 *                  of help1.  Make the ppt for help1
 *                  be something that denotes a cross-ref.
 */
 do_xref(ppt,src,mx)
     char *ppt, *src;
     char *mx[];
{
    int i,j,k;
    char one_ref[MAXNAMELEN];
    char *s1, *index();

    for(i=0; mx[i] != NULL; i++) {
	strcpy(one_ref,mx[i]+1);
	if (i > 0) {
	    j = takeit("\nNext %s %s cross-reference: %s\nTake it?",
		       ppt,src,one_ref);
	    if (j == 1) continue;
	    if (j == -1) break;
	}
	do_one_xref(one_ref,src,ppt);
    }

    putchar('\n');
    return(0);
}

	
/* **************************************************************
 * do_one_xref: evaluate and do a single cross-reference
 *
 *      This routine accepts a single file name and src name
 *   for a single cross-reference.  It then checks the file
 *   for type, and performs the appropriate action.
 *
 *      If the file is not accessible, an error message is 
 *   printed.
 */
 do_one_xref(xref_file,src,ppt)
     char *xref_file, *src, *ppt;
{
    int i,j;
    FILE *xr_fp, *fopen();
    char *wvec[MAXMATCHES];
    char *index(), *s1, lbuf[MAXLINELEN], xbuf[MAXNAMELEN+15];

    if ( (xr_fp = fopen(xref_file,"r")) == NULL) {
	printf(" Sorry, %s cross-reference file inaccessible.\n",xref_file);
	return(-1);
    }

    if (fgets(lbuf,MAXLINELEN,xr_fp) != NULL) {
	if ( *lbuf != '@' ) {
	    s1 = index(xref_file,'.');
	    if (s1 != NULL) *s1 = '\0';
	    printf(" Cross reference text called \"%s\" is available\n",
		   xref_file);
	    if (ppt != NULL)
	      printf("\n HELP: %s <<%s>>\n",ppt,xref_file);
	    if (s1 != NULL) *s1 = '.';
	    fclose(xr_fp);
	    fkoff(VIEWPROGRAM,VIEWPROGOPTS1,VIEWPROGOPTS2,xref_file,NULL);
	}
	else {
	    char pptbuf[MAXNAMELEN+13], pathbuf[256];
	    makewvec(lbuf+1,wvec,MAXMATCHES);
	    printf("\n following cross-reference `help");
	    for(i=0; wvec[i] != NULL; i++) printf(" %s",wvec[i]);
	    s1 = index(xref_file,'.');
	    if (s1 != NULL) *s1 = '\0';
	    printf("' to find help for %s\n\n",xref_file);
	    sprintf(pptbuf,"<<%s>> ",xref_file);
	    if (s1 != NULL) *s1  = '.';
	    getwd(pathbuf);
	    chdir(helpdir);
	    help1(pptbuf,wvec,1,1, 0);
	    chdir(pathbuf);
	    putchar('\n');
	}
    }
    else {
	printf(" Sorry, cross-ref file %s seems to be empty!\n\n",xref_file);
    }
    return(0);
}


/* **************************************************************
 * printlist - print a list of strings in columnar or list form
 *
 *     This routine takes a list of names in a vector and prints
 *   the list according to the current help mode.  This code
 *   used to be in printtopix() but I decided to move it here
 *   so it could be used for other things.
 */
printlist(vec,namecnt)
     char *vec[];
     int namecnt;
{
    int i,j,k;
    int longlen, namewidth, rowcnt, colcnt, xrefs;
    char *s1, *s2;
    static char row[TERMWID+1];

	longlen = xrefs = 0;
	for(i=0; i < namecnt; i++ ) {
	        if (*vec[i] == '@') xrefs++;
		else
		  longlen = ((k=strlen(vec[i]))>longlen)?k:longlen;
	}

	/* here print the names out in nice columns */
	namewidth = longlen + COLUMNSPACE;
	rowcnt = TERMWID / namewidth;
	colcnt = (namecnt + (rowcnt-1)) / rowcnt ;
	if (colcnt <= 0) colcnt = 1;

	if (col_flag && rowcnt >= 1) {
	        printf("\n");
		for(i=0; i < colcnt ; i++ ) {
			for(k=0; k < TERMWID; row[k++] = ' ');
			row[k] = '\0';
			for(j=0, s1 = row; 
		    	    (i+j+xrefs) < namecnt; 
		    	    j += colcnt) {
				row[strlen(row)] = ' ';
				strcpy(s1,vec[i+j+xrefs]);
				s1 = s1 + namewidth;
			}
			printf("    %s\n",row);
		}
		printf("\n");
	}
	else {
		for(i=xrefs; i < namecnt; i++)
			printf("%s\n",vec[i]);
 	}
 
}

#ifdef BSD

/* ****************  funky keyboard stuff below here  ************* */

/* **************************************************************
 * pushback - push a string back onto the tty input queue
 *
 *     This routine accepts a string and pushes it onto the tty
 *   input queue, as if the user had typed it.
 *   The input is a file descriptor that points to a tty, and
 *   a pointer to the string.
 */
pushback(fd, str)
     int fd;
     char *str;
{
    char *s1;

    for(s1=str; *s1; ioctl( fd, TIOCSTI, s1++));
}


/* **************************************************************
 * getpending - get pending chars from tty, then push them back
 *
 *     This routine grabs chars pending on a specified file
 *   descriptor, puts them into the specified buffer, and pushes
 *   them back onto the tty input queue.
 *   The buffer passed by the caller had BETTER be long enough,
 *   or everything gets munched.
 *   If the parameter no_pushback is non-zero, the characters are
 *   not pushed back.
 *   The number of characters obtained is returned.
 */
getpending(fd, buf, buflen, no_pushback)
     int fd;
     char *buf;
     int buflen, no_pushback;
{
    int i,j;
    char x = '\n';

    ioctl(fd, FIONREAD, &i);
    if (i <= 0) return(0);
    j = read(fd, buf, buflen);
    buf[j-1] = '\0';
    for(i = j-2; buf[i] > '\030' ; buf[i--] = '\0' );
    if (i <= 0) return(0);
    if ( !no_pushback) pushback(fd, buf);
    return(i);
}


/* **************************************************************
 * init_funky - initialize funky stuff, and save startup values
 *
 *         This routine starts up breaking for ESC, and
 *   saves the initial tchars structure.
 */
init_funky()
{
    char *malloc();

    if ( isatty(0) ) {
	is_tty = 1;
	init_tchars = (struct tchars *) malloc( sizeof(struct tchars));
	here_tchars = (struct tchars *) malloc( sizeof(struct tchars));
	ioctl(0,TIOCGETC,init_tchars);
	ioctl(0,TIOCGETC,here_tchars);
	here_tchars->t_brkc = '\033';
	ioctl(0,TIOCSETC,here_tchars);
    }
    
    return;
}


/* **************************************************************
 * read_gets: do a gets by read(2)ing a line
 *
 *       Read a string into a buffer, from the given file
 *    descriptor.  There are four arguments:
 *
 *		fd	file descriptor (usually 0)  
 *  		buf     char * to buffer
 * 		blen    buffer length in bytes
 *		spec	do special ^[ and ^D processing
 *
 *    If the special processing is enabled, ESC invokes helpname
 *    completion, and ^D invokes possible completion listing.
 *    When the user finally inputs a line that does not require
 *    special processing, it is returned as a string to caller.
 *    If anything goes wrong, or a true EOF is given, -1 is
 *    returned.
 *    When special processing is not enabled, the input line is 
 *    simply returned to the caller as a string.
 */
read_gets(fd, buf, blen, do_special)
     int fd, blen, do_special;
     char *buf;
{
    char *s1, *s2, endc;
    static  char bufcopy[MAXLINELEN];
    static  char *wvec[MAXMATCHES];
    int i,j,k;


    for(endc = '\0'; endc != LF ; ) {
	fflush(stdout);
	i = read(fd, buf, blen);

	/* now, having read data, analyze it for special stuff */
	if (i <= 0) return(-1);
	else {
	    s1 = buf + (i - 1);
	    if (*s1 == ESC || *s1 == LF || *s1 == RET) {
		endc = (*s1 == ESC)?(*s1):(LF);
		*s1 = '\0';
	    }
	    else {
		*++s1 = '\0';
		endc = *s1;
	    }
	    for(s1 = buf + (strlen(buf) - 1);
		*s1 == ' '  ||   *s1 == '	';
		*s1-- = '\0');
	    for(s1 = buf; *s1 == ' ' || *s1 == '	'; s1++);

	    if (endc == LF || endc == RET || do_special == 0) return(i-1);
	    else {
		*gbl_match = '\0';

		/* if this is a non-topic command, just beep */
		if ( index( SPEC_CHARS, *s1) ) {
		    fputs("  ",stdout);
		    for(s1 = buf; *s1; s1++) fputs("",stdout);
		    fflush(stdout);
		    pushback(stdin->_file,buf);
		    continue;
		}
		else
		/* do special stuff here */
		if (endc == ESC) {
		    strcpy(bufcopy,buf);
		    i = makewvec(bufcopy, wvec, MAXMATCHES);
		    help1("", wvec, 0, 0, ESC_COMP);
		    fputs("  ",stdout);
		    for(s1 = buf; *s1; s1++) fputs("",stdout);
		    fflush(stdout);
		    pushback(stdin->_file,buf);
		    if ( *gbl_match ) {
			pushback(stdin->_file,(gbl_match + strlen(wvec[i-1])));
		    }
		    else fputs("",stdout);
		    fflush(stdout);
		}
		else
		  if (endc == '\0') {   /* must have been eof */
		      strcpy(bufcopy,buf);
		      i = makewvec(bufcopy, wvec, MAXMATCHES);
		      help1("" , wvec, 0, 0, CTRLD_COMP);
		      printf("\n%s ",gbl_ppt);
		      if (*gbl_match == '\0') fputs("", stdout);
		      fflush(stdout);
		      pushback(stdin->_file,buf);

		  }
	    }
	}
    }
}
		


#endif 
SHAR_EOF
fi # end of overwriting check
#	End of shell archive
exit 0



More information about the Mod.sources mailing list