Annotation of OpenXM_contrib/gnuplot/help.c, Revision 1.1.1.1
1.1 maekawa 1: #ifndef lint
2: static char *RCSid = "$Id: help.c,v 1.29 1998/04/14 00:15:36 drd Exp $";
3: #endif
4:
5: /* GNUPLOT - help.c */
6:
7: /*[
8: * Copyright 1986 - 1993, 1998 Thomas Williams, Colin Kelley
9: *
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.
15: *
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,
20: * provided you
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
28: * software.
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.
32: *
33: * This software is provided "as is" without express or implied warranty
34: * to the extent permitted by applicable law.
35: ]*/
36:
37: #include "plot.h"
38:
39: #define SAME 0 /* for strcmp() */
40:
41: #include "help.h" /* values passed back */
42:
43: void int_error __PROTO((char str[], int t_num));
44:
45: #if defined(__EMX__) || defined(DJGPP) || defined(DOS386)
46: /* we have plenty of memory under __EMX__ or DJGPP */
47: # ifdef MSDOS
48: # undef MSDOS
49: # endif
50: #endif
51:
52: /*
53: ** help -- help subsystem that understands defined keywords
54: **
55: ** Looks for the desired keyword in the help file at runtime, so you
56: ** can give extra help or supply local customizations by merely editing
57: ** the help file.
58: **
59: ** The original (single-file) idea and algorithm is by John D. Johnson,
60: ** Hewlett-Packard Company. Thanx and a tip of the Hatlo hat!
61: **
62: ** Much extension by David Kotz for use in gnutex, and then in gnuplot.
63: ** Added output paging support, both unix and builtin. Rewrote completely
64: ** to read helpfile into memory, avoiding reread of help file. 12/89.
65: **
66: ** Modified by Russell Lang to avoid reading completely into memory
67: ** if MSDOS defined. This uses much less memory. 6/91
68: **
69: ** The help file looks like this (the question marks are really in column 1):
70: **
71: ** ?topic
72: ** This line is printed when the user wants help on "topic".
73: ** ?keyword
74: ** ?Keyword
75: ** ?KEYWORD
76: ** These lines will be printed on the screen if the user wanted
77: ** help on "keyword", "Keyword", or "KEYWORD". No casefolding is
78: ** done on the keywords.
79: ** ?subject
80: ** ?alias
81: ** This line is printed for help on "subject" and "alias".
82: ** ?
83: ** ??
84: ** Since there is a null keyword for this line, this section
85: ** is printed when the user wants general help (when a help
86: ** keyword isn't given). A command summary is usually here.
87: ** Notice that the null keyword is equivalent to a "?" keyword
88: ** here, because of the '?' and '??' topic lines above.
89: ** If multiple keywords are given, the first is considered the
90: ** 'primary' keyword. This affects a listing of available topics.
91: ** ?last-subject
92: ** Note that help sections are terminated by the start of the next
93: ** '?' entry or by EOF. So you can't have a leading '?' on a line
94: ** of any help section. You can re-define the magic character to
95: ** recognize in column 1, though, if '?' is too useful. (Try ^A.)
96: */
97:
98: #define KEYFLAG '?' /* leading char in help file topic lines */
99:
100: /*
101: ** Calling sequence:
102: ** int result; # 0 == success
103: ** char *keyword; # topic to give help on
104: ** char *pathname; # path of help file
105: ** int subtopics; # set to TRUE if only subtopics to be listed
106: ** # returns TRUE if subtopics were found
107: ** result = help(keyword, pathname, &subtopics);
108: ** Sample:
109: ** cmd = "search\n";
110: ** helpfile = "/usr/local/lib/program/program.help";
111: ** subtopics = FALSE;
112: ** if (help(cmd, helpfile, &subtopics) != H_FOUND)
113: ** printf("Sorry, no help for %s", cmd);
114: **
115: **
116: ** Speed this up by replacing the stdio calls with open/close/read/write.
117: */
118: #ifdef WDLEN
119: # define PATHSIZE WDLEN
120: #else
121: # define PATHSIZE BUFSIZ
122: #endif
123:
124: typedef struct line_s LINEBUF;
125: struct line_s {
126: char *line; /* the text of this line */
127: LINEBUF *next; /* the next line */
128: };
129:
130: typedef struct linkey_s LINKEY;
131: struct linkey_s {
132: char *key; /* the name of this key */
133: long pos; /* ftell position */
134: LINEBUF *text; /* the text for this key */
135: TBOOLEAN primary; /* TRUE -> is a primary name for a text block */
136: LINKEY *next; /* the next key in linked list */
137: };
138:
139: typedef struct key_s KEY;
140: struct key_s {
141: char *key; /* the name of this key */
142: long pos; /* ftell position */
143: LINEBUF *text; /* the text for this key */
144: TBOOLEAN primary; /* TRUE -> is a primary name for a text block */
145: };
146: static LINKEY *keylist = NULL; /* linked list of keys */
147: static KEY *keys = NULL; /* array of keys */
148: static int keycount = 0; /* number of keys */
149: static FILE *helpfp = NULL;
150:
151: static int LoadHelp __PROTO((char *path));
152: static void sortkeys __PROTO((void));
153: static int keycomp __PROTO((struct key_s * a, struct key_s * b));
154: static LINEBUF *storeline __PROTO((char *text));
155: static LINKEY *storekey __PROTO((char *key));
156: static KEY *FindHelp __PROTO((char *keyword));
157: static TBOOLEAN Ambiguous __PROTO((struct key_s * key, int len));
158:
159: /* Help output */
160: static void PrintHelp __PROTO((struct key_s * key, int *subtopics));
161: static void ShowSubtopics __PROTO((struct key_s * key, int *subtopics));
162:
163: #if defined(PIPES)
164: static FILE *outfile; /* for unix pager, if any */
165: #endif
166: static int pagelines; /* count for builtin pager */
167: #define SCREENSIZE 24 /* lines on screen (most have at least 24) */
168:
169: /* help:
170: * print a help message
171: * also print available subtopics, if subtopics is TRUE
172: */
173: int help(keyword, path, subtopics)
174: char *keyword; /* on this topic */
175: char *path; /* from this file */
176: TBOOLEAN *subtopics; /* (in) - subtopics only? */
177: /* (out) - are there subtopics? */
178: {
179: static char oldpath[PATHSIZE] = ""; /* previous help file */
180: int status; /* result of LoadHelp */
181: KEY *key; /* key that matches keyword */
182:
183: /*
184: ** Load the help file if necessary (say, first time we enter this routine,
185: ** or if the help file changes from the last time we were called).
186: ** Also may occur if in-memory copy was freed.
187: ** Calling routine may access errno to determine cause of H_ERROR.
188: */
189: errno = 0;
190: if (strncmp(oldpath, path, PATHSIZE) != SAME)
191: FreeHelp();
192: if (keys == NULL) {
193: status = LoadHelp(path);
194: if (status == H_ERROR)
195: return (status);
196:
197: /* save the new path in oldpath */
198: safe_strncpy(oldpath, path, PATHSIZE);
199: }
200: /* look for the keyword in the help file */
201: key = FindHelp(keyword);
202: if (key != NULL) {
203: /* found the keyword: print help and return */
204: PrintHelp(key, subtopics);
205: status = H_FOUND;
206: } else {
207: status = H_NOTFOUND;
208: }
209:
210: return (status);
211: }
212:
213: /* we only read the file once, into memory
214: * except for MSDOS when we don't read all the file -
215: * just the keys and location of the text
216: */
217: static int LoadHelp(path)
218: char *path;
219: {
220: LINKEY *key; /* this key */
221: long pos = 0; /* ftell location within help file */
222: char buf[BUFSIZ]; /* line from help file */
223: LINEBUF *head; /* head of text list */
224: LINEBUF *firsthead = NULL;
225: TBOOLEAN primary; /* first ? line of a set is primary */
226: TBOOLEAN flag;
227:
228: if ((helpfp = fopen(path, "r")) == NULL) {
229: /* can't open help file, so error exit */
230: return (H_ERROR);
231: }
232: /*
233: ** The help file is open. Look in there for the keyword.
234: */
235: if (!fgets(buf, BUFSIZ - 1, helpfp) || *buf != KEYFLAG)
236: return (H_ERROR); /* it is probably not the .gih file */
237:
238: while (!feof(helpfp)) {
239: /*
240: ** Make an entry for each synonym keyword
241: */
242: primary = TRUE;
243: while (buf[0] == KEYFLAG) {
244: key = storekey(buf + 1); /* store this key */
245: key->primary = primary;
246: key->text = NULL; /* fill in with real value later */
247: key->pos = 0; /* fill in with real value later */
248: primary = FALSE;
249: pos = ftell(helpfp);
250: if (fgets(buf, BUFSIZ - 1, helpfp) == (char *) NULL)
251: break;
252: }
253: /*
254: ** Now store the text for this entry.
255: ** buf already contains the first line of text.
256: */
257: #ifndef MSDOS
258: firsthead = storeline(buf);
259: head = firsthead;
260: #endif
261: while ((fgets(buf, BUFSIZ - 1, helpfp) != (char *) NULL)
262: && (buf[0] != KEYFLAG)) {
263: #ifndef MSDOS
264: /* save text line */
265: head->next = storeline(buf);
266: head = head->next;
267: #endif
268: }
269: /* make each synonym key point to the same text */
270: do {
271: key->pos = pos;
272: key->text = firsthead;
273: flag = key->primary;
274: key = key->next;
275: } while (flag != TRUE && key != NULL);
276: }
277: #ifndef MSDOS
278: (void) fclose(helpfp);
279: #endif
280:
281: /* we sort the keys so we can use binary search later */
282: sortkeys();
283: return (H_FOUND); /* ok */
284: }
285:
286: /* make a new line buffer and save this string there */
287: static LINEBUF *
288: storeline(text)
289: char *text;
290: {
291: LINEBUF *new;
292:
293: new = (LINEBUF *) malloc(sizeof(LINEBUF));
294: if (new == NULL)
295: int_error("not enough memory to store help file", -1);
296: if (text != NULL) {
297: new->line = (char *) malloc((unsigned int) (strlen(text) + 1));
298: if (new->line == NULL)
299: int_error("not enough memory to store help file", -1);
300: (void) strcpy(new->line, text);
301: } else
302: new->line = NULL;
303:
304: new->next = NULL;
305:
306: return (new);
307: }
308:
309: /* Add this keyword to the keys list, with the given text */
310: static LINKEY *
311: storekey(key)
312: char *key;
313: {
314: LINKEY *new;
315:
316: key[strlen(key) - 1] = NUL; /* cut off \n */
317:
318: new = (LINKEY *) malloc(sizeof(LINKEY));
319: if (new == NULL)
320: int_error("not enough memory to store help file", -1);
321: new->key = (char *) malloc((unsigned int) (strlen(key) + 1));
322: if (new->key == NULL)
323: int_error("not enough memory to store help file", -1);
324: (void) strcpy(new->key, key);
325:
326: /* add to front of list */
327: new->next = keylist;
328: keylist = new;
329: keycount++;
330: return (new);
331: }
332:
333: /* we sort the keys so we can use binary search later */
334: /* We have a linked list of keys and the number.
335: * to sort them we need an array, so we reform them into an array,
336: * and then throw away the list.
337: */
338: static void sortkeys()
339: {
340: LINKEY *p, *n; /* pointers to linked list */
341: int i; /* index into key array */
342:
343: /* allocate the array */
344: keys = (KEY *) malloc((unsigned int) ((keycount + 1) * sizeof(KEY)));
345: if (keys == NULL)
346: int_error("not enough memory to store help file", -1);
347:
348: /* copy info from list to array, freeing list */
349: for (p = keylist, i = 0; p != NULL; p = n, i++) {
350: keys[i].key = p->key;
351: keys[i].pos = p->pos;
352: keys[i].text = p->text;
353: keys[i].primary = p->primary;
354: n = p->next;
355: free((char *) p);
356: }
357:
358: /* a null entry to terminate subtopic searches */
359: keys[keycount].key = NULL;
360: keys[keycount].pos = 0;
361: keys[keycount].text = NULL;
362:
363: /* sort the array */
364: /* note that it only moves objects of size (two pointers + long + int) */
365: /* it moves no strings */
366: qsort((char *) keys, keycount, sizeof(KEY), (sortfunc) keycomp);
367: }
368:
369: static int keycomp(a, b)
370: KEY *a, *b;
371: {
372: return (strcmp(a->key, b->key));
373: }
374:
375: /* Free the help file from memory. */
376: /* May be called externally if space is needed */
377: void FreeHelp()
378: {
379: int i; /* index into keys[] */
380: LINEBUF *t, *next;
381:
382: if (keys == NULL)
383: return;
384:
385: for (i = 0; i < keycount; i++) {
386: free((char *) keys[i].key);
387: if (keys[i].primary) /* only try to release text once! */
388: for (t = keys[i].text; t != NULL; t = next) {
389: free((char *) t->line);
390: next = t->next;
391: free((char *) t);
392: }
393: }
394: free((char *) keys);
395: keys = NULL;
396: keycount = 0;
397: #ifdef MSDOS
398: (void) fclose(helpfp);
399: #endif
400: }
401:
402: /* FindHelp:
403: * Find the key that matches the keyword.
404: * The keys[] array is sorted by key.
405: * We could use a binary search, but a linear search will aid our
406: * attempt to allow abbreviations. We search for the first thing that
407: * matches all the text we're given. If not an exact match, then
408: * it is an abbreviated match, and there must be no other abbreviated
409: * matches -- for if there are, the abbreviation is ambiguous.
410: * We print the ambiguous matches in that case, and return not found.
411: */
412: static KEY * /* NULL if not found */
413: FindHelp(keyword)
414: char *keyword; /* string we look for */
415: {
416: KEY *key;
417: int len = strlen(keyword);
418: int compare;
419:
420: for (key = keys, compare = 1; key->key != NULL && compare > 0; key++) {
421: compare = strncmp(keyword, key->key, len);
422: if (compare == 0) /* we have a match! */
423: if (!Ambiguous(key, len)) {
424: /* non-ambiguous abbreviation */
425: (void) strcpy(keyword, key->key); /* give back the full spelling */
426: return (key); /* found!! */
427: }
428: }
429:
430: /* not found, or ambiguous */
431: return (NULL);
432: }
433:
434: /* Ambiguous:
435: * Check the key for ambiguity up to the given length.
436: * It is ambiguous if it is not a complete string and there are other
437: * keys following it with the same leading substring.
438: */
439: static TBOOLEAN
440: Ambiguous(key, len)
441: KEY *key;
442: int len;
443: {
444: char *first;
445: char *prev;
446: TBOOLEAN status = FALSE; /* assume not ambiguous */
447: int compare;
448: int sublen;
449:
450: if (key->key[len] == NUL)
451: return (FALSE);
452:
453: for (prev = first = key->key, compare = 0, key++;
454: key->key != NULL && compare == 0; key++) {
455: compare = strncmp(first, key->key, len);
456: if (compare == 0) {
457: /* So this key matches the first one, up to len.
458: * But is it different enough from the previous one
459: * to bother printing it as a separate choice?
460: */
461: sublen = instring(prev + len, ' ');
462: if (strncmp(key->key, prev, len + sublen) != 0) {
463: /* yup, this is different up to the next space */
464: if (!status) {
465: /* first one we have printed is special */
466: fprintf(stderr,
467: "Ambiguous request '%.*s'; possible matches:\n",
468: len, first);
469: fprintf(stderr, "\t%s\n", prev);
470: status = TRUE;
471: }
472: fprintf(stderr, "\t%s\n", key->key);
473: prev = key->key;
474: }
475: }
476: }
477:
478: return (status);
479: }
480:
481: /* PrintHelp:
482: * print the text for key
483: */
484: static void PrintHelp(key, subtopics)
485: KEY *key;
486: TBOOLEAN *subtopics; /* (in) - subtopics only? */
487: /* (out) - are there subtopics? */
488: {
489: LINEBUF *t;
490: #ifdef MSDOS
491: char buf[BUFSIZ]; /* line from help file */
492: #endif
493:
494: StartOutput();
495:
496: if (subtopics == NULL || !*subtopics) {
497: #ifdef MSDOS
498: fseek(helpfp, key->pos, 0);
499: while ((fgets(buf, BUFSIZ - 1, helpfp) != (char *) NULL)
500: && (buf[0] != KEYFLAG)) {
501: OutLine(buf);
502: }
503: #else
504: for (t = key->text; t != NULL; t = t->next)
505: OutLine(t->line); /* print text line */
506: #endif
507: }
508: ShowSubtopics(key, subtopics);
509: OutLine("\n");
510:
511: EndOutput();
512: }
513:
514:
515: /* ShowSubtopics:
516: * Print a list of subtopic names
517: */
518: #define PER_LINE 4
519:
520: static void ShowSubtopics(key, subtopics)
521: KEY *key; /* the topic */
522: TBOOLEAN *subtopics; /* (out) are there any subtopics */
523: {
524: int subt = 0; /* printed any subtopics yet? */
525: KEY *subkey; /* subtopic key */
526: int len; /* length of key name */
527: char line[BUFSIZ]; /* subtopic output line */
528: char *start; /* position of subname in key name */
529: int sublen; /* length of subname */
530: int pos = 0;
531: int spacelen = 0; /* Moved from inside for() loop */
532: char *prev = NULL; /* the last thing we put on the list */
533:
534: *line = NUL;
535: len = strlen(key->key);
536:
537: for (subkey = key + 1; subkey->key != NULL; subkey++) {
538: int ispacelen = 0;
539: if (strncmp(subkey->key, key->key, len) == 0) {
540: /* find this subtopic name */
541: start = subkey->key + len;
542: if (len > 0) {
543: if (*start == ' ')
544: start++; /* skip space */
545: else
546: break; /* not the same topic after all */
547: } else {
548: /* here we are looking for main topics */
549: if (!subkey->primary)
550: continue; /* not a main topic */
551: }
552: sublen = instring(start, ' ');
553: if (prev == NULL || strncmp(start, prev, sublen) != 0) {
554: if (subt == 0) {
555: subt++;
556: if (len)
557: (void) sprintf(line, "\nSubtopics available for %s:\n",
558: key->key);
559: else
560: (void) sprintf(line, "\nHelp topics available:\n");
561: OutLine(line);
562: *line = NUL;
563: pos = 0;
564: }
565: if (pos == PER_LINE) {
566: (void) strcat(line, "\n");
567: OutLine(line);
568: *line = NUL;
569: pos = 0;
570: }
571: /* adapted by DvdSchaaf */
572: {
573: #define FIRSTCOL 6
574: #define COLLENGTH 15
575:
576: if (pos == 0)
577: spacelen = FIRSTCOL;
578: for (ispacelen = 0;
579: ispacelen < spacelen; ispacelen++)
580: (void) strcat(line, " ");
581: /* commented out *
582: (void) strcat(line, "\t");
583: */
584: (void) strncat(line, start, sublen);
585: spacelen = COLLENGTH - sublen;
586: if (spacelen <= 0)
587: spacelen = 1;
588: }
589: pos++;
590: prev = start;
591: }
592: } else {
593: /* new topic */
594: break;
595: }
596: }
597:
598: /* put out the last line */
599: if (subt > 0 && pos > 0) {
600: (void) strcat(line, "\n");
601: OutLine(line);
602: }
603: /*
604: if (subt == 0) {
605: OutLine("\n");
606: OutLine("No subtopics available\n");
607: }
608: */
609:
610: if (subtopics)
611: *subtopics = (subt != 0);
612: }
613:
614:
615: /* StartOutput:
616: * Open a file pointer to a pipe to user's $PAGER, if there is one,
617: * otherwise use our own pager.
618: */
619: void StartOutput()
620: {
621: #if defined(PIPES)
622: char *pager_name = getenv("PAGER");
623:
624: if (pager_name != NULL && *pager_name != NUL)
625: if ((outfile = popen(pager_name, "w")) != (FILE *) NULL)
626: return; /* success */
627: outfile = stderr;
628: /* fall through to built-in pager */
629: #endif
630:
631: /* built-in pager */
632: pagelines = 0;
633: }
634:
635: #if defined(ATARI) || defined(MTOS)
636: # ifndef READLINE
637: # error cannot compile atari versions without -DREADLINE
638: # endif
639: #endif
640:
641: /* write a line of help output */
642: /* line should contain only one \n, at the end */
643: void OutLine(line)
644: char *line;
645: {
646: int c; /* dummy input char */
647: #if defined(PIPES)
648: if (outfile != stderr) {
649: fputs(line, outfile);
650: return;
651: }
652: #endif
653:
654: /* built-in dumb pager */
655: /* leave room for prompt line */
656: if (pagelines >= SCREENSIZE - 2) {
657: fputs("Press return for more: ", stderr);
658: #if defined(ATARI) || defined(MTOS)
659: do
660: c = tos_getch();
661: while (c != '\x04' && c != '\r' && c != '\n');
662: #else
663: do
664: c = getchar();
665: while (c != EOF && c != '\n');
666: #endif
667: pagelines = 0;
668: }
669: fputs(line, stderr);
670: pagelines++;
671: }
672:
673: void EndOutput()
674: {
675: #if defined(PIPES)
676: if (outfile != stderr)
677: (void) pclose(outfile);
678: #endif
679: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>