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