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