#ifndef lint
static char *RCSid = "$Id: help.c,v 3.26 92/03/24 22:34:24 woo Exp Locker: woo $";
#endif
/* GNUPLOT - help.c */
/*
* Copyright (C) 1986, 1987, 1990, 1991, 1992 Thomas Williams, Colin Kelley
*
* Permission to use, copy, and distribute this software and its
* documentation for any purpose with or without fee is hereby granted,
* provided that the above copyright notice appear in all copies and
* that both that copyright notice and this permission notice appear
* in supporting documentation.
*
* Permission to modify the software is granted, but not the right to
* distribute the modified code. Modifications are to be distributed
* as patches to released version.
*
* This software is provided "as is" without express or implied warranty.
*
*
* AUTHORS
*
* Original Software:
* Thomas Williams, Colin Kelley.
*
* Gnuplot 2.0 additions:
* Russell Lang, Dave Kotz, John Campbell.
*
* Gnuplot 3.0 additions:
* Gershon Elber and many others.
*
* Send your comments or suggestions to
* info-gnuplot@ames.arc.nasa.gov.
* This is a mailing list; to join it send a note to
* info-gnuplot-request@ames.arc.nasa.gov.
* Send bug reports to
* bug-gnuplot@ames.arc.nasa.gov.
*/
#include <stdio.h>
extern int errno;
extern int strcmp();
extern int strlen();
extern char *strcpy();
extern char *strncpy();
extern char *strcat();
extern char *strncat();
extern char *getenv();
extern FILE *fopen();
extern char *malloc();
extern int instring();
#define SAME 0 /* for strcmp() */
#include "help.h" /* values passed back */
/* help -- help subsystem that understands defined keywords
**
** Looks for the desired keyword in the help file at runtime, so you
** can give extra help or supply local customizations by merely editing
** the help file.
**
** The original (single-file) idea and algorithm is by John D. Johnson,
** Hewlett-Packard Company. Thanx and a tip of the Hatlo hat!
**
** Much extension by David Kotz for use in gnutex, and then in gnuplot.
** Added output paging support, both unix and builtin. Rewrote completely
** to read helpfile into memory, avoiding reread of help file. 12/89.
**
** Modified by Russell Lang to avoid reading completely into memory
** if MSDOS defined. This uses much less memory. 6/91
**
** The help file looks like this (the question marks are really in column 1):
**
** ?topic
** This line is printed when the user wants help on "topic".
** ?keyword
** ?Keyword
** ?KEYWORD
** These lines will be printed on the screen if the user wanted
** help on "keyword", "Keyword", or "KEYWORD". No casefolding is
** done on the keywords.
** ?subject
** ?alias
** This line is printed for help on "subject" and "alias".
** ?
** ??
** Since there is a null keyword for this line, this section
** is printed when the user wants general help (when a help
** keyword isn't given). A command summary is usually here.
** Notice that the null keyword is equivalent to a "?" keyword
** here, because of the '?' and '??' topic lines above.
** If multiple keywords are given, the first is considered the
** 'primary' keyword. This affects a listing of available topics.
** ?last-subject
** Note that help sections are terminated by the start of the next
** '?' entry or by EOF. So you can't have a leading '?' on a line
** of any help section. You can re-define the magic character to
** recognize in column 1, though, if '?' is too useful. (Try ^A.)
*/
#define KEYFLAG '?' /* leading char in help file topic lines */
/*
** Calling sequence:
** int result; # 0 == success
** char *keyword; # topic to give help on
** char *pathname; # path of help file
** int subtopics; # set to TRUE if only subtopics to be listed
** # returns TRUE if subtopics were found
** result = help(keyword, pathname, &subtopics);
** Sample:
** cmd = "search\n";
** helpfile = "/usr/local/lib/program/program.help";
** subtopics = FALSE;
** if (help(cmd, helpfile, &subtopics) != H_FOUND)
** printf("Sorry, no help for %s", cmd);
**
**
** Speed this up by replacing the stdio calls with open/close/read/write.
*/
#ifdef WDLEN
# define PATHSIZE WDLEN
#else
# define PATHSIZE BUFSIZ
#endif
typedef int boolean;
#ifndef TRUE
#define TRUE (1)
#define FALSE (0)
#endif
typedef struct line_s LINEBUF;
struct line_s {
char *line; /* the text of this line */
LINEBUF *next; /* the next line */
};
typedef struct linkey_s LINKEY;
struct linkey_s {
char *key; /* the name of this key */
long pos; /* ftell position */
LINEBUF *text; /* the text for this key */
boolean primary; /* TRUE -> is a primary name for a text block */
LINKEY *next; /* the next key in linked list */
};
typedef struct key_s KEY;
struct key_s {
char *key; /* the name of this key */
long pos; /* ftell position */
LINEBUF *text; /* the text for this key */
boolean primary; /* TRUE -> is a primary name for a text block */
};
static LINKEY *keylist = NULL; /* linked list of keys */
static KEY *keys = NULL; /* array of keys */
static int keycount = 0; /* number of keys */
static FILE *helpfp = NULL;
static int LoadHelp();
static void sortkeys();
static int keycomp();
static LINEBUF *storeline();
static LINKEY *storekey();
static KEY *FindHelp();
static boolean Ambiguous();
/* Help output */
static void PrintHelp();
static void ShowSubtopics();
static void StartOutput();
static void OutLine();
static void EndOutput();
static FILE *outfile; /* for unix pager, if any */
static int pagelines; /* count for builtin pager */
#define SCREENSIZE 24 /* lines on screen (most have at least 24) */
/* help:
* print a help message
* also print available subtopics, if subtopics is TRUE
*/
help(keyword, path, subtopics)
char *keyword; /* on this topic */
char *path; /* from this file */
boolean *subtopics; /* (in) - subtopics only? */
/* (out) - are there subtopics? */
{
static char oldpath[PATHSIZE] = ""; /* previous help file */
int status; /* result of LoadHelp */
KEY *key; /* key that matches keyword */
/*
** Load the help file if necessary (say, first time we enter this routine,
** or if the help file changes from the last time we were called).
** Also may occur if in-memory copy was freed.
** Calling routine may access errno to determine cause of H_ERROR.
*/
errno = 0;
if (strncmp(oldpath, path, PATHSIZE) != SAME)
FreeHelp();
if (keys == NULL) {
status = LoadHelp(path);
if (status == H_ERROR)
return(status);
/* save the new path in oldpath */
if (strlen(path) < PATHSIZE)
(void) strcpy(oldpath, path);
else { /* not enough room in oldpath, sigh */
(void) strncpy(oldpath, path, PATHSIZE - 1);
oldpath[PATHSIZE - 1] = '\0';
}
}
/* look for the keyword in the help file */
key = FindHelp(keyword);
if (key != NULL) {
/* found the keyword: print help and return */
PrintHelp(key, subtopics);
status = H_FOUND;
} else {
status = H_NOTFOUND;
}
return(status);
}
/* we only read the file once, into memory
* except for MSDOS when we don't read all the file -
* just the keys and location of the text
*/
static int
LoadHelp(path)
char *path;
{
LINKEY *key; /* this key */
long pos; /* ftell location within help file */
char buf[BUFSIZ]; /* line from help file */
LINEBUF *head; /* head of text list */
LINEBUF *firsthead = NULL;
boolean primary; /* first ? line of a set is primary */
boolean flag;
if ((helpfp = fopen(path, "r")) == NULL) {
/* can't open help file, so error exit */
return (H_ERROR);
}
/*
** The help file is open. Look in there for the keyword.
*/
(void) fgets(buf, BUFSIZ - 1, helpfp);
while (!feof(helpfp)) {
/*
** Make an entry for each synonym keyword
*/
primary = TRUE;
while (buf[0] == KEYFLAG) {
key = storekey(buf+1); /* store this key */
key->primary = primary;
key->text = NULL; /* fill in with real value later */
key->pos = 0; /* fill in with real value later */
primary = FALSE;
pos = ftell(helpfp);
if (fgets(buf, BUFSIZ - 1, helpfp) == (char *)NULL)
break;
}
/*
** Now store the text for this entry.
** buf already contains the first line of text.
*/
#ifndef MSDOS
firsthead = storeline(buf);
head = firsthead;
#endif
while ( (fgets(buf, BUFSIZ - 1, helpfp) != (char *)NULL)
&& (buf[0] != KEYFLAG) ){
#ifndef MSDOS
/* save text line */
head->next = storeline(buf);
head = head->next;
#endif
}
/* make each synonym key point to the same text */
do {
key->pos = pos;
key->text = firsthead;
flag = key->primary;
key = key->next;
} while ( flag!=TRUE && key!=NULL );
}
#ifndef MSDOS
(void) fclose(helpfp);
#endif
/* we sort the keys so we can use binary search later */
sortkeys();
return(H_FOUND); /* ok */
}
/* make a new line buffer and save this string there */
static LINEBUF *
storeline(text)
char *text;
{
LINEBUF *new;
new = (LINEBUF *)malloc(sizeof(LINEBUF));
if (new == NULL)
int_error("not enough memory to store help file", -1);
if (text != NULL) {
new->line = (char *) malloc((unsigned int)(strlen(text)+1));
if (new->line == NULL)
int_error("not enough memory to store help file", -1);
(void) strcpy(new->line, text);
} else
new->line = NULL;
new->next = NULL;
return(new);
}
/* Add this keyword to the keys list, with the given text */
static LINKEY *
storekey(key)
char *key;
{
LINKEY *new;
key[strlen(key)-1] = '\0'; /* cut off \n */
new = (LINKEY *)malloc(sizeof(LINKEY));
if (new == NULL)
int_error("not enough memory to store help file", -1);
new->key = (char *) malloc((unsigned int)(strlen(key)+1));
if (new->key == NULL)
int_error("not enough memory to store help file", -1);
(void) strcpy(new->key, key);
/* add to front of list */
new->next = keylist;
keylist = new;
keycount++;
return(new);
}
/* we sort the keys so we can use binary search later */
/* We have a linked list of keys and the number.
* to sort them we need an array, so we reform them into an array,
* and then throw away the list.
*/
static void
sortkeys()
{
LINKEY *p,*n; /* pointers to linked list */
int i; /* index into key array */
/* allocate the array */
keys = (KEY *)malloc((unsigned int)((keycount+1) * sizeof(KEY)));
if (keys == NULL)
int_error("not enough memory to store help file", -1);
/* copy info from list to array, freeing list */
for (p = keylist, i = 0; p != NULL; p = n, i++) {
keys[i].key = p->key;
keys[i].pos = p->pos;
keys[i].text = p->text;
keys[i].primary = p->primary;
n = p->next;
free( (char *)p );
}
/* a null entry to terminate subtopic searches */
keys[keycount].key = NULL;
keys[keycount].pos = 0;
keys[keycount].text = NULL;
/* sort the array */
/* note that it only moves objects of size (two pointers + long + int) */
/* it moves no strings */
qsort((char *)keys, keycount, sizeof(KEY), keycomp);
}
static int
keycomp(a, b)
KEY *a,*b;
{
return (strcmp(a->key, b->key));
}
/* Free the help file from memory. */
/* May be called externally if space is needed */
void
FreeHelp()
{
int i; /* index into keys[] */
LINEBUF *t, *next;
if (keys == NULL)
return;
for (i = 0; i < keycount; i++) {
free( (char *)keys[i].key );
if (keys[i].primary) /* only try to release text once! */
for (t = keys[i].text; t != NULL; t = next) {
free( (char *)t->line );
next = t->next;
free( (char *)t );
}
}
free( (char *)keys );
keys = NULL;
keycount = 0;
#ifdef MSDOS
(void) fclose(helpfp);
#endif
}
/* FindHelp:
* Find the key that matches the keyword.
* The keys[] array is sorted by key.
* We could use a binary search, but a linear search will aid our
* attempt to allow abbreviations. We search for the first thing that
* matches all the text we're given. If not an exact match, then
* it is an abbreviated match, and there must be no other abbreviated
* matches -- for if there are, the abbreviation is ambiguous.
* We print the ambiguous matches in that case, and return not found.
*/
static KEY * /* NULL if not found */
FindHelp(keyword)
char *keyword; /* string we look for */
{
KEY *key;
int len = strlen(keyword);
int compare;
for (key = keys, compare = 1; key->key != NULL && compare > 0; key++) {
compare = strncmp(keyword, key->key, len);
if (compare == 0) /* we have a match! */
if (!Ambiguous(key, len)) {
/* non-ambiguous abbreviation */
(void) strcpy(keyword, key->key); /* give back the full spelling */
return(key); /* found!! */
}
}
/* not found, or ambiguous */
return(NULL);
}
/* Ambiguous:
* Check the key for ambiguity up to the given length.
* It is ambiguous if it is not a complete string and there are other
* keys following it with the same leading substring.
*/
static boolean
Ambiguous(key, len)
KEY *key;
int len;
{
char *first;
char *prev;
boolean status = FALSE; /* assume not ambiguous */
int compare;
int sublen;
if (key->key[len] == '\0')
return(FALSE);
for (prev = first = key->key, compare = 0, key++;
key->key != NULL && compare == 0; key++) {
compare = strncmp(first, key->key, len);
if (compare == 0) {
/* So this key matches the first one, up to len.
* But is it different enough from the previous one
* to bother printing it as a separate choice?
*/
sublen = instring(prev+len, ' ');
if (strncmp(key->key, prev, len+sublen) != 0) {
/* yup, this is different up to the next space */
if (!status) {
/* first one we have printed is special */
fprintf(stderr,
"Ambiguous request '%.*s'; possible matches:\n",
len, first);
fprintf(stderr, "\t%s\n", prev);
status = TRUE;
}
fprintf(stderr, "\t%s\n", key->key);
prev = key->key;
}
}
}
return(status);
}
/* PrintHelp:
* print the text for key
*/
static void
PrintHelp(key, subtopics)
KEY *key;
boolean *subtopics; /* (in) - subtopics only? */
/* (out) - are there subtopics? */
{
LINEBUF *t;
#ifdef MSDOS
char buf[BUFSIZ]; /* line from help file */
#endif
StartOutput();
if (subtopics == NULL || !*subtopics) {
#ifdef MSDOS
fseek(helpfp,key->pos,0);
while ( (fgets(buf, BUFSIZ - 1, helpfp) != (char *)NULL)
&& (buf[0] != KEYFLAG) ) {
OutLine(buf);
}
#else
for (t = key->text; t != NULL; t = t->next)
OutLine(t->line); /* print text line */
#endif
}
ShowSubtopics(key, subtopics);
OutLine("\n");
EndOutput();
}
/* ShowSubtopics:
* Print a list of subtopic names
*/
#define PER_LINE 4
static void
ShowSubtopics(key, subtopics)
KEY *key; /* the topic */
boolean *subtopics; /* (out) are there any subtopics */
{
int subt = 0; /* printed any subtopics yet? */
KEY *subkey; /* subtopic key */
int len; /* length of key name */
char line[BUFSIZ]; /* subtopic output line */
char *start; /* position of subname in key name */
int sublen; /* length of subname */
int pos;
char *prev = NULL; /* the last thing we put on the list */
*line = '\0';
len = strlen(key->key);
for (subkey = key+1; subkey->key != NULL; subkey++) {
if (strncmp(subkey->key, key->key, len) == 0) {
/* find this subtopic name */
start = subkey->key + len;
if (len > 0)
if (*start == ' ')
start++; /* skip space */
else
break; /* not the same topic after all */
else /* here we are looking for main topics */
if (!subkey->primary)
continue; /* not a main topic */
sublen = instring(start, ' ');
if (prev == NULL || strncmp(start, prev, sublen) != 0) {
if (subt == 0) {
subt++;
if (len)
(void) sprintf(line, "\nSubtopics available for %s:\n",
key->key);
else
(void) sprintf(line, "\nHelp topics available:\n");
OutLine(line);
*line = '\0';
pos = 0;
}
if (pos == PER_LINE) {
(void) strcat(line, "\n");
OutLine(line);
*line = '\0';
pos = 0;
}
(void) strcat(line, "\t");
(void) strncat(line, start, sublen);
pos++;
prev = start;
}
} else {
/* new topic */
break;
}
}
/* put out the last line */
if (subt > 0 && pos > 0) {
(void) strcat(line, "\n");
OutLine(line);
}
/*
if (subt == 0) {
OutLine("\n");
OutLine("No subtopics available\n");
}
*/
if (subtopics)
*subtopics = (subt != 0);
}
/* StartOutput:
* Open a file pointer to a pipe to user's $PAGER, if there is one,
* otherwise use our own pager.
*/
static void
StartOutput()
{
#ifdef unix
char *pager_name = getenv("PAGER");
extern FILE *popen();
if (pager_name != NULL && *pager_name != '\0')
if ((outfile = popen(pager_name, "w")) != (FILE *)NULL)
return; /* success */
outfile = stderr;
/* fall through to built-in pager */
#endif
/* built-in pager */
pagelines = 0;
}
/* write a line of help output */
/* line should contain only one \n, at the end */
static void
OutLine(line)
char *line;
{
int c; /* dummy input char */
#ifdef unix
if (outfile != stderr) {
fputs(line, outfile);
return;
}
#endif
/* built-in dumb pager */
/* leave room for prompt line */
if (pagelines >= SCREENSIZE - 2) {
fprintf(stderr,"Press return for more: ");
do
c = getchar();
while (c != EOF && c != '\n');
pagelines = 0;
}
fputs(line, stderr);
pagelines++;
}
static void
EndOutput()
{
#ifdef unix
extern int pclose();
if (outfile != stderr)
(void) pclose(outfile);
#endif
}