Remove the svn:externals property that pulls a copy of libcharset
[sgt/halibut] / bk_man.c
index 6774c2e..21e33f3 100644 (file)
--- a/bk_man.c
+++ b/bk_man.c
@@ -7,22 +7,24 @@
 #include <assert.h>
 #include "halibut.h"
 
-static void man_text(FILE *, word *,
-                    int newline, int quote_props, int charset);
-static void man_codepara(FILE *, word *, int charset);
-static int man_convert(wchar_t const *s, int maxlen,
-                      char **result, int quote_props,
-                      int charset, charset_state *state);
-
 typedef struct {
     wchar_t *th;
     int headnumbers;
     int mindepth;
     char *filename;
     int charset;
+    wchar_t *bullet, *lquote, *rquote;
 } manconfig;
 
+static void man_text(FILE *, word *,
+                    int newline, int quote_props, manconfig *conf);
+static void man_codepara(FILE *, word *, int charset);
+static int man_convert(wchar_t const *s, int maxlen,
+                      char **result, int quote_props,
+                      int charset, charset_state *state);
+
 static manconfig man_configure(paragraph *source) {
+    paragraph *p;
     manconfig ret;
 
     /*
@@ -33,34 +35,73 @@ static manconfig man_configure(paragraph *source) {
     ret.mindepth = 0;
     ret.filename = dupstr("output.1");
     ret.charset = CS_ASCII;
+    ret.bullet = L"\x2022\0o\0\0";
+    ret.lquote = L"\x2018\0\x2019\0\"\0\"\0\0";
+    ret.rquote = uadv(ret.lquote);
+
+    /*
+     * Two-pass configuration so that we can pick up global config
+     * (e.g. `quotes') before having it overridden by specific
+     * config (`man-quotes'), irrespective of the order in which
+     * they occur.
+     */
+    for (p = source; p; p = p->next) {
+       if (p->type == para_Config) {
+           if (!ustricmp(p->keyword, L"quotes")) {
+               if (*uadv(p->keyword) && *uadv(uadv(p->keyword))) {
+                   ret.lquote = uadv(p->keyword);
+                   ret.rquote = uadv(ret.lquote);
+               }
+           }
+       }
+    }
 
-    for (; source; source = source->next) {
-       if (source->type == para_Config) {
-           if (!ustricmp(source->keyword, L"man-identity")) {
+    for (p = source; p; p = p->next) {
+       if (p->type == para_Config) {
+           if (!ustricmp(p->keyword, L"man-identity")) {
                wchar_t *wp, *ep;
 
-               wp = uadv(source->keyword);
+               wp = uadv(p->keyword);
                ep = wp;
                while (*ep)
                    ep = uadv(ep);
                sfree(ret.th);
-               ret.th = mknewa(wchar_t, ep - wp + 1);
+               ret.th = snewn(ep - wp + 1, wchar_t);
                memcpy(ret.th, wp, (ep - wp + 1) * sizeof(wchar_t));
-           } else if (!ustricmp(source->keyword, L"man-charset")) {
-               char *csname = utoa_dup(uadv(source->keyword), CS_ASCII);
-               ret.charset = charset_from_localenc(csname);
-               sfree(csname);
-           } else if (!ustricmp(source->keyword, L"man-headnumbers")) {
-               ret.headnumbers = utob(uadv(source->keyword));
-           } else if (!ustricmp(source->keyword, L"man-mindepth")) {
-               ret.mindepth = utoi(uadv(source->keyword));
-           } else if (!ustricmp(source->keyword, L"man-filename")) {
+           } else if (!ustricmp(p->keyword, L"man-charset")) {
+               ret.charset = charset_from_ustr(&p->fpos, uadv(p->keyword));
+           } else if (!ustricmp(p->keyword, L"man-headnumbers")) {
+               ret.headnumbers = utob(uadv(p->keyword));
+           } else if (!ustricmp(p->keyword, L"man-mindepth")) {
+               ret.mindepth = utoi(uadv(p->keyword));
+           } else if (!ustricmp(p->keyword, L"man-filename")) {
                sfree(ret.filename);
-               ret.filename = dupstr(adv(source->origkeyword));
+               ret.filename = dupstr(adv(p->origkeyword));
+           } else if (!ustricmp(p->keyword, L"man-bullet")) {
+               ret.bullet = uadv(p->keyword);
+           } else if (!ustricmp(p->keyword, L"man-quotes")) {
+               if (*uadv(p->keyword) && *uadv(uadv(p->keyword))) {
+                   ret.lquote = uadv(p->keyword);
+                   ret.rquote = uadv(ret.lquote);
+               }
            }
        }
     }
 
+    /*
+     * Now process fallbacks on quote characters and bullets.
+     */
+    while (*uadv(ret.rquote) && *uadv(uadv(ret.rquote)) &&
+          (!cvt_ok(ret.charset, ret.lquote) ||
+           !cvt_ok(ret.charset, ret.rquote))) {
+       ret.lquote = uadv(ret.rquote);
+       ret.rquote = uadv(ret.lquote);
+    }
+
+    while (*ret.bullet && *uadv(ret.bullet) &&
+          !cvt_ok(ret.charset, ret.bullet))
+       ret.bullet = uadv(ret.bullet);
+
     return ret;
 }
 
@@ -83,6 +124,7 @@ void man_backend(paragraph *sourceform, keywordlist *keywords,
     paragraph *p;
     FILE *fp;
     manconfig conf;
+    int had_described_thing;
 
     IGNORE(unused);
     IGNORE(keywords);
@@ -103,7 +145,7 @@ void man_backend(paragraph *sourceform, keywordlist *keywords,
     for (p = sourceform; p; p = p->next)
        if (p->type == para_VersionID) {
            fprintf(fp, ".\\\" ");
-           man_text(fp, p->words, TRUE, 0, conf.charset);
+           man_text(fp, p->words, TRUE, 0, &conf);
        }
 
     /* .TH name-of-program manual-section */
@@ -124,6 +166,13 @@ void man_backend(paragraph *sourceform, keywordlist *keywords,
 
     fprintf(fp, ".UC\n");
 
+    had_described_thing = FALSE;
+#define cleanup_described_thing do { \
+    if (had_described_thing) \
+       fprintf(fp, "\n"); \
+    had_described_thing = FALSE; \
+} while (0)
+
     for (p = sourceform; p; p = p->next) switch (p->type) {
        /*
         * Things we ignore because we've already processed them or
@@ -146,6 +195,7 @@ void man_backend(paragraph *sourceform, keywordlist *keywords,
       case para_Heading:
       case para_Subsect:
 
+       cleanup_described_thing;
        {
            int depth;
            if (p->type == para_Subsect)
@@ -155,12 +205,15 @@ void man_backend(paragraph *sourceform, keywordlist *keywords,
            else
                depth = 0;
            if (depth >= conf.mindepth) {
-               fprintf(fp, ".SH \"");
+               if (depth > conf.mindepth)
+                   fprintf(fp, ".SS \"");
+               else
+                   fprintf(fp, ".SH \"");
                if (conf.headnumbers && p->kwtext) {
-                   man_text(fp, p->kwtext, FALSE, QUOTE_QUOTES, conf.charset);
+                   man_text(fp, p->kwtext, FALSE, QUOTE_QUOTES, &conf);
                    fprintf(fp, " ");
                }
-               man_text(fp, p->words, FALSE, QUOTE_QUOTES, conf.charset);
+               man_text(fp, p->words, FALSE, QUOTE_QUOTES, &conf);
                fprintf(fp, "\"\n");
            }
            break;
@@ -170,6 +223,7 @@ void man_backend(paragraph *sourceform, keywordlist *keywords,
         * Code paragraphs.
         */
       case para_Code:
+       cleanup_described_thing;
        fprintf(fp, ".PP\n");
        man_codepara(fp, p->words, conf.charset);
        break;
@@ -179,8 +233,9 @@ void man_backend(paragraph *sourceform, keywordlist *keywords,
         */
       case para_Normal:
       case para_Copyright:
+       cleanup_described_thing;
        fprintf(fp, ".PP\n");
-       man_text(fp, p->words, TRUE, 0, conf.charset);
+       man_text(fp, p->words, TRUE, 0, &conf);
        break;
 
        /*
@@ -190,29 +245,48 @@ void man_backend(paragraph *sourceform, keywordlist *keywords,
       case para_BiblioCited:
       case para_Bullet:
       case para_NumberedList:
+       if (p->type != para_Description)
+           cleanup_described_thing;
+
        if (p->type == para_Bullet) {
-           fprintf(fp, ".IP \"\\fBo\\fP\"\n");   /* FIXME: configurable? */
+           char *bullettext;
+           man_convert(conf.bullet, -1, &bullettext, QUOTE_QUOTES,
+                       conf.charset, NULL);
+           fprintf(fp, ".IP \"\\fB%s\\fP\"\n", bullettext);
+           sfree(bullettext);
        } else if (p->type == para_NumberedList) {
            fprintf(fp, ".IP \"");
-           man_text(fp, p->kwtext, FALSE, QUOTE_QUOTES, conf.charset);
+           man_text(fp, p->kwtext, FALSE, QUOTE_QUOTES, &conf);
            fprintf(fp, "\"\n");
        } else if (p->type == para_Description) {
-           /*
-            * Do nothing; the .xP for this paragraph is the .IP
-            * which has come before it in the DescribedThing.
-            */
+           if (had_described_thing) {
+               /*
+                * Do nothing; the .xP for this paragraph is the
+                * .IP which has come before it in the
+                * DescribedThing.
+                */
+           } else {
+               /*
+                * A \dd without a preceding \dt is given a blank
+                * one.
+                */
+               fprintf(fp, ".IP \"\"\n");
+           }
        } else if (p->type == para_BiblioCited) {
            fprintf(fp, ".IP \"");
-           man_text(fp, p->kwtext, FALSE, QUOTE_QUOTES, conf.charset);
+           man_text(fp, p->kwtext, FALSE, QUOTE_QUOTES, &conf);
            fprintf(fp, "\"\n");
        }
-       man_text(fp, p->words, TRUE, 0, conf.charset);
+       man_text(fp, p->words, TRUE, 0, &conf);
+       had_described_thing = FALSE;
        break;
 
       case para_DescribedThing:
+       cleanup_described_thing;
        fprintf(fp, ".IP \"");
-       man_text(fp, p->words, FALSE, QUOTE_QUOTES, conf.charset);
+       man_text(fp, p->words, FALSE, QUOTE_QUOTES, &conf);
        fprintf(fp, "\"\n");
+       had_described_thing = TRUE;
        break;
 
       case para_Rule:
@@ -220,18 +294,22 @@ void man_backend(paragraph *sourceform, keywordlist *keywords,
         * This isn't terribly good. Anyone who wants to do better
         * should feel free!
         */
+       cleanup_described_thing;
        fprintf(fp, ".PP\n----------------------------------------\n");
        break;
 
       case para_LcontPush:
       case para_QuotePush:
+       cleanup_described_thing;
        fprintf(fp, ".RS\n");
        break;
       case para_LcontPop:
       case para_QuotePop:
+       cleanup_described_thing;
        fprintf(fp, ".RE\n");
        break;
     }
+    cleanup_described_thing;
 
     /*
      * Tidy up.
@@ -274,7 +352,7 @@ static int man_convert(wchar_t const *s, int maxlen,
 
     psize = 384;
     plen = 0;
-    p = mknewa(char, psize);
+    p = snewn(psize, char);
     err = 0;
 
     while (slen > 0) {
@@ -284,7 +362,7 @@ static int man_convert(wchar_t const *s, int maxlen,
                    plen += ret;
            if (psize - plen < 256) {
                psize = plen + 256;
-               p = resize(p, psize);
+               p = sresize(p, psize, char);
            }
        }
     }
@@ -332,7 +410,8 @@ static int man_convert(wchar_t const *s, int maxlen,
 }
 
 static void man_rdaddwc(rdstringc *rs, word *text, word *end,
-                       int quote_props, int charset, charset_state *state) {
+                       int quote_props, manconfig *conf,
+                       charset_state *state) {
     char *c;
 
     for (; text && text != end; text = text->next) switch (text->type) {
@@ -364,7 +443,7 @@ static void man_rdaddwc(rdstringc *rs, word *text, word *end,
             attraux(text->aux) == attr_Only)) {
            if (rs->pos > 0)
                quote_props &= ~QUOTE_INITCTRL;   /* not at start any more */
-           man_convert(NULL, 0, &c, quote_props, charset, state);
+           man_convert(NULL, 0, &c, quote_props, conf->charset, state);
            rdaddsc(rs, c);
            sfree(c);
            *state = charset_init_state;
@@ -375,7 +454,7 @@ static void man_rdaddwc(rdstringc *rs, word *text, word *end,
                    attraux(text->aux) == attr_Only)) {
            if (rs->pos > 0)
                quote_props &= ~QUOTE_INITCTRL;   /* not at start any more */
-           man_convert(NULL, 0, &c, quote_props, charset, state);
+           man_convert(NULL, 0, &c, quote_props, conf->charset, state);
            rdaddsc(rs, c);
            sfree(c);
            *state = charset_init_state;
@@ -387,24 +466,26 @@ static void man_rdaddwc(rdstringc *rs, word *text, word *end,
 
            if (rs->pos > 0)
                quote_props &= ~QUOTE_INITCTRL;   /* not at start any more */
-           if (man_convert(text->text, 0, &c, quote_props, charset, &s2) ||
+           if (man_convert(text->text, 0, &c, quote_props, conf->charset, &s2) ||
                !text->alt) {
                rdaddsc(rs, c);
                *state = s2;
            } else {
-               man_rdaddwc(rs, text->alt, NULL, quote_props, charset, state);
+               man_rdaddwc(rs, text->alt, NULL, quote_props, conf, state);
            }
            sfree(c);
        } else if (removeattr(text->type) == word_WhiteSpace) {
            if (rs->pos > 0)
                quote_props &= ~QUOTE_INITCTRL;   /* not at start any more */
-           man_convert(L" ", 1, &c, quote_props, charset, state);
+           man_convert(L" ", 1, &c, quote_props, conf->charset, state);
            rdaddsc(rs, c);
            sfree(c);
        } else if (removeattr(text->type) == word_Quote) {
            if (rs->pos > 0)
                quote_props &= ~QUOTE_INITCTRL;   /* not at start any more */
-           man_convert(L"\"", 1, &c, quote_props, charset, state);
+           man_convert(quoteaux(text->aux) == quote_Open ?
+                       conf->lquote : conf->rquote, 0,
+                       &c, quote_props, conf->charset, state);
            rdaddsc(rs, c);
            sfree(c);
        }
@@ -413,7 +494,7 @@ static void man_rdaddwc(rdstringc *rs, word *text, word *end,
             attraux(text->aux) == attr_Only)) {
            if (rs->pos > 0)
                quote_props &= ~QUOTE_INITCTRL;   /* not at start any more */
-           man_convert(NULL, 0, &c, quote_props, charset, state);
+           man_convert(NULL, 0, &c, quote_props, conf->charset, state);
            rdaddsc(rs, c);
            sfree(c);
            *state = charset_init_state;
@@ -421,17 +502,17 @@ static void man_rdaddwc(rdstringc *rs, word *text, word *end,
        }
        break;
     }
-    man_convert(NULL, 0, &c, quote_props, charset, state);
+    man_convert(NULL, 0, &c, quote_props, conf->charset, state);
     rdaddsc(rs, c);
     sfree(c);
 }
 
 static void man_text(FILE *fp, word *text, int newline,
-                    int quote_props, int charset) {
+                    int quote_props, manconfig *conf) {
     rdstringc t = { 0, 0, NULL };
     charset_state state = CHARSET_INIT_STATE;
 
-    man_rdaddwc(&t, text, NULL, quote_props | QUOTE_INITCTRL, charset, &state);
+    man_rdaddwc(&t, text, NULL, quote_props | QUOTE_INITCTRL, conf, &state);
     fprintf(fp, "%s", t.text);
     sfree(t.text);
     if (newline)