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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 ** -> docbook-xml, suitable for inclusion in the
49 #include "makedoc-defs.h"
54 extern char *sys_errlist[];
57 #define strerror(x) ((x) > 0 && (x) < sys_nerr) ? sys_errlist[(x)] : 0
58 #endif /* !HAVE_STRERROR */
66 F_CONF, F_MAN, F_SGML, F_NONE
72 #define D_TAB (1 << 3)
74 #define D_INIT (1 << 5)
79 #define D_IL (1 << 10)
80 #define D_TT (1 << 11)
105 enum output_formats_t OutputFormat = F_NONE;
109 static char *get_token (char *, size_t, char *);
110 static char *skip_ws (char *);
111 static const char *type2human (int);
112 static int buff2type (const char *);
113 static int flush_doc (int, FILE *);
114 static int handle_docline (char *, FILE *, int);
115 static int print_it (int, char *, FILE *, int);
116 static void print_confline (const char *, int, const char *, FILE *);
117 static void handle_confline (char *, FILE *);
118 static void makedoc (FILE *, FILE *);
119 static void pretty_default (char *, size_t, const char *, int);
120 static int sgml_fputc (int, FILE *, int);
121 static int sgml_fputs (const char *, FILE *, int);
122 static int sgml_id_fputs (const char *, FILE *);
124 int main (int argc, char *argv[])
129 if ((Progname = strrchr (argv[0], '/')))
134 while ((c = getopt (argc, argv, "cmsd")) != EOF)
138 case 'c': OutputFormat = F_CONF; break;
139 case 'm': OutputFormat = F_MAN; break;
140 case 's': OutputFormat = F_SGML; break;
141 case 'd': Debug++; break;
144 fprintf (stderr, "%s: bad command line parameter.\n", Progname);
152 if ((f = fopen (argv[optind], "r")) == NULL)
154 fprintf (stderr, "%s: Can't open %s (%s).\n",
155 Progname, argv[optind], strerror (errno));
162 switch (OutputFormat)
166 case F_SGML: makedoc (f, stdout); break;
169 fprintf (stderr, "%s: No output format specified.\n",
182 static void makedoc (FILE *in, FILE *out)
184 char buffer[BUFFSIZE];
185 char token[BUFFSIZE];
189 int docstat = D_INIT;
191 while ((fgets (buffer, sizeof (buffer), in)))
194 if ((p = strchr (buffer, '\n')) == NULL)
196 fprintf (stderr, "%s: Line %d too long. Ask a wizard to enlarge\n"
197 "%s: my buffer size.\n", Progname, line, Progname);
203 if (!(p = get_token (token, sizeof (token), buffer)))
208 fprintf (stderr, "%s: line %d. first token: \"%s\".\n",
209 Progname, line, token);
212 if (!strcmp (token, "/*++*/"))
214 else if (!strcmp (token, "/*--*/"))
216 docstat = flush_doc (docstat, out);
219 else if (active && (!strcmp (token, "/**") || !strcmp (token, "**")))
220 docstat = handle_docline (p, out, docstat);
221 else if (active && !strcmp (token, "{"))
223 docstat = flush_doc (docstat, out);
224 handle_confline (p, out);
227 flush_doc (docstat, out);
231 /* skip whitespace */
233 static char *skip_ws (char *s)
235 while (*s && isspace ((unsigned char) *s))
241 /* isolate a token */
243 static char single_char_tokens[] = "[]{},;|";
245 static char *get_token (char *d, size_t l, char *s)
252 fprintf (stderr, "%s: get_token called for `%s'.\n",
258 fprintf (stderr, "%s: argumet after skip_ws(): `%s'.\n",
264 fprintf (stderr, "%s: no more tokens on this line.\n", Progname);
268 if (strchr (single_char_tokens, *s))
272 fprintf (stderr, "%s: found single character token `%c'.\n",
284 fprintf (stderr, "%s: found quote character.\n", Progname);
291 for (t = s; *t && --l > 0; t++)
293 if (*t == '\\' && !t[1])
296 if (is_quoted && *t == '\\')
300 case 'n': *d = '\n'; break;
301 case 't': *d = '\t'; break;
302 case 'r': *d = '\r'; break;
303 case 'a': *d = '\a'; break;
310 if (is_quoted && *t == '"')
315 else if (!is_quoted && strchr (single_char_tokens, *t))
317 else if (!is_quoted && isspace ((unsigned char) *t))
327 fprintf (stderr, "%s: Got %stoken: `%s'.\n",
328 Progname, is_quoted ? "quoted " : "", dd);
329 fprintf (stderr, "%s: Remainder: `%s'.\n",
338 ** Configuration line parser
340 ** The following code parses a line from init.h which declares
341 ** a configuration variable.
345 /* note: the following enum must be in the same order as the
346 * following string definitions!
371 { "DT_NONE", "-none-" },
372 { "DT_BOOL", "boolean" },
373 { "DT_NUM", "number" },
374 { "DT_STR", "string" },
375 { "DT_PATH", "path" },
376 { "DT_QUAD", "quadoption" },
377 { "DT_SORT", "sort order" },
378 { "DT_RX", "regular expression" },
379 { "DT_MAGIC", "folder magic" },
381 { "DT_ADDR", "e-mail address" },
386 static int buff2type (const char *s)
390 for (type = DT_NONE; types[type].machine; type++)
391 if (!strcmp (types[type].machine, s))
397 static const char *type2human (int type)
399 return types[type].human;
401 static void handle_confline (char *s, FILE *out)
403 char varname[BUFFSIZE];
410 /* xxx - put this into an actual state machine? */
413 if (!(s = get_token (varname, sizeof (varname), s))) return;
416 if (!(s = get_token (buff, sizeof (buff), s))) return;
419 if (!(s = get_token (buff, sizeof (buff), s))) return;
421 type = buff2type (buff);
423 /* possibly a "|" or comma */
424 if (!(s = get_token (buff, sizeof (buff), s))) return;
426 if (!strcmp (buff, "|"))
428 if (Debug) fprintf (stderr, "%s: Expecting <subtype> <comma>.\n", Progname);
429 /* ignore subtype and comma */
430 if (!(s = get_token (buff, sizeof (buff), s))) return;
431 if (!(s = get_token (buff, sizeof (buff), s))) return;
438 if (!(s = get_token (buff, sizeof (buff), s))) return;
439 if (!strcmp (buff, ","))
443 /* option name or UL &address */
444 if (!(s = get_token (buff, sizeof (buff), s))) return;
445 if (!strcmp (buff, "UL"))
446 if (!(s = get_token (buff, sizeof (buff), s))) return;
449 if (!(s = get_token (buff, sizeof (buff), s))) return;
451 if (Debug) fprintf (stderr, "%s: Expecting default value.\n", Progname);
453 /* <default value> or UL <default value> */
454 if (!(s = get_token (buff, sizeof (buff), s))) return;
455 if (!strcmp (buff, "UL"))
457 if (Debug) fprintf (stderr, "%s: Skipping UL.\n", Progname);
458 if (!(s = get_token (buff, sizeof (buff), s))) return;
461 memset (tmp, 0, sizeof (tmp));
465 if (!strcmp (buff, "}"))
468 strncpy (tmp + strlen (tmp), buff, sizeof (tmp) - strlen (tmp));
470 while ((s = get_token (buff, sizeof (buff), s)));
472 pretty_default (val, sizeof (val), tmp, type);
473 print_confline (varname, type, val, out);
476 static void pretty_default (char *t, size_t l, const char *s, int type)
485 if (!strcasecmp (s, "M_YES")) strncpy (t, "yes", l);
486 else if (!strcasecmp (s, "M_NO")) strncpy (t, "no", l);
487 else if (!strcasecmp (s, "M_ASKYES")) strncpy (t, "ask-yes", l);
488 else if (!strcasecmp (s, "M_ASKNO")) strncpy (t, "ask-no", l);
494 strncpy (t, "yes", l);
496 strncpy (t, "no", l);
502 strncpy (t, s + 5, l);
503 for (; *t; t++) *t = tolower ((unsigned char) *t);
509 strncpy (t, s + 2, l);
510 for (; *t; t++) *t = tolower ((unsigned char) *t);
518 if (!strcmp (s, "0"))
530 static void char_to_escape (char *dest, unsigned int c)
534 case '\r': strcpy (dest, "\\r"); break; /* __STRCPY_CHECKED__ */
535 case '\n': strcpy (dest, "\\n"); break; /* __STRCPY_CHECKED__ */
536 case '\t': strcpy (dest, "\\t"); break; /* __STRCPY_CHECKED__ */
537 case '\f': strcpy (dest, "\\f"); break; /* __STRCPY_CHECKED__ */
538 default: sprintf (dest, "\\%03o", c); break;
541 static void conf_char_to_escape (unsigned int c , FILE *out)
544 char_to_escape (buff, c);
548 static void conf_print_strval (const char *v, FILE *out)
552 if (*v < ' ' || *v & 0x80)
554 conf_char_to_escape ((unsigned int) *v, out);
558 if (*v == '"' || *v == '\\')
564 static void man_print_strval (const char *v, FILE *out)
568 if (*v < ' ' || *v & 0x80)
571 conf_char_to_escape ((unsigned int) *v, out);
576 fputs ("\\(rq", out);
586 static void sgml_print_strval (const char *v, FILE *out)
591 if (*v < ' ' || *v & 0x80)
593 char_to_escape (buff, (unsigned int) *v);
594 sgml_fputs (buff, out, 1);
597 sgml_fputc ((unsigned int) *v, out, 1);
601 static int sgml_fputc (int c, FILE *out, int full)
605 /* the bare minimum for escaping */
606 case '<': return fputs ("<", out);
607 case '>': return fputs (">", out);
608 case '&': return fputs ("&", out);
609 /* map to entities, fall-through to raw if !full */
610 case '$': if (full) return fputs ("$", out);
611 case '_': if (full) return fputs ("_", out);
612 case '%': if (full) return fputs ("%", out);
613 case '\\': if (full) return fputs ("\", out);
614 case '"': if (full) return fputs (""", out);
615 case '[': if (full) return fputs ("[", out);
616 case ']': if (full) return fputs ("]", out);
617 case '~': if (full) return fputs ("˜", out);
618 case '|': if (full) return fputs ("|", out);
619 case '^': if (full) return fputs ("ˆ", out);
620 default: return fputc (c, out);
624 static int sgml_fputs (const char *s, FILE *out, int full)
627 if (sgml_fputc ((unsigned int) *s, out, full) == EOF)
633 /* reduce CDATA to ID */
634 static int sgml_id_fputs (const char *s, FILE* out)
647 if (*s == '>' && !*(s+1))
650 if (fputc ((unsigned int) id, out) == EOF)
657 static void print_confline (const char *varname, int type, const char *val, FILE *out)
659 if (type == DT_SYN) return;
661 switch (OutputFormat)
663 /* configuration file */
666 if (type == DT_STR || type == DT_RX || type == DT_ADDR || type == DT_PATH)
668 fprintf (out, "\n# set %s=\"", varname);
669 conf_print_strval (val, out);
672 else if (type != DT_SYN)
673 fprintf (out, "\n# set %s=%s", varname, val);
675 fprintf (out, "\n#\n# Name: %s", varname);
676 fprintf (out, "\n# Type: %s", type2human (type));
677 if (type == DT_STR || type == DT_RX || type == DT_ADDR || type == DT_PATH)
679 fputs ("\n# Default: \"", out);
680 conf_print_strval (val, out);
684 fprintf (out, "\n# Default: %s", val);
693 fprintf (out, "\n.TP\n.B %s\n", varname);
694 fputs (".nf\n", out);
695 fprintf (out, "Type: %s\n", type2human (type));
696 if (type == DT_STR || type == DT_RX || type == DT_ADDR || type == DT_PATH)
698 fputs ("Default: \\(lq", out);
699 man_print_strval (val, out);
700 fputs ("\\(rq\n", out);
703 fputs ("Default: ", out);
704 man_print_strval (val, out);
713 /* SGML based manual */
716 fputs ("\n<sect2 id=\"", out);
717 sgml_id_fputs(varname, out);
718 fputs ("\">\n<title>", out);
719 sgml_fputs (varname, out, 1);
720 fprintf (out, "</title>\n<literallayout>Type: %s", type2human (type));
723 if (type == DT_STR || type == DT_RX || type == DT_ADDR || type == DT_PATH)
727 fputs ("\nDefault: <quote><literal>", out);
728 sgml_print_strval (val, out);
729 fputs ("</literal></quote>", out);
733 fputs ("\nDefault: (empty)", out);
735 fputs ("</literallayout>\n", out);
738 fprintf (out, "\nDefault: %s</literallayout>\n", val);
748 ** Documentation line parser
750 ** The following code parses specially formatted documentation
751 ** comments in init.h.
753 ** The format is very remotely inspired by nroff. Most important, it's
754 ** easy to parse and convert, and it was easy to generate from the SGML
755 ** source of mutt's original manual.
757 ** - \fI switches to italics
758 ** - \fB switches to boldface
759 ** - \fP switches to normal display
760 ** - .dl on a line starts a definition list (name taken taken from HTML).
761 ** - .dt starts a term in a definition list.
762 ** - .dd starts a definition in a definition list.
763 ** - .de on a line finishes a definition list.
764 ** - .il on a line starts an itemzied list
765 ** - .dd starts an item in an itemized list
766 ** - .ie on a line finishes an itemized list
767 ** - .ts on a line starts a "tscreen" environment (name taken from SGML).
768 ** - .te on a line finishes this environment.
769 ** - .pp on a line starts a paragraph.
770 ** - \$word will be converted to a reference to word, where appropriate.
771 ** Note that \$$word is possible as well.
772 ** - '. ' in the beginning of a line expands to two space characters.
773 ** This is used to protect indentations in tables.
776 /* close eventually-open environments. */
778 static int fd_recurse = 0;
780 static int flush_doc (int docstat, FILE *out)
782 if (docstat & D_INIT)
787 fprintf (stderr, "%s: Internal error, recursion in flush_doc()!\n", Progname);
791 if (docstat & (D_PA))
792 docstat = print_it (SP_END_PAR, NULL, out, docstat);
794 if (docstat & (D_TAB))
795 docstat = print_it (SP_END_TAB, NULL, out, docstat);
797 if (docstat & (D_DL))
798 docstat = print_it (SP_END_DL, NULL, out, docstat);
800 if (docstat & (D_EM | D_BF | D_TT))
801 docstat = print_it (SP_END_FT, NULL, out, docstat);
803 docstat = print_it (SP_END_SECT, NULL, out, docstat);
805 docstat = print_it (SP_NEWLINE, NULL, out, 0);
811 /* print something. */
813 static int print_it (int special, char *str, FILE *out, int docstat)
815 int onl = docstat & (D_NL|D_NP);
817 docstat &= ~(D_NL|D_NP|D_INIT);
819 switch (OutputFormat)
821 /* configuration file */
826 static int Continuation = 0;
828 case SP_END_FT: docstat &= ~(D_EM|D_BF|D_TT); break;
829 case SP_START_BF: docstat |= D_BF; break;
830 case SP_START_EM: docstat |= D_EM; break;
831 case SP_START_TT: docstat |= D_TT; break;
919 for (i = strlen (str) ; i < 8 ; i++)
938 docstat &= ~(D_EM|D_BF|D_TT);
945 docstat &= ~(D_EM|D_TT);
952 docstat &= ~(D_BF|D_TT);
959 docstat &= ~(D_BF|D_EM);
983 fputs (".IP\n", out);
990 fputs ("\n.IP\n.DS\n.sp\n.ft CR\n.nf\n", out);
991 docstat |= D_TAB | D_NL;
996 fputs ("\n.fi\n.ec\n.ft P\n.sp\n", out);
1003 fputs (".RS\n.PD 0\n", out);
1009 fputs (".TP\n", out);
1015 fputs (".TP\n\\(hy ", out);
1022 fputs (".RE\n.PD 1", out);
1028 fputs (".RS\n.PD 0\n", out);
1034 fputs (".RE\n.PD 1", out);
1045 fputs ("\\(rq", out);
1046 else if (*str == '\\')
1047 fputs ("\\\\", out);
1048 else if (*str == '-')
1050 else if (!strncmp (str, "``", 2))
1052 fputs ("\\(lq", out);
1055 else if (!strncmp (str, "''", 2))
1057 fputs ("\\(rq", out);
1070 /* SGML based manual */
1077 if (docstat & D_EM) fputs ("</emphasis>", out);
1078 if (docstat & D_BF) fputs ("</emphasis>", out);
1079 if (docstat & D_TT) fputs ("</literal>", out);
1080 docstat &= ~(D_EM|D_BF|D_TT);
1085 fputs ("<emphasis role=\"bold\">", out);
1087 docstat &= ~(D_EM|D_TT);
1092 fputs ("<emphasis>", out);
1094 docstat &= ~(D_BF|D_TT);
1099 fputs ("<literal>", out);
1101 docstat &= ~(D_BF|D_EM);
1126 fputs("</para>\n", out);
1127 fputs ("<para>\n", out);
1136 fputs ("</para>\n", out);
1144 fputs ("\n</para>\n", out);
1147 fputs ("\n<screen>\n", out);
1148 docstat |= D_TAB | D_NL;
1153 fputs ("</screen>", out);
1162 fputs ("\n</para>\n", out);
1165 fputs ("\n<informaltable>\n<tgroup cols=\"2\">\n<tbody>\n", out);
1171 fputs ("<row><entry>", out);
1178 fputs("</entry><entry>", out);
1180 fputs ("<listitem><para>", out);
1186 fputs ("</entry></row>\n", out);
1188 fputs ("</para></listitem>", out);
1194 fputs ("</entry></row></tbody></tgroup></informaltable>\n", out);
1195 docstat &= ~(D_DD|D_DL);
1202 fputs ("\n</para>\n", out);
1205 fputs ("\n<itemizedlist>\n", out);
1211 fputs ("</para></listitem></itemizedlist>\n", out);
1212 docstat &= ~(D_DD|D_DL);
1217 fputs ("</sect2>", out);
1222 if (docstat & D_TAB)
1223 sgml_fputs (str, out, 0);
1230 if (!strncmp (str, "``", 2))
1232 fputs ("<quote>", out);
1235 else if (!strncmp (str, "''", 2))
1237 fputs ("</quote>", out);
1241 sgml_fputc (*str, out, 1);
1250 /* make gcc happy (unreached) */
1258 void print_ref (FILE *out, int output_dollar, const char *ref)
1260 switch (OutputFormat)
1270 fputs ("<link linkend=\"", out);
1271 sgml_id_fputs (ref, out);
1274 fputs ("$", out);
1275 sgml_fputs (ref, out, 1);
1276 fputs ("</link>", out);
1284 static int commit_buff (char *buff, char **d, FILE *out, int docstat)
1289 docstat = print_it (SP_STR, buff, out, docstat);
1296 static int handle_docline (char *l, FILE *out, int docstat)
1298 char buff[BUFFSIZE];
1303 fprintf (stderr, "%s: handle_docline `%s'\n", Progname, l);
1305 if (!strncmp (l, ".pp", 3))
1306 return print_it (SP_NEWPAR, NULL, out, docstat);
1307 else if (!strncmp (l, ".ts", 3))
1308 return print_it (SP_START_TAB, NULL, out, docstat);
1309 else if (!strncmp (l, ".te", 3))
1310 return print_it (SP_END_TAB, NULL, out, docstat);
1311 else if (!strncmp (l, ".dl", 3))
1312 return print_it (SP_START_DL, NULL, out, docstat);
1313 else if (!strncmp (l, ".de", 3))
1314 return print_it (SP_END_DL, NULL, out, docstat);
1315 else if (!strncmp (l, ".il", 3))
1316 return print_it (SP_START_IL, NULL, out, docstat);
1317 else if (!strncmp (l, ".ie", 3))
1318 return print_it (SP_END_IL, NULL, out, docstat);
1319 else if (!strncmp (l, ". ", 2))
1322 for (s = l, d = buff; *s; s++)
1324 if (!strncmp (s, "\\(as", 4))
1329 else if (!strncmp (s, "\\(rs", 4))
1334 else if (!strncmp (s, "\\fI", 3))
1336 docstat = commit_buff (buff, &d, out, docstat);
1337 docstat = print_it (SP_START_EM, NULL, out, docstat);
1340 else if (!strncmp (s, "\\fB", 3))
1342 docstat = commit_buff (buff, &d, out, docstat);
1343 docstat = print_it (SP_START_BF, NULL, out, docstat);
1346 else if (!strncmp (s, "\\fC", 3))
1348 docstat = commit_buff (buff, &d, out, docstat);
1349 docstat = print_it (SP_START_TT, NULL, out, docstat);
1352 else if (!strncmp (s, "\\fP", 3))
1354 docstat = commit_buff (buff, &d, out, docstat);
1355 docstat = print_it (SP_END_FT, NULL, out, docstat);
1358 else if (!strncmp (s, ".dt", 3))
1362 docstat = commit_buff (buff, &d, out, docstat);
1363 docstat = print_it (SP_END_DD, NULL, out, docstat);
1365 docstat = commit_buff (buff, &d, out, docstat);
1366 docstat = print_it (SP_DT, NULL, out, docstat);
1369 else if (!strncmp (s, ".dd", 3))
1371 if ((docstat & D_IL) && (docstat & D_DD))
1373 docstat = commit_buff (buff, &d, out, docstat);
1374 docstat = print_it (SP_END_DD, NULL, out, docstat);
1376 docstat = commit_buff (buff, &d, out, docstat);
1377 docstat = print_it (SP_DD, NULL, out, docstat);
1382 int output_dollar = 0;
1399 while (isalnum ((unsigned char) *s) || (*s && strchr("-_<>", *s)))
1402 docstat = commit_buff (buff, &d, out, docstat);
1405 print_ref (out, output_dollar, ref);
1414 docstat = commit_buff (buff, &d, out, docstat);
1415 return print_it (SP_NEWLINE, NULL, out, docstat);