Welcome to little lamb

Code » qmdoc » commit 8ae8c8e

index: Add <INDEX> for index of all pages

author Olivier Brunel
2023-07-07 12:34:20 UTC
committer Olivier Brunel
2023-07-07 16:59:36 UTC
parent 9a5ab122af1632bf7c7aaca9a977205866ed7d57

index: Add <INDEX> for index of all pages

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;