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