2 static char *RCSid() { return RCSid("$Id: help.c,v 1.19 2006/07/07 18:06:30 sfeam Exp $"); }
8 * Copyright 1986 - 1993, 1998, 2004 Thomas Williams, Colin Kelley
10 * Permission to use, copy, and distribute this software and its
11 * documentation for any purpose with or without fee is hereby granted,
12 * provided that the above copyright notice appear in all copies and
13 * that both that copyright notice and this permission notice appear
14 * in supporting documentation.
16 * Permission to modify the software is granted, but not the right to
17 * distribute the complete modified source code. Modifications are to
18 * be distributed as patches to the released version. Permission to
19 * distribute binaries produced by compiling modified sources is granted,
21 * 1. distribute the corresponding source modifications from the
22 * released version in the form of a patch file along with the binaries,
23 * 2. add special version identification to distinguish your version
24 * in addition to the base release version number,
25 * 3. provide your name and address as the primary contact for the
26 * support of your modified version, and
27 * 4. retain our contact information in regard to use of the base
29 * Permission to distribute the released version of the source code along
30 * with corresponding source modifications in the form of a patch file is
31 * granted with same provisions 2 through 4 for binary distributions.
33 * This software is provided "as is" without express or implied warranty
34 * to the extent permitted by applicable law.
43 ** help -- help subsystem that understands defined keywords
45 ** Looks for the desired keyword in the help file at runtime, so you
46 ** can give extra help or supply local customizations by merely editing
49 ** The original (single-file) idea and algorithm is by John D. Johnson,
50 ** Hewlett-Packard Company. Thanx and a tip of the Hatlo hat!
52 ** Much extension by David Kotz for use in gnutex, and then in gnuplot.
53 ** Added output paging support, both unix and builtin. Rewrote completely
54 ** to read helpfile into memory, avoiding reread of help file. 12/89.
56 ** Modified by Russell Lang to avoid reading completely into memory
57 ** if DOS16 defined. This uses much less memory. 6/91
59 ** The help file looks like this (the question marks are really in column 1):
62 ** This line is printed when the user wants help on "topic".
66 ** These lines will be printed on the screen if the user wanted
67 ** help on "keyword", "Keyword", or "KEYWORD". No casefolding is
68 ** done on the keywords.
71 ** This line is printed for help on "subject" and "alias".
74 ** Since there is a null keyword for this line, this section
75 ** is printed when the user wants general help (when a help
76 ** keyword isn't given). A command summary is usually here.
77 ** Notice that the null keyword is equivalent to a "?" keyword
78 ** here, because of the '?' and '??' topic lines above.
79 ** If multiple keywords are given, the first is considered the
80 ** 'primary' keyword. This affects a listing of available topics.
82 ** Note that help sections are terminated by the start of the next
83 ** '?' entry or by EOF. So you can't have a leading '?' on a line
84 ** of any help section. You can re-define the magic character to
85 ** recognize in column 1, though, if '?' is too useful. (Try ^A.)
88 #define KEYFLAG '?' /* leading char in help file topic lines */
92 ** int result; # 0 == success
93 ** char *keyword; # topic to give help on
94 ** char *pathname; # path of help file
95 ** int subtopics; # set to TRUE if only subtopics to be listed
96 ** # returns TRUE if subtopics were found
97 ** result = help(keyword, pathname, &subtopics);
100 ** helpfile = "/usr/local/lib/program/program.help";
101 ** subtopics = FALSE;
102 ** if (help(cmd, helpfile, &subtopics) != H_FOUND)
103 ** printf("Sorry, no help for %s", cmd);
106 ** Speed this up by replacing the stdio calls with open/close/read/write.
109 # define PATHSIZE WDLEN
111 # define PATHSIZE BUFSIZ
114 typedef struct line_s LINEBUF;
116 char *line; /* the text of this line */
117 LINEBUF *next; /* the next line */
120 typedef struct linkey_s LINKEY;
122 char *key; /* the name of this key */
123 long pos; /* ftell position */
124 LINEBUF *text; /* the text for this key */
125 TBOOLEAN primary; /* TRUE -> is a primary name for a text block */
126 LINKEY *next; /* the next key in linked list */
129 typedef struct key_s KEY;
131 char *key; /* the name of this key */
132 long pos; /* ftell position */
133 LINEBUF *text; /* the text for this key */
134 TBOOLEAN primary; /* TRUE -> is a primary name for a text block */
136 static LINKEY *keylist = NULL; /* linked list of keys */
137 static KEY *keys = NULL; /* array of keys */
138 static int keycount = 0; /* number of keys */
139 static FILE *helpfp = NULL;
140 static KEY empty_key = {NULL, 0, NULL, 0};
142 static int LoadHelp __PROTO((char *path));
143 static void sortkeys __PROTO((void));
144 int keycomp __PROTO((SORTFUNC_ARGS a, SORTFUNC_ARGS b));
145 static LINEBUF *storeline __PROTO((char *text));
146 static LINKEY *storekey __PROTO((char *key));
147 static KEY *FindHelp __PROTO((char *keyword));
148 static TBOOLEAN Ambiguous __PROTO((struct key_s * key, size_t len));
151 static void PrintHelp __PROTO((struct key_s * key, TBOOLEAN *subtopics));
152 static void ShowSubtopics __PROTO((struct key_s * key, TBOOLEAN *subtopics));
155 static FILE *outfile; /* for unix pager, if any */
157 static int pagelines; /* count for builtin pager */
158 #define SCREENSIZE 24 /* lines on screen (most have at least 24) */
161 * print a help message
162 * also print available subtopics, if subtopics is TRUE
166 char *keyword, /* on this topic */
167 char *path, /* from this file */
168 TBOOLEAN *subtopics) /* (in) - subtopics only? */
169 /* (out) - are there subtopics? */
171 static char oldpath[PATHSIZE] = ""; /* previous help file */
172 int status; /* result of LoadHelp */
173 KEY *key; /* key that matches keyword */
176 ** Load the help file if necessary (say, first time we enter this routine,
177 ** or if the help file changes from the last time we were called).
178 ** Also may occur if in-memory copy was freed.
179 ** Calling routine may access errno to determine cause of H_ERROR.
182 if (strncmp(oldpath, path, PATHSIZE) != 0)
185 status = LoadHelp(path);
186 if (status == H_ERROR)
189 /* save the new path in oldpath */
190 safe_strncpy(oldpath, path, PATHSIZE);
192 /* look for the keyword in the help file */
193 key = FindHelp(keyword);
195 /* found the keyword: if help exists, print and return */
197 PrintHelp(key, subtopics);
206 /* we only read the file once, into memory
207 * except for DOS16 when we don't read all the file -
208 * just the keys and location of the text
213 LINKEY *key = 0; /* this key */
214 long pos = 0; /* ftell location within help file */
215 char buf[BUFSIZ]; /* line from help file */
216 LINEBUF *head; /* head of text list */
217 LINEBUF *firsthead = NULL;
218 TBOOLEAN primary; /* first ? line of a set is primary */
221 if ((helpfp = fopen(path, "r")) == NULL) {
222 /* can't open help file, so error exit */
226 ** The help file is open. Look in there for the keyword.
228 if (!fgets(buf, BUFSIZ - 1, helpfp) || *buf != KEYFLAG)
229 return (H_ERROR); /* it is probably not the .gih file */
231 while (!feof(helpfp)) {
233 ** Make an entry for each synonym keyword
236 while (buf[0] == KEYFLAG) {
237 key = storekey(buf + 1); /* store this key */
238 key->primary = primary;
239 key->text = NULL; /* fill in with real value later */
240 key->pos = 0; /* fill in with real value later */
243 if (fgets(buf, BUFSIZ - 1, helpfp) == (char *) NULL)
247 ** Now store the text for this entry.
248 ** buf already contains the first line of text.
251 firsthead = storeline(buf);
254 while ((fgets(buf, BUFSIZ - 1, helpfp) != (char *) NULL)
255 && (buf[0] != KEYFLAG)) {
258 head->next = storeline(buf);
262 /* make each synonym key point to the same text */
265 key->text = firsthead;
268 } while (flag != TRUE && key != NULL);
271 (void) fclose(helpfp);
274 /* we sort the keys so we can use binary search later */
276 return (H_FOUND); /* ok */
279 /* make a new line buffer and save this string there */
281 storeline(char *text)
285 new = (LINEBUF *) gp_alloc(sizeof(LINEBUF), "new line buffer");
287 new->line = gp_strdup(text);
296 /* Add this keyword to the keys list, with the given text */
302 key[strlen(key) - 1] = NUL; /* cut off \n */
304 new = (LINKEY *) gp_alloc(sizeof(LINKEY), "new key list");
306 new->key = gp_strdup(key);
308 /* add to front of list */
315 /* we sort the keys so we can use binary search later */
316 /* We have a linked list of keys and the number.
317 * to sort them we need an array, so we reform them into an array,
318 * and then throw away the list.
323 LINKEY *p, *n; /* pointers to linked list */
324 int i; /* index into key array */
326 /* allocate the array */
327 keys = (KEY *) gp_alloc((keycount + 1) * sizeof(KEY), "key array");
329 /* copy info from list to array, freeing list */
330 for (p = keylist, i = 0; p != NULL; p = n, i++) {
331 keys[i].key = p->key;
332 keys[i].pos = p->pos;
333 keys[i].text = p->text;
334 keys[i].primary = p->primary;
339 /* a null entry to terminate subtopic searches */
340 keys[keycount].key = NULL;
341 keys[keycount].pos = 0;
342 keys[keycount].text = NULL;
345 /* note that it only moves objects of size (two pointers + long + int) */
346 /* it moves no strings */
347 /* HBB 20010720: removed superfluous, potentially dangerous casts */
348 qsort(keys, keycount, sizeof(KEY), keycomp);
351 /* HBB 20010720: changed to make this match the prototype qsort()
352 * really expects. Casting function pointers, as we did before, is
354 /* HBB 20010720: removed 'static' to avoid HP-sUX gcc bug */
356 keycomp(SORTFUNC_ARGS arg1, SORTFUNC_ARGS arg2)
361 return (strcmp(a->key, b->key));
364 /* Free the help file from memory. */
365 /* May be called externally if space is needed */
369 int i; /* index into keys[] */
375 for (i = 0; i < keycount; i++) {
376 free((char *) keys[i].key);
377 if (keys[i].primary) /* only try to release text once! */
378 for (t = keys[i].text; t != NULL; t = next) {
379 free((char *) t->line);
388 (void) fclose(helpfp);
393 * Find the key that matches the keyword.
394 * The keys[] array is sorted by key.
395 * We could use a binary search, but a linear search will aid our
396 * attempt to allow abbreviations. We search for the first thing that
397 * matches all the text we're given. If not an exact match, then
398 * it is an abbreviated match, and there must be no other abbreviated
399 * matches -- for if there are, the abbreviation is ambiguous.
400 * We print the ambiguous matches in that case, and return not found.
402 static KEY * /* NULL if not found */
403 FindHelp(char *keyword) /* string we look for */
406 size_t len = strcspn(keyword, " ");
408 for (key = keys; key->key != NULL; key++) {
409 if (!strncmp(keyword, key->key, len)) { /* we have a match! */
410 if (!Ambiguous(key, len)) {
411 size_t key_len = strlen(key->key);
412 size_t keyword_len = strlen(keyword);
414 if (key_len != len) {
415 /* Expand front portion of keyword */
416 int i, shift = key_len - len;
418 for (i=keyword_len+shift; i >= len; i--)
419 keyword[i] = keyword[i-shift];
420 strncpy(keyword, key->key, key_len); /* give back the full spelling */
422 keyword_len += shift;
424 /* Check for another subword */
425 if (keyword[len] == ' ') {
426 len = len + 1 + strcspn(keyword + len + 1, " ");
427 key--; /* start with current key */
429 return (key); /* found!! non-ambiguous abbreviation */
440 * Check the key for ambiguity up to the given length.
441 * It is ambiguous if it is not a complete string and there are other
442 * keys following it with the same leading substring.
445 Ambiguous(KEY *key, size_t len)
449 TBOOLEAN status = FALSE; /* assume not ambiguous */
453 if (key->key[len] == NUL)
456 for (prev = first = key->key, compare = 0, key++;
457 key->key != NULL && compare == 0; key++) {
458 compare = strncmp(first, key->key, len);
460 /* So this key matches the first one, up to len.
461 * But is it different enough from the previous one
462 * to bother printing it as a separate choice?
464 sublen = strcspn(prev + len, " ");
465 if (strncmp(key->key, prev, len + sublen) != 0) {
466 /* yup, this is different up to the next space */
468 /* first one we have printed is special */
470 "Ambiguous request '%.*s'; possible matches:\n",
472 fprintf(stderr, "\t%s\n", prev);
475 fprintf(stderr, "\t%s\n", key->key);
485 * print the text for key
490 TBOOLEAN *subtopics) /* (in) - subtopics only? */
491 /* (out) - are there subtopics? */
495 char buf[BUFSIZ]; /* line from help file */
500 if (subtopics == NULL || !*subtopics) {
502 fseek(helpfp, key->pos, 0);
503 while ((fgets(buf, BUFSIZ - 1, helpfp) != (char *) NULL)
504 && (buf[0] != KEYFLAG)) {
508 for (t = key->text; t != NULL; t = t->next)
509 OutLine(t->line); /* print text line */
512 ShowSubtopics(key, subtopics);
520 * Print a list of subtopic names
522 /* The maximum number of subtopics per line */
527 KEY *key, /* the topic */
528 TBOOLEAN *subtopics) /* (out) are there any subtopics */
530 int subt = 0; /* printed any subtopics yet? */
531 KEY *subkey; /* subtopic key */
532 size_t len; /* length of key name */
533 char line[BUFSIZ]; /* subtopic output line */
534 char *start; /* position of subname in key name */
535 size_t sublen; /* length of subname */
536 char *prev = NULL; /* the last thing we put on the list */
538 #define MAXSTARTS 256
539 int stopics = 0; /* count of (and index to next) subtopic name */
540 char *starts[MAXSTARTS]; /* saved positions of subnames */
543 len = strlen(key->key);
545 for (subkey = key + 1; subkey->key != NULL; subkey++) {
546 if (strncmp(subkey->key, key->key, len) == 0) {
547 /* find this subtopic name */
548 start = subkey->key + len;
551 start++; /* skip space */
553 break; /* not the same topic after all */
555 /* here we are looking for main topics */
556 if (!subkey->primary)
557 continue; /* not a main topic */
559 sublen = strcspn(start, " ");
560 if (prev == NULL || strncmp(start, prev, sublen) != 0) {
564 strcpy(line, "\nSubtopics available for ");
565 strncat(line, key->key, BUFSIZ - 25 - 2 - 1);
568 strcpy(line, "\nHelp topics available:\n");
572 starts[stopics++] = start;
581 /* The number of the first column for subtopic entries */
583 /* Length of a subtopic entry; if COLLENGTH is exceeded,
584 * the next column is skipped */
589 /* sort subtopics by row - default */
591 int spacelen = 0, ispacelen;
594 for (subtopic = 0; subtopic < stopics; subtopic++) {
595 start = starts[subtopic];
596 sublen = strcspn(start, " ");
599 /* adapted by DvdSchaaf */
600 for (ispacelen = 0; ispacelen < spacelen; ispacelen++)
601 (void) strcat(line, " ");
602 (void) strncat(line, start, sublen);
604 spacelen = COLLENGTH - sublen;
605 while (spacelen <= 0) {
606 spacelen += COLLENGTH;
611 if (pos >= PER_LINE) {
612 (void) strcat(line, "\n");
619 /* put out the last line */
620 if (subt > 0 && pos > 0) {
621 (void) strcat(line, "\n");
625 #else /* COLUMN_HELP */
627 /* sort subtopics by column */
628 int subtopic, sublen;
629 int spacelen = 0, ispacelen;
631 int rows = (int) (stopics / PER_LINE) + 1;
633 for (row = 0; row < rows; row++) {
635 for (ispacelen = 0; ispacelen < FIRSTCOL; ispacelen++)
636 (void) strcat(line, " ");
638 for (col = 0; col < PER_LINE; col++) {
639 subtopic = row + rows * col;
640 if (subtopic >= stopics) {
643 start = starts[subtopic];
644 sublen = strcspn(start, " ");
645 (void) strncat(line, start, sublen);
646 spacelen = COLLENGTH - sublen;
649 for (ispacelen = 0; ispacelen < spacelen; ispacelen++)
650 (void) strcat(line, " ");
653 (void) strcat(line, "\n");
657 #endif /* COLUMN_HELP */
660 *subtopics = (subt != 0);
665 * Open a file pointer to a pipe to user's $PAGER, if there is one,
666 * otherwise use our own pager.
672 char *pager_name = getenv("PAGER");
674 if (pager_name != NULL && *pager_name != NUL)
675 if ((outfile = popen(pager_name, "w")) != (FILE *) NULL)
676 return; /* success */
678 /* fall through to built-in pager */
686 /* write a line of help output */
687 /* line should contain only one \n, at the end */
689 OutLine(const char *line)
691 int c; /* dummy input char */
693 if (outfile != stderr) {
694 fputs(line, outfile);
699 /* built-in dumb pager */
700 /* leave room for prompt line */
701 if (pagelines >= SCREENSIZE - 2) {
702 fputs("Press return for more: ", stderr);
703 #if defined(ATARI) || defined(MTOS)
706 while (c != '\x04' && c != '\r' && c != '\n');
710 while (c != EOF && c != '\n');
722 if (outfile != stderr)
723 (void) pclose(outfile);