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