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