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