Added an info(1) backend, which constructs .info files directly
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Fri, 9 Apr 2004 18:16:43 +0000 (18:16 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Fri, 9 Apr 2004 18:16:43 +0000 (18:16 +0000)
without going through the .texi source stage. A few things left to
do, notably documentation, but the basics all seem to be there.

git-svn-id: svn://svn.tartarus.org/sgt/halibut@4047 cda61777-01e9-0310-a592-d414129be87e

Makefile
bk_info.c [new file with mode: 0644]
bk_xhtml.c
doc/.cvsignore
doc/Makefile
halibut.h
main.c
misc.c
ustring.c

index 3264a70..3802c58 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -100,7 +100,7 @@ SRC := ../
 
 MODULES := main malloc ustring error help licence version misc tree234
 MODULES += input keywords contents index style biblio
-MODULES += bk_text bk_xhtml bk_whlp bk_man
+MODULES += bk_text bk_xhtml bk_whlp bk_man bk_info
 MODULES += winhelp
 
 OBJECTS := $(addsuffix .o,$(MODULES))
diff --git a/bk_info.c b/bk_info.c
new file mode 100644 (file)
index 0000000..088c72d
--- /dev/null
+++ b/bk_info.c
@@ -0,0 +1,965 @@
+/*
+ * info backend for Halibut
+ * 
+ * TODO:
+ * 
+ *  - basic vital configuration: Info dir entries in heading, and
+ *    how to allocate node names
+ *  - escape, warn or simply remove commas and colons in node
+ *    names; also test colons in index terms.
+ *  - test everything in info(1), and probably jed too
+ * 
+ * Later:
+ * 
+ *  - configurable indentation, bullets, emphasis, quotes etc?
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include "halibut.h"
+
+typedef struct {
+    char *filename;
+    int maxfilesize;
+} infoconfig;
+
+typedef struct node_tag node;
+struct node_tag {
+    node *listnext;
+    node *up, *prev, *next, *lastchild;
+    int pos, started_menu, filenum;
+    char *name;
+    rdstringc text;
+};
+
+typedef struct {
+    char *text;
+    int nnodes, nodesize;
+    node **nodes;
+} info_idx;
+
+static int info_convert(wchar_t *, char **);
+
+static void info_heading(rdstringc *, word *, word *, int);
+static void info_rule(rdstringc *, int, int);
+static void info_para(rdstringc *, word *, char *, word *, keywordlist *,
+                     int, int, int);
+static void info_codepara(rdstringc *, word *, int, int);
+static void info_versionid(rdstringc *, word *);
+static void info_menu_item(rdstringc *, node *, paragraph *);
+static word *info_transform_wordlist(word *, keywordlist *);
+static int info_check_index(word *, node *, indexdata *);
+
+static void info_rdaddwc(rdstringc *, word *, word *, int);
+
+static node *info_node_new(char *name);
+static char *info_node_name(paragraph *p);
+
+static infoconfig info_configure(paragraph *source) {
+    infoconfig ret;
+
+    /*
+     * Defaults.
+     */
+    ret.filename = dupstr("output.info");
+    ret.maxfilesize = 64 << 10;
+
+    for (; source; source = source->next) {
+       if (source->type == para_Config) {
+           if (!ustricmp(source->keyword, L"info-filename")) {
+               sfree(ret.filename);
+               ret.filename = utoa_dup(uadv(source->keyword));
+           } else if (!ustricmp(source->keyword, L"info-max-file-size")) {
+               ret.maxfilesize = utoi(uadv(source->keyword));
+           }
+       }
+    }
+
+    return ret;
+}
+
+paragraph *info_config_filename(char *filename)
+{
+    paragraph *p;
+    wchar_t *ufilename, *up;
+    int len;
+
+    p = mknew(paragraph);
+    memset(p, 0, sizeof(*p));
+    p->type = para_Config;
+    p->next = NULL;
+    p->fpos.filename = "<command line>";
+    p->fpos.line = p->fpos.col = -1;
+
+    ufilename = ufroma_dup(filename);
+    len = ustrlen(ufilename) + 2 + lenof(L"info-filename");
+    p->keyword = mknewa(wchar_t, len);
+    up = p->keyword;
+    ustrcpy(up, L"info-filename");
+    up = uadv(up);
+    ustrcpy(up, ufilename);
+    up = uadv(up);
+    *up = L'\0';
+    assert(up - p->keyword < len);
+    sfree(ufilename);
+
+    return p;
+}
+
+void info_backend(paragraph *sourceform, keywordlist *keywords,
+                 indexdata *idx) {
+    paragraph *p;
+    infoconfig conf;
+    word *prefix, *body, *wp;
+    word spaceword;
+    char *prefixextra;
+    int nesting, nestindent;
+    int indentb, indenta;
+    int filepos;
+    int has_index;
+    rdstringc intro_text = { 0, 0, NULL };
+    node *topnode, *currnode;
+    word bullet;
+    FILE *fp;
+
+    /*
+     * FIXME
+     */
+    int width = 70, listindentbefore = 1, listindentafter = 3;
+    int indent_code = 2, index_width = 40;
+
+    IGNORE(keywords);                 /* we don't happen to need this */
+    IGNORE(idx);                      /* or this */
+
+    conf = info_configure(sourceform);
+
+    /*
+     * Go through and create a node for each section.
+     */
+    topnode = info_node_new("Top");
+    currnode = topnode;
+    for (p = sourceform; p; p = p->next) switch (p->type) {
+       /*
+        * Chapter titles.
+        */
+      case para_Chapter:
+      case para_Appendix:
+      case para_UnnumberedChapter:
+      case para_Heading:
+      case para_Subsect:
+       {
+           node *newnode, *upnode;
+           char *nodename;
+
+           nodename = info_node_name(p);
+           newnode = info_node_new(nodename);
+           sfree(nodename);
+
+           p->private_data = newnode;
+
+           if (p->parent)
+               upnode = (node *)p->parent->private_data;
+           else
+               upnode = topnode;
+           assert(upnode);
+           newnode->up = upnode;
+
+           currnode->next = newnode;
+           newnode->prev = currnode;
+
+           currnode->listnext = newnode;
+           currnode = newnode;
+       }
+       break;
+    }
+
+    /*
+     * Set up the display form of each index entry.
+     */
+    {
+       int i;
+       indexentry *entry;
+
+       for (i = 0; (entry = index234(idx->entries, i)) != NULL; i++) {
+           info_idx *ii = mknew(info_idx);
+           rdstringc rs = { 0, 0, NULL };
+
+           ii->nnodes = ii->nodesize = 0;
+           ii->nodes = NULL;
+
+           info_rdaddwc(&rs, entry->text, NULL, FALSE);
+           /*
+            * FIXME: splatter colons.
+            */
+           ii->text = rs.text;
+
+           entry->backend_data = ii;
+       }
+    }
+
+    /*
+     * An Info file begins with a piece of introductory text which
+     * is apparently never shown anywhere. This seems to me to be a
+     * good place to put the copyright notice and the version IDs.
+     * 
+     * FIXME: also Info directory entries are expected to go here.
+     * This will need to be a configurable thing of some sort.
+     */
+
+    rdaddsc(&intro_text,
+           "This Info file generated by Halibut, ");
+    rdaddsc(&intro_text, version);
+    rdaddsc(&intro_text, "\n\n");
+
+    for (p = sourceform; p; p = p->next)
+       if (p->type == para_Copyright)
+           info_para(&intro_text, NULL, NULL, p->words, keywords,
+                     0, 0, width);
+
+    for (p = sourceform; p; p = p->next)
+       if (p->type == para_VersionID)
+           info_versionid(&intro_text, p->words);
+
+    if (intro_text.text[intro_text.pos-1] != '\n')
+       rdaddc(&intro_text, '\n');
+
+    /* Do the title */
+    for (p = sourceform; p; p = p->next)
+       if (p->type == para_Title)
+           info_heading(&topnode->text, NULL, p->words, width);
+
+    nestindent = listindentbefore + listindentafter;
+    nesting = 0;
+
+    currnode = topnode;
+
+    /* Do the main document */
+    for (p = sourceform; p; p = p->next) switch (p->type) {
+
+      case para_QuotePush:
+       nesting += 2;
+       break;
+      case para_QuotePop:
+       nesting -= 2;
+       assert(nesting >= 0);
+       break;
+
+      case para_LcontPush:
+       nesting += nestindent;
+       break;
+      case para_LcontPop:
+       nesting -= nestindent;
+       assert(nesting >= 0);
+       break;
+
+       /*
+        * Things we ignore because we've already processed them or
+        * aren't going to touch them in this pass.
+        */
+      case para_IM:
+      case para_BR:
+      case para_Biblio:                       /* only touch BiblioCited */
+      case para_VersionID:
+      case para_NoCite:
+      case para_Title:
+       break;
+
+       /*
+        * Chapter titles.
+        */
+      case para_Chapter:
+      case para_Appendix:
+      case para_UnnumberedChapter:
+      case para_Heading:
+      case para_Subsect:
+       currnode = p->private_data;
+       assert(currnode);
+       assert(currnode->up);
+
+       if (!currnode->up->started_menu) {
+           rdaddsc(&currnode->up->text, "* Menu:\n\n");
+           currnode->up->started_menu = TRUE;
+       }
+       info_menu_item(&currnode->up->text, currnode, p);
+
+       has_index |= info_check_index(p->words, currnode, idx);
+       info_heading(&currnode->text, p->kwtext, p->words, width);
+       nesting = 0;
+       break;
+
+      case para_Rule:
+       info_rule(&currnode->text, nesting, width - nesting);
+       break;
+
+      case para_Normal:
+      case para_Copyright:
+      case para_DescribedThing:
+      case para_Description:
+      case para_BiblioCited:
+      case para_Bullet:
+      case para_NumberedList:
+       has_index |= info_check_index(p->words, currnode, idx);
+       if (p->type == para_Bullet) {
+           bullet.next = NULL;
+           bullet.alt = NULL;
+           bullet.type = word_Normal;
+           bullet.text = L"-";        /* FIXME: configurability */
+           prefix = &bullet;
+           prefixextra = NULL;
+           indentb = listindentbefore;
+           indenta = listindentafter;
+       } else if (p->type == para_NumberedList) {
+           prefix = p->kwtext;
+           prefixextra = ".";         /* FIXME: configurability */
+           indentb = listindentbefore;
+           indenta = listindentafter;
+       } else if (p->type == para_Description) {
+           prefix = NULL;
+           prefixextra = NULL;
+           indentb = listindentbefore;
+           indenta = listindentafter;
+       } else {
+           prefix = NULL;
+           prefixextra = NULL;
+           indentb = indenta = 0;
+       }
+       if (p->type == para_BiblioCited) {
+           body = dup_word_list(p->kwtext);
+           for (wp = body; wp->next; wp = wp->next);
+           wp->next = &spaceword;
+           spaceword.next = p->words;
+           spaceword.alt = NULL;
+           spaceword.type = word_WhiteSpace;
+           spaceword.text = NULL;
+       } else {
+           wp = NULL;
+           body = p->words;
+       }
+       info_para(&currnode->text, prefix, prefixextra, body, keywords,
+                 nesting + indentb, indenta,
+                 width - nesting - indentb - indenta);
+       if (wp) {
+           wp->next = NULL;
+           free_word_list(body);
+       }
+       break;
+
+      case para_Code:
+       info_codepara(&currnode->text, p->words,
+                     nesting + indent_code,
+                     width - nesting - 2 * indent_code);
+       break;
+    }
+
+    /*
+     * Create an index node if required.
+     */
+    if (has_index) {
+       node *newnode;
+       int i, j, k;
+       indexentry *entry;
+
+       newnode = info_node_new("Index");
+       newnode->up = topnode;
+
+       currnode->next = newnode;
+       newnode->prev = currnode;
+       currnode->listnext = newnode;
+
+       rdaddsc(&newnode->text, "Index\n-----\n\n* Menu:\n\n");
+
+       info_menu_item(&topnode->text, newnode, NULL);
+
+       for (i = 0; (entry = index234(idx->entries, i)) != NULL; i++) {
+           info_idx *ii = (info_idx *)entry->backend_data;
+
+           for (j = 0; j < ii->nnodes; j++) {
+               int pos0 = newnode->text.pos;
+               rdaddsc(&newnode->text, "* ");
+               /*
+                * When we have multiple references for a single
+                * index term, we only display the actual term on
+                * the first line, to make it clear that the terms
+                * really are the same.
+                */
+               if (j == 0)
+                   rdaddsc(&newnode->text, ii->text);
+               for (k = newnode->text.pos - pos0; k < index_width; k++)
+                   rdaddc(&newnode->text, ' ');
+               rdaddsc(&newnode->text, ": ");
+               rdaddsc(&newnode->text, ii->nodes[j]->name);
+               rdaddsc(&newnode->text, ".\n");
+           }
+       }
+    }
+
+    /*
+     * Finalise the text of each node, by adding the ^_ delimiter
+     * and the node line at the top.
+     */
+    for (currnode = topnode; currnode; currnode = currnode->listnext) {
+       char *origtext = currnode->text.text;
+       currnode->text.text = NULL;
+       currnode->text.pos = currnode->text.size = 0;
+       rdaddsc(&currnode->text, "\037\nFile: ");
+       rdaddsc(&currnode->text, conf.filename);
+       rdaddsc(&currnode->text, ",  Node: ");
+       rdaddsc(&currnode->text, currnode->name);
+       if (currnode->prev) {
+           rdaddsc(&currnode->text, ",  Prev: ");
+           rdaddsc(&currnode->text, currnode->prev->name);
+       }
+       rdaddsc(&currnode->text, ",  Up: ");
+       rdaddsc(&currnode->text, (currnode->up ?
+                                 currnode->up->name : "(dir)"));
+       if (currnode->next) {
+           rdaddsc(&currnode->text, ",  Next: ");
+           rdaddsc(&currnode->text, currnode->next->name);
+       }
+       rdaddsc(&currnode->text, "\n\n");
+       rdaddsc(&currnode->text, origtext);
+       /*
+        * Just make _absolutely_ sure we end with a newline.
+        */
+       if (currnode->text.text[currnode->text.pos-1] != '\n')
+           rdaddc(&currnode->text, '\n');
+
+       sfree(origtext);
+    }    
+
+    /*
+     * Compute the offsets for the tag table.
+     */
+    filepos = intro_text.pos;
+    for (currnode = topnode; currnode; currnode = currnode->listnext) {
+       currnode->pos = filepos;
+       filepos += currnode->text.pos;
+    }
+
+    /*
+     * Split into sub-files.
+     */
+    if (conf.maxfilesize > 0) {
+       int currfilesize = intro_text.pos, currfilenum = 1;
+       for (currnode = topnode; currnode; currnode = currnode->listnext) {
+           if (currfilesize > intro_text.pos &&
+               currfilesize + currnode->text.pos > conf.maxfilesize) {
+               currfilenum++;
+               currfilesize = intro_text.pos;
+           }
+           currnode->filenum = currfilenum;
+           currfilesize += currnode->text.pos;
+       }
+    }
+
+    /*
+     * Write the primary output file.
+     */
+    fp = fopen(conf.filename, "w");
+    if (!fp) {
+       error(err_cantopenw, conf.filename);
+       return;
+    }
+    fputs(intro_text.text, fp);
+    if (conf.maxfilesize == 0) {
+       for (currnode = topnode; currnode; currnode = currnode->listnext)
+           fputs(currnode->text.text, fp);
+    } else {
+       int filenum = 0;
+       fprintf(fp, "\037\nIndirect:\n");
+       for (currnode = topnode; currnode; currnode = currnode->listnext)
+           if (filenum != currnode->filenum) {
+               filenum = currnode->filenum;
+               fprintf(fp, "%s-%d: %d\n", conf.filename, filenum,
+                       currnode->pos);
+           }
+    }
+    fprintf(fp, "\037\nTag Table:\n");
+    if (conf.maxfilesize > 0)
+       fprintf(fp, "(Indirect)\n");
+    for (currnode = topnode; currnode; currnode = currnode->listnext)
+       fprintf(fp, "Node: %s\177%d\n", currnode->name, currnode->pos);
+    fprintf(fp, "\037\nEnd Tag Table\n");
+    fclose(fp);
+
+    /*
+     * Write the subfiles.
+     */
+    if (conf.maxfilesize > 0) {
+       int filenum = 0;
+       fp = NULL;
+
+       for (currnode = topnode; currnode; currnode = currnode->listnext) {
+           if (filenum != currnode->filenum) {
+               char *fname;
+
+               filenum = currnode->filenum;
+
+               if (fp)
+                   fclose(fp);
+               fname = mknewa(char, strlen(conf.filename) + 40);
+               sprintf(fname, "%s-%d", conf.filename, filenum);
+               fp = fopen(fname, "w");
+               if (!fp) {
+                   error(err_cantopenw, fname);
+                   return;
+               }
+               sfree(fname);
+               fputs(intro_text.text, fp);
+           }
+           fputs(currnode->text.text, fp);
+       }
+
+       if (fp)
+           fclose(fp);
+    }
+}
+
+static int info_check_index(word *w, node *n, indexdata *idx)
+{
+    int ret = 0;
+
+    for (; w; w = w->next) {
+       if (w->type == word_IndexRef) {
+           indextag *tag;
+           int i;
+
+           tag = index_findtag(idx, w->text);
+           if (!tag)
+               break;
+
+           for (i = 0; i < tag->nrefs; i++) {
+               indexentry *entry = tag->refs[i];
+               info_idx *ii = (info_idx *)entry->backend_data;
+
+               if (ii->nnodes > 0 && ii->nodes[ii->nnodes-1] == n) {
+                   /*
+                    * If the same index term is indexed twice
+                    * within the same section, we only want to
+                    * mention it once in the index. So do nothing
+                    * here.
+                    */
+                   continue;
+               }
+
+               if (ii->nnodes >= ii->nodesize) {
+                   ii->nodesize += 32;
+                   ii->nodes = resize(ii->nodes, ii->nodesize);
+               }
+
+               ii->nodes[ii->nnodes++] = n;
+
+               ret = 1;
+           }
+       }
+    }
+
+    return ret;
+}
+
+/*
+ * Convert a wide string into a string of chars. If `result' is
+ * non-NULL, mallocs the resulting string and stores a pointer to
+ * it in `*result'. If `result' is NULL, merely checks whether all
+ * characters in the string are feasible for the output character
+ * set.
+ *
+ * Return is nonzero if all characters are OK. If not all
+ * characters are OK but `result' is non-NULL, a result _will_
+ * still be generated!
+ */
+static int info_convert(wchar_t *s, char **result) {
+    /*
+     * FIXME. Currently this is ISO8859-1 only.
+     */
+    int doing = (result != 0);
+    int ok = TRUE;
+    char *p = NULL;
+    int plen = 0, psize = 0;
+
+    for (; *s; s++) {
+       wchar_t c = *s;
+       char outc;
+
+       if ((c >= 32 && c <= 126) ||
+           (c >= 160 && c <= 255)) {
+           /* Char is OK. */
+           outc = (char)c;
+       } else {
+           /* Char is not OK. */
+           ok = FALSE;
+           outc = 0xBF;               /* approximate the good old DEC `uh?' */
+       }
+       if (doing) {
+           if (plen >= psize) {
+               psize = plen + 256;
+               p = resize(p, psize);
+           }
+           p[plen++] = outc;
+       }
+    }
+    if (doing) {
+       p = resize(p, plen+1);
+       p[plen] = '\0';
+       *result = p;
+    }
+    return ok;
+}
+
+static word *info_transform_wordlist(word *words, keywordlist *keywords)
+{
+    word *ret = dup_word_list(words);
+    word *w;
+    keyword *kwl;
+
+    for (w = ret; w; w = w->next) {
+       w->private_data = NULL;
+       if (w->type == word_UpperXref || w->type == word_LowerXref) {
+           kwl = kw_lookup(keywords, w->text);
+           if (kwl) {
+               if (kwl->para->type == para_NumberedList ||
+                   kwl->para->type == para_BiblioCited) {
+                   /*
+                    * In Info, we do nothing special for xrefs to
+                    * numbered list items or bibliography entries.
+                    */
+                    break;
+               } else {
+                   /*
+                    * An xref to a different section has its text
+                    * completely replaced.
+                    */
+                   word *w2, *w3, *w4;
+                   w2 = w3 = w->next;
+                   w4 = NULL;
+                   while (w2) {
+                       if (w2->type == word_XrefEnd) {
+                           w4 = w2->next;
+                           w2->next = NULL;
+                           break;
+                       }
+                       w2 = w2->next;
+                   }
+                   free_word_list(w3);
+
+                   /*
+                    * Now w is the UpperXref / LowerXref we
+                    * started with, and w4 is the next word after
+                    * the corresponding XrefEnd (if any). The
+                    * simplest thing is just to stick a pointer to
+                    * the target node structure in the private
+                    * data field of the xref word, and let
+                    * info_rdaddwc and friends read the node name
+                    * out from there.
+                    */
+                   w->next = w4;
+                   w->private_data = kwl->para->private_data;
+                   assert(w->private_data);
+               }
+           }
+       }
+    }
+
+    return ret;
+}
+
+static void info_rdaddwc(rdstringc *rs, word *words, word *end, int xrefs) {
+    char *c;
+
+    for (; words && words != end; words = words->next) switch (words->type) {
+      case word_HyperLink:
+      case word_HyperEnd:
+      case word_XrefEnd:
+      case word_IndexRef:
+       break;
+
+      case word_Normal:
+      case word_Emph:
+      case word_Code:
+      case word_WeakCode:
+      case word_WhiteSpace:
+      case word_EmphSpace:
+      case word_CodeSpace:
+      case word_WkCodeSpace:
+      case word_Quote:
+      case word_EmphQuote:
+      case word_CodeQuote:
+      case word_WkCodeQuote:
+       assert(words->type != word_CodeQuote &&
+              words->type != word_WkCodeQuote);
+       if (towordstyle(words->type) == word_Emph &&
+           (attraux(words->aux) == attr_First ||
+            attraux(words->aux) == attr_Only))
+           rdaddc(rs, '_');           /* FIXME: configurability */
+       else if (towordstyle(words->type) == word_Code &&
+                (attraux(words->aux) == attr_First ||
+                 attraux(words->aux) == attr_Only))
+           rdaddc(rs, '`');           /* FIXME: configurability */
+       if (removeattr(words->type) == word_Normal) {
+           if (info_convert(words->text, &c))
+               rdaddsc(rs, c);
+           else
+               info_rdaddwc(rs, words->alt, NULL, FALSE);
+           sfree(c);
+       } else if (removeattr(words->type) == word_WhiteSpace) {
+           rdaddc(rs, ' ');
+       } else if (removeattr(words->type) == word_Quote) {
+           rdaddc(rs, quoteaux(words->aux) == quote_Open ? '`' : '\'');
+                                      /* FIXME: configurability */
+       }
+       if (towordstyle(words->type) == word_Emph &&
+           (attraux(words->aux) == attr_Last ||
+            attraux(words->aux) == attr_Only))
+           rdaddc(rs, '_');           /* FIXME: configurability */
+       else if (towordstyle(words->type) == word_Code &&
+                (attraux(words->aux) == attr_Last ||
+                 attraux(words->aux) == attr_Only))
+           rdaddc(rs, '\'');          /* FIXME: configurability */
+       break;
+
+      case word_UpperXref:
+      case word_LowerXref:
+       if (xrefs && words->private_data) {
+           rdaddsc(rs, "*Note ");
+           rdaddsc(rs, ((node *)words->private_data)->name);
+           rdaddsc(rs, "::");
+       }
+       break;
+    }
+}
+
+static int info_width_internal(word *words, int xrefs);
+
+static int info_width_internal_list(word *words, int xrefs) {
+    int w = 0;
+    while (words) {
+       w += info_width_internal(words, xrefs);
+       words = words->next;
+    }
+    return w;
+}
+
+static int info_width_internal(word *words, int xrefs) {
+    switch (words->type) {
+      case word_HyperLink:
+      case word_HyperEnd:
+      case word_XrefEnd:
+      case word_IndexRef:
+       return 0;
+
+      case word_Normal:
+      case word_Emph:
+      case word_Code:
+      case word_WeakCode:
+       return (((words->type == word_Emph ||
+                 words->type == word_Code)
+                ? (attraux(words->aux) == attr_Only ? 2 :
+                   attraux(words->aux) == attr_Always ? 0 : 1)
+                : 0) +
+               (info_convert(words->text, NULL) ?
+                ustrlen(words->text) :
+                info_width_internal_list(words->alt, xrefs)));
+
+      case word_WhiteSpace:
+      case word_EmphSpace:
+      case word_CodeSpace:
+      case word_WkCodeSpace:
+      case word_Quote:
+      case word_EmphQuote:
+      case word_CodeQuote:
+      case word_WkCodeQuote:
+       assert(words->type != word_CodeQuote &&
+              words->type != word_WkCodeQuote);
+       return (((towordstyle(words->type) == word_Emph ||
+                 towordstyle(words->type) == word_Code)
+                ? (attraux(words->aux) == attr_Only ? 2 :
+                   attraux(words->aux) == attr_Always ? 0 : 1)
+                : 0) + 1);
+
+      case word_UpperXref:
+      case word_LowerXref:
+       if (xrefs && words->private_data) {
+           /* "*Note " plus "::" comes to 8 characters */
+           return 8 + strlen(((node *)words->private_data)->name);
+       }
+       break;
+    }
+    return 0;                         /* should never happen */
+}
+
+static int info_width_noxrefs(word *words)
+{
+    return info_width_internal(words, FALSE);
+}
+static int info_width_xrefs(word *words)
+{
+    return info_width_internal(words, TRUE);
+}
+
+static void info_heading(rdstringc *text, word *tprefix,
+                        word *words, int width) {
+    rdstringc t = { 0, 0, NULL };
+    int margin, length;
+    int firstlinewidth, wrapwidth;
+    int i;
+    wrappedline *wrapping, *p;
+
+    if (tprefix) {
+       info_rdaddwc(&t, tprefix, NULL, FALSE);
+       rdaddsc(&t, ": ");             /* FIXME: configurability */
+    }
+    margin = length = (t.text ? strlen(t.text) : 0);
+
+    margin = 0;
+    firstlinewidth = width - length;
+    wrapwidth = width;
+
+    wrapping = wrap_para(words, firstlinewidth, wrapwidth, info_width_noxrefs);
+    for (p = wrapping; p; p = p->next) {
+       info_rdaddwc(&t, p->begin, p->end, FALSE);
+       length = (t.text ? strlen(t.text) : 0);
+       for (i = 0; i < margin; i++)
+           rdaddc(text, ' ');
+       rdaddsc(text, t.text);
+       rdaddc(text, '\n');
+       for (i = 0; i < margin; i++)
+           rdaddc(text, ' ');
+       while (length--)
+           rdaddc(text, '-');
+       rdaddc(text, '\n');
+       margin = 0;
+       sfree(t.text);
+       t = empty_rdstringc;
+    }
+    wrap_free(wrapping);
+    rdaddc(text, '\n');
+
+    sfree(t.text);
+}
+
+static void info_rule(rdstringc *text, int indent, int width) {
+    while (indent--) rdaddc(text, ' ');
+    while (width--) rdaddc(text, '-');
+    rdaddc(text, '\n');
+    rdaddc(text, '\n');
+}
+
+static void info_para(rdstringc *text, word *prefix, char *prefixextra,
+                     word *input, keywordlist *keywords,
+                     int indent, int extraindent, int width) {
+    wrappedline *wrapping, *p;
+    word *words;
+    rdstringc pfx = { 0, 0, NULL };
+    int e;
+    int i;
+    int firstlinewidth = width;
+
+    words = info_transform_wordlist(input, keywords);
+
+    if (prefix) {
+       info_rdaddwc(&pfx, prefix, NULL, FALSE);
+       if (prefixextra)
+           rdaddsc(&pfx, prefixextra);
+       for (i = 0; i < indent; i++)
+           rdaddc(text, ' ');
+       rdaddsc(text, pfx.text);
+       /* If the prefix is too long, shorten the first line to fit. */
+       e = extraindent - strlen(pfx.text);
+       if (e < 0) {
+           firstlinewidth += e;       /* this decreases it, since e < 0 */
+           if (firstlinewidth < 0) {
+               e = indent + extraindent;
+               firstlinewidth = width;
+               rdaddc(text, '\n');
+           } else
+               e = 0;
+       }
+       sfree(pfx.text);
+    } else
+       e = indent + extraindent;
+
+    wrapping = wrap_para(words, firstlinewidth, width, info_width_xrefs);
+    for (p = wrapping; p; p = p->next) {
+       for (i = 0; i < e; i++)
+           rdaddc(text, ' ');
+       info_rdaddwc(text, p->begin, p->end, TRUE);
+       rdaddc(text, '\n');
+       e = indent + extraindent;
+    }
+    wrap_free(wrapping);
+    rdaddc(text, '\n');
+
+    free_word_list(words);
+}
+
+static void info_codepara(rdstringc *text, word *words,
+                         int indent, int width) {
+    int i;
+
+    for (; words; words = words->next) if (words->type == word_WeakCode) {
+       char *c;
+       info_convert(words->text, &c);
+       if (strlen(c) > (size_t)width) {
+           /* FIXME: warn */
+       }
+       for (i = 0; i < indent; i++)
+           rdaddc(text, ' ');
+       rdaddsc(text, c);
+       rdaddc(text, '\n');
+       sfree(c);
+    }
+
+    rdaddc(text, '\n');
+}
+
+static void info_versionid(rdstringc *text, word *words) {
+    rdaddc(text, '[');                /* FIXME: configurability */
+    info_rdaddwc(text, words, NULL, FALSE);
+    rdaddsc(text, "]\n");
+}
+
+static node *info_node_new(char *name)
+{
+    node *n;
+
+    n = mknew(node);
+    n->text.text = NULL;
+    n->text.pos = n->text.size = 0;
+    n->up = n->next = n->prev = n->lastchild = n->listnext = NULL;
+    n->name = dupstr(name);
+    n->started_menu = FALSE;
+
+    return n;
+}
+
+static char *info_node_name(paragraph *p)
+{
+    rdstringc rsc = { 0, 0, NULL };
+    info_rdaddwc(&rsc, p->kwtext ? p->kwtext : p->words, NULL, FALSE);
+    return rsc.text;
+}
+
+static void info_menu_item(rdstringc *text, node *n, paragraph *p)
+{
+    /*
+     * FIXME: Depending on how we're doing node names in this info
+     * file, we might want to do
+     * 
+     *   * Node name:: Chapter title
+     * 
+     * _or_
+     * 
+     *   * Chapter number: Node name.
+     * 
+     * 
+     */
+    rdaddsc(text, "* ");
+    rdaddsc(text, n->name);
+    rdaddsc(text, "::");
+    if (p) {
+       rdaddc(text, ' ');
+       info_rdaddwc(text, p->words, NULL, FALSE);
+    }
+    rdaddc(text, '\n');
+}
index cad2e8e..5a10137 100644 (file)
@@ -913,7 +913,8 @@ static void xhtml_do_index_body(FILE *fp)
 }
 static void xhtml_do_index()
 {
-  word temp_word = { NULL, NULL, word_Normal, 0, 0, L"Index", { NULL, 0, 0} };
+  word temp_word = { NULL, NULL, word_Normal, 0, 0, L"Index",
+      { NULL, 0, 0}, NULL };
   FILE *fp = fopen(conf.index_filename, "w");
 
   if (fp==NULL)
index c59962b..72e10da 100644 (file)
@@ -3,3 +3,4 @@
 *.gid *.GID
 *.log
 *.1
+*.info *.info-*
index 624bf54..762b1d1 100644 (file)
@@ -7,10 +7,10 @@ HALIBUT = ../build/halibut
 all: Contents.html halibut.1
 
 Contents.html: $(INPUTS) $(HALIBUT)
-       $(HALIBUT) --text=halibut.txt --html $(INPUTS)
+       $(HALIBUT) --text=halibut.txt --html --info=halibut.info $(INPUTS)
 
 halibut.1: manpage.but
        $(HALIBUT) --man=halibut.1 manpage.but
 
 clean:
-       rm -f *.html *.txt *.hlp *.cnt *.1
+       rm -f *.html *.txt *.hlp *.cnt *.1 *.info*
index 3fc752d..3e600c7 100644 (file)
--- a/halibut.h
+++ b/halibut.h
@@ -127,6 +127,8 @@ struct word_Tag {
     int breaks;                               /* can a line break after it? */
     wchar_t *text;
     filepos fpos;
+
+    void *private_data;               /* for temp use in backends */
 };
 enum {
     /* ORDERING CONSTRAINT: these normal-word types ... */
@@ -252,9 +254,9 @@ char *ustrtoa(wchar_t *s, char *outbuf, int size);
 wchar_t *ustrfroma(char *s, wchar_t *outbuf, int size);
 char *utoa_dup(wchar_t *s);
 wchar_t *ufroma_dup(char *s);
-int ustrlen(wchar_t *s);
+int ustrlen(wchar_t const *s);
 wchar_t *uadv(wchar_t *s);
-wchar_t *ustrcpy(wchar_t *dest, wchar_t *source);
+wchar_t *ustrcpy(wchar_t *dest, wchar_t const *source);
 wchar_t utolower(wchar_t);
 int uisalpha(wchar_t);
 int ustrcmp(wchar_t *lhs, wchar_t *rhs);
@@ -305,10 +307,10 @@ struct tagRdstringc {
 extern const rdstring empty_rdstring;
 extern const rdstringc empty_rdstringc;
 void rdadd(rdstring *rs, wchar_t c);
-void rdadds(rdstring *rs, wchar_t *p);
+void rdadds(rdstring *rs, wchar_t const *p);
 wchar_t *rdtrim(rdstring *rs);
 void rdaddc(rdstringc *rs, char c);
-void rdaddsc(rdstringc *rs, char *p);
+void rdaddsc(rdstringc *rs, char const *p);
 char *rdtrimc(rdstringc *rs);
 
 int compare_wordlists(word *a, word *b);
@@ -436,4 +438,10 @@ paragraph *whlp_config_filename(char *filename);
 void man_backend(paragraph *, keywordlist *, indexdata *);
 paragraph *man_config_filename(char *filename);
 
+/*
+ * bk_info.c
+ */
+void info_backend(paragraph *, keywordlist *, indexdata *);
+paragraph *info_config_filename(char *filename);
+
 #endif
diff --git a/main.c b/main.c
index b509928..ecb2d37 100644 (file)
--- a/main.c
+++ b/main.c
@@ -24,6 +24,7 @@ static const struct backend {
     {"whlp", whlp_backend, whlp_config_filename, 0x0004},
     {"winhelp", whlp_backend, whlp_config_filename, 0x0004},
     {"man", man_backend, man_config_filename, 0x0008},
+    {"info", info_backend, info_config_filename, 0x0010},
 };
 
 int main(int argc, char **argv) {
diff --git a/misc.c b/misc.c
index a54cc6c..6d5497b 100644 (file)
--- a/misc.c
+++ b/misc.c
@@ -62,7 +62,7 @@ void rdadd(rdstring *rs, wchar_t c) {
     rs->text[rs->pos++] = c;
     rs->text[rs->pos] = 0;
 }
-void rdadds(rdstring *rs, wchar_t *p) {
+void rdadds(rdstring *rs, wchar_t const *p) {
     int len = ustrlen(p);
     if (rs->pos >= rs->size - len) {
        rs->size = rs->pos + len + 128;
@@ -84,7 +84,7 @@ void rdaddc(rdstringc *rs, char c) {
     rs->text[rs->pos++] = c;
     rs->text[rs->pos] = 0;
 }
-void rdaddsc(rdstringc *rs, char *p) {
+void rdaddsc(rdstringc *rs, char const *p) {
     int len = strlen(p);
     if (rs->pos >= rs->size - len) {
        rs->size = rs->pos + len + 128;
index c4af519..51c279b 100644 (file)
--- a/ustring.c
+++ b/ustring.c
@@ -78,7 +78,7 @@ wchar_t *ufroma_dup(char *s) {
     return buf;
 }
 
-int ustrlen(wchar_t *s) {
+int ustrlen(wchar_t const *s) {
     int len = 0;
     while (*s++) len++;
     return len;
@@ -88,7 +88,7 @@ wchar_t *uadv(wchar_t *s) {
     return s + 1 + ustrlen(s);
 }
 
-wchar_t *ustrcpy(wchar_t *dest, wchar_t *source) {
+wchar_t *ustrcpy(wchar_t *dest, wchar_t const *source) {
     wchar_t *ret = dest;
     do {
        *dest++ = *source;