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)
75 return (strcmp (((var_t *) a)->name, ((var_t *) b)->name));
78 enum output_formats_t {
79 F_CONF, F_MAN, F_SGML, F_NONE
85 #define D_TAB (1 << 3)
87 #define D_INIT (1 << 5)
107 enum output_formats_t OutputFormat = F_NONE;
111 static char *get_token (char *, size_t, char *);
112 static char *skip_ws (char *);
113 static const char *type2human (int);
114 static int buff2type (const char *);
115 static int flush_doc (int);
116 static int handle_docline (char *, int);
117 static int print_it (int, char *, int);
118 static void print_confline (const char *, int, const char *);
119 static void handle_confline (char *);
120 static void makedoc (FILE *, FILE *);
121 static void pretty_default (char *, size_t, const char *, int);
122 static int sgml_fputc (int);
123 static int sgml_fputs (const char *);
124 static void add_var (const char *);
125 static int add_s (const char *);
126 static int add_c (int);
128 int main (int argc, char *argv[])
133 if ((Progname = strrchr (argv[0], '/')))
138 while ((c = getopt (argc, argv, "cmsd")) != EOF) {
141 OutputFormat = F_CONF;
144 OutputFormat = F_MAN;
147 OutputFormat = F_SGML;
154 fprintf (stderr, "%s: bad command line parameter.\n", Progname);
160 if (optind != argc) {
161 if ((f = fopen (argv[optind], "r")) == NULL) {
162 fprintf (stderr, "%s: Can't open %s (%s).\n",
163 Progname, argv[optind], strerror (errno));
170 switch (OutputFormat) {
178 fprintf (stderr, "%s: No output format specified.\n", Progname);
189 static void add_var (const char *name)
191 outbuf = realloc (outbuf, (++outcount) * sizeof (var_t));
192 outbuf[outcount - 1].seen = 0;
193 outbuf[outcount - 1].name = strdup (name);
194 outbuf[outcount - 1].descr = NULL;
197 static int add (const char *s)
199 size_t lnew = STRLEN (s), lold = STRLEN (outbuf[outcount - 1].descr);
203 if (!outbuf[outcount - 1].seen) {
205 outbuf[outcount - 1].seen = 1;
209 outbuf[outcount - 1].descr = strdup (s);
211 outbuf[outcount - 1].descr =
212 realloc (outbuf[outcount - 1].descr, lold + lnew + 1);
213 memcpy (&(outbuf[outcount - 1].descr[lold - 1]) + 1, s, lnew);
215 outbuf[outcount - 1].descr[lold + lnew] = '\0';
219 static int add_c (int c)
221 char buf[2] = "\0\0";
227 static void makedoc (FILE * in, FILE * out)
229 char buffer[BUFFSIZE];
230 char token[BUFFSIZE];
234 int docstat = D_INIT;
236 while ((fgets (buffer, sizeof (buffer), in))) {
238 if ((p = strchr (buffer, '\n')) == NULL) {
239 fprintf (stderr, "%s: Line %d too long. Ask a wizard to enlarge\n"
240 "%s: my buffer size.\n", Progname, line, Progname);
246 if (!(p = get_token (token, sizeof (token), buffer)))
250 fprintf (stderr, "%s: line %d. first token: \"%s\".\n",
251 Progname, line, token);
254 if (!strcmp (token, "/*++*/"))
256 else if (!strcmp (token, "/*--*/")) {
257 docstat = flush_doc (docstat);
260 else if (active && (!strcmp (token, "/**") || !strcmp (token, "**")))
261 docstat = handle_docline (p, docstat);
262 else if (active && !strcmp (token, "{")) {
263 docstat = flush_doc (docstat);
269 qsort (outbuf, outcount, sizeof (var_t), &var_cmp);
270 for (line = 0; line < outcount; line++) {
271 if (outbuf[line].descr) {
272 fprintf (out, "%s\n", outbuf[line].descr);
273 free (outbuf[line].descr);
275 free (outbuf[line].name);
280 /* skip whitespace */
282 static char *skip_ws (char *s)
284 while (*s && isspace ((unsigned char) *s))
290 /* isolate a token */
292 static char single_char_tokens[] = "[]{},;|";
294 static char *get_token (char *d, size_t l, char *s)
301 fprintf (stderr, "%s: get_token called for `%s'.\n", Progname, s);
306 fprintf (stderr, "%s: argumet after skip_ws(): `%s'.\n", Progname, s);
310 fprintf (stderr, "%s: no more tokens on this line.\n", Progname);
314 if (strchr (single_char_tokens, *s)) {
316 fprintf (stderr, "%s: found single character token `%c'.\n",
326 fprintf (stderr, "%s: found quote character.\n", Progname);
333 for (t = s; *t && --l > 0; t++) {
334 if (*t == '\\' && !t[1])
337 if (is_quoted && *t == '\\') {
338 switch ((*d = *++t)) {
357 if (is_quoted && *t == '"') {
361 else if (!is_quoted && strchr (single_char_tokens, *t))
363 else if (!is_quoted && isspace ((unsigned char) *t))
372 fprintf (stderr, "%s: Got %stoken: `%s'.\n",
373 Progname, is_quoted ? "quoted " : "", dd);
374 fprintf (stderr, "%s: Remainder: `%s'.\n", Progname, t);
382 ** Configuration line parser
384 ** The following code parses a line from init.h which declares
385 ** a configuration variable.
389 /* note: the following enum must be in the same order as the
390 * following string definitions!
412 "DT_NONE", "-none-"}, {
413 "DT_BOOL", "boolean"}, {
414 "DT_NUM", "number"}, {
415 "DT_STR", "string"}, {
416 "DT_PATH", "path"}, {
417 "DT_QUAD", "quadoption"}, {
418 "DT_SORT", "sort order"}, {
419 "DT_RX", "regular expression"}, {
420 "DT_MAGIC", "folder magic"}, {
422 "DT_ADDR", "e-mail address"}, {
427 static int buff2type (const char *s)
431 for (type = DT_NONE; types[type].machine; type++)
432 if (!strcmp (types[type].machine, s))
438 static const char *type2human (int type)
440 return types[type].human;
442 static void handle_confline (char *s)
444 char varname[BUFFSIZE];
451 /* xxx - put this into an actual state machine? */
454 if (!(s = get_token (varname, sizeof (varname), s)))
458 if (!(s = get_token (buff, sizeof (buff), s)))
462 if (!(s = get_token (buff, sizeof (buff), s)))
465 type = buff2type (buff);
467 /* possibly a "|" or comma */
468 if (!(s = get_token (buff, sizeof (buff), s)))
471 if (!strcmp (buff, "|")) {
473 fprintf (stderr, "%s: Expecting <subtype> <comma>.\n", Progname);
474 /* ignore subtype and comma */
475 if (!(s = get_token (buff, sizeof (buff), s)))
477 if (!(s = get_token (buff, sizeof (buff), s)))
484 if (!(s = get_token (buff, sizeof (buff), s)))
486 if (!strcmp (buff, ","))
490 /* option name or UL &address */
491 if (!(s = get_token (buff, sizeof (buff), s)))
493 if (!strcmp (buff, "UL"))
494 if (!(s = get_token (buff, sizeof (buff), s)))
498 if (!(s = get_token (buff, sizeof (buff), s)))
502 fprintf (stderr, "%s: Expecting default value.\n", Progname);
504 /* <default value> or UL <default value> */
505 if (!(s = get_token (buff, sizeof (buff), s)))
507 if (!strcmp (buff, "UL")) {
509 fprintf (stderr, "%s: Skipping UL.\n", Progname);
510 if (!(s = get_token (buff, sizeof (buff), s)))
514 memset (tmp, 0, sizeof (tmp));
517 if (!strcmp (buff, "}"))
520 strncpy (tmp + STRLEN (tmp), buff, sizeof (tmp) - STRLEN (tmp));
522 while ((s = get_token (buff, sizeof (buff), s)));
524 pretty_default (val, sizeof (val), tmp, type);
526 print_confline (varname, type, val);
529 static void pretty_default (char *t, size_t l, const char *s, int type)
537 if (!strcasecmp (s, "M_YES"))
538 strncpy (t, "yes", l);
539 else if (!strcasecmp (s, "M_NO"))
540 strncpy (t, "no", l);
541 else if (!strcasecmp (s, "M_ASKYES"))
542 strncpy (t, "ask-yes", l);
543 else if (!strcasecmp (s, "M_ASKNO"))
544 strncpy (t, "ask-no", l);
550 strncpy (t, "yes", l);
552 strncpy (t, "no", l);
558 strncpy (t, s + 5, l);
560 *t = tolower ((unsigned char) *t);
566 strncpy (t, s + 2, l);
568 *t = tolower ((unsigned char) *t);
576 if (!strcmp (s, "0"))
588 static void char_to_escape (char *dest, unsigned int c)
592 strcpy (dest, "\\r");
593 break; /* __STRCPY_CHECKED__ */
595 strcpy (dest, "\\n");
596 break; /* __STRCPY_CHECKED__ */
598 strcpy (dest, "\\t");
599 break; /* __STRCPY_CHECKED__ */
601 strcpy (dest, "\\f");
602 break; /* __STRCPY_CHECKED__ */
604 sprintf (dest, "\\%03o", c);
608 static void conf_char_to_escape (unsigned int c)
612 char_to_escape (buff, c);
616 static void conf_print_strval (const char *v)
619 if (*v < ' ' || *v & 0x80) {
620 conf_char_to_escape ((unsigned int) *v);
624 if (*v == '"' || *v == '\\')
630 static void man_print_strval (const char *v)
633 if (*v < ' ' || *v & 0x80) {
635 conf_char_to_escape ((unsigned int) *v);
648 static void sgml_print_strval (const char *v)
653 if (*v < ' ' || *v & 0x80) {
654 char_to_escape (buff, (unsigned int) *v);
658 sgml_fputc ((unsigned int) *v);
662 static int sgml_fputc (int c)
670 return add ("$");
672 return add ("_");
674 return add ("%");
676 return add ("&");
678 return add ("\");
680 return add ("&dquot;");
682 return add ("[");
684 return add ("]");
686 return add ("˜");
692 static int sgml_fputs (const char *s)
695 if (sgml_fputc ((unsigned int) *s) == EOF)
701 static void print_confline (const char *varname, int type, const char *val)
706 switch (OutputFormat) {
707 /* configuration file */
710 if (type == DT_STR || type == DT_RX || type == DT_ADDR
711 || type == DT_PATH) {
715 conf_print_strval (val);
718 else if (type != DT_SYN) {
725 add ("\n#\n# Name: ");
728 add (type2human (type));
729 if (type == DT_STR || type == DT_RX || type == DT_ADDR
730 || type == DT_PATH) {
731 add ("\n# Default: \"");
732 conf_print_strval (val);
736 add ("\n# Default: ");
751 add (type2human (type));
753 if (type == DT_STR || type == DT_RX || type == DT_ADDR
754 || type == DT_PATH) {
755 add ("Default: \\(lq");
756 man_print_strval (val);
769 /* SGML based manual */
773 sgml_fputs (varname);
774 add ("<label id=\"");
777 add ("\n<p>\nType: <tt>");
778 add (type2human (type));
781 if (type == DT_STR || type == DT_RX || type == DT_ADDR
782 || type == DT_PATH) {
783 add ("<p>\nDefault: <tt>&dquot;");
784 sgml_print_strval (val);
785 add ("&dquot;</tt>");
788 add ("<p>\nDefault: <tt>");
801 ** Documentation line parser
803 ** The following code parses specially formatted documentation
804 ** comments in init.h.
806 ** The format is very remotely inspired by nroff. Most important, it's
807 ** easy to parse and convert, and it was easy to generate from the SGML
808 ** source of mutt's original manual.
810 ** - \fI switches to italics
811 ** - \fB switches to boldface
812 ** - \fP switches to normal display
813 ** - .dl on a line starts a definition list (name taken taken from HTML).
814 ** - .dt starts a term in a definition list.
815 ** - .dd starts a definition in a definition list.
816 ** - .de on a line finishes a definition list.
817 ** - .ts on a line starts a "tscreen" environment (name taken from SGML).
818 ** - .te on a line finishes this environment.
819 ** - .pp on a line starts a paragraph.
820 ** - \$word will be converted to a reference to word, where appropriate.
821 ** Note that \$$word is possible as well.
822 ** - '. ' in the beginning of a line expands to two space characters.
823 ** This is used to protect indentations in tables.
826 /* close eventually-open environments. */
828 static int fd_recurse = 0;
830 static int flush_doc (int docstat)
832 if (docstat & D_INIT)
836 fprintf (stderr, "%s: Internal error, recursion in flush_doc()!\n",
841 if (docstat & (D_TAB))
842 docstat = print_it (SP_END_TAB, NULL, docstat);
844 if (docstat & (D_DL))
845 docstat = print_it (SP_END_DL, NULL, docstat);
847 if (docstat & (D_EM | D_BF))
848 docstat = print_it (SP_END_FT, NULL, docstat);
850 docstat = print_it (SP_NEWLINE, NULL, 0);
856 /* print something. */
858 static int print_it (int special, char *str, int docstat)
860 int onl = docstat & (D_NL | D_NP);
862 docstat &= ~(D_NL | D_NP | D_INIT);
864 switch (OutputFormat) {
865 /* configuration file */
869 static int Continuation = 0;
872 docstat &= ~(D_EM | D_BF);
947 if (docstat & D_DT) {
950 for (i = STRLEN (str); i < 8; i++)
968 docstat &= ~(D_EM | D_BF);
1011 add ("\n.IP\n.DS\n.sp\n.ft CR\n.nf\n");
1012 docstat |= D_TAB | D_NL;
1017 add ("\n.fi\n.ec\n.ft P\n.sp\n");
1047 for (; *str; str++) {
1050 else if (*str == '\\')
1052 else if (!strncmp (str, "``", 2)) {
1056 else if (!strncmp (str, "''", 2)) {
1070 /* SGML based manual */
1080 docstat &= ~(D_EM | D_BF);
1123 add ("\n<tscreen><verb>\n");
1124 docstat |= D_TAB | D_NL;
1129 add ("\n</verb></tscreen>");
1136 add ("\n<descrip>\n");
1152 add ("</descrip>\n");
1158 if (docstat & D_TAB)
1167 /* make gcc happy (unreached) */
1175 void print_ref (int output_dollar, const char *ref)
1177 switch (OutputFormat) {
1200 static int commit_buff (char *buff, char **d, int docstat)
1204 docstat = print_it (SP_STR, buff, docstat);
1211 static int handle_docline (char *l, int docstat)
1213 char buff[BUFFSIZE];
1219 fprintf (stderr, "%s: handle_docline `%s'\n", Progname, l);
1221 if (!strncmp (l, ".pp", 3))
1222 return print_it (SP_NEWPAR, NULL, docstat);
1223 else if (!strncmp (l, ".ts", 3))
1224 return print_it (SP_START_TAB, NULL, docstat);
1225 else if (!strncmp (l, ".te", 3))
1226 return print_it (SP_END_TAB, NULL, docstat);
1227 else if (!strncmp (l, ".dl", 3))
1228 return print_it (SP_START_DL, NULL, docstat);
1229 else if (!strncmp (l, ".de", 3))
1230 return print_it (SP_END_DL, NULL, docstat);
1231 else if (!strncmp (l, ". ", 2))
1234 for (s = l, d = buff; *s; s++) {
1235 if (!strncmp (s, "\\(as", 4)) {
1239 else if (!strncmp (s, "\\(rs", 4)) {
1243 else if (!strncmp (s, "\\fI", 3)) {
1244 docstat = commit_buff (buff, &d, docstat);
1245 docstat = print_it (SP_START_EM, NULL, docstat);
1248 else if (!strncmp (s, "\\fB", 3)) {
1249 docstat = commit_buff (buff, &d, docstat);
1250 docstat = print_it (SP_START_BF, NULL, docstat);
1253 else if (!strncmp (s, "\\fP", 3)) {
1254 docstat = commit_buff (buff, &d, docstat);
1255 docstat = print_it (SP_END_FT, NULL, docstat);
1258 else if (!strncmp (s, ".dt", 3)) {
1259 docstat = commit_buff (buff, &d, docstat);
1260 docstat = print_it (SP_DT, NULL, docstat);
1263 else if (!strncmp (s, ".dd", 3)) {
1264 docstat = commit_buff (buff, &d, docstat);
1265 docstat = print_it (SP_DD, NULL, docstat);
1268 else if (*s == '$') {
1269 int output_dollar = 0;
1283 while (isalnum ((unsigned char) *s) || *s == '-' || *s == '_')
1286 docstat = commit_buff (buff, &d, docstat);
1289 print_ref (output_dollar, ref);
1298 docstat = commit_buff (buff, &d, docstat);
1299 return print_it (SP_NEWLINE, NULL, docstat);