Initial release of Maemo 5 port of gnuplot
[gnuplot] / src / help.c
1 #ifndef lint
2 static char *RCSid() { return RCSid("$Id: help.c,v 1.19 2006/07/07 18:06:30 sfeam Exp $"); }
3 #endif
4
5 /* GNUPLOT - help.c */
6
7 /*[
8  * Copyright 1986 - 1993, 1998, 2004   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 "help.h"
38
39 #include "alloc.h"
40 #include "util.h"
41
42 /*
43  ** help -- help subsystem that understands defined keywords
44  **
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
47  ** the help file.
48  **
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!
51  **
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.
55  **
56  ** Modified by Russell Lang to avoid reading completely into memory
57  ** if DOS16 defined.  This uses much less memory.  6/91
58  **
59  ** The help file looks like this (the question marks are really in column 1):
60  **
61  **   ?topic
62  **   This line is printed when the user wants help on "topic".
63  **   ?keyword
64  **   ?Keyword
65  **   ?KEYWORD
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.
69  **   ?subject
70  **   ?alias
71  **   This line is printed for help on "subject" and "alias".
72  **   ?
73  **   ??
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.
81  **   ?last-subject
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.)
86  */
87
88 #define KEYFLAG '?'             /* leading char in help file topic lines */
89
90 /*
91  ** Calling sequence:
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);
98  ** Sample:
99  **   cmd = "search\n";
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);
104  **
105  **
106  ** Speed this up by replacing the stdio calls with open/close/read/write.
107  */
108 #ifdef WDLEN
109 # define PATHSIZE WDLEN
110 #else
111 # define PATHSIZE BUFSIZ
112 #endif
113
114 typedef struct line_s LINEBUF;
115 struct line_s {
116     char *line;                 /* the text of this line */
117     LINEBUF *next;              /* the next line */
118 };
119
120 typedef struct linkey_s LINKEY;
121 struct linkey_s {
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 */
127 };
128
129 typedef struct key_s KEY;
130 struct key_s {
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 */
135 };
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};
141
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));
149
150 /* Help output */
151 static void PrintHelp __PROTO((struct key_s * key, TBOOLEAN *subtopics));
152 static void ShowSubtopics __PROTO((struct key_s * key, TBOOLEAN *subtopics));
153
154 #if defined(PIPES)
155 static FILE *outfile;           /* for unix pager, if any */
156 #endif
157 static int pagelines;           /* count for builtin pager */
158 #define SCREENSIZE 24           /* lines on screen (most have at least 24) */
159
160 /* help:
161  * print a help message
162  * also print available subtopics, if subtopics is TRUE
163  */
164 int
165 help(
166     char *keyword,              /* on this topic */
167     char *path,                 /* from this file */
168     TBOOLEAN *subtopics)        /* (in) - subtopics only? */
169                                 /* (out) - are there subtopics? */
170 {
171     static char oldpath[PATHSIZE] = "";         /* previous help file */
172     int status;                 /* result of LoadHelp */
173     KEY *key;                   /* key that matches keyword */
174
175     /*
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.
180      */
181     errno = 0;
182     if (strncmp(oldpath, path, PATHSIZE) != 0)
183         FreeHelp();
184     if (keys == NULL) {
185         status = LoadHelp(path);
186         if (status == H_ERROR)
187             return (status);
188
189         /* save the new path in oldpath */
190         safe_strncpy(oldpath, path, PATHSIZE);
191     }
192     /* look for the keyword in the help file */
193     key = FindHelp(keyword);
194     if (key != NULL) {
195         /* found the keyword: if help exists, print and return */
196         if (key->text)
197             PrintHelp(key, subtopics);
198         status = H_FOUND;
199     } else {
200         status = H_NOTFOUND;
201     }
202
203     return (status);
204 }
205
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
209  */
210 static int
211 LoadHelp(char *path)
212 {
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 */
219     TBOOLEAN flag;
220
221     if ((helpfp = fopen(path, "r")) == NULL) {
222         /* can't open help file, so error exit */
223         return (H_ERROR);
224     }
225     /*
226      ** The help file is open.  Look in there for the keyword.
227      */
228     if (!fgets(buf, BUFSIZ - 1, helpfp) || *buf != KEYFLAG)
229         return (H_ERROR);       /* it is probably not the .gih file */
230
231     while (!feof(helpfp)) {
232         /*
233          ** Make an entry for each synonym keyword
234          */
235         primary = TRUE;
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 */
241             primary = FALSE;
242             pos = ftell(helpfp);
243             if (fgets(buf, BUFSIZ - 1, helpfp) == (char *) NULL)
244                 break;
245         }
246         /*
247          ** Now store the text for this entry.
248          ** buf already contains the first line of text.
249          */
250 #ifndef DOS16
251         firsthead = storeline(buf);
252         head = firsthead;
253 #endif
254         while ((fgets(buf, BUFSIZ - 1, helpfp) != (char *) NULL)
255                && (buf[0] != KEYFLAG)) {
256 #ifndef DOS16
257             /* save text line */
258             head->next = storeline(buf);
259             head = head->next;
260 #endif
261         }
262         /* make each synonym key point to the same text */
263         do {
264             key->pos = pos;
265             key->text = firsthead;
266             flag = key->primary;
267             key = key->next;
268         } while (flag != TRUE && key != NULL);
269     }
270 #ifndef DOS16
271     (void) fclose(helpfp);
272 #endif
273
274     /* we sort the keys so we can use binary search later */
275     sortkeys();
276     return (H_FOUND);           /* ok */
277 }
278
279 /* make a new line buffer and save this string there */
280 static LINEBUF *
281 storeline(char *text)
282 {
283     LINEBUF *new;
284
285     new = (LINEBUF *) gp_alloc(sizeof(LINEBUF), "new line buffer");
286     if (text)
287         new->line = gp_strdup(text);
288     else
289         new->line = NULL;
290
291     new->next = NULL;
292
293     return (new);
294 }
295
296 /* Add this keyword to the keys list, with the given text */
297 static LINKEY *
298 storekey(char *key)
299 {
300     LINKEY *new;
301
302     key[strlen(key) - 1] = NUL; /* cut off \n  */
303
304     new = (LINKEY *) gp_alloc(sizeof(LINKEY), "new key list");
305     if (key)
306         new->key = gp_strdup(key);
307
308     /* add to front of list */
309     new->next = keylist;
310     keylist = new;
311     keycount++;
312     return (new);
313 }
314
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.
319  */
320 static void
321 sortkeys()
322 {
323     LINKEY *p, *n;              /* pointers to linked list */
324     int i;                      /* index into key array */
325
326     /* allocate the array */
327     keys = (KEY *) gp_alloc((keycount + 1) * sizeof(KEY), "key array");
328
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;
335         n = p->next;
336         free((char *) p);
337     }
338
339     /* a null entry to terminate subtopic searches */
340     keys[keycount].key = NULL;
341     keys[keycount].pos = 0;
342     keys[keycount].text = NULL;
343
344     /* sort the array */
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);
349 }
350
351 /* HBB 20010720: changed to make this match the prototype qsort()
352  * really expects. Casting function pointers, as we did before, is
353  * illegal! */
354 /* HBB 20010720: removed 'static' to avoid HP-sUX gcc bug */
355 int
356 keycomp(SORTFUNC_ARGS arg1, SORTFUNC_ARGS arg2)
357 {
358     const KEY *a = arg1;
359     const KEY *b = arg2;
360
361     return (strcmp(a->key, b->key));
362 }
363
364 /* Free the help file from memory. */
365 /* May be called externally if space is needed */
366 void
367 FreeHelp()
368 {
369     int i;                      /* index into keys[] */
370     LINEBUF *t, *next;
371
372     if (keys == NULL)
373         return;
374
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);
380                 next = t->next;
381                 free((char *) t);
382             }
383     }
384     free((char *) keys);
385     keys = NULL;
386     keycount = 0;
387 #ifdef DOS16
388     (void) fclose(helpfp);
389 #endif
390 }
391
392 /* FindHelp:
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.
401  */
402 static KEY * /* NULL if not found */
403 FindHelp(char *keyword)         /* string we look for */
404 {
405     KEY *key;
406     size_t len = strcspn(keyword, " ");
407
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);
413
414                 if (key_len != len) {
415                     /* Expand front portion of keyword */
416                     int i, shift = key_len - len;
417
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 */
421                     len = key_len;
422                     keyword_len += shift;
423                 }
424                 /* Check for another subword */
425                 if (keyword[len] == ' ') {
426                     len = len + 1 + strcspn(keyword + len + 1, " ");
427                     key--; /* start with current key */
428                 } else
429                     return (key);  /* found!!  non-ambiguous abbreviation */
430             } else
431                 return (&empty_key);
432         }
433     }
434
435     /* not found */
436     return (NULL);
437 }
438
439 /* Ambiguous:
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.
443  */
444 static TBOOLEAN
445 Ambiguous(KEY *key, size_t len)
446 {
447     char *first;
448     char *prev;
449     TBOOLEAN status = FALSE;    /* assume not ambiguous */
450     int compare;
451     size_t sublen;
452
453     if (key->key[len] == NUL)
454         return (FALSE);
455
456     for (prev = first = key->key, compare = 0, key++;
457          key->key != NULL && compare == 0; key++) {
458         compare = strncmp(first, key->key, len);
459         if (compare == 0) {
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?
463              */
464             sublen = strcspn(prev + len, " ");
465             if (strncmp(key->key, prev, len + sublen) != 0) {
466                 /* yup, this is different up to the next space */
467                 if (!status) {
468                     /* first one we have printed is special */
469                     fprintf(stderr,
470                             "Ambiguous request '%.*s'; possible matches:\n",
471                             (int)len, first);
472                     fprintf(stderr, "\t%s\n", prev);
473                     status = TRUE;
474                 }
475                 fprintf(stderr, "\t%s\n", key->key);
476                 prev = key->key;
477             }
478         }
479     }
480
481     return (status);
482 }
483
484 /* PrintHelp:
485  * print the text for key
486  */
487 static void
488 PrintHelp(
489     KEY *key,
490     TBOOLEAN *subtopics)        /* (in) - subtopics only? */
491                                 /* (out) - are there subtopics? */
492 {
493     LINEBUF *t;
494 #ifdef DOS16
495     char buf[BUFSIZ];           /* line from help file */
496 #endif
497
498     StartOutput();
499
500     if (subtopics == NULL || !*subtopics) {
501 #ifdef DOS16
502         fseek(helpfp, key->pos, 0);
503         while ((fgets(buf, BUFSIZ - 1, helpfp) != (char *) NULL)
504                && (buf[0] != KEYFLAG)) {
505             OutLine(buf);
506         }
507 #else
508         for (t = key->text; t != NULL; t = t->next)
509             OutLine(t->line);   /* print text line */
510 #endif
511     }
512     ShowSubtopics(key, subtopics);
513     OutLine("\n");
514
515     EndOutput();
516 }
517
518
519 /* ShowSubtopics:
520  *  Print a list of subtopic names
521  */
522 /* The maximum number of subtopics per line */
523 #define PER_LINE 4
524
525 static void
526 ShowSubtopics(
527     KEY *key,                   /* the topic */
528     TBOOLEAN *subtopics)        /* (out) are there any subtopics */
529 {
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 */
537
538 #define MAXSTARTS 256
539     int stopics = 0;            /* count of (and index to next) subtopic name */
540     char *starts[MAXSTARTS];    /* saved positions of subnames */
541
542     *line = NUL;
543     len = strlen(key->key);
544
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;
549             if (len > 0) {
550                 if (*start == ' ')
551                     start++;    /* skip space */
552                 else
553                     break;      /* not the same topic after all  */
554             } else {
555                 /* here we are looking for main topics */
556                 if (!subkey->primary)
557                     continue;   /* not a main topic */
558             }
559             sublen = strcspn(start, " ");
560             if (prev == NULL || strncmp(start, prev, sublen) != 0) {
561                 if (subt == 0) {
562                     subt++;
563                     if (len) {
564                         strcpy(line, "\nSubtopics available for ");
565                         strncat(line, key->key, BUFSIZ - 25 - 2 - 1);
566                         strcat(line, ":\n");
567                     } else
568                         strcpy(line, "\nHelp topics available:\n");
569                     OutLine(line);
570                     *line = NUL;
571                 }
572                 starts[stopics++] = start;
573                 prev = start;
574             }
575         } else {
576             /* new topic */
577             break;
578         }
579     }
580
581 /* The number of the first column for subtopic entries */
582 #define FIRSTCOL        4
583 /* Length of a subtopic entry; if COLLENGTH is exceeded,
584  * the next column is skipped */
585 #define COLLENGTH       18
586
587 #ifndef COLUMN_HELP
588     {
589         /* sort subtopics by row - default */
590         int subtopic;
591         int spacelen = 0, ispacelen;
592         int pos = 0;
593
594         for (subtopic = 0; subtopic < stopics; subtopic++) {
595             start = starts[subtopic];
596             sublen = strcspn(start, " ");
597             if (pos == 0)
598                 spacelen = FIRSTCOL;
599             /* adapted by DvdSchaaf */
600             for (ispacelen = 0; ispacelen < spacelen; ispacelen++)
601                 (void) strcat(line, " ");
602             (void) strncat(line, start, sublen);
603
604             spacelen = COLLENGTH - sublen;
605             while (spacelen <= 0) {
606                 spacelen += COLLENGTH;
607                 pos++;
608             }
609
610             pos++;
611             if (pos >= PER_LINE) {
612                 (void) strcat(line, "\n");
613                 OutLine(line);
614                 *line = NUL;
615                 pos = 0;
616             }
617         }
618
619         /* put out the last line */
620         if (subt > 0 && pos > 0) {
621             (void) strcat(line, "\n");
622             OutLine(line);
623         }
624     }
625 #else /* COLUMN_HELP */
626     {
627         /* sort subtopics by column */
628         int subtopic, sublen;
629         int spacelen = 0, ispacelen;
630         int row, col;
631         int rows = (int) (stopics / PER_LINE) + 1;
632
633         for (row = 0; row < rows; row++) {
634             *line = NUL;
635             for (ispacelen = 0; ispacelen < FIRSTCOL; ispacelen++)
636                 (void) strcat(line, " ");
637
638             for (col = 0; col < PER_LINE; col++) {
639                 subtopic = row + rows * col;
640                 if (subtopic >= stopics) {
641                     break;
642                 } else {
643                     start = starts[subtopic];
644                     sublen = strcspn(start, " ");
645                     (void) strncat(line, start, sublen);
646                     spacelen = COLLENGTH - sublen;
647                     if (spacelen <= 0)
648                         spacelen = 1;
649                     for (ispacelen = 0; ispacelen < spacelen; ispacelen++)
650                         (void) strcat(line, " ");
651                 }
652             }
653             (void) strcat(line, "\n");
654             OutLine(line);
655         }
656     }
657 #endif /* COLUMN_HELP */
658
659     if (subtopics)
660         *subtopics = (subt != 0);
661 }
662
663
664 /* StartOutput:
665  * Open a file pointer to a pipe to user's $PAGER, if there is one,
666  * otherwise use our own pager.
667  */
668 void
669 StartOutput()
670 {
671 #if defined(PIPES)
672     char *pager_name = getenv("PAGER");
673
674     if (pager_name != NULL && *pager_name != NUL)
675         if ((outfile = popen(pager_name, "w")) != (FILE *) NULL)
676             return;             /* success */
677     outfile = stderr;
678     /* fall through to built-in pager */
679 #endif
680
681     /* built-in pager */
682     pagelines = 0;
683 }
684
685
686 /* write a line of help output  */
687 /* line should contain only one \n, at the end */
688 void
689 OutLine(const char *line)
690 {
691     int c;                      /* dummy input char */
692 #if defined(PIPES)
693     if (outfile != stderr) {
694         fputs(line, outfile);
695         return;
696     }
697 #endif
698
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)
704         do
705             c = tos_getch();
706         while (c != '\x04' && c != '\r' && c != '\n');
707 #else
708         do
709             c = getchar();
710         while (c != EOF && c != '\n');
711 #endif
712         pagelines = 0;
713     }
714     fputs(line, stderr);
715     pagelines++;
716 }
717
718 void
719 EndOutput()
720 {
721 #if defined(PIPES)
722     if (outfile != stderr)
723         (void) pclose(outfile);
724 #endif
725 }