home *** CD-ROM | disk | FTP | other *** search
/ The C Users' Group Library 1994 August / wc-cdrom-cusersgrouplibrary-1994-08.iso / vol_300 / 334_01 / help.c < prev    next >
Text File  |  1991-02-05  |  16KB  |  599 lines

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