author | Olivier Brunel
<jjk@jjacky.com> 2023-07-07 12:34:20 UTC |
committer | Olivier Brunel
<jjk@jjacky.com> 2023-07-07 16:59:36 UTC |
parent | 9a5ab122af1632bf7c7aaca9a977205866ed7d57 |
src/doc/qmdoc.1.md | +8 | -5 |
src/include/qmdoc.h | +4 | -0 |
src/qmdoc/qmdoc.c | +107 | -7 |
diff --git a/src/doc/qmdoc.1.md b/src/doc/qmdoc.1.md index 4049a30..6500fa9 100644 --- a/src/doc/qmdoc.1.md +++ b/src/doc/qmdoc.1.md @@ -302,7 +302,7 @@ the order they were processed. In addition, the so-called /index mode/ will have *qmdoc* generate a global TOC (comprised of the TOCs of all pages) and generate an `index.html` file featuring -said global TOC. +said global TOC, as well as an alphabetical index of all pages & links. This `index.html` will also be linked from every page, as if it was the first generated page, titled "Table of Contents". @@ -317,14 +317,17 @@ By default, index mode is enabled if no `index.md` was given - and thus no `index.html` would have been generated - or if one was given as first file to be processed. -In the later case the file will actually be generated /last/, and an special tag -`<TOC>` can be used, to be replaced by the global TOC. The rest of the page is -of course processed as usual. +In the later case the file will actually be generated /last/, and some special +tags can be used : + +- Special tag `<TOC>` can be used, to be replaced with the global TOC. +- Special tag `<INDEX>` can be used, to be replaced with an alphabetical index + of all pages & links processed. ! HINT: ! You can also force index mode via `--index` so that if an `index.md` was ! specified, regardless of its position, it will be processed last and support -! the `<TOC>` tag. +! the special tags. ! ! Of course if none was given, *qmdoc*'s internal one will be used as usual. diff --git a/src/include/qmdoc.h b/src/include/qmdoc.h index 4629e78..5bac465 100644 --- a/src/include/qmdoc.h +++ b/src/include/qmdoc.h @@ -11,6 +11,10 @@ const char metas[] = const char index_title[] = "Table of Contents"; const char index_md[] = +"# Index\n" +"\n" +"<INDEX>\n" +"\n" "# Table of Contents\n" "\n" "<TOC>" diff --git a/src/qmdoc/qmdoc.c b/src/qmdoc/qmdoc.c index 5c9aa1c..9904d91 100644 --- a/src/qmdoc/qmdoc.c +++ b/src/qmdoc/qmdoc.c @@ -1,4 +1,5 @@ #define _GNU_SOURCE /* qsort_r() */ +#include <ctype.h> /* toupper() */ #include <stdlib.h> #include <unistd.h> #include <time.h> @@ -172,6 +173,7 @@ enum { ERR_TEXT = -104, ERR_BUFFERED = -105, ERR_TOC = -106, + ERR_INDEX = -107, }; static int @@ -231,6 +233,8 @@ escape_text(struct qmdoc *ctx, const char *text, size_t size) return 1; } +#define escape_str(ctx,s) escape_text(ctx, s, strlen(s)) + static int anchor(struct qmdoc *ctx, const char *text, size_t size) { @@ -1213,6 +1217,21 @@ leave_span(MD_SPANTYPE type, void *details, void *ctx_) return 0; } +struct pgidx { + size_t off; + int pg; +}; + +static int +cmp_idx(const void *idx1_, const void *idx2_, void *sa_) +{ + const stralloc *sa = sa_; + const struct pgidx *idx1 = idx1_; + const struct pgidx *idx2 = idx2_; + + return strcmp(sa->s + idx1->off, sa->s + idx2->off); +} + static int text(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *ctx_) { @@ -1271,13 +1290,94 @@ text(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *ctx_) }; #undef TAG - if ((ctx->doc.flags & DOC_IS_INDEX) && size == 5 - && !strncmp(text, "<TOC>", 5)) { - /* buf.otoc is where our current page's toc begins, which we - * don't want to include in the full toc */ - if (!raw_text(ctx, ctx->buf.sa.s, ctx->buf.otoc)) - return ERR_TOC; - return 0; + if (ctx->doc.flags & DOC_IS_INDEX) { + if (size == 5 && !strncmp(text, "<TOC>", 5)) { + /* buf.otoc is where our current page's toc begins, which we + * don't want to include in the full toc */ + if (!raw_text(ctx, ctx->buf.sa.s, ctx->buf.otoc)) + return ERR_TOC; + return 0; + } else if (size == 7 && !strncmp(text, "<INDEX>", 7)) { + genalloc ga = GENALLOC_ZERO; + int n = NB_ENTRIES(ctx); + + /* let's build the list of all pages & links */ + for (int i = 1; i < n; ++i) { + struct entry *entry = &ENTRY(ctx)[i]; + struct pgidx idx; + + if (entry->has_page) + idx.pg = entry->page; + else + idx.pg = get_page(entry->dkey, ctx); + + /* ignore links w/ no pages behind */ + if (idx.pg < 0) continue; + + if (entry->is_page) + idx.off = PAGES(ctx)[idx.pg].nameoff; + else + idx.off = entry->noff; + + if (!genalloc_append(struct pgidx, &ga, &idx)) + return ERR_INDEX; + } + + /* sort things: here there are no groups, and we always want + * basic alphabetical ascending order */ + genalloc_qsort_r(struct pgidx, &ga, cmp_idx, &ctx->sa); + + /* now we can actually make the index */ + int r = ERR_TOC; + char letter = 0; + for (int i = 0, n = genalloc_len(struct pgidx, &ga); i < n; ++i) { + struct pgidx *idx = &genalloc_s(struct pgidx, &ga)[i]; + + if (letter != toupper(ctx->sa.s[idx->off])) { + letter = toupper(ctx->sa.s[idx->off]); + if ((letter && !raw_str(ctx, "</ul>")) + || !raw_str(ctx, "<h2 id=\"") + || !raw_text(ctx, &letter, 1) + || !raw_str(ctx, "\">") + || !raw_text(ctx, &letter, 1) + || !raw_str(ctx, "<a href=\"#") + || !raw_text(ctx, &letter, 1) + || !raw_str(ctx, "\">") + || !raw_str(ctx, "</a></h2><ul>")) + goto idx_err; + /* TOC */ + ctx->doc.flags |= DOC_BUFFERING; + if (!raw_str(ctx, "<li><a href=\"") + || !escape_str(ctx, ctx->sa.s + + PAGES(ctx)[ENTRY(ctx)[0].page].dstoff) + || !raw_str(ctx, "#") + || !raw_text(ctx, &letter, 1) + || !raw_str(ctx, "\" title=\"") + || !raw_text(ctx, &letter, 1) + || !raw_str(ctx, "\">") + || !raw_text(ctx, &letter, 1) + || !raw_str(ctx, "</a>")) + return ERR_TOC; + ctx->doc.flags &= ~DOC_BUFFERING; + } + + if (!raw_str(ctx, "<li><a href=\"") + || !raw_str(ctx, ctx->sa.s + PAGES(ctx)[idx->pg].dstoff) + || !raw_str(ctx, "\" title=\"") + || !raw_str(ctx, ctx->sa.s + PAGES(ctx)[idx->pg].nameoff) + || !raw_str(ctx, "\">") + || !raw_str(ctx, ctx->sa.s + idx->off) + || !raw_str(ctx, "</a>")) + goto idx_err; + } + if (!raw_str(ctx, "</ul>")) + goto idx_err; + + r = 0; +idx_err: + genalloc_free(struct pgidx, &ga); + return r; + } } int i, n;