[comp.os.minix] expl command

wheels@mks.UUCP (Gerry Wheeler) (11/29/88)

I've been collecting stuff off the net for some time, so now it's my
turn to offer something.  Here is a program to provide quick online
explanations.  The explanations themselves are simply text files, so
anything at all can be used.  I have started typing in summaries of the
MINIX commands and their options, for those times when I know which
command I want to use, but can't remember quite how to use it.  I may
also eventually type in summaries of the library routines and their
arguments. 

As you can see from the date, this is a new program, so I expect people
will find one or two problems in it.  In particular, I haven't compiled
it on a version of PC MINIX.  If someone would do that and let me know
how it works I'd appreciate it.  I'll collect bug reports, and repost
(or post diffs) in a week or two. 

Also, give me some feedback if you find this useful.  Perhaps it would
be worthwhile posting (or emailing) the explanations as they get
entered. 

The use of the program, and the creation of the text files is pretty
well covered in the comments.

------------------------------ cut here ------------------------------

/*
 * The expl command, by Gerry Wheeler, November 25 1988.
 *
 * Copyright 1988 by Gerry Wheeler.
 * Permission is granted to distribute this program provided
 * this copyright remains intact, and provided that no cost
 * beyond the cost of media/distribution is charged.
 *
 * The expl command is based on a similar command I
 * saw on a Honeywell system at the University of Waterloo in
 * the early 1970's. It provides a very simple way to have
 * short explanations of commands online. It is intended as
 * an adjunct to the man command. These explanations should
 * be short enough to fit on one or two screens.
 *
 * Each explanation is kept in a file in some tree in the file
 * system. The default place is under /usr/expl.
 *
 * If an explanation needs no subtopics, then it is put into
 * a file with the name of the topic. If subtopics are possible,
 * then the name of the topic is used as a directory, and the
 * explanation of that topic is put in a file called .TEXT within
 * that directory.
 *
 * Here are some examples:
 *
 * If a user types the command "expl foo", then the name
 * /usr/expl/foo is found. If it is a file, it is printed.
 * If it is a directory, then the file /usr/expl/foo/.TEXT
 * should exist and is printed.
 *
 * If a user types the command "expl foo bar", then the
 * name /usr/expl/foo/bar is searched for, and the same
 * procedure is used as above.
 *
 * The search can go to any arbitrary depth, allowing any number
 * of nested subtopics. On those operating systems that permit
 * them, links can be used to allow multiple ways of finding
 * a particular explanation. For example, an explanation of
 * regular expressions might be placed as a subtopic of mined,
 * grep, and gres. These can all be links to the same file.
 *
 * The expl command will attempt to find the most reasonable
 * explanation if an exact match cannot be found. It can do this
 * by sequentially removing subtopics from the end of the user's
 * request and trying again. At the topmost level, the file
 * /usr/expl/.TEXT contains instructions on how to use the expl
 * command.
 */

#include <stdio.h>
#include <sys/stat.h>

/** I couldn't get these to work, so I've extracted the
 ** important parts.
 ** #include <fs/const.h>
 ** #include <fs/type.h>
 **/

#define NAME_SIZE 14		/* length of MINIX file names */
typedef unsigned short inode_nr; /* type of inode number */

struct dir_struct {		/* directory entry */
  inode_nr d_inum;		/* inode number */
  char d_name[NAME_SIZE];	/* character string */
};

#define FALSE 0
#define TRUE  1
#define OK    0

static char *expldir = "/usr/expl"; /* starting place for file searches */
static char *textfile = "/.TEXT"; /* alternate name, in case of directory */

/*
 * Print a message about the topic. Called with a string, a count of
 * topic/subtopic names, and a copy of argv. It prints the string
 * first, then repeats the items from argv. Useful for diagnostics
 * and error messages.
 */

static void topic_msg(str, num_topics, argv)
char *str;
int num_topics;
char *argv[];
{
	register int arg;

	printf("%s ", str);
	for (arg = 1; arg <= num_topics; ++arg)
		printf("%s ", argv[arg]);
	printf("\b.\n");
}


/*
 * Tell the user that we can't explain the requested topic. Called
 * with a count of topic/subtopic names, and a copy of argv.
 */

static void unable(num_topics, argv)
int num_topics;
char *argv[];
{
	topic_msg("Unable to explain", num_topics, argv);
}


/*
 * Tell the user that we'lltry to explain some other topic. Called
 * with a count of topic/subtopic names, and a copy of argv.
 */

static void able(num_topics, argv)
int num_topics;
char *argv[];
{
	topic_msg("Found explanation of", num_topics, argv);
}


/*
 * Print one item in a list of subtopics. Keep track of screen
 * formatting so it all looks pretty. Don't print names starting
 * with a dot, and don't print names of deleted or special files.
 */

static void displayname(dirname, name)
char *dirname, *name;
{
	static int column = 0;
	register unsigned short mode;
	struct stat statbuf;

	if (*name == '.')
		return;		/* name starts with '.' */

	/*
	 * Concatenate the name of the directory with the
	 * name of the subtopic to get a file name. Then
	 * stat the name and save the result in statbuf.
	 */

	{
		register char *fullname;
		register int status;

		if ((fullname = (char *)malloc(strlen(dirname) + strlen(name) + 5)) == NULL)
			return;		/* no room to work */

		strcpy(fullname, dirname);
		strcat(fullname, "/");
		strcat(fullname, name);

		status = stat(fullname, &statbuf);

		free(fullname);
		if (status != 0)
			return;		/* can't stat name */
	}

	/*
	 * If it's not a file or directory, we don't want it.
	 */

	mode = statbuf.st_mode & S_IFMT;
	if (mode != S_IFDIR && mode != S_IFREG)
		return;

	/*
	 * Print the name. If we're getting too close to the
	 * right margin, print a newline.
	 */

	printf("%-*s", NAME_SIZE + 2, name);
	column += NAME_SIZE + 2;

	if ((column + NAME_SIZE) > 79) {
		printf("\n");
		column = 0;
	}
}


/*
 * Display the contents of a file by calling the more command.
 */

static int displayfile(name)
char *name;
{
	register char *cmd;
	register int status;

	if ((cmd = (char *)malloc(strlen(name) + 10)) == NULL)
		return ~OK;

	strcpy(cmd, "more ");
	strcat(cmd, name);

	printf("\n");
	status = system(cmd);
	printf("\n");

	free(cmd);
	return((status == 0) ? OK : ~OK);
}


/*
 * Display the contents of the file .TEXT within a directory.
 */

static int displaydir(name)
char *name;
{
	register char *newname;
	register int status;

	if ((newname = (char *)malloc(strlen(name) + 10)) == NULL)
		return ~OK;

	strcpy(newname, name);
	strcat(newname, textfile);

	status = displayfile(newname);

	free(newname);
	return status;
}


/*
 * Display an index of subtopics by finding the file names in
 * the directory.
 */

static void displayindex(dirname)
char *dirname;
{
	struct dir_struct dirent;
	int fd;

	/*
	 * Open the directory corresponding to the current topic.
	 */

	if ((fd = open(dirname, 0)) < 0) {
		printf("\nUnable to provide list of subtopics.\n");
		return;
	}

	printf("\nExplanations are available on the following %stopics:\n\n",
				strcmp(dirname, expldir) == 0 ? "" : "sub");

	for (;;) {
		register int len;

		/*
		 * Try to read one directory entry. If succesful,
		 * display it. If not, we either had an error, or
		 * we reached the end of the directory.
		 */

		len = read(fd, &dirent, sizeof(dirent));

		if (len == sizeof(dirent)) {
			displayname(dirname, dirent.d_name);
		}
		else if (len < 0) {
			printf("error reading directory \"%s\"\n", dirname);
			break;
		}
		else
			break;
	}
	printf("\n");
	close(fd);
}


/*
 * The main routine. Determine what the user is asking for by looking
 * at argv. If it can't be displayed, try dropping the last subtopic
 * name and looking again.
 */

main(argc, argv)
int argc;
char *argv[];
{
	register int arg, num_topics, tryagain, failed;
	register char *name;

	num_topics = argc - 1;		/* initially, use all args */

	/*
	 * Determine the length of the combined args and allocate
	 * enough memory to hold all of them, plus path separators
	 * for each ('/'), plus the name of the top of the explanation
	 * tree (/usr/expl), plus a little extra for a trailing '\0'
	 * and some safety.
	 */

	{
		register unsigned length;

		for (arg = 1, length = 0; arg <= num_topics; ++arg)
			length += strlen(argv[arg]) + 1;
		length += strlen(expldir) + 5;

		if ((name = (char *)malloc(length)) == NULL) {
			printf("%s: insufficient working memory\n", argv[0]);
			exit(1);
		}
	}

	failed = FALSE;

	do {
		struct stat statbuf;

		/*
		 * Build the desired file name by concatenating the
		 * initial directory name and each of the args being
		 * considered.
		 */

		strcpy(name, expldir);

		for (arg = 1; arg <= num_topics; ++arg) {
			strcat(name, "/");
			strcat(name, argv[arg]);
		}

		/*
		 * See if such a thing exists, and whether it is
		 * a file or a directory. If it can't be found,
		 * give a message. Otherwise, try to print it.
		 */

		if (stat(name, &statbuf) != 0) {
			unable(num_topics, argv);
			failed = TRUE;
			tryagain = TRUE;
		}
		else {
			register unsigned short mode;

			mode = statbuf.st_mode & S_IFMT;

			if (mode == S_IFDIR) {		/* a directory */
				if (failed)
					able(num_topics, argv);
				if (displaydir(name) == OK) {
					displayindex(name);
					tryagain = FALSE;
				}
				else
					tryagain = TRUE;
			}
			else if (mode == S_IFREG) {	/* a file */
				if (failed)
					able(num_topics, argv);
				tryagain = (displayfile(name) != OK);
			}
			else {				/* neither */
				unable(num_topics, argv);
				failed = TRUE;
				tryagain = TRUE;
			}
		}
	} while (--num_topics > 0 && tryagain);

	free(name);
	exit(0);
}


-- 
     Gerry Wheeler                           Phone: (519)884-2251
Mortice Kern Systems Inc.               UUCP: uunet!watmath!mks!wheels
   35 King St. North                             BIX: join mks
Waterloo, Ontario  N2J 2W9                  CompuServe: 73260,1043

ast@cs.vu.nl (Andy Tanenbaum) (11/30/88)

In article <581@mks.UUCP> wheels@mks.UUCP (Gerry Wheeler) writes:
>Here is a program to provide quick online explanations.

To some extent, the help program in 1.3 already does this.

Andy Tanenbaum (ast@cs.vu.nl)

wheels@mks.UUCP (Gerry Wheeler) (12/01/88)

In article <581@mks.UUCP> I wrote:
>Here is a program to provide quick online explanations.

In article <1732@ast.cs.vu.nl>, ast@cs.vu.nl (Andy Tanenbaum) writes:
> To some extent, the help program in 1.3 already does this.

Ah, I must have missed that. Could we see a list of commands that are in
MINIX-PC 1.3 that are not in MINIX-ST?
-- 
     Gerry Wheeler                           Phone: (519)884-2251
Mortice Kern Systems Inc.               UUCP: uunet!watmath!mks!wheels
   35 King St. North                             BIX: join mks
Waterloo, Ontario  N2J 2W9                  CompuServe: 73260,1043