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