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");
743       add_s ("<indexterm><primary>Configuration Variables</primary><secondary>");
744       sgml_fputs (varname);
745       add_s ("</secondary></indexterm>\n\n");
746
747       if (type == DT_STR || type == DT_RX || type == DT_ADDR
748           || type == DT_PATH) {
749         add_s ("<para>\nDefault: <literal>&quot;");
750         sgml_print_strval (val);
751         add_s ("&quot;</literal>");
752       }
753       else {
754         add_s ("<para>\n"); 
755         add_s (type == DT_SYS ? "Value: " : "Default: ");
756         add_s ("<literal>");
757         add_s (val);
758         add_s ("</literal>");
759       }
760       add_s ("</para>\n");
761       break;
762     }
763     /* make gcc happy */
764   default:
765     break;
766   }
767 }
768
769 /**
770  ** Documentation line parser
771  **
772  ** The following code parses specially formatted documentation 
773  ** comments in init.h.
774  **
775  ** The format is very remotely inspired by nroff. Most important, it's
776  ** easy to parse and convert, and it was easy to generate from the SGML 
777  ** source of mutt's original manual.
778  **
779  ** - \fI switches to italics
780  ** - \fB switches to boldface
781  ** - \fT switches to typewriter for SGML
782  ** - \fP switches to normal display
783  ** - .dl on a line starts a definition list (name taken taken from HTML).
784  ** - .dt starts a term in a definition list.
785  ** - .dd starts a definition in a definition list.
786  ** - .de on a line finishes a definition list.
787  ** - .ts on a line starts a "tscreen" environment (name taken from SGML).
788  ** - .te on a line finishes this environment.
789  ** - .pp on a line starts a paragraph.
790  ** - \$word will be converted to a reference to word, where appropriate.
791  **   Note that \$$word is possible as well.
792  ** - '. ' in the beginning of a line expands to two space characters.
793  **   This is used to protect indentations in tables.
794  **/
795
796 /* close eventually-open environments. */
797
798 static int fd_recurse = 0;
799
800 static int flush_doc (int docstat)
801 {
802   if (docstat & D_INIT)
803     return D_INIT;
804
805   if (fd_recurse++) {
806     fprintf (stderr, "%s: Internal error, recursion in flush_doc()!\n",
807              Progname);
808     exit (1);
809   }
810
811   if (docstat & (D_PA))
812     docstat = print_it (SP_END_PAR, NULL, docstat);
813
814   if (docstat & (D_TAB))
815     docstat = print_it (SP_END_TAB, NULL, docstat);
816
817   if (docstat & (D_DL))
818     docstat = print_it (SP_END_DL, NULL, docstat);
819
820   if (docstat & (D_EM | D_BF | D_TT))
821     docstat = print_it (SP_END_FT, NULL, docstat);
822
823   docstat = print_it (SP_END_SECT, NULL, docstat);
824
825   docstat = print_it (SP_NEWLINE, NULL, 0);
826
827   fd_recurse--;
828   return D_INIT;
829 }
830
831 /* print something. */
832
833 static int print_it (int special, char *str, int docstat)
834 {
835   int onl = docstat & (D_NL | D_NP);
836
837   docstat &= ~(D_NL | D_NP | D_INIT);
838
839   switch (OutputFormat) {
840     /* configuration file */
841   case F_CONF:
842     {
843       switch (special) {
844         static int Continuation = 0;
845
846       case SP_END_FT:
847         docstat &= ~(D_EM | D_BF | D_TT);
848         break;
849       case SP_START_BF:
850         docstat |= D_BF;
851         break;
852       case SP_START_EM:
853         docstat |= D_EM;
854         break;
855       case SP_START_TT:
856         docstat |= D_TT;
857         break;
858       case SP_NEWLINE:
859         {
860           if (onl)
861             docstat |= onl;
862           else {
863             add_s ("\n# ");
864             docstat |= D_NL;
865           }
866           if (docstat & D_DL)
867             ++Continuation;
868           break;
869         }
870       case SP_NEWPAR:
871         {
872           if (onl & D_NP) {
873             docstat |= onl;
874             break;
875           }
876
877           if (!(onl & D_NL))
878             add_s ("\n# ");
879           add_s ("\n# ");
880           docstat |= D_NP;
881           break;
882         }
883       case SP_START_TAB:
884         {
885           if (!onl)
886             add_s ("\n# ");
887           docstat |= D_TAB;
888           break;
889         }
890       case SP_END_TAB:
891         {
892           docstat &= ~D_TAB;
893           docstat |= D_NL;
894           break;
895         }
896       case SP_START_DL:
897         {
898           docstat |= D_DL;
899           break;
900         }
901       case SP_DT:
902         {
903           Continuation = 0;
904           docstat |= D_DT;
905           break;
906         }
907       case SP_DD:
908         {
909           Continuation = 0;
910           break;
911         }
912       case SP_END_DL:
913         {
914           Continuation = 0;
915           docstat &= ~D_DL;
916           break;
917         }
918       case SP_STR:
919         {
920           if (Continuation) {
921             Continuation = 0;
922             add_s ("        ");
923           }
924           add_s (str);
925           if (docstat & D_DT) {
926             int i;
927
928             for (i = STRLEN (str); i < 8; i++)
929               add_c (' ');
930             docstat &= ~D_DT;
931             docstat |= D_NL;
932           }
933           break;
934         }
935       }
936       break;
937     }
938
939     /* manual page */
940   case F_MAN:
941     {
942       switch (special) {
943       case SP_END_FT:
944         {
945           add_s ("\\fP");
946           docstat &= ~(D_EM | D_BF | D_TT);
947           break;
948         }
949       case SP_START_BF:
950         {
951           add_s ("\\fB");
952           docstat |= D_BF;
953           docstat &= ~(D_EM | D_TT);
954           break;
955         }
956       case SP_START_EM:
957         {
958           add_s ("\\fI");
959           docstat |= D_EM;
960           docstat &= ~(D_BF | D_TT);
961           break;
962         }
963       case SP_START_TT:
964         {
965           docstat |= D_TT;
966           docstat &= ~(D_BF | D_EM);
967           break;
968         }
969       case SP_NEWLINE:
970         {
971           if (onl)
972             docstat |= onl;
973           else {
974             add_c ('\n');
975             docstat |= D_NL;
976           }
977           break;
978         }
979       case SP_NEWPAR:
980         {
981           if (onl & D_NP) {
982             docstat |= onl;
983             break;
984           }
985
986           if (!(onl & D_NL))
987             add_c ('\n');
988           add_s (".IP\n");
989
990           docstat |= D_NP;
991           break;
992         }
993       case SP_START_TAB:
994         {
995           add_s ("\n.IP\n.DS\n.sp\n.ft CR\n.nf\n");
996           docstat |= D_TAB | D_NL;
997           break;
998         }
999       case SP_END_TAB:
1000         {
1001           add_s ("\n.fi\n.ec\n.ft P\n.sp\n");
1002           docstat &= ~D_TAB;
1003           docstat |= D_NL;
1004           break;
1005         }
1006       case SP_START_DL:
1007         {
1008           add_s ("\n.RS");
1009           docstat |= D_DL;
1010           break;
1011         }
1012       case SP_DT:
1013         {
1014           add_s ("\n.IP ");
1015           break;
1016         }
1017       case SP_DD:
1018         {
1019           add_s ("\n");
1020           break;
1021         }
1022       case SP_END_DL:
1023         {
1024           add_s ("\n.RE");
1025           docstat &= ~D_DL;
1026           break;
1027         }
1028       case SP_STR:
1029         {
1030           while (*str) {
1031             for (; *str; str++) {
1032               if (*str == '"')
1033                 add_s ("\\(rq");
1034               else if (*str == '\\')
1035                 add_s ("\\\\");
1036               else if (!strncmp (str, "``", 2)) {
1037                 add_s ("\\(lq");
1038                 str++;
1039               }
1040               else if (!strncmp (str, "''", 2)) {
1041                 add_s ("\\(rq");
1042                 str++;
1043               }
1044               else
1045                 add_c (*str);
1046             }
1047           }
1048           break;
1049         }
1050       }
1051       break;
1052     }
1053
1054     /* SGML based manual */
1055   case F_SGML:
1056     {
1057       switch (special) {
1058       case SP_END_FT:
1059         {
1060           if (docstat & D_EM)
1061             add_s ("</emphasis>");
1062           if (docstat & D_BF)
1063             add_s ("</emphasis>");
1064           if (docstat & D_TT)
1065             add_s ("</literal>");
1066           docstat &= ~(D_EM | D_BF | D_TT);
1067           break;
1068         }
1069       case SP_START_BF:
1070         {
1071           add_s ("<emphasis role=\"bold\">");
1072           docstat |= D_BF;
1073           docstat &= ~(D_EM | D_TT);
1074           break;
1075         }
1076       case SP_START_EM:
1077         {
1078           add_s ("<emphasis>");
1079           docstat |= D_EM;
1080           docstat &= ~(D_BF | D_TT);
1081           break;
1082         }
1083       case SP_START_TT:
1084         {
1085           add_s ("<literal>");
1086           docstat |= D_TT;
1087           docstat &= ~(D_EM | D_BF);
1088           break;
1089         }
1090       case SP_NEWLINE:
1091         {
1092           if (onl)
1093             docstat |= onl;
1094           else {
1095             add_s ("\n");
1096             docstat |= D_NL;
1097           }
1098           break;
1099         }
1100       case SP_NEWPAR:
1101         {
1102           if (onl & D_NP) {
1103             docstat |= onl;
1104             break;
1105           }
1106
1107           if (!(onl & D_NL))
1108             add_s ("\n");
1109           if (docstat & D_PA)
1110             add_s ("</para>\n");
1111           add_s ("<para>\n");
1112
1113           docstat |= D_NP;
1114           docstat |= D_PA;
1115           break;
1116         }
1117       case SP_START_TAB:
1118         {
1119           add_s ("\n<screen>\n");
1120           docstat |= D_TAB | D_NL;
1121           break;
1122         }
1123       case SP_END_TAB:
1124         {
1125           add_s ("\n</screen>");
1126           docstat &= ~D_TAB;
1127           docstat |= D_NL;
1128           break;
1129         }
1130       case SP_START_DL:
1131         {
1132           add_s ("\n<variablelist>\n");
1133           docstat |= D_DL;
1134           break;
1135         }
1136       case SP_DT:
1137         {
1138           add_s ("<varlistentry><term>");
1139           break;
1140         }
1141       case SP_DD:
1142         {
1143           add_s ("</term>\n<listitem><para>\n");
1144           docstat |= D_DD;
1145           break;
1146         }
1147       case SP_END_DL:
1148         {
1149           add_s ("</para></listitem></varlistentry></variablelist>\n");
1150           docstat &= ~(D_DL|D_DD);
1151           break;
1152         }
1153       case SP_END_PAR:
1154         {
1155           add_s ("</para>\n");
1156           docstat &= ~D_PA;
1157           break;
1158         }
1159       case SP_END_DD:
1160         {
1161           add_s ("</para></listitem></varlistentry>\n");
1162           docstat &= ~D_DD;
1163           break;
1164         }
1165       case SP_END_SECT:
1166         {
1167           add_s ("</sect1>\n");
1168           break;
1169         }
1170       case SP_STR:
1171         {
1172           if (docstat & D_TAB)
1173             add_s (str);
1174           else
1175             sgml_fputs (str);
1176           break;
1177         }
1178       }
1179       break;
1180     }
1181     /* make gcc happy (unreached) */
1182   default:
1183     break;
1184   }
1185
1186   return docstat;
1187 }
1188
1189 void print_ref (int output_dollar, const char *ref)
1190 {
1191   switch (OutputFormat) {
1192   case F_CONF:
1193   case F_MAN:
1194     if (output_dollar)
1195       add_c ('$');
1196     add_s (ref);
1197     break;
1198
1199   case F_SGML:
1200     add_s ("<link linkend=\"");
1201     sgml_id_fputs (ref);
1202     add_s ("\">\n");
1203     if (output_dollar)
1204       add_s ("&dollar;");
1205     sgml_fputs (ref);
1206     add_s ("</link>");
1207     break;
1208
1209   default:
1210     break;
1211   }
1212 }
1213
1214 static int commit_buff (char *buff, char **d, int docstat)
1215 {
1216   if (*d > buff) {
1217     **d = '\0';
1218     docstat = print_it (SP_STR, buff, docstat);
1219     *d = buff;
1220   }
1221
1222   return docstat;
1223 }
1224
1225 static int handle_docline (char *l, int docstat)
1226 {
1227   char buff[BUFFSIZE];
1228   char *s, *d;
1229
1230   l = skip_ws (l);
1231
1232   if (Debug)
1233     fprintf (stderr, "%s: handle_docline `%s'\n", Progname, l);
1234
1235   if (!strncmp (l, ".pp", 3))
1236     return print_it (SP_NEWPAR, NULL, docstat);
1237   else if (!strncmp (l, ".ts", 3))
1238     return print_it (SP_START_TAB, NULL, docstat);
1239   else if (!strncmp (l, ".te", 3))
1240     return print_it (SP_END_TAB, NULL, docstat);
1241   else if (!strncmp (l, ".dl", 3))
1242     return print_it (SP_START_DL, NULL, docstat);
1243   else if (!strncmp (l, ".de", 3))
1244     return print_it (SP_END_DL, NULL, docstat);
1245   else if (!strncmp (l, ". ", 2))
1246     *l = ' ';
1247
1248   for (s = l, d = buff; *s; s++) {
1249     if (!strncmp (s, "\\(as", 4)) {
1250       *d++ = '*';
1251       s += 3;
1252     }
1253     else if (!strncmp (s, "\\(rs", 4)) {
1254       *d++ = '\\';
1255       s += 3;
1256     }
1257     else if (!strncmp (s, "\\fI", 3)) {
1258       docstat = commit_buff (buff, &d, docstat);
1259       docstat = print_it (SP_START_EM, NULL, docstat);
1260       s += 2;
1261     }
1262     else if (!strncmp (s, "\\fB", 3)) {
1263       docstat = commit_buff (buff, &d, docstat);
1264       docstat = print_it (SP_START_BF, NULL, docstat);
1265       s += 2;
1266     }
1267     else if (!strncmp (s, "\\fT", 3)) {
1268       docstat = commit_buff (buff, &d, docstat);
1269       docstat = print_it (SP_START_TT, NULL, docstat);
1270       s += 2;
1271     }
1272     else if (!strncmp (s, "\\fP", 3)) {
1273       docstat = commit_buff (buff, &d, docstat);
1274       docstat = print_it (SP_END_FT, NULL, docstat);
1275       s += 2;
1276     }
1277     else if (!strncmp (s, ".dt", 3)) {
1278       if (docstat & D_DD) {
1279         docstat = commit_buff (buff, &d, docstat);
1280         docstat = print_it (SP_END_DD, NULL, docstat);
1281       }
1282       docstat = commit_buff (buff, &d, docstat);
1283       docstat = print_it (SP_DT, NULL, docstat);
1284       s += 3;
1285     }
1286     else if (!strncmp (s, ".dd", 3)) {
1287       docstat = commit_buff (buff, &d, docstat);
1288       docstat = print_it (SP_DD, NULL, docstat);
1289       s += 3;
1290     }
1291     else if (*s == '$') {
1292       int output_dollar = 0;
1293       char *ref;
1294       char save;
1295
1296       ++s;
1297       if (*s == '$') {
1298         output_dollar = 1;
1299         ++s;
1300       }
1301       if (*s == '$') {
1302         *d++ = '$';
1303       }
1304       else {
1305         ref = s;
1306         while (isalnum ((unsigned char) *s) || *s == '-' || *s == '_')
1307           ++s;
1308
1309         docstat = commit_buff (buff, &d, docstat);
1310         save = *s;
1311         *s = 0;
1312         print_ref (output_dollar, ref);
1313         *s = save;
1314         --s;
1315       }
1316     }
1317     else
1318       *d++ = *s;
1319   }
1320
1321   docstat = commit_buff (buff, &d, docstat);
1322   return print_it (SP_NEWLINE, NULL, docstat);
1323 }