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