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