2 * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
20 ** This program parses mutt's init.h and generates documentation in
21 ** three different formats:
23 ** -> a commented muttrc configuration file
24 ** -> nroff, suitable for inclusion in a manual page
25 ** -> linuxdoc-sgml, suitable for inclusion in the
52 extern char *sys_errlist[];
55 #define strerror(x) ((x) > 0 && (x) < sys_nerr) ? sys_errlist[(x)] : 0
56 #endif /* !HAVE_STRERROR */
62 #define STRLEN(s) (s ? strlen(s) : 0)
70 static int outcount = 0;
71 static var_t* outbuf = NULL;
73 static int var_cmp (const void* a, const void* b) {
74 return (strcmp (((var_t*) a)->name, ((var_t*) b)->name));
79 F_CONF, F_MAN, F_SGML, F_NONE
85 #define D_TAB (1 << 3)
87 #define D_INIT (1 << 5)
108 enum output_formats_t OutputFormat = F_NONE;
112 static char *get_token (char *, size_t, char *);
113 static char *skip_ws (char *);
114 static const char *type2human (int);
115 static int buff2type (const char *);
116 static int flush_doc (int);
117 static int handle_docline (char *, int);
118 static int print_it (int, char *, int);
119 static void print_confline (const char *, int, const char *);
120 static void handle_confline (char *);
121 static void makedoc (FILE *, FILE *);
122 static void pretty_default (char *, size_t, const char *, int);
123 static int sgml_fputc (int);
124 static int sgml_fputs (const char *);
125 static void add_var (const char*);
126 static int add_s (const char*);
127 static int add_c (int);
129 int main (int argc, char *argv[])
134 if ((Progname = strrchr (argv[0], '/')))
139 while ((c = getopt (argc, argv, "cmsd")) != EOF)
143 case 'c': OutputFormat = F_CONF; break;
144 case 'm': OutputFormat = F_MAN; break;
145 case 's': OutputFormat = F_SGML; break;
146 case 'd': Debug++; break;
149 fprintf (stderr, "%s: bad command line parameter.\n", Progname);
157 if ((f = fopen (argv[optind], "r")) == NULL)
159 fprintf (stderr, "%s: Can't open %s (%s).\n",
160 Progname, argv[optind], strerror (errno));
167 switch (OutputFormat)
171 case F_SGML: makedoc (f, stdout); break;
174 fprintf (stderr, "%s: No output format specified.\n",
186 static void add_var (const char* name) {
187 outbuf = realloc (outbuf, (++outcount) * sizeof(var_t));
188 outbuf[outcount-1].seen = 0;
189 outbuf[outcount-1].name = strdup (name);
190 outbuf[outcount-1].descr = NULL;
193 static int add (const char* s) {
194 size_t lnew = STRLEN(s), lold = STRLEN(outbuf[outcount-1].descr);
198 if (!outbuf[outcount-1].seen) {
200 outbuf[outcount-1].seen = 1;
204 outbuf[outcount-1].descr = strdup (s);
206 outbuf[outcount-1].descr = realloc (outbuf[outcount-1].descr, lold + lnew + 1);
207 memcpy (&(outbuf[outcount-1].descr[lold-1])+1, s, lnew);
209 outbuf[outcount-1].descr[lold+lnew] = '\0';
213 static int add_c (int c) {
214 char buf[2] = "\0\0";
219 static void makedoc (FILE *in, FILE *out)
221 char buffer[BUFFSIZE];
222 char token[BUFFSIZE];
226 int docstat = D_INIT;
228 while ((fgets (buffer, sizeof (buffer), in)))
231 if ((p = strchr (buffer, '\n')) == NULL)
233 fprintf (stderr, "%s: Line %d too long. Ask a wizard to enlarge\n"
234 "%s: my buffer size.\n", Progname, line, Progname);
240 if (!(p = get_token (token, sizeof (token), buffer)))
245 fprintf (stderr, "%s: line %d. first token: \"%s\".\n",
246 Progname, line, token);
249 if (!strcmp (token, "/*++*/"))
251 else if (!strcmp (token, "/*--*/"))
253 docstat = flush_doc (docstat);
256 else if (active && (!strcmp (token, "/**") || !strcmp (token, "**")))
257 docstat = handle_docline (p, docstat);
258 else if (active && !strcmp (token, "{"))
260 docstat = flush_doc (docstat);
266 qsort (outbuf, outcount, sizeof (var_t), &var_cmp);
267 for (line = 0; line < outcount; line++) {
268 if (outbuf[line].descr) {
269 fprintf (out, "%s\n", outbuf[line].descr);
270 free (outbuf[line].descr);
272 free (outbuf[line].name);
277 /* skip whitespace */
279 static char *skip_ws (char *s)
281 while (*s && isspace ((unsigned char) *s))
287 /* isolate a token */
289 static char single_char_tokens[] = "[]{},;|";
291 static char *get_token (char *d, size_t l, char *s)
298 fprintf (stderr, "%s: get_token called for `%s'.\n",
304 fprintf (stderr, "%s: argumet after skip_ws(): `%s'.\n",
310 fprintf (stderr, "%s: no more tokens on this line.\n", Progname);
314 if (strchr (single_char_tokens, *s))
318 fprintf (stderr, "%s: found single character token `%c'.\n",
330 fprintf (stderr, "%s: found quote character.\n", Progname);
337 for (t = s; *t && --l > 0; t++)
339 if (*t == '\\' && !t[1])
342 if (is_quoted && *t == '\\')
346 case 'n': *d = '\n'; break;
347 case 't': *d = '\t'; break;
348 case 'r': *d = '\r'; break;
349 case 'a': *d = '\a'; break;
356 if (is_quoted && *t == '"')
361 else if (!is_quoted && strchr (single_char_tokens, *t))
363 else if (!is_quoted && isspace ((unsigned char) *t))
373 fprintf (stderr, "%s: Got %stoken: `%s'.\n",
374 Progname, is_quoted ? "quoted " : "", dd);
375 fprintf (stderr, "%s: Remainder: `%s'.\n",
384 ** Configuration line parser
386 ** The following code parses a line from init.h which declares
387 ** a configuration variable.
391 /* note: the following enum must be in the same order as the
392 * following string definitions!
417 { "DT_NONE", "-none-" },
418 { "DT_BOOL", "boolean" },
419 { "DT_NUM", "number" },
420 { "DT_STR", "string" },
421 { "DT_PATH", "path" },
422 { "DT_QUAD", "quadoption" },
423 { "DT_SORT", "sort order" },
424 { "DT_RX", "regular expression" },
425 { "DT_MAGIC", "folder magic" },
427 { "DT_ADDR", "e-mail address" },
432 static int buff2type (const char *s)
436 for (type = DT_NONE; types[type].machine; type++)
437 if (!strcmp (types[type].machine, s))
443 static const char *type2human (int type)
445 return types[type].human;
447 static void handle_confline (char *s)
449 char varname[BUFFSIZE];
456 /* xxx - put this into an actual state machine? */
459 if (!(s = get_token (varname, sizeof (varname), s))) return;
462 if (!(s = get_token (buff, sizeof (buff), s))) return;
465 if (!(s = get_token (buff, sizeof (buff), s))) return;
467 type = buff2type (buff);
469 /* possibly a "|" or comma */
470 if (!(s = get_token (buff, sizeof (buff), s))) return;
472 if (!strcmp (buff, "|"))
474 if (Debug) fprintf (stderr, "%s: Expecting <subtype> <comma>.\n", Progname);
475 /* ignore subtype and comma */
476 if (!(s = get_token (buff, sizeof (buff), s))) return;
477 if (!(s = get_token (buff, sizeof (buff), s))) return;
484 if (!(s = get_token (buff, sizeof (buff), s))) return;
485 if (!strcmp (buff, ","))
489 /* option name or UL &address */
490 if (!(s = get_token (buff, sizeof (buff), s))) return;
491 if (!strcmp (buff, "UL"))
492 if (!(s = get_token (buff, sizeof (buff), s))) return;
495 if (!(s = get_token (buff, sizeof (buff), s))) return;
497 if (Debug) fprintf (stderr, "%s: Expecting default value.\n", Progname);
499 /* <default value> or UL <default value> */
500 if (!(s = get_token (buff, sizeof (buff), s))) return;
501 if (!strcmp (buff, "UL"))
503 if (Debug) fprintf (stderr, "%s: Skipping UL.\n", Progname);
504 if (!(s = get_token (buff, sizeof (buff), s))) return;
507 memset (tmp, 0, sizeof (tmp));
511 if (!strcmp (buff, "}"))
514 strncpy (tmp + STRLEN(tmp), buff, sizeof (tmp) - STRLEN(tmp));
516 while ((s = get_token (buff, sizeof (buff), s)));
518 pretty_default (val, sizeof (val), tmp, type);
520 print_confline (varname, type, val);
523 static void pretty_default (char *t, size_t l, const char *s, int type)
532 if (!strcasecmp (s, "M_YES")) strncpy (t, "yes", l);
533 else if (!strcasecmp (s, "M_NO")) strncpy (t, "no", l);
534 else if (!strcasecmp (s, "M_ASKYES")) strncpy (t, "ask-yes", l);
535 else if (!strcasecmp (s, "M_ASKNO")) strncpy (t, "ask-no", l);
541 strncpy (t, "yes", l);
543 strncpy (t, "no", l);
549 strncpy (t, s + 5, l);
550 for (; *t; t++) *t = tolower ((unsigned char) *t);
556 strncpy (t, s + 2, l);
557 for (; *t; t++) *t = tolower ((unsigned char) *t);
565 if (!strcmp (s, "0"))
577 static void char_to_escape (char *dest, unsigned int c)
581 case '\r': strcpy (dest, "\\r"); break; /* __STRCPY_CHECKED__ */
582 case '\n': strcpy (dest, "\\n"); break; /* __STRCPY_CHECKED__ */
583 case '\t': strcpy (dest, "\\t"); break; /* __STRCPY_CHECKED__ */
584 case '\f': strcpy (dest, "\\f"); break; /* __STRCPY_CHECKED__ */
585 default: sprintf (dest, "\\%03o", c); break;
588 static void conf_char_to_escape (unsigned int c)
591 char_to_escape (buff, c);
595 static void conf_print_strval (const char *v)
599 if (*v < ' ' || *v & 0x80)
601 conf_char_to_escape ((unsigned int) *v);
605 if (*v == '"' || *v == '\\')
611 static void man_print_strval (const char *v)
615 if (*v < ' ' || *v & 0x80)
618 conf_char_to_escape ((unsigned int) *v);
631 static void sgml_print_strval (const char *v)
636 if (*v < ' ' || *v & 0x80)
638 char_to_escape (buff, (unsigned int) *v);
642 sgml_fputc ((unsigned int) *v);
646 static int sgml_fputc (int c)
650 case '<': return add ("<");
651 case '>': return add (">");
652 case '$': return add ("$");
653 case '_': return add ("_");
654 case '%': return add ("%");
655 case '&': return add ("&");
656 case '\\': return add ("\");
657 case '"': return add ("&dquot;");
658 case '[': return add ("[");
659 case ']': return add ("]");
660 case '~': return add ("˜");
661 default: return add_c (c);
665 static int sgml_fputs (const char *s)
668 if (sgml_fputc ((unsigned int) *s) == EOF)
674 static void print_confline (const char *varname, int type, const char *val)
676 if (type == DT_SYN) return;
678 switch (OutputFormat)
680 /* configuration file */
683 if (type == DT_STR || type == DT_RX || type == DT_ADDR || type == DT_PATH)
688 conf_print_strval (val);
691 else if (type != DT_SYN)
699 add ("\n#\n# Name: ");
702 add (type2human (type));
703 if (type == DT_STR || type == DT_RX || type == DT_ADDR || type == DT_PATH)
705 add ("\n# Default: \"");
706 conf_print_strval (val);
711 add ("\n# Default: ");
726 add (type2human (type));
728 if (type == DT_STR || type == DT_RX || type == DT_ADDR || type == DT_PATH)
730 add ("Default: \\(lq");
731 man_print_strval (val);
744 /* SGML based manual */
748 sgml_fputs (varname);
749 add ("<label id=\"");
752 add ("\n<p>\nType: <tt>");
753 add (type2human (type));
756 if (type == DT_STR || type == DT_RX || type == DT_ADDR || type == DT_PATH)
758 add ("<p>\nDefault: <tt>&dquot;");
759 sgml_print_strval (val);
760 add ("&dquot;</tt>");
764 add ("<p>\nDefault: <tt>");
777 ** Documentation line parser
779 ** The following code parses specially formatted documentation
780 ** comments in init.h.
782 ** The format is very remotely inspired by nroff. Most important, it's
783 ** easy to parse and convert, and it was easy to generate from the SGML
784 ** source of mutt's original manual.
786 ** - \fI switches to italics
787 ** - \fB switches to boldface
788 ** - \fP switches to normal display
789 ** - .dl on a line starts a definition list (name taken taken from HTML).
790 ** - .dt starts a term in a definition list.
791 ** - .dd starts a definition in a definition list.
792 ** - .de on a line finishes a definition list.
793 ** - .ts on a line starts a "tscreen" environment (name taken from SGML).
794 ** - .te on a line finishes this environment.
795 ** - .pp on a line starts a paragraph.
796 ** - \$word will be converted to a reference to word, where appropriate.
797 ** Note that \$$word is possible as well.
798 ** - '. ' in the beginning of a line expands to two space characters.
799 ** This is used to protect indentations in tables.
802 /* close eventually-open environments. */
804 static int fd_recurse = 0;
806 static int flush_doc (int docstat)
808 if (docstat & D_INIT)
813 fprintf (stderr, "%s: Internal error, recursion in flush_doc()!\n", Progname);
817 if (docstat & (D_TAB))
818 docstat = print_it (SP_END_TAB, NULL, docstat);
820 if (docstat & (D_DL))
821 docstat = print_it (SP_END_DL, NULL, docstat);
823 if (docstat & (D_EM | D_BF))
824 docstat = print_it (SP_END_FT, NULL, docstat);
826 docstat = print_it (SP_NEWLINE, NULL, 0);
832 /* print something. */
834 static int print_it (int special, char *str, int docstat)
836 int onl = docstat & (D_NL|D_NP);
838 docstat &= ~(D_NL|D_NP|D_INIT);
840 switch (OutputFormat)
842 /* configuration file */
847 static int Continuation = 0;
849 case SP_END_FT: docstat &= ~(D_EM|D_BF); break;
850 case SP_START_BF: docstat |= D_BF; break;
851 case SP_START_EM: docstat |= D_EM; break;
926 for (i = STRLEN(str) ; i < 8 ; i++)
945 docstat &= ~(D_EM|D_BF);
990 add ("\n.IP\n.DS\n.sp\n.ft CR\n.nf\n");
991 docstat |= D_TAB | D_NL;
996 add ("\n.fi\n.ec\n.ft P\n.sp\n");
1031 else if (*str == '\\')
1033 else if (!strncmp (str, "``", 2))
1038 else if (!strncmp (str, "''", 2))
1053 /* SGML based manual */
1060 if (docstat & D_EM) add("</em>");
1061 if (docstat & D_BF) add("</bf>");
1062 docstat &= ~(D_EM|D_BF);
1107 add ("\n<tscreen><verb>\n");
1108 docstat |= D_TAB | D_NL;
1113 add ("\n</verb></tscreen>");
1120 add ("\n<descrip>\n");
1136 add ("</descrip>\n");
1142 if (docstat & D_TAB)
1151 /* make gcc happy (unreached) */
1159 void print_ref (int output_dollar, const char *ref)
1161 switch (OutputFormat)
1185 static int commit_buff (char *buff, char **d, int docstat)
1190 docstat = print_it (SP_STR, buff, docstat);
1197 static int handle_docline (char *l, int docstat)
1199 char buff[BUFFSIZE];
1204 fprintf (stderr, "%s: handle_docline `%s'\n", Progname, l);
1206 if (!strncmp (l, ".pp", 3))
1207 return print_it (SP_NEWPAR, NULL, docstat);
1208 else if (!strncmp (l, ".ts", 3))
1209 return print_it (SP_START_TAB, NULL, docstat);
1210 else if (!strncmp (l, ".te", 3))
1211 return print_it (SP_END_TAB, NULL, docstat);
1212 else if (!strncmp (l, ".dl", 3))
1213 return print_it (SP_START_DL, NULL, docstat);
1214 else if (!strncmp (l, ".de", 3))
1215 return print_it (SP_END_DL, NULL, docstat);
1216 else if (!strncmp (l, ". ", 2))
1219 for (s = l, d = buff; *s; s++)
1221 if (!strncmp (s, "\\(as", 4))
1226 else if (!strncmp (s, "\\(rs", 4))
1231 else if (!strncmp (s, "\\fI", 3))
1233 docstat = commit_buff (buff, &d, docstat);
1234 docstat = print_it (SP_START_EM, NULL, docstat);
1237 else if (!strncmp (s, "\\fB", 3))
1239 docstat = commit_buff (buff, &d, docstat);
1240 docstat = print_it (SP_START_BF, NULL, docstat);
1243 else if (!strncmp (s, "\\fP", 3))
1245 docstat = commit_buff (buff, &d, docstat);
1246 docstat = print_it (SP_END_FT, NULL, docstat);
1249 else if (!strncmp (s, ".dt", 3))
1251 docstat = commit_buff (buff, &d, docstat);
1252 docstat = print_it (SP_DT, NULL, docstat);
1255 else if (!strncmp (s, ".dd", 3))
1257 docstat = commit_buff (buff, &d, docstat);
1258 docstat = print_it (SP_DD, NULL, docstat);
1263 int output_dollar = 0;
1280 while (isalnum ((unsigned char) *s) || *s == '-' || *s == '_')
1283 docstat = commit_buff (buff, &d, docstat);
1286 print_ref (output_dollar, ref);
1295 docstat = commit_buff (buff, &d, docstat);
1296 return print_it (SP_NEWLINE, NULL, docstat);