Rocco Rutte:
[apps/madmutt.git] / makedoc.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
4  *
5  * Parts were written/modified by:
6  * Rocco Rutte <pdmef@cs.tu-berlin.de>
7  *
8  * This file is part of mutt-ng, see http://www.muttng.org/.
9  * It's licensed under the GNU General Public License,
10  * please see the file GPL in the top level source directory.
11  */
12
13 /**
14  ** This program parses mutt's init.h and generates documentation in
15  ** three different formats:
16  **
17  ** -> a commented muttrc configuration file
18  ** -> nroff, suitable for inclusion in a manual page
19  ** -> linuxdoc-sgml, suitable for inclusion in the 
20  **    SGML-based manual
21  **
22  **/
23
24 #if HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <ctype.h>
32
33 #include <errno.h>
34
35 #ifdef HAVE_UNISTD_H
36 # include <unistd.h>
37 #endif
38
39 #ifdef HAVE_GETOPT_H
40 # include <getopt.h>
41 #endif
42
43 #ifndef HAVE_STRERROR
44 #ifndef STDC_HEADERS
45 extern int sys_nerr;
46 extern char *sys_errlist[];
47 #endif
48
49 #define strerror(x) ((x) > 0 && (x) < sys_nerr) ? sys_errlist[(x)] : 0
50 #endif /* !HAVE_STRERROR */
51
52 extern int optind;
53
54 #define BUFFSIZE 2048
55
56 #define STRLEN(s) (s ? strlen(s) : 0)
57
58 typedef struct {
59   short seen;
60   char *name;
61   char *descr;
62 } var_t;
63
64 static int outcount = 0;
65 static var_t *outbuf = NULL;
66
67 static int var_cmp (const void *a, const void *b)
68 {
69   return (strcmp (((var_t *) a)->name, ((var_t *) b)->name));
70 }
71
72 enum output_formats_t {
73   F_CONF, F_MAN, F_SGML, F_NONE
74 };
75
76 #define D_NL            (1 << 0)
77 #define D_EM            (1 << 1)
78 #define D_BF            (1 << 2)
79 #define D_TT            (1 << 3)
80 #define D_TAB           (1 << 4)
81 #define D_NP            (1 << 5)
82 #define D_INIT          (1 << 6)
83 #define D_DL            (1 << 7)
84 #define D_DT            (1 << 8)
85 #define D_DD            (1 << 9)
86 #define D_PA            (1 << 10)
87
88 enum {
89   SP_START_EM,
90   SP_START_BF,
91   SP_START_TT,
92   SP_END_FT,
93   SP_END_PAR,
94   SP_NEWLINE,
95   SP_NEWPAR,
96   SP_STR,
97   SP_START_TAB,
98   SP_END_TAB,
99   SP_START_DL,
100   SP_DT,
101   SP_DD,
102   SP_END_DD,
103   SP_END_DL,
104   SP_END_SECT,
105   SP_REFER
106 };
107
108 enum output_formats_t OutputFormat = F_NONE;
109 char *Progname;
110 short Debug = 0;
111
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 int sgml_fputc (int);
123 static int sgml_fputs (const char *);
124 static int sgml_id_fputs (const char *);
125 static void add_var (const char *);
126 static int add_s (const char *);
127 static int add_c (int);
128
129 int main (int argc, char *argv[])
130 {
131   int c;
132   FILE *f;
133
134   if ((Progname = strrchr (argv[0], '/')))
135     Progname++;
136   else
137     Progname = argv[0];
138
139   while ((c = getopt (argc, argv, "cmsd")) != EOF) {
140     switch (c) {
141     case 'c':
142       OutputFormat = F_CONF;
143       break;
144     case 'm':
145       OutputFormat = F_MAN;
146       break;
147     case 's':
148       OutputFormat = F_SGML;
149       break;
150     case 'd':
151       Debug++;
152       break;
153     default:
154       {
155         fprintf (stderr, "%s: bad command line parameter.\n", Progname);
156         exit (1);
157       }
158     }
159   }
160
161   if (optind != argc) {
162     if ((f = fopen (argv[optind], "r")) == NULL) {
163       fprintf (stderr, "%s: Can't open %s (%s).\n",
164                Progname, argv[optind], strerror (errno));
165       exit (1);
166     }
167   }
168   else
169     f = stdin;
170
171   switch (OutputFormat) {
172   case F_CONF:
173   case F_MAN:
174   case F_SGML:
175     makedoc (f, stdout);
176     break;
177   default:
178     {
179       fprintf (stderr, "%s: No output format specified.\n", Progname);
180       exit (1);
181     }
182   }
183
184   if (f != stdin)
185     fclose (f);
186
187   exit (1);
188 }
189
190 static void add_var (const char *name)
191 {
192   outbuf = realloc (outbuf, (++outcount) * sizeof (var_t));
193   outbuf[outcount - 1].seen = 0;
194   outbuf[outcount - 1].name = strdup (name);
195   outbuf[outcount - 1].descr = NULL;
196 }
197
198 static int add_s (const char *s)
199 {
200   size_t lnew = STRLEN (s), lold = STRLEN (outbuf[outcount - 1].descr);
201
202   if (lnew == 0)
203     return (0);
204   if (!outbuf[outcount - 1].seen) {
205     lold = 0;
206     outbuf[outcount - 1].seen = 1;
207   }
208
209   if (lold == 0)
210     outbuf[outcount - 1].descr = strdup (s);
211   else {
212     outbuf[outcount - 1].descr =
213       realloc (outbuf[outcount - 1].descr, lold + lnew + 1);
214     memcpy (&(outbuf[outcount - 1].descr[lold - 1]) + 1, s, lnew);
215   }
216   outbuf[outcount - 1].descr[lold + lnew] = '\0';
217   return (1);
218 }
219
220 static int add_c (int c)
221 {
222   char buf[2] = "\0\0";
223
224   buf[0] = c;
225   return (add_s (buf));
226 }
227
228 static void makedoc (FILE * in, FILE * out)
229 {
230   char buffer[BUFFSIZE];
231   char token[BUFFSIZE];
232   char *p;
233   int active = 0;
234   int line = 0;
235   int docstat = D_INIT;
236
237   while ((fgets (buffer, sizeof (buffer), in))) {
238     line++;
239     if ((p = strchr (buffer, '\n')) == NULL) {
240       fprintf (stderr, "%s: Line %d too long.  Ask a wizard to enlarge\n"
241                "%s: my buffer size.\n", Progname, line, Progname);
242       exit (1);
243     }
244     else
245       *p = '\0';
246
247     if (!(p = get_token (token, sizeof (token), buffer)))
248       continue;
249
250     if (Debug) {
251       fprintf (stderr, "%s: line %d.  first token: \"%s\".\n",
252                Progname, line, token);
253     }
254
255     if (!strcmp (token, "/*++*/"))
256       active = 1;
257     else if (!strcmp (token, "/*--*/")) {
258       docstat = flush_doc (docstat);
259       active = 0;
260     }
261     else if (active && (!strcmp (token, "/**") || !strcmp (token, "**")))
262       docstat = handle_docline (p, docstat);
263     else if (active && !strcmp (token, "{")) {
264       docstat = flush_doc (docstat);
265       handle_confline (p);
266     }
267   }
268   flush_doc (docstat);
269   fputs ("\n", out);
270   qsort (outbuf, outcount, sizeof (var_t), &var_cmp);
271   for (line = 0; line < outcount; line++) {
272     if (outbuf[line].descr) {
273       fprintf (out, "%s\n", outbuf[line].descr);
274       free (outbuf[line].descr);
275     }
276     free (outbuf[line].name);
277   }
278   free (outbuf);
279 }
280
281 /* skip whitespace */
282
283 static char *skip_ws (char *s)
284 {
285   while (*s && isspace ((unsigned char) *s))
286     s++;
287
288   return s;
289 }
290
291 /* isolate a token */
292
293 static char single_char_tokens[] = "[]{},;|";
294
295 static char *get_token (char *d, size_t l, char *s)
296 {
297   char *t;
298   short is_quoted = 0;
299   char *dd = d;
300
301   if (Debug)
302     fprintf (stderr, "%s: get_token called for `%s'.\n", Progname, s);
303
304   s = skip_ws (s);
305
306   if (Debug > 1)
307     fprintf (stderr, "%s: argumet after skip_ws():  `%s'.\n", Progname, s);
308
309   if (!*s) {
310     if (Debug)
311       fprintf (stderr, "%s: no more tokens on this line.\n", Progname);
312     return NULL;
313   }
314
315   if (strchr (single_char_tokens, *s)) {
316     if (Debug) {
317       fprintf (stderr, "%s: found single character token `%c'.\n",
318                Progname, *s);
319     }
320     d[0] = *s++;
321     d[1] = 0;
322     return s;
323   }
324
325   if (*s == '"') {
326     if (Debug) {
327       fprintf (stderr, "%s: found quote character.\n", Progname);
328     }
329
330     s++;
331     is_quoted = 1;
332   }
333
334   for (t = s; *t && --l > 0; t++) {
335     if (*t == '\\' && !t[1])
336       break;
337
338     if (is_quoted && *t == '\\') {
339       switch ((*d = *++t)) {
340       case 'n':
341         *d = '\n';
342         break;
343       case 't':
344         *d = '\t';
345         break;
346       case 'r':
347         *d = '\r';
348         break;
349       case 'a':
350         *d = '\a';
351         break;
352       }
353
354       d++;
355       continue;
356     }
357
358     if (is_quoted && *t == '"') {
359       t++;
360       break;
361     }
362     else if (!is_quoted && strchr (single_char_tokens, *t))
363       break;
364     else if (!is_quoted && isspace ((unsigned char) *t))
365       break;
366     else
367       *d++ = *t;
368   }
369
370   *d = '\0';
371
372   if (Debug) {
373     fprintf (stderr, "%s: Got %stoken: `%s'.\n",
374              Progname, is_quoted ? "quoted " : "", dd);
375     fprintf (stderr, "%s: Remainder: `%s'.\n", Progname, t);
376   }
377
378   return t;
379 }
380
381
382 /**
383  ** Configuration line parser
384  ** 
385  ** The following code parses a line from init.h which declares
386  ** a configuration variable.
387  **
388  **/
389
390 /* note: the following enum must be in the same order as the
391  * following string definitions!
392  */
393
394 enum {
395   DT_NONE = 0,
396   DT_BOOL,
397   DT_NUM,
398   DT_STR,
399   DT_PATH,
400   DT_QUAD,
401   DT_SORT,
402   DT_RX,
403   DT_MAGIC,
404   DT_SYN,
405   DT_ADDR,
406   DT_SYS
407 };
408
409 struct {
410   char *machine;
411   char *human;
412 } types[] = {
413   {
414   "DT_NONE", "-none-"}, {
415   "DT_BOOL", "boolean"}, {
416   "DT_NUM", "number"}, {
417   "DT_STR", "string"}, {
418   "DT_PATH", "path"}, {
419   "DT_QUAD", "quadoption"}, {
420   "DT_SORT", "sort order"}, {
421   "DT_RX", "regular expression"}, {
422   "DT_MAGIC", "folder magic"}, {
423   "DT_SYN", NULL}, {
424   "DT_ADDR", "e-mail address"}, {
425   "DT_SYS", "system property"}, {
426   NULL, NULL}
427 };
428
429
430 static int buff2type (const char *s)
431 {
432   int type;
433
434   for (type = DT_NONE; types[type].machine; type++)
435     if (!strcmp (types[type].machine, s))
436       return type;
437
438   return DT_NONE;
439 }
440
441 static const char *type2human (int type)
442 {
443   return types[type].human;
444 }
445 static void handle_confline (char *s)
446 {
447   char varname[BUFFSIZE];
448   char buff[BUFFSIZE];
449   int type;
450
451   char val[BUFFSIZE];
452
453   /* xxx - put this into an actual state machine? */
454
455   /* variable name */
456   if (!(s = get_token (varname, sizeof (varname), s)))
457     return;
458
459   /* comma */
460   if (!(s = get_token (buff, sizeof (buff), s)))
461     return;
462
463   /* type */
464   if (!(s = get_token (buff, sizeof (buff), s)))
465     return;
466
467   type = buff2type (buff);
468
469   /* possibly a "|" or comma */
470   if (!(s = get_token (buff, sizeof (buff), s)))
471     return;
472
473   if (!strcmp (buff, "|")) {
474     if (Debug)
475       fprintf (stderr, "%s: Expecting <subtype> <comma>.\n", Progname);
476     /* ignore subtype and comma */
477     if (!(s = get_token (buff, sizeof (buff), s)))
478       return;
479     if (!(s = get_token (buff, sizeof (buff), s)))
480       return;
481   }
482
483   /* redraw, comma */
484
485   while (1) {
486     if (!(s = get_token (buff, sizeof (buff), s)))
487       return;
488     if (!strcmp (buff, ","))
489       break;
490   }
491
492   /* option name or UL &address */
493   if (!(s = get_token (buff, sizeof (buff), s)))
494     return;
495   if (!strcmp (buff, "UL"))
496     if (!(s = get_token (buff, sizeof (buff), s)))
497       return;
498
499   /* comma */
500   if (!(s = get_token (buff, sizeof (buff), s)))
501     return;
502
503   if (Debug)
504     fprintf (stderr, "%s: Expecting default value.\n", Progname);
505
506   /* <default value> or UL <default value> */
507   if (!(s = get_token (buff, sizeof (buff), s)))
508     return;
509   if (!strcmp (buff, "UL")) {
510     if (Debug)
511       fprintf (stderr, "%s: Skipping UL.\n", Progname);
512     if (!(s = get_token (buff, sizeof (buff), s)))
513       return;
514   }
515
516   memset (val, 0, sizeof (val));
517
518   do {
519     if (!strcmp (buff, "}"))
520       break;
521
522     strncpy (val + STRLEN (val), buff, sizeof (val) - STRLEN (val));
523   }
524   while ((s = get_token (buff, sizeof (buff), s)));
525
526   add_var (varname);
527   print_confline (varname, type, val);
528 }
529
530 static void char_to_escape (char *dest, unsigned int c)
531 {
532   switch (c) {
533   case '\r':
534     strcpy (dest, "\\r");
535     break;                      /* __STRCPY_CHECKED__ */
536   case '\n':
537     strcpy (dest, "\\n");
538     break;                      /* __STRCPY_CHECKED__ */
539   case '\t':
540     strcpy (dest, "\\t");
541     break;                      /* __STRCPY_CHECKED__ */
542   case '\f':
543     strcpy (dest, "\\f");
544     break;                      /* __STRCPY_CHECKED__ */
545   default:
546     sprintf (dest, "\\%03o", c);
547     break;
548   }
549 }
550 static void conf_char_to_escape (unsigned int c)
551 {
552   char buff[16];
553
554   char_to_escape (buff, c);
555   add_s (buff);
556 }
557
558 static void conf_print_strval (const char *v)
559 {
560   for (; *v; v++) {
561     if (*v < ' ' || *v & 0x80) {
562       conf_char_to_escape ((unsigned int) *v);
563       continue;
564     }
565
566     if (*v == '"' || *v == '\\')
567       add_c ('\\');
568     add_c (*v);
569   }
570 }
571
572 static void man_print_strval (const char *v)
573 {
574   for (; *v; v++) {
575     if (*v < ' ' || *v & 0x80) {
576       add_c ('\\');
577       conf_char_to_escape ((unsigned int) *v);
578       continue;
579     }
580
581     if (*v == '"')
582       add_s ("\\(rq");
583     else if (*v == '\\')
584       add_s ("\\\\");
585     else
586       add_c (*v);
587   }
588 }
589
590 static void sgml_print_strval (const char *v)
591 {
592   char buff[16];
593
594   for (; *v; v++) {
595     if (*v < ' ' || *v & 0x80) {
596       char_to_escape (buff, (unsigned int) *v);
597       sgml_fputs (buff);
598       continue;
599     }
600     sgml_fputc ((unsigned int) *v);
601   }
602 }
603
604 static int sgml_fputc (int c)
605 {
606   switch (c) {
607   case '<':
608     return add_s ("&lt;");
609   case '>':
610     return add_s ("&gt;");
611 #if 0
612   case '$':
613     return add_s ("&dollar;");
614   case '_':
615     return add_s ("&lowbar;");
616   case '%':
617     return add_s ("&percnt;");
618 #endif
619   case '&':
620     return add_s ("&amp;");
621 #if 0
622   case '\\':
623     return add_s ("&bsol;");
624   case '"':
625     return add_s ("&quot;");
626   case '[':
627     return add_s ("&lsqb;");
628   case ']':
629     return add_s ("&rsqb;");
630   case '~':
631     return add_s ("&tilde;");
632 #endif
633   default:
634     return add_c (c);
635   }
636 }
637
638 static int sgml_fputs (const char *s)
639 {
640   for (; *s; s++)
641     if (sgml_fputc ((unsigned int) *s) == EOF)
642       return EOF;
643
644   return 0;
645 }
646
647 /* reduce CDATA to ID */
648 static int sgml_id_fputs (const char *s) {
649  char id;
650
651  for (; *s; s++) {
652    if (*s == '_')
653      id = '-';
654    else
655      id = *s;
656    if (sgml_fputc ((unsigned int) id) == EOF)
657      return EOF;
658  }
659  return 0;
660 }
661
662 static void print_confline (const char *varname, int type, const char *val)
663 {
664   if (type == DT_SYN)
665     return;
666
667   switch (OutputFormat) {
668     /* configuration file */
669   case F_CONF:
670     {
671       if (type == DT_SYS) {
672         add_s ("\n# set ?");
673         add_s (varname);
674         add_s (" prints ");
675         add_s (val);
676         break;
677       }
678       if (type == DT_STR || type == DT_RX || type == DT_ADDR
679           || type == DT_PATH) {
680         add_s ("\n# set ");
681         add_s (varname);
682         add_s ("=\"");
683         conf_print_strval (val);
684         add_s ("\"");
685       }
686       else if (type != DT_SYN) {
687         add_s ("\n# set ");
688         add_s (varname);
689         add_s ("=");
690         add_s (val);
691       }
692
693       add_s ("\n#\n# Name: ");
694       add_s (varname);
695       add_s ("\n# Type: ");
696       add_s (type2human (type));
697       if (type == DT_STR || type == DT_RX || type == DT_ADDR
698           || type == DT_PATH) {
699         add_s ("\n# Default: \"");
700         conf_print_strval (val);
701         add_s ("\"");
702       }
703       else {
704         add_s ("\n# Default: ");
705         add_s (val);
706       }
707
708       add_s ("\n# ");
709       break;
710     }
711
712     /* manual page */
713   case F_MAN:
714     {
715       add_s (".TP\n.B ");
716       add_s (varname);
717       add_s ("\n.nf\n");
718       add_s ("Type: ");
719       add_s (type2human (type));
720       add_c ('\n');
721       if (type == DT_STR || type == DT_RX || type == DT_ADDR
722           || type == DT_PATH) {
723         add_s ("Default: \\(lq");
724         man_print_strval (val);
725         add_s ("\\(rq\n");
726       }
727       else {
728         add_s (type == DT_SYS ? "Value: " : "Default: ");
729         add_s (val);
730         add_c ('\n');
731       }
732       add_s (".fi");
733
734       break;
735     }
736
737     /* SGML based manual */
738   case F_SGML:
739     {
740       add_s ("\n<muttng-doc:vardef id=\"");
741       sgml_id_fputs (varname);
742       add_s ("\" name=\"");
743       sgml_fputs (varname);
744       add_s ("\">\n<para>Type: <literal>");
745       add_s (type2human (type));
746       add_s ("</literal></para>\n");
747
748       if (type == DT_STR || type == DT_RX || type == DT_ADDR
749           || type == DT_PATH) {
750         add_s ("<para>\nDefault: <literal>&quot;");
751         sgml_print_strval (val);
752         add_s ("&quot;</literal>");
753       }
754       else {
755         add_s ("<para>\n"); 
756         add_s (type == DT_SYS ? "Value: " : "Default: ");
757         add_s ("<literal>");
758         add_s (val);
759         add_s ("</literal>");
760       }
761       add_s ("</para>\n");
762       break;
763     }
764     /* make gcc happy */
765   default:
766     break;
767   }
768 }
769
770 /**
771  ** Documentation line parser
772  **
773  ** The following code parses specially formatted documentation 
774  ** comments in init.h.
775  **
776  ** The format is very remotely inspired by nroff. Most important, it's
777  ** easy to parse and convert, and it was easy to generate from the SGML 
778  ** source of mutt's original manual.
779  **
780  ** - \fI switches to italics
781  ** - \fB switches to boldface
782  ** - \fT switches to typewriter for SGML
783  ** - \fP switches to normal display
784  ** - .dl on a line starts a definition list (name taken taken from HTML).
785  ** - .dt starts a term in a definition list.
786  ** - .dd starts a definition in a definition list.
787  ** - .de on a line finishes a definition list.
788  ** - .ts on a line starts a "tscreen" environment (name taken from SGML).
789  ** - .te on a line finishes this environment.
790  ** - .pp on a line starts a paragraph.
791  ** - \$word will be converted to a reference to word, where appropriate.
792  **   Note that \$$word is possible as well.
793  ** - '. ' in the beginning of a line expands to two space characters.
794  **   This is used to protect indentations in tables.
795  **/
796
797 /* close eventually-open environments. */
798
799 static int fd_recurse = 0;
800
801 static int flush_doc (int docstat)
802 {
803   if (docstat & D_INIT)
804     return D_INIT;
805
806   if (fd_recurse++) {
807     fprintf (stderr, "%s: Internal error, recursion in flush_doc()!\n",
808              Progname);
809     exit (1);
810   }
811
812   if (docstat & (D_PA))
813     docstat = print_it (SP_END_PAR, NULL, docstat);
814
815   if (docstat & (D_TAB))
816     docstat = print_it (SP_END_TAB, NULL, docstat);
817
818   if (docstat & (D_DL))
819     docstat = print_it (SP_END_DL, NULL, docstat);
820
821   if (docstat & (D_EM | D_BF | D_TT))
822     docstat = print_it (SP_END_FT, NULL, docstat);
823
824   docstat = print_it (SP_END_SECT, NULL, docstat);
825
826   docstat = print_it (SP_NEWLINE, NULL, 0);
827
828   fd_recurse--;
829   return D_INIT;
830 }
831
832 /* print something. */
833
834 static int print_it (int special, char *str, int docstat)
835 {
836   int onl = docstat & (D_NL | D_NP);
837
838   docstat &= ~(D_NL | D_NP | D_INIT);
839
840   switch (OutputFormat) {
841     /* configuration file */
842   case F_CONF:
843     {
844       switch (special) {
845         static int Continuation = 0;
846
847       case SP_END_FT:
848         docstat &= ~(D_EM | D_BF | D_TT);
849         break;
850       case SP_START_BF:
851         docstat |= D_BF;
852         break;
853       case SP_START_EM:
854         docstat |= D_EM;
855         break;
856       case SP_START_TT:
857         docstat |= D_TT;
858         break;
859       case SP_NEWLINE:
860         {
861           if (onl)
862             docstat |= onl;
863           else {
864             add_s ("\n# ");
865             docstat |= D_NL;
866           }
867           if (docstat & D_DL)
868             ++Continuation;
869           break;
870         }
871       case SP_NEWPAR:
872         {
873           if (onl & D_NP) {
874             docstat |= onl;
875             break;
876           }
877
878           if (!(onl & D_NL))
879             add_s ("\n# ");
880           add_s ("\n# ");
881           docstat |= D_NP;
882           break;
883         }
884       case SP_START_TAB:
885         {
886           if (!onl)
887             add_s ("\n# ");
888           docstat |= D_TAB;
889           break;
890         }
891       case SP_END_TAB:
892         {
893           docstat &= ~D_TAB;
894           docstat |= D_NL;
895           break;
896         }
897       case SP_START_DL:
898         {
899           docstat |= D_DL;
900           break;
901         }
902       case SP_DT:
903         {
904           Continuation = 0;
905           docstat |= D_DT;
906           break;
907         }
908       case SP_DD:
909         {
910           Continuation = 0;
911           break;
912         }
913       case SP_END_DL:
914         {
915           Continuation = 0;
916           docstat &= ~D_DL;
917           break;
918         }
919       case SP_STR:
920         {
921           if (Continuation) {
922             Continuation = 0;
923             add_s ("        ");
924           }
925           add_s (str);
926           if (docstat & D_DT) {
927             int i;
928
929             for (i = STRLEN (str); i < 8; i++)
930               add_c (' ');
931             docstat &= ~D_DT;
932             docstat |= D_NL;
933           }
934           break;
935         }
936       }
937       break;
938     }
939
940     /* manual page */
941   case F_MAN:
942     {
943       switch (special) {
944       case SP_END_FT:
945         {
946           add_s ("\\fP");
947           docstat &= ~(D_EM | D_BF | D_TT);
948           break;
949         }
950       case SP_START_BF:
951         {
952           add_s ("\\fB");
953           docstat |= D_BF;
954           docstat &= ~(D_EM | D_TT);
955           break;
956         }
957       case SP_START_EM:
958         {
959           add_s ("\\fI");
960           docstat |= D_EM;
961           docstat &= ~(D_BF | D_TT);
962           break;
963         }
964       case SP_START_TT:
965         {
966           docstat |= D_TT;
967           docstat &= ~(D_BF | D_EM);
968           break;
969         }
970       case SP_NEWLINE:
971         {
972           if (onl)
973             docstat |= onl;
974           else {
975             add_c ('\n');
976             docstat |= D_NL;
977           }
978           break;
979         }
980       case SP_NEWPAR:
981         {
982           if (onl & D_NP) {
983             docstat |= onl;
984             break;
985           }
986
987           if (!(onl & D_NL))
988             add_c ('\n');
989           add_s (".IP\n");
990
991           docstat |= D_NP;
992           break;
993         }
994       case SP_START_TAB:
995         {
996           add_s ("\n.IP\n.DS\n.sp\n.ft CR\n.nf\n");
997           docstat |= D_TAB | D_NL;
998           break;
999         }
1000       case SP_END_TAB:
1001         {
1002           add_s ("\n.fi\n.ec\n.ft P\n.sp\n");
1003           docstat &= ~D_TAB;
1004           docstat |= D_NL;
1005           break;
1006         }
1007       case SP_START_DL:
1008         {
1009           add_s ("\n.RS");
1010           docstat |= D_DL;
1011           break;
1012         }
1013       case SP_DT:
1014         {
1015           add_s ("\n.IP ");
1016           break;
1017         }
1018       case SP_DD:
1019         {
1020           add_s ("\n");
1021           break;
1022         }
1023       case SP_END_DL:
1024         {
1025           add_s ("\n.RE");
1026           docstat &= ~D_DL;
1027           break;
1028         }
1029       case SP_STR:
1030         {
1031           while (*str) {
1032             for (; *str; str++) {
1033               if (*str == '"')
1034                 add_s ("\\(rq");
1035               else if (*str == '\\')
1036                 add_s ("\\\\");
1037               else if (!strncmp (str, "``", 2)) {
1038                 add_s ("\\(lq");
1039                 str++;
1040               }
1041               else if (!strncmp (str, "''", 2)) {
1042                 add_s ("\\(rq");
1043                 str++;
1044               }
1045               else
1046                 add_c (*str);
1047             }
1048           }
1049           break;
1050         }
1051       }
1052       break;
1053     }
1054
1055     /* SGML based manual */
1056   case F_SGML:
1057     {
1058       switch (special) {
1059       case SP_END_FT:
1060         {
1061           if (docstat & D_EM)
1062             add_s ("</emphasis>");
1063           if (docstat & D_BF)
1064             add_s ("</emphasis>");
1065           if (docstat & D_TT)
1066             add_s ("</literal>");
1067           docstat &= ~(D_EM | D_BF | D_TT);
1068           break;
1069         }
1070       case SP_START_BF:
1071         {
1072           add_s ("<emphasis role=\"bold\">");
1073           docstat |= D_BF;
1074           docstat &= ~(D_EM | D_TT);
1075           break;
1076         }
1077       case SP_START_EM:
1078         {
1079           add_s ("<emphasis>");
1080           docstat |= D_EM;
1081           docstat &= ~(D_BF | D_TT);
1082           break;
1083         }
1084       case SP_START_TT:
1085         {
1086           add_s ("<literal>");
1087           docstat |= D_TT;
1088           docstat &= ~(D_EM | D_BF);
1089           break;
1090         }
1091       case SP_NEWLINE:
1092         {
1093           if (onl)
1094             docstat |= onl;
1095           else {
1096             add_s ("\n");
1097             docstat |= D_NL;
1098           }
1099           break;
1100         }
1101       case SP_NEWPAR:
1102         {
1103           if (onl & D_NP) {
1104             docstat |= onl;
1105             break;
1106           }
1107
1108           if (!(onl & D_NL))
1109             add_s ("\n");
1110           if (docstat & D_PA)
1111             add_s ("</para>\n");
1112           add_s ("<para>\n");
1113
1114           docstat |= D_NP;
1115           docstat |= D_PA;
1116           break;
1117         }
1118       case SP_START_TAB:
1119         {
1120           add_s ("\n<screen>\n");
1121           docstat |= D_TAB | D_NL;
1122           break;
1123         }
1124       case SP_END_TAB:
1125         {
1126           add_s ("\n</screen>");
1127           docstat &= ~D_TAB;
1128           docstat |= D_NL;
1129           break;
1130         }
1131       case SP_START_DL:
1132         {
1133           add_s ("\n<variablelist>\n");
1134           docstat |= D_DL;
1135           break;
1136         }
1137       case SP_DT:
1138         {
1139           add_s ("<varlistentry><term>");
1140           break;
1141         }
1142       case SP_DD:
1143         {
1144           add_s ("</term>\n<listitem><para>\n");
1145           docstat |= D_DD;
1146           break;
1147         }
1148       case SP_END_DL:
1149         {
1150           add_s ("</para></listitem></varlistentry></variablelist>\n");
1151           docstat &= ~(D_DL|D_DD);
1152           break;
1153         }
1154       case SP_END_PAR:
1155         {
1156           add_s ("</para>\n");
1157           docstat &= ~D_PA;
1158           break;
1159         }
1160       case SP_END_DD:
1161         {
1162           add_s ("</para></listitem></varlistentry>\n");
1163           docstat &= ~D_DD;
1164           break;
1165         }
1166       case SP_END_SECT:
1167         {
1168           add_s ("</muttng-doc:vardef>\n");
1169           break;
1170         }
1171       case SP_STR:
1172         {
1173           if (docstat & D_TAB)
1174             add_s (str);
1175           else
1176             sgml_fputs (str);
1177           break;
1178         }
1179       }
1180       break;
1181     }
1182     /* make gcc happy (unreached) */
1183   default:
1184     break;
1185   }
1186
1187   return docstat;
1188 }
1189
1190 void print_ref (int output_dollar, const char *ref)
1191 {
1192   switch (OutputFormat) {
1193   case F_CONF:
1194   case F_MAN:
1195     if (output_dollar)
1196       add_c ('$');
1197     add_s (ref);
1198     break;
1199
1200   case F_SGML:
1201     add_s ("<link linkend=\"");
1202     sgml_id_fputs (ref);
1203     add_s ("\">\n");
1204     if (output_dollar)
1205       add_s ("$");
1206     sgml_fputs (ref);
1207     add_s ("</link>");
1208     break;
1209
1210   default:
1211     break;
1212   }
1213 }
1214
1215 static int commit_buff (char *buff, char **d, int docstat)
1216 {
1217   if (*d > buff) {
1218     **d = '\0';
1219     docstat = print_it (SP_STR, buff, docstat);
1220     *d = buff;
1221   }
1222
1223   return docstat;
1224 }
1225
1226 static int handle_docline (char *l, int docstat)
1227 {
1228   char buff[BUFFSIZE];
1229   char *s, *d;
1230
1231   l = skip_ws (l);
1232
1233   if (Debug)
1234     fprintf (stderr, "%s: handle_docline `%s'\n", Progname, l);
1235
1236   if (!strncmp (l, ".pp", 3))
1237     return print_it (SP_NEWPAR, NULL, docstat);
1238   else if (!strncmp (l, ".ts", 3))
1239     return print_it (SP_START_TAB, NULL, docstat);
1240   else if (!strncmp (l, ".te", 3))
1241     return print_it (SP_END_TAB, NULL, docstat);
1242   else if (!strncmp (l, ".dl", 3))
1243     return print_it (SP_START_DL, NULL, docstat);
1244   else if (!strncmp (l, ".de", 3))
1245     return print_it (SP_END_DL, NULL, docstat);
1246   else if (!strncmp (l, ". ", 2))
1247     *l = ' ';
1248
1249   for (s = l, d = buff; *s; s++) {
1250     if (!strncmp (s, "\\(as", 4)) {
1251       *d++ = '*';
1252       s += 3;
1253     }
1254     else if (!strncmp (s, "\\(rs", 4)) {
1255       *d++ = '\\';
1256       s += 3;
1257     }
1258     else if (!strncmp (s, "\\fI", 3)) {
1259       docstat = commit_buff (buff, &d, docstat);
1260       docstat = print_it (SP_START_EM, NULL, docstat);
1261       s += 2;
1262     }
1263     else if (!strncmp (s, "\\fB", 3)) {
1264       docstat = commit_buff (buff, &d, docstat);
1265       docstat = print_it (SP_START_BF, NULL, docstat);
1266       s += 2;
1267     }
1268     else if (!strncmp (s, "\\fT", 3)) {
1269       docstat = commit_buff (buff, &d, docstat);
1270       docstat = print_it (SP_START_TT, NULL, docstat);
1271       s += 2;
1272     }
1273     else if (!strncmp (s, "\\fP", 3)) {
1274       docstat = commit_buff (buff, &d, docstat);
1275       docstat = print_it (SP_END_FT, NULL, docstat);
1276       s += 2;
1277     }
1278     else if (!strncmp (s, ".dt", 3)) {
1279       if (docstat & D_DD) {
1280         docstat = commit_buff (buff, &d, docstat);
1281         docstat = print_it (SP_END_DD, NULL, docstat);
1282       }
1283       docstat = commit_buff (buff, &d, docstat);
1284       docstat = print_it (SP_DT, NULL, docstat);
1285       s += 3;
1286     }
1287     else if (!strncmp (s, ".dd", 3)) {
1288       docstat = commit_buff (buff, &d, docstat);
1289       docstat = print_it (SP_DD, NULL, docstat);
1290       s += 3;
1291     }
1292     else if (*s == '$') {
1293       int output_dollar = 0;
1294       char *ref;
1295       char save;
1296
1297       ++s;
1298       if (*s == '$') {
1299         output_dollar = 1;
1300         ++s;
1301       }
1302       if (*s == '$') {
1303         *d++ = '$';
1304       }
1305       else {
1306         ref = s;
1307         while (isalnum ((unsigned char) *s) || *s == '-' || *s == '_')
1308           ++s;
1309
1310         docstat = commit_buff (buff, &d, docstat);
1311         save = *s;
1312         *s = 0;
1313         print_ref (output_dollar, ref);
1314         *s = save;
1315         --s;
1316       }
1317     }
1318     else
1319       *d++ = *s;
1320   }
1321
1322   docstat = commit_buff (buff, &d, docstat);
1323   return print_it (SP_NEWLINE, NULL, docstat);
1324 }