/*
- * info backend for Halibut
+ * Info backend for Halibut
+ *
+ * The Info file format isn't well-specified, and what specification
+ * there is is scattered all over the place. Sources include:
+ * (info), from GNU Texinfo.
+ * (texinfo), also from GNU Texinfo.
+ * (Emacs)Misc Help, and (emacs)Info Lookup, from GNU Emacs.
+ * info.el, from GNU Emacs.
*
* Possible future work:
*
* * Section 1.2: Nodename. Title of section.
*
* - might be helpful to diagnose duplicate node names!
+ *
+ * - Indices generated by makeinfo use a menu rather than a bunch of
+ * cross-references, which reduces visual clutter rather. For
+ * singly-referenced items, it looks like:
+ * * toner cartridge, replacing: Toner.
+ * It does a horrid job on multiply-referenced entries, though,
+ * perhaps because the name before the colon is meant to be unique.
+ * Info's 'i' command requires the use of a menu -- it fails to
+ * find any index entries at all with Halibut's current index format.
+ *
+ * - The string "*note" is matched case-insensitively, so we could
+ * make things slightly less ugly by using the lower-case version
+ * when the user asks for \k. Unfortunately, standalone Info seems
+ * to match node names case-sensitively, so we can't downcase that.
+ *
+ * - The character encoding used in an Info file can be configured using
+ * an Emacs local variables block at the end, like this:
+ * Local Variables:
+ * coding: iso-8859-1
+ * End:
*/
#include <stdio.h>
#include "halibut.h"
typedef struct {
+ wchar_t *underline;
+} alignstruct;
+
+typedef struct {
char *filename;
int maxfilesize;
int charset;
int listindentbefore, listindentafter;
int indent_code, width, index_width;
+ alignstruct atitle, achapter, *asect;
+ int nasect;
wchar_t *bullet, *listsuffix;
wchar_t *startemph, *endemph;
wchar_t *lquote, *rquote;
- wchar_t *sectsuffix, *underline;
+ wchar_t *sectsuffix;
wchar_t *rule;
+ wchar_t *index_text;
} infoconfig;
typedef struct {
static int info_rdaddc(info_data *, char);
static int info_rdaddsc(info_data *, char const *);
-static void info_heading(info_data *, word *, word *, int, infoconfig *);
+static void info_heading(info_data *, word *, word *, alignstruct, int,
+ infoconfig *);
static void info_rule(info_data *, int, int, infoconfig *);
static void info_para(info_data *, word *, wchar_t *, word *, keywordlist *,
int, int, int, infoconfig *);
static int info_rdaddwc(info_data *, word *, word *, int, infoconfig *);
static node *info_node_new(char *name, int charset);
-static char *info_node_name(paragraph *p, infoconfig *);
+static char *info_node_name_for_para(paragraph *p, infoconfig *);
+static char *info_node_name_for_text(wchar_t *text, infoconfig *);
static infoconfig info_configure(paragraph *source) {
infoconfig ret;
paragraph *p;
+ int n;
/*
* Defaults.
ret.lquote = L"\x2018\0\x2019\0`\0'\0\0";
ret.rquote = uadv(ret.lquote);
ret.sectsuffix = L": ";
- ret.underline = L"\x203E\0-\0\0";
+ /*
+ * Default underline characters are chosen to match those recognised by
+ * Info-fontify-node.
+ */
+ ret.atitle.underline = L"*\0\0";
+ ret.achapter.underline = L"=\0\0";
+ ret.nasect = 2;
+ ret.asect = snewn(ret.nasect, alignstruct);
+ ret.asect[0].underline = L"-\0\0";
+ ret.asect[1].underline = L".\0\0";
+ ret.index_text = L"Index";
/*
* Two-pass configuration so that we can pick up global config
ret.lquote = uadv(p->keyword);
ret.rquote = uadv(ret.lquote);
}
+ } else if (!ustricmp(p->keyword, L"index")) {
+ ret.index_text = uadv(p->keyword);
}
}
}
} else if (!ustricmp(p->keyword, L"info-section-suffix")) {
ret.sectsuffix = uadv(p->keyword);
} else if (!ustricmp(p->keyword, L"info-underline")) {
- ret.underline = uadv(p->keyword);
+ ret.atitle.underline = ret.achapter.underline =
+ uadv(p->keyword);
+ for (n = 0; n < ret.nasect; n++)
+ ret.asect[n].underline = ret.atitle.underline;
+ } else if (!ustricmp(p->keyword, L"info-chapter-underline")) {
+ ret.achapter.underline = uadv(p->keyword);
+ } else if (!ustricmp(p->keyword, L"info-section-underline")) {
+ wchar_t *q = uadv(p->keyword);
+ int n = 0;
+ if (uisdigit(*q)) {
+ n = utoi(q);
+ q = uadv(q);
+ }
+ if (n >= ret.nasect) {
+ int i;
+ ret.asect = sresize(ret.asect, n+1, alignstruct);
+ for (i = ret.nasect; i <= n; i++)
+ ret.asect[i] = ret.asect[ret.nasect-1];
+ ret.nasect = n+1;
+ }
+ ret.asect[n].underline = q;
+ } else if (!ustricmp(p->keyword, L"text-title-underline")) {
+ ret.atitle.underline = uadv(p->keyword);
} else if (!ustricmp(p->keyword, L"info-bullet")) {
ret.bullet = uadv(p->keyword);
} else if (!ustricmp(p->keyword, L"info-rule")) {
ret.endemph = uadv(ret.startemph);
}
- while (*ret.underline && *uadv(ret.underline) &&
- !cvt_ok(ret.charset, ret.underline))
- ret.underline = uadv(ret.underline);
-
+ while (*ret.atitle.underline && *uadv(ret.atitle.underline) &&
+ !cvt_ok(ret.charset, ret.atitle.underline))
+ ret.atitle.underline = uadv(ret.atitle.underline);
+
+ while (*ret.achapter.underline && *uadv(ret.achapter.underline) &&
+ !cvt_ok(ret.charset, ret.achapter.underline))
+ ret.achapter.underline = uadv(ret.achapter.underline);
+
+ for (n = 0; n < ret.nasect; n++) {
+ while (*ret.asect[n].underline && *uadv(ret.asect[n].underline) &&
+ !cvt_ok(ret.charset, ret.asect[n].underline))
+ ret.asect[n].underline = uadv(ret.asect[n].underline);
+ }
+
while (*ret.bullet && *uadv(ret.bullet) &&
!cvt_ok(ret.charset, ret.bullet))
ret.bullet = uadv(ret.bullet);
node *newnode, *upnode;
char *nodename;
- nodename = info_node_name(p, &conf);
+ nodename = info_node_name_for_para(p, &conf);
newnode = info_node_new(nodename, conf.charset);
sfree(nodename);
/* Do the title */
for (p = sourceform; p; p = p->next)
if (p->type == para_Title)
- info_heading(&topnode->text, NULL, p->words, conf.width, &conf);
+ info_heading(&topnode->text, NULL, p->words,
+ conf.atitle, conf.width, &conf);
nestindent = conf.listindentbefore + conf.listindentafter;
nesting = 0;
info_menu_item(&currnode->up->text, currnode, p, &conf);
has_index |= info_check_index(p->words, currnode, idx);
- info_heading(&currnode->text, p->kwtext, p->words, conf.width, &conf);
+ if (p->type == para_Chapter || p->type == para_Appendix ||
+ p->type == para_UnnumberedChapter)
+ info_heading(&currnode->text, p->kwtext, p->words,
+ conf.achapter, conf.width, &conf);
+ else
+ info_heading(&currnode->text, p->kwtext, p->words,
+ conf.asect[p->aux>=conf.nasect?conf.nasect-1:p->aux],
+ conf.width, &conf);
nesting = 0;
break;
node *newnode;
int i, j, k;
indexentry *entry;
+ char *nodename;
+
+ nodename = info_node_name_for_text(conf.index_text, &conf);
+ newnode = info_node_new(nodename, conf.charset);
+ sfree(nodename);
- newnode = info_node_new("Index", conf.charset);
newnode->up = topnode;
currnode->next = newnode;
newnode->prev = currnode;
currnode->listnext = newnode;
- info_rdaddsc(&newnode->text, "Index\n-----\n\n");
+ k = info_rdadds(&newnode->text, conf.index_text);
+ info_rdaddsc(&newnode->text, "\n");
+ while (k > 0) {
+ info_rdadds(&newnode->text, conf.achapter.underline);
+ k -= ustrwid(conf.achapter.underline, conf.charset);
+ }
+ info_rdaddsc(&newnode->text, "\n\n");
info_menu_item(&topnode->text, newnode, NULL, &conf);
}
static void info_heading(info_data *text, word *tprefix,
- word *words, int width, infoconfig *cfg) {
+ word *words, alignstruct align,
+ int width, infoconfig *cfg) {
int length;
int firstlinewidth, wrapwidth;
wrappedline *wrapping, *p;
for (p = wrapping; p; p = p->next) {
length += info_rdaddwc(text, p->begin, p->end, FALSE, cfg);
info_rdadd(text, L'\n');
- while (length > 0) {
- info_rdadds(text, cfg->underline);
- length -= ustrwid(cfg->underline, cfg->charset);
+ if (*align.underline) {
+ while (length > 0) {
+ info_rdadds(text, align.underline);
+ length -= ustrwid(align.underline, cfg->charset);
+ }
+ info_rdadd(text, L'\n');
}
- info_rdadd(text, L'\n');
length = 0;
}
wrap_free(wrapping);
return n;
}
-static char *info_node_name(paragraph *par, infoconfig *cfg)
+static char *info_node_name_core(info_data *id, filepos *fpos)
{
- info_data id = EMPTY_INFO_DATA;
char *p, *q;
- id.charset = cfg->charset;
- info_rdaddwc(&id, par->kwtext ? par->kwtext : par->words,
- NULL, FALSE, cfg);
- info_rdaddsc(&id, NULL);
-
/*
- * We cannot have commas or colons in a node name. Remove any
- * that we find, with a warning.
+ * We cannot have commas, colons or parentheses in a node name.
+ * Remove any that we find, with a warning.
*/
- p = q = id.output.text;
+ p = q = id->output.text;
while (*p) {
- if (*p == ':' || *p == ',') {
- error(err_infonodechar, &par->fpos, *p);
+ if (*p == ':' || *p == ',' || *p == '(' || *p == ')') {
+ error(err_infonodechar, fpos, *p);
} else {
*q++ = *p;
}
p++;
}
- *p = '\0';
+ *q = '\0';
+
+ return id->output.text;
+}
+
+static char *info_node_name_for_para(paragraph *par, infoconfig *cfg)
+{
+ info_data id = EMPTY_INFO_DATA;
+
+ id.charset = cfg->charset;
+ info_rdaddwc(&id, par->kwtext ? par->kwtext : par->words,
+ NULL, FALSE, cfg);
+ info_rdaddsc(&id, NULL);
+
+ return info_node_name_core(&id, &par->fpos);
+}
+
+static char *info_node_name_for_text(wchar_t *text, infoconfig *cfg)
+{
+ info_data id = EMPTY_INFO_DATA;
+
+ id.charset = cfg->charset;
+ info_rdadds(&id, text);
+ info_rdaddsc(&id, NULL);
- return id.output.text;
+ return info_node_name_core(&id, NULL);
}
static void info_menu_item(info_data *text, node *n, paragraph *p,