use m_strdup and m_strlen that are inlined for efficiency
[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 name=\"");
741       sgml_fputs (varname);
742       add_s ("\">\n<para>Type: <literal>");
743       add_s (type2human (type));
744       add_s ("</literal></para>\n");
745
746       if (type == DT_STR || type == DT_RX || type == DT_ADDR
747           || type == DT_PATH) {
748         add_s ("<para>\nDefault: <literal>&quot;");
749         sgml_print_strval (val);
750         add_s ("&quot;</literal>");
751       }
752       else {
753         add_s ("<para>\n"); 
754         add_s (type == DT_SYS ? "Value: " : "Default: ");
755         add_s ("<literal>");
756         add_s (val);
757         add_s ("</literal>");
758       }
759       add_s ("</para>\n");
760       break;
761     }
762     /* make gcc happy */
763   default:
764     break;
765   }
766 }
767
768 /**
769  ** Documentation line parser
770  **
771  ** The following code parses specially formatted documentation 
772  ** comments in init.h.
773  **
774  ** The format is very remotely inspired by nroff. Most important, it's
775  ** easy to parse and convert, and it was easy to generate from the SGML 
776  ** source of mutt's original manual.
777  **
778  ** - \fI switches to italics
779  ** - \fB switches to boldface
780  ** - \fT switches to typewriter for SGML
781  ** - \fP switches to normal display
782  ** - .dl on a line starts a definition list (name taken taken from HTML).
783  ** - .dt starts a term in a definition list.
784  ** - .dd starts a definition in a definition list.
785  ** - .de on a line finishes a definition list.
786  ** - .ts on a line starts a "tscreen" environment (name taken from SGML).
787  ** - .te on a line finishes this environment.
788  ** - .pp on a line starts a paragraph.
789  ** - \$word will be converted to a reference to word, where appropriate.
790  **   Note that \$$word is possible as well.
791  ** - '. ' in the beginning of a line expands to two space characters.
792  **   This is used to protect indentations in tables.
793  **/
794
795 /* close eventually-open environments. */
796
797 static int fd_recurse = 0;
798
799 static int flush_doc (int docstat)
800 {
801   if (docstat & D_INIT)
802     return D_INIT;
803
804   if (fd_recurse++) {
805     fprintf (stderr, "%s: Internal error, recursion in flush_doc()!\n",
806              Progname);
807     exit (1);
808   }
809
810   if (docstat & (D_PA))
811     docstat = print_it (SP_END_PAR, NULL, docstat);
812
813   if (docstat & (D_TAB))
814     docstat = print_it (SP_END_TAB, NULL, docstat);
815
816   if (docstat & (D_DL))
817     docstat = print_it (SP_END_DL, NULL, docstat);
818
819   if (docstat & (D_EM | D_BF | D_TT))
820     docstat = print_it (SP_END_FT, NULL, docstat);
821
822   docstat = print_it (SP_END_SECT, NULL, docstat);
823
824   docstat = print_it (SP_NEWLINE, NULL, 0);
825
826   fd_recurse--;
827   return D_INIT;
828 }
829
830 /* print something. */
831
832 static int print_it (int special, char *str, int docstat)
833 {
834   int onl = docstat & (D_NL | D_NP);
835
836   docstat &= ~(D_NL | D_NP | D_INIT);
837
838   switch (OutputFormat) {
839     /* configuration file */
840   case F_CONF:
841     {
842       switch (special) {
843         static int Continuation = 0;
844
845       case SP_END_FT:
846         docstat &= ~(D_EM | D_BF | D_TT);
847         break;
848       case SP_START_BF:
849         docstat |= D_BF;
850         break;
851       case SP_START_EM:
852         docstat |= D_EM;
853         break;
854       case SP_START_TT:
855         docstat |= D_TT;
856         break;
857       case SP_NEWLINE:
858         {
859           if (onl)
860             docstat |= onl;
861           else {
862             add_s ("\n# ");
863             docstat |= D_NL;
864           }
865           if (docstat & D_DL)
866             ++Continuation;
867           break;
868         }
869       case SP_NEWPAR:
870         {
871           if (onl & D_NP) {
872             docstat |= onl;
873             break;
874           }
875
876           if (!(onl & D_NL))
877             add_s ("\n# ");
878           add_s ("\n# ");
879           docstat |= D_NP;
880           break;
881         }
882       case SP_START_TAB:
883         {
884           if (!onl)
885             add_s ("\n# ");
886           docstat |= D_TAB;
887           break;
888         }
889       case SP_END_TAB:
890         {
891           docstat &= ~D_TAB;
892           docstat |= D_NL;
893           break;
894         }
895       case SP_START_DL:
896         {
897           docstat |= D_DL;
898           break;
899         }
900       case SP_DT:
901         {
902           Continuation = 0;
903           docstat |= D_DT;
904           break;
905         }
906       case SP_DD:
907         {
908           Continuation = 0;
909           break;
910         }
911       case SP_END_DL:
912         {
913           Continuation = 0;
914           docstat &= ~D_DL;
915           break;
916         }
917       case SP_STR:
918         {
919           if (Continuation) {
920             Continuation = 0;
921             add_s ("        ");
922           }
923           add_s (str);
924           if (docstat & D_DT) {
925             int i;
926
927             for (i = STRLEN (str); i < 8; i++)
928               add_c (' ');
929             docstat &= ~D_DT;
930             docstat |= D_NL;
931           }
932           break;
933         }
934       }
935       break;
936     }
937
938     /* manual page */
939   case F_MAN:
940     {
941       switch (special) {
942       case SP_END_FT:
943         {
944           add_s ("\\fP");
945           docstat &= ~(D_EM | D_BF | D_TT);
946           break;
947         }
948       case SP_START_BF:
949         {
950           add_s ("\\fB");
951           docstat |= D_BF;
952           docstat &= ~(D_EM | D_TT);
953           break;
954         }
955       case SP_START_EM:
956         {
957           add_s ("\\fI");
958           docstat |= D_EM;
959           docstat &= ~(D_BF | D_TT);
960           break;
961         }
962       case SP_START_TT:
963         {
964           docstat |= D_TT;
965           docstat &= ~(D_BF | D_EM);
966           break;
967         }
968       case SP_NEWLINE:
969         {
970           if (onl)
971             docstat |= onl;
972           else {
973             add_c ('\n');
974             docstat |= D_NL;
975           }
976           break;
977         }
978       case SP_NEWPAR:
979         {
980           if (onl & D_NP) {
981             docstat |= onl;
982             break;
983           }
984
985           if (!(onl & D_NL))
986             add_c ('\n');
987           add_s (".IP\n");
988
989           docstat |= D_NP;
990           break;
991         }
992       case SP_START_TAB:
993         {
994           add_s ("\n.IP\n.DS\n.sp\n.ft CR\n.nf\n");
995           docstat |= D_TAB | D_NL;
996           break;
997         }
998       case SP_END_TAB:
999         {
1000           add_s ("\n.fi\n.ec\n.ft P\n.sp\n");
1001           docstat &= ~D_TAB;
1002           docstat |= D_NL;
1003           break;
1004         }
1005       case SP_START_DL:
1006         {
1007           add_s ("\n.RS");
1008           docstat |= D_DL;
1009           break;
1010         }
1011       case SP_DT:
1012         {
1013           add_s ("\n.IP ");
1014           break;
1015         }
1016       case SP_DD:
1017         {
1018           add_s ("\n");
1019           break;
1020         }
1021       case SP_END_DL:
1022         {
1023           add_s ("\n.RE");
1024           docstat &= ~D_DL;
1025           break;
1026         }
1027       case SP_STR:
1028         {
1029           while (*str) {
1030             for (; *str; str++) {
1031               if (*str == '"')
1032                 add_s ("\\(rq");
1033               else if (*str == '\\')
1034                 add_s ("\\\\");
1035               else if (!strncmp (str, "``", 2)) {
1036                 add_s ("\\(lq");
1037                 str++;
1038               }
1039               else if (!strncmp (str, "''", 2)) {
1040                 add_s ("\\(rq");
1041                 str++;
1042               }
1043               else
1044                 add_c (*str);
1045             }
1046           }
1047           break;
1048         }
1049       }
1050       break;
1051     }
1052
1053     /* SGML based manual */
1054   case F_SGML:
1055     {
1056       switch (special) {
1057       case SP_END_FT:
1058         {
1059           if (docstat & D_EM)
1060             add_s ("</emphasis>");
1061           if (docstat & D_BF)
1062             add_s ("</emphasis>");
1063           if (docstat & D_TT)
1064             add_s ("</literal>");
1065           docstat &= ~(D_EM | D_BF | D_TT);
1066           break;
1067         }
1068       case SP_START_BF:
1069         {
1070           add_s ("<emphasis role=\"bold\">");
1071           docstat |= D_BF;
1072           docstat &= ~(D_EM | D_TT);
1073           break;
1074         }
1075       case SP_START_EM:
1076         {
1077           add_s ("<emphasis>");
1078           docstat |= D_EM;
1079           docstat &= ~(D_BF | D_TT);
1080           break;
1081         }
1082       case SP_START_TT:
1083         {
1084           add_s ("<literal>");
1085           docstat |= D_TT;
1086           docstat &= ~(D_EM | D_BF);
1087           break;
1088         }
1089       case SP_NEWLINE:
1090         {
1091           if (onl)
1092             docstat |= onl;
1093           else {
1094             add_s ("\n");
1095             docstat |= D_NL;
1096           }
1097           break;
1098         }
1099       case SP_NEWPAR:
1100         {
1101           if (onl & D_NP) {
1102             docstat |= onl;
1103             break;
1104           }
1105
1106           if (!(onl & D_NL))
1107             add_s ("\n");
1108           if (docstat & D_PA)
1109             add_s ("</para>\n");
1110           add_s ("<para>\n");
1111
1112           docstat |= D_NP;
1113           docstat |= D_PA;
1114           break;
1115         }
1116       case SP_START_TAB:
1117         {
1118           add_s ("\n<screen>\n");
1119           docstat |= D_TAB | D_NL;
1120           break;
1121         }
1122       case SP_END_TAB:
1123         {
1124           add_s ("\n</screen>");
1125           docstat &= ~D_TAB;
1126           docstat |= D_NL;
1127           break;
1128         }
1129       case SP_START_DL:
1130         {
1131           add_s ("\n<variablelist>\n");
1132           docstat |= D_DL;
1133           break;
1134         }
1135       case SP_DT:
1136         {
1137           add_s ("<varlistentry><term>");
1138           break;
1139         }
1140       case SP_DD:
1141         {
1142           add_s ("</term>\n<listitem><para>\n");
1143           docstat |= D_DD;
1144           break;
1145         }
1146       case SP_END_DL:
1147         {
1148           add_s ("</para></listitem></varlistentry></variablelist>\n");
1149           docstat &= ~(D_DL|D_DD);
1150           break;
1151         }
1152       case SP_END_PAR:
1153         {
1154           add_s ("</para>\n");
1155           docstat &= ~D_PA;
1156           break;
1157         }
1158       case SP_END_DD:
1159         {
1160           add_s ("</para></listitem></varlistentry>\n");
1161           docstat &= ~D_DD;
1162           break;
1163         }
1164       case SP_END_SECT:
1165         {
1166           add_s ("</muttng-doc:vardef>\n");
1167           break;
1168         }
1169       case SP_STR:
1170         {
1171           if (docstat & D_TAB)
1172             add_s (str);
1173           else
1174             sgml_fputs (str);
1175           break;
1176         }
1177       }
1178       break;
1179     }
1180     /* make gcc happy (unreached) */
1181   default:
1182     break;
1183   }
1184
1185   return docstat;
1186 }
1187
1188 void print_ref (int output_dollar, const char *ref)
1189 {
1190   switch (OutputFormat) {
1191   case F_CONF:
1192   case F_MAN:
1193     if (output_dollar)
1194       add_c ('$');
1195     add_s (ref);
1196     break;
1197
1198   case F_SGML:
1199     add_s ("<link linkend=\"");
1200     sgml_id_fputs (ref);
1201     add_s ("\">\n");
1202     if (output_dollar)
1203       add_s ("$");
1204     sgml_fputs (ref);
1205     add_s ("</link>");
1206     break;
1207
1208   default:
1209     break;
1210   }
1211 }
1212
1213 static int commit_buff (char *buff, char **d, int docstat)
1214 {
1215   if (*d > buff) {
1216     **d = '\0';
1217     docstat = print_it (SP_STR, buff, docstat);
1218     *d = buff;
1219   }
1220
1221   return docstat;
1222 }
1223
1224 static int handle_docline (char *l, int docstat)
1225 {
1226   char buff[BUFFSIZE];
1227   char *s, *d;
1228
1229   l = skip_ws (l);
1230
1231   if (Debug)
1232     fprintf (stderr, "%s: handle_docline `%s'\n", Progname, l);
1233
1234   if (!strncmp (l, ".pp", 3))
1235     return print_it (SP_NEWPAR, NULL, docstat);
1236   else if (!strncmp (l, ".ts", 3))
1237     return print_it (SP_START_TAB, NULL, docstat);
1238   else if (!strncmp (l, ".te", 3))
1239     return print_it (SP_END_TAB, NULL, docstat);
1240   else if (!strncmp (l, ".dl", 3))
1241     return print_it (SP_START_DL, NULL, docstat);
1242   else if (!strncmp (l, ".de", 3))
1243     return print_it (SP_END_DL, NULL, docstat);
1244   else if (!strncmp (l, ". ", 2))
1245     *l = ' ';
1246
1247   for (s = l, d = buff; *s; s++) {
1248     if (!strncmp (s, "\\(as", 4)) {
1249       *d++ = '*';
1250       s += 3;
1251     }
1252     else if (!strncmp (s, "\\(rs", 4)) {
1253       *d++ = '\\';
1254       s += 3;
1255     }
1256     else if (!strncmp (s, "\\fI", 3)) {
1257       docstat = commit_buff (buff, &d, docstat);
1258       docstat = print_it (SP_START_EM, NULL, docstat);
1259       s += 2;
1260     }
1261     else if (!strncmp (s, "\\fB", 3)) {
1262       docstat = commit_buff (buff, &d, docstat);
1263       docstat = print_it (SP_START_BF, NULL, docstat);
1264       s += 2;
1265     }
1266     else if (!strncmp (s, "\\fT", 3)) {
1267       docstat = commit_buff (buff, &d, docstat);
1268       docstat = print_it (SP_START_TT, NULL, docstat);
1269       s += 2;
1270     }
1271     else if (!strncmp (s, "\\fP", 3)) {
1272       docstat = commit_buff (buff, &d, docstat);
1273       docstat = print_it (SP_END_FT, NULL, docstat);
1274       s += 2;
1275     }
1276     else if (!strncmp (s, ".dt", 3)) {
1277       if (docstat & D_DD) {
1278         docstat = commit_buff (buff, &d, docstat);
1279         docstat = print_it (SP_END_DD, NULL, docstat);
1280       }
1281       docstat = commit_buff (buff, &d, docstat);
1282       docstat = print_it (SP_DT, NULL, docstat);
1283       s += 3;
1284     }
1285     else if (!strncmp (s, ".dd", 3)) {
1286       docstat = commit_buff (buff, &d, docstat);
1287       docstat = print_it (SP_DD, NULL, docstat);
1288       s += 3;
1289     }
1290     else if (*s == '$') {
1291       int output_dollar = 0;
1292       char *ref;
1293       char save;
1294
1295       ++s;
1296       if (*s == '$') {
1297         output_dollar = 1;
1298         ++s;
1299       }
1300       if (*s == '$') {
1301         *d++ = '$';
1302       }
1303       else {
1304         ref = s;
1305         while (isalnum ((unsigned char) *s) || *s == '-' || *s == '_')
1306           ++s;
1307
1308         docstat = commit_buff (buff, &d, docstat);
1309         save = *s;
1310         *s = 0;
1311         print_ref (output_dollar, ref);
1312         *s = save;
1313         --s;
1314       }
1315     }
1316     else
1317       *d++ = *s;
1318   }
1319
1320   docstat = commit_buff (buff, &d, docstat);
1321   return print_it (SP_NEWLINE, NULL, docstat);
1322 }