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