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