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