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