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