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