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