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