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