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 static char lastvar[BUFFSIZE];
63 static int sort_error = 0;
67 F_CONF, F_MAN, F_SGML, F_NONE
73 #define D_TAB (1 << 3)
75 #define D_INIT (1 << 5)
96 enum output_formats_t OutputFormat = F_NONE;
100 static char *get_token (char *, size_t, char *);
101 static char *skip_ws (char *);
102 static const char *type2human (int);
103 static int buff2type (const char *);
104 static int flush_doc (int, FILE *);
105 static int handle_docline (char *, FILE *, int);
106 static int print_it (int, char *, FILE *, int);
107 static void print_confline (const char *, int, const char *, FILE *);
108 static void handle_confline (char *, FILE *);
109 static void makedoc (FILE *, FILE *);
110 static void pretty_default (char *, size_t, const char *, int);
111 static int sgml_fputc (int, FILE *);
112 static int sgml_fputs (const char *, FILE *);
114 int main (int argc, char *argv[])
119 if ((Progname = strrchr (argv[0], '/')))
124 while ((c = getopt (argc, argv, "cmsd")) != EOF)
128 case 'c': OutputFormat = F_CONF; break;
129 case 'm': OutputFormat = F_MAN; break;
130 case 's': OutputFormat = F_SGML; break;
131 case 'd': Debug++; break;
134 fprintf (stderr, "%s: bad command line parameter.\n", Progname);
142 if ((f = fopen (argv[optind], "r")) == NULL)
144 fprintf (stderr, "%s: Can't open %s (%s).\n",
145 Progname, argv[optind], strerror (errno));
152 switch (OutputFormat)
158 makedoc (f, stdout); break;
161 fprintf (stderr, "%s: No output format specified.\n",
173 static void remember (const char* s)
177 strncpy (lastvar, s, strlen (s));
181 static void makedoc (FILE *in, FILE *out)
183 char buffer[BUFFSIZE];
184 char token[BUFFSIZE];
188 int docstat = D_INIT;
190 while ((fgets (buffer, sizeof (buffer), in)))
193 if ((p = strchr (buffer, '\n')) == NULL)
195 fprintf (stderr, "%s: Line %d too long. Ask a wizard to enlarge\n"
196 "%s: my buffer size.\n", Progname, line, Progname);
202 if (!(p = get_token (token, sizeof (token), buffer)))
207 fprintf (stderr, "%s: line %d. first token: \"%s\".\n",
208 Progname, line, token);
211 if (!strcmp (token, "/*++*/"))
213 else if (!strcmp (token, "/*--*/"))
215 docstat = flush_doc (docstat, out);
218 else if (active && (!strcmp (token, "/**") || !strcmp (token, "**")))
219 docstat = handle_docline (p, out, docstat);
220 else if (active && !strcmp (token, "{"))
222 docstat = flush_doc (docstat, out);
223 handle_confline (p, out);
226 flush_doc (docstat, out);
231 "***************************************************************************\n"
232 "*** There have been sorting errors for the manual's variable reference. ***\n"
233 "*** If you're not a developer playing with the code, please report this ***\n"
234 "*** error to <mutt-ng-devel@lists.berlios.de> ***\n"
236 "*** I'm waiting 5 seconds for you to take notice. ***\n"
237 "***************************************************************************\n\n",
243 /* skip whitespace */
245 static char *skip_ws (char *s)
247 while (*s && isspace ((unsigned char) *s))
253 /* isolate a token */
255 static char single_char_tokens[] = "[]{},;|";
257 static char *get_token (char *d, size_t l, char *s)
264 fprintf (stderr, "%s: get_token called for `%s'.\n",
270 fprintf (stderr, "%s: argumet after skip_ws(): `%s'.\n",
276 fprintf (stderr, "%s: no more tokens on this line.\n", Progname);
280 if (strchr (single_char_tokens, *s))
284 fprintf (stderr, "%s: found single character token `%c'.\n",
296 fprintf (stderr, "%s: found quote character.\n", Progname);
303 for (t = s; *t && --l > 0; t++)
305 if (*t == '\\' && !t[1])
308 if (is_quoted && *t == '\\')
312 case 'n': *d = '\n'; break;
313 case 't': *d = '\t'; break;
314 case 'r': *d = '\r'; break;
315 case 'a': *d = '\a'; break;
322 if (is_quoted && *t == '"')
327 else if (!is_quoted && strchr (single_char_tokens, *t))
329 else if (!is_quoted && isspace ((unsigned char) *t))
339 fprintf (stderr, "%s: Got %stoken: `%s'.\n",
340 Progname, is_quoted ? "quoted " : "", dd);
341 fprintf (stderr, "%s: Remainder: `%s'.\n",
350 ** Configuration line parser
352 ** The following code parses a line from init.h which declares
353 ** a configuration variable.
357 /* note: the following enum must be in the same order as the
358 * following string definitions!
383 { "DT_NONE", "-none-" },
384 { "DT_BOOL", "boolean" },
385 { "DT_NUM", "number" },
386 { "DT_STR", "string" },
387 { "DT_PATH", "path" },
388 { "DT_QUAD", "quadoption" },
389 { "DT_SORT", "sort order" },
390 { "DT_RX", "regular expression" },
391 { "DT_MAGIC", "folder magic" },
393 { "DT_ADDR", "e-mail address" },
398 static int buff2type (const char *s)
402 for (type = DT_NONE; types[type].machine; type++)
403 if (!strcmp (types[type].machine, s))
409 static const char *type2human (int type)
411 return types[type].human;
413 static void handle_confline (char *s, FILE *out)
415 char varname[BUFFSIZE];
422 /* xxx - put this into an actual state machine? */
425 if (!(s = get_token (varname, sizeof (varname), s)))
429 if (lastvar[0] && strncmp (lastvar, varname, strlen (lastvar)) > 0)
431 fprintf (stderr, "*** MAKEDOC WARNING: $%s must come before $%s ***\n",
439 if (!(s = get_token (buff, sizeof (buff), s))) return;
442 if (!(s = get_token (buff, sizeof (buff), s))) return;
444 type = buff2type (buff);
446 /* possibly a "|" or comma */
447 if (!(s = get_token (buff, sizeof (buff), s))) return;
449 if (!strcmp (buff, "|"))
451 if (Debug) fprintf (stderr, "%s: Expecting <subtype> <comma>.\n", Progname);
452 /* ignore subtype and comma */
453 if (!(s = get_token (buff, sizeof (buff), s))) return;
454 if (!(s = get_token (buff, sizeof (buff), s))) return;
461 if (!(s = get_token (buff, sizeof (buff), s))) return;
462 if (!strcmp (buff, ","))
466 /* option name or UL &address */
467 if (!(s = get_token (buff, sizeof (buff), s))) return;
468 if (!strcmp (buff, "UL"))
469 if (!(s = get_token (buff, sizeof (buff), s))) return;
472 if (!(s = get_token (buff, sizeof (buff), s))) return;
474 if (Debug) fprintf (stderr, "%s: Expecting default value.\n", Progname);
476 /* <default value> or UL <default value> */
477 if (!(s = get_token (buff, sizeof (buff), s))) return;
478 if (!strcmp (buff, "UL"))
480 if (Debug) fprintf (stderr, "%s: Skipping UL.\n", Progname);
481 if (!(s = get_token (buff, sizeof (buff), s))) return;
484 memset (tmp, 0, sizeof (tmp));
488 if (!strcmp (buff, "}"))
491 strncpy (tmp + strlen (tmp), buff, sizeof (tmp) - strlen (tmp));
493 while ((s = get_token (buff, sizeof (buff), s)));
495 pretty_default (val, sizeof (val), tmp, type);
496 print_confline (varname, type, val, out);
499 static void pretty_default (char *t, size_t l, const char *s, int type)
508 if (!strcasecmp (s, "M_YES")) strncpy (t, "yes", l);
509 else if (!strcasecmp (s, "M_NO")) strncpy (t, "no", l);
510 else if (!strcasecmp (s, "M_ASKYES")) strncpy (t, "ask-yes", l);
511 else if (!strcasecmp (s, "M_ASKNO")) strncpy (t, "ask-no", l);
517 strncpy (t, "yes", l);
519 strncpy (t, "no", l);
525 strncpy (t, s + 5, l);
526 for (; *t; t++) *t = tolower ((unsigned char) *t);
532 strncpy (t, s + 2, l);
533 for (; *t; t++) *t = tolower ((unsigned char) *t);
541 if (!strcmp (s, "0"))
553 static void char_to_escape (char *dest, unsigned int c)
557 case '\r': strcpy (dest, "\\r"); break; /* __STRCPY_CHECKED__ */
558 case '\n': strcpy (dest, "\\n"); break; /* __STRCPY_CHECKED__ */
559 case '\t': strcpy (dest, "\\t"); break; /* __STRCPY_CHECKED__ */
560 case '\f': strcpy (dest, "\\f"); break; /* __STRCPY_CHECKED__ */
561 default: sprintf (dest, "\\%03o", c); break;
564 static void conf_char_to_escape (unsigned int c , FILE *out)
567 char_to_escape (buff, c);
571 static void conf_print_strval (const char *v, FILE *out)
575 if (*v < ' ' || *v & 0x80)
577 conf_char_to_escape ((unsigned int) *v, out);
581 if (*v == '"' || *v == '\\')
587 static void man_print_strval (const char *v, FILE *out)
591 if (*v < ' ' || *v & 0x80)
594 conf_char_to_escape ((unsigned int) *v, out);
599 fputs ("\\(rq", out);
607 static void sgml_print_strval (const char *v, FILE *out)
612 if (*v < ' ' || *v & 0x80)
614 char_to_escape (buff, (unsigned int) *v);
615 sgml_fputs (buff, out);
618 sgml_fputc ((unsigned int) *v, out);
622 static int sgml_fputc (int c, FILE *out)
626 case '<': return fputs ("<", out);
627 case '>': return fputs (">", out);
628 case '$': return fputs ("$", out);
629 case '_': return fputs ("_", out);
630 case '%': return fputs ("%", out);
631 case '&': return fputs ("&", out);
632 case '\\': return fputs ("\", out);
633 case '"': return fputs ("&dquot;", out);
634 case '[': return fputs ("[", out);
635 case ']': return fputs ("]", out);
636 case '~': return fputs ("˜", out);
637 default: return fputc (c, out);
641 static int sgml_fputs (const char *s, FILE *out)
644 if (sgml_fputc ((unsigned int) *s, out) == EOF)
650 static void print_confline (const char *varname, int type, const char *val, FILE *out)
652 if (type == DT_SYN) return;
654 switch (OutputFormat)
656 /* configuration file */
659 if (type == DT_STR || type == DT_RX || type == DT_ADDR || type == DT_PATH)
661 fprintf (out, "\n# set %s=\"", varname);
662 conf_print_strval (val, out);
665 else if (type != DT_SYN)
666 fprintf (out, "\n# set %s=%s", varname, val);
668 fprintf (out, "\n#\n# Name: %s", varname);
669 fprintf (out, "\n# Type: %s", type2human (type));
670 if (type == DT_STR || type == DT_RX || type == DT_ADDR || type == DT_PATH)
672 fputs ("\n# Default: \"", out);
673 conf_print_strval (val, out);
677 fprintf (out, "\n# Default: %s", val);
686 fprintf (out, "\n.TP\n.B %s\n", varname);
687 fputs (".nf\n", out);
688 fprintf (out, "Type: %s\n", type2human (type));
689 if (type == DT_STR || type == DT_RX || type == DT_ADDR || type == DT_PATH)
691 fputs ("Default: \\(lq", out);
692 man_print_strval (val, out);
693 fputs ("\\(rq\n", out);
696 fprintf (out, "Default: %s\n", val);
703 /* SGML based manual */
706 fputs ("\n<sect2>", out); sgml_fputs (varname, out);
707 fprintf (out, "<label id=\"%s\">", varname);
708 fprintf (out, "\n<p>\nType: %s<newline>", type2human (type));
710 if (type == DT_STR || type == DT_RX || type == DT_ADDR || type == DT_PATH)
712 fputs ("\nDefault: &dquot;", out);
713 sgml_print_strval (val, out);
714 fputs ("&dquot;\n", out);
717 fprintf (out, "\nDefault: %s\n", val);
727 ** Documentation line parser
729 ** The following code parses specially formatted documentation
730 ** comments in init.h.
732 ** The format is very remotely inspired by nroff. Most important, it's
733 ** easy to parse and convert, and it was easy to generate from the SGML
734 ** source of mutt's original manual.
736 ** - \fI switches to italics
737 ** - \fB switches to boldface
738 ** - \fP switches to normal display
739 ** - .dl on a line starts a definition list (name taken taken from HTML).
740 ** - .dt starts a term in a definition list.
741 ** - .dd starts a definition in a definition list.
742 ** - .de on a line finishes a definition list.
743 ** - .ts on a line starts a "tscreen" environment (name taken from SGML).
744 ** - .te on a line finishes this environment.
745 ** - .pp on a line starts a paragraph.
746 ** - \$word will be converted to a reference to word, where appropriate.
747 ** Note that \$$word is possible as well.
748 ** - '. ' in the beginning of a line expands to two space characters.
749 ** This is used to protect indentations in tables.
752 /* close eventually-open environments. */
754 static int fd_recurse = 0;
756 static int flush_doc (int docstat, FILE *out)
758 if (docstat & D_INIT)
763 fprintf (stderr, "%s: Internal error, recursion in flush_doc()!\n", Progname);
767 if (docstat & (D_TAB))
768 docstat = print_it (SP_END_TAB, NULL, out, docstat);
770 if (docstat & (D_DL))
771 docstat = print_it (SP_END_DL, NULL, out, docstat);
773 if (docstat & (D_EM | D_BF))
774 docstat = print_it (SP_END_FT, NULL, out, docstat);
776 docstat = print_it (SP_NEWLINE, NULL, out, 0);
782 /* print something. */
784 static int print_it (int special, char *str, FILE *out, int docstat)
786 int onl = docstat & (D_NL|D_NP);
788 docstat &= ~(D_NL|D_NP|D_INIT);
790 switch (OutputFormat)
792 /* configuration file */
797 static int Continuation = 0;
799 case SP_END_FT: docstat &= ~(D_EM|D_BF); break;
800 case SP_START_BF: docstat |= D_BF; break;
801 case SP_START_EM: docstat |= D_EM; break;
876 for (i = strlen (str) ; i < 8 ; i++)
895 docstat &= ~(D_EM|D_BF);
933 fputs (".IP\n", out);
940 fputs ("\n.IP\n.DS\n.sp\n.ft CR\n.nf\n", out);
941 docstat |= D_TAB | D_NL;
946 fputs ("\n.fi\n.ec\n.ft P\n.sp\n", out);
953 fputs ("\n.RS", out);
959 fputs ("\n.IP ", out);
969 fputs ("\n.RE", out);
980 fputs ("\\(rq", out);
981 else if (*str == '\\')
983 else if (!strncmp (str, "``", 2))
985 fputs ("\\(lq", out);
988 else if (!strncmp (str, "''", 2))
990 fputs ("\\(rq", out);
1003 /* SGML based manual */
1010 if (docstat & D_EM) fputs ("</em>", out);
1011 if (docstat & D_BF) fputs ("</bf>", out);
1012 docstat &= ~(D_EM|D_BF);
1017 fputs ("<bf>", out);
1024 fputs ("<em>", out);
1050 fputs ("<p>\n", out);
1057 fputs ("\n<tscreen><verb>\n", out);
1058 docstat |= D_TAB | D_NL;
1063 fputs ("\n</verb></tscreen>", out);
1070 fputs ("\n<descrip>\n", out);
1076 fputs ("<tag>", out);
1081 fputs ("</tag>", out);
1086 fputs ("</descrip>\n", out);
1092 if (docstat & D_TAB)
1095 sgml_fputs (str, out);
1101 /* make gcc happy (unreached) */
1109 void print_ref (FILE *out, int output_dollar, const char *ref)
1111 switch (OutputFormat)
1121 fprintf (out, "<ref id=\"%s\" name=\"", ref);
1123 fputs ("$", out);
1124 sgml_fputs (ref, out);
1133 static int commit_buff (char *buff, char **d, FILE *out, int docstat)
1138 docstat = print_it (SP_STR, buff, out, docstat);
1145 static int handle_docline (char *l, FILE *out, int docstat)
1147 char buff[BUFFSIZE];
1152 fprintf (stderr, "%s: handle_docline `%s'\n", Progname, l);
1154 if (!strncmp (l, ".pp", 3))
1155 return print_it (SP_NEWPAR, NULL, out, docstat);
1156 else if (!strncmp (l, ".ts", 3))
1157 return print_it (SP_START_TAB, NULL, out, docstat);
1158 else if (!strncmp (l, ".te", 3))
1159 return print_it (SP_END_TAB, NULL, out, docstat);
1160 else if (!strncmp (l, ".dl", 3))
1161 return print_it (SP_START_DL, NULL, out, docstat);
1162 else if (!strncmp (l, ".de", 3))
1163 return print_it (SP_END_DL, NULL, out, docstat);
1164 else if (!strncmp (l, ". ", 2))
1167 for (s = l, d = buff; *s; s++)
1169 if (!strncmp (s, "\\(as", 4))
1174 else if (!strncmp (s, "\\(rs", 4))
1179 else if (!strncmp (s, "\\fI", 3))
1181 docstat = commit_buff (buff, &d, out, docstat);
1182 docstat = print_it (SP_START_EM, NULL, out, docstat);
1185 else if (!strncmp (s, "\\fB", 3))
1187 docstat = commit_buff (buff, &d, out, docstat);
1188 docstat = print_it (SP_START_BF, NULL, out, docstat);
1191 else if (!strncmp (s, "\\fP", 3))
1193 docstat = commit_buff (buff, &d, out, docstat);
1194 docstat = print_it (SP_END_FT, NULL, out, docstat);
1197 else if (!strncmp (s, ".dt", 3))
1199 docstat = commit_buff (buff, &d, out, docstat);
1200 docstat = print_it (SP_DT, NULL, out, docstat);
1203 else if (!strncmp (s, ".dd", 3))
1205 docstat = commit_buff (buff, &d, out, docstat);
1206 docstat = print_it (SP_DD, NULL, out, docstat);
1211 int output_dollar = 0;
1228 while (isalnum ((unsigned char) *s) || *s == '-' || *s == '_')
1231 docstat = commit_buff (buff, &d, out, docstat);
1234 print_ref (out, output_dollar, ref);
1243 docstat = commit_buff (buff, &d, out, docstat);
1244 return print_it (SP_NEWLINE, NULL, out, docstat);