git » homepage.git » commit 2dfd2b1

Index gen

author Alan Dipert
2025-10-08 05:06:26 UTC
committer Alan Dipert
2025-10-08 05:06:26 UTC
parent 18a2920fd84ba3de6ac3f116d4d113cec4a43c5e

Index gen

AGENTS.md +2 -1
Makefile +15 -11
README.md +2 -2
md/Home.md +0 -1
md/HomepageDesign.md +1 -1
tools/gen_index.sh +37 -0
tools/index_list.awk +91 -0
tpl/style.css +4 -0

diff --git a/AGENTS.md b/AGENTS.md
index afbacdc..b880fda 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -15,11 +15,12 @@ Welcome! This document describes how automated helpers should operate when worki
 - Always run `make assets && make` after touching Markdown, templates, or build scripts unless instructed otherwise.
 - `make deploy` is reserved for publishing—never call it unless explicitly asked and the git worktree is clean.
 - Ensure `tools/mdlink2html.awk` and build helpers continue to support wiki-style intra-site links.
+- Keep the generated `out/Index.html` in sync; the `tools/gen_index.sh` helper runs automatically from `make`.
 - Keep updates idempotent: don’t delete generated output or vendor directories unless instructed.
 
 ## Blog Post Style Guidelines
 
-When drafting or editing Markdown articles under `md_export/`:
+When drafting or editing Markdown articles under `md/`:
 
 - Begin with a level-one heading that matches the filename (e.g., `# HomepageDesign`).
 - Use short, conversational paragraphs aimed at technical readers; prefer present tense and active voice.
diff --git a/Makefile b/Makefile
index 385b69b..e3aebba 100644
--- a/Makefile
+++ b/Makefile
@@ -8,24 +8,25 @@ CSS := tpl/style.css
 MD2HTML ?= /usr/bin/cmark-gfm
 CMARK_FLAGS := --to html --extension table --validate-utf8
 BUILDINFO := tools/buildinfo.sh
+INDEX_HTML := $(OUT)/Index.html
 DEPLOY_HOST ?= arsien23i2@dreamhost:tailrecursion.com/~alan
 
 MD_FILES := $(shell find $(SRC) -type f -name '*.md' | LC_ALL=C sort)
 HTML := $(patsubst $(SRC)/%.md,$(OUT)/%.html,$(MD_FILES))
 
-all: $(OUT)/style.css $(HTML)
+all: $(OUT)/style.css $(HTML) $(INDEX_HTML)
 
 $(OUT)/style.css: $(CSS)
-	mkdir -p $(OUT)
-	cp $(CSS) $@
+	@mkdir -p $(OUT)
+	@cp $(CSS) $@
 
 $(OUT):
-	mkdir -p $(OUT)
+	@mkdir -p $(OUT)
 
 $(OUT)/%.html: $(SRC)/%.md $(HEAD) $(FOOT) tools/mdlink2html.awk $(BUILDINFO) | $(OUT)
-	mkdir -p $(@D)
-	awk -f tools/mdlink2html.awk $< > $@.rewritten.md
-	$(MD2HTML) $(CMARK_FLAGS) $@.rewritten.md > $@.body.html
+	@mkdir -p $(@D)
+	@awk -f tools/mdlink2html.awk $< > $@.rewritten.md
+	@$(MD2HTML) $(CMARK_FLAGS) $@.rewritten.md > $@.body.html
 	@css_prefix="$$(dirname "$@" | sed -e 's#^$(OUT)##' -e 's#^/##' -e 's#[^/][^/]*#../#g')"; \
 		page_title=$$(basename "$<" .md); \
 		build_info=$$($(BUILDINFO)); \
@@ -35,11 +36,14 @@ $(OUT)/%.html: $(SRC)/%.md $(HEAD) $(FOOT) tools/mdlink2html.awk $(BUILDINFO) |
 		sed -e "s|@CSS@|$${css_prefix}|g" -e "s|@BUILDINFO@|$${build_info_esc}|g" -e "s|@TITLE@|$${page_title_esc}|g" $(FOOT) > $@.foot.html; \
 		cat $@.head.html $@.body.html $@.foot.html > $@; \
 		rm -f $@.head.html $@.foot.html
-	rm -f $@.rewritten.md $@.body.html
+	@rm -f $@.rewritten.md $@.body.html
 
 assets:
-	mkdir -p $(OUT)
-	rsync -a --include='*/' --exclude='*.md' --exclude='*.MD' --prune-empty-dirs $(SRC)/ $(OUT)/
+	@mkdir -p $(OUT)
+	@rsync -a --include='*/' --exclude='*.md' --exclude='*.MD' --prune-empty-dirs $(SRC)/ $(OUT)/
+
+$(INDEX_HTML): $(HTML) tools/gen_index.sh tools/index_list.awk $(HEAD) $(FOOT) $(BUILDINFO) | $(OUT)
+	@tools/gen_index.sh $@ $(SRC)
 
 deploy: check-git-clean assets all
 	@if [ -z "$(DEPLOY_HOST)" ]; then \
@@ -53,7 +57,7 @@ tree:
 	printf 'HTML outputs:   ' && if [ -d $(OUT) ]; then find $(OUT) -type f -name '*.html' | wc -l; else echo 0; fi
 
 clean:
-	rm -rf $(OUT)
+	@rm -rf $(OUT)
 
 help:
 	@echo 'Static site generator targets:'
diff --git a/README.md b/README.md
index 7eebf65..04bf84e 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,12 @@
 # Homepage static site generator
 
-This converts Markdown from `md_export/` to HTML in `out/` and can deploy the result to your shared host.
+This converts Markdown from `md/` to HTML in `out/` and can deploy the result to your shared host.
 
 > AI helpers: read `AGENTS.md` before making changes.
 
 ## Usage
 make assets && make
-xdg-open out/index.html  # or open on macOS
+xdg-open out/Index.html  # or open on macOS
 
 > Note: `make deploy` requires a clean git worktree. Commit or stash changes first if you plan to publish.
 
diff --git a/md/Home.md b/md/Home.md
index 3de48c9..20cb45c 100644
--- a/md/Home.md
+++ b/md/Home.md
@@ -36,4 +36,3 @@ Updates
 | 2020-11-10 | Added three [Cognicast](https://www.cognitect.com/cognicast/) appearances to [TechWorks](./TechWorks.md).                                  |
 | 2020-11-09 | Added [FairDivision](./FairDivision.md).                                                                                                   |
 | 2020-11-02 | Deployed new [Zim](https://zim-wiki.org/)-based site and established redirects from previous URLs.                                         |
-
diff --git a/md/HomepageDesign.md b/md/HomepageDesign.md
index 0ab7939..505d765 100644
--- a/md/HomepageDesign.md
+++ b/md/HomepageDesign.md
@@ -8,7 +8,7 @@ For years I maintained this site directly out of [Zim](https://zim-wiki.org/). Z
 
 ## Static site generator today
 
-Today the site is rebuilt with a tiny static site generator that lives right alongside the content. Everything under `md_export/` is authored in Markdown, and a `make` pipeline rewrites wiki links, runs [`cmark-gfm`](https://github.com/github/cmark-gfm) to render HTML, wraps the result in a shared template, and copies any non-Markdown assets into the `out/` directory. Deploys are a simple `make deploy`, which rsyncs the generated files to my DreamHost account. The build footer shows who published the site, when, and from which git revision.
+Today the site is rebuilt with a tiny static site generator that lives right alongside the content. Everything under `md/` is authored in Markdown, and a `make` pipeline rewrites wiki links, runs [`cmark-gfm`](https://github.com/github/cmark-gfm) to render HTML, wraps the result in a shared template, and copies any non-Markdown assets into the `out/` directory. Deploys are a simple `make deploy`, which rsyncs the generated files to my DreamHost account. The build footer shows who published the site, when, and from which git revision.
 
 ## Why change?
 
diff --git a/tools/gen_index.sh b/tools/gen_index.sh
new file mode 100755
index 0000000..cf934c2
--- /dev/null
+++ b/tools/gen_index.sh
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+output=${1:?"usage: gen_index.sh out/Index.html [src-dir]"}
+src_dir=${2:-md}
+script_dir=$(cd "$(dirname "$0")" && pwd)
+root_dir=$(cd "$script_dir/.." && pwd)
+cd "$root_dir"
+
+build_info=$(tools/buildinfo.sh)
+build_info_esc=$(printf '%s\n' "$build_info" | sed 's/[\/&]/\\&/g')
+css_prefix=""
+page_title="Index"
+
+head_tmp=$(mktemp)
+foot_tmp=$(mktemp)
+body_tmp=$(mktemp)
+
+sed -e "s|@CSS@|$css_prefix|g" \
+    -e "s|@BUILDINFO@|$build_info_esc|g" \
+    -e "s|@TITLE@|$page_title|g" \
+    tpl/head.html > "$head_tmp"
+
+sed -e "s|@CSS@|$css_prefix|g" \
+    -e "s|@BUILDINFO@|$build_info_esc|g" \
+    -e "s|@TITLE@|$page_title|g" \
+    tpl/foot.html > "$foot_tmp"
+
+{
+  echo "<h1>Index of Alan's Homepage</h1>"
+  echo "<p>Browse every page in this wiki-style site:</p>"
+  find "$src_dir" -type f -name '*.md' | LC_ALL=C sort | awk -v prefix="$src_dir" -f tools/index_list.awk
+} > "$body_tmp"
+
+cat "$head_tmp" "$body_tmp" "$foot_tmp" > "$output"
+
+rm -f "$head_tmp" "$body_tmp" "$foot_tmp"
diff --git a/tools/index_list.awk b/tools/index_list.awk
new file mode 100755
index 0000000..bda3856
--- /dev/null
+++ b/tools/index_list.awk
@@ -0,0 +1,91 @@
+#!/usr/bin/env awk -f
+# Generate a nested unordered list of site pages from a list of Markdown files.
+
+function escape_regex(s,    i, c, out, specials) {
+  specials = ".^$[]()|*+?{}\\"
+  out = ""
+  for (i = 1; i <= length(s); i++) {
+    c = substr(s, i, 1)
+    if (index(specials, c)) {
+      out = out "\\" c
+    } else {
+      out = out c
+    }
+  }
+  return out
+}
+
+function indent(depth,    i, s) {
+  s = ""
+  for (i = 1; i < depth; i++) {
+    s = s "  "
+  }
+  return s
+}
+
+function render(node, depth,    i, child, display, link, pad, link_tmp) {
+  for (i = 1; i <= child_count[node]; i++) {
+    child = children[node, i]
+    display = name[child]
+    link = href[child]
+    pad = indent(depth)
+
+    if (link != "") {
+      link_tmp = link
+      sub(/\.md$/, ".html", link_tmp)
+      link_tmp = "./" link_tmp
+      printf("%s<li><a href=\"%s\">%s</a>", pad, link_tmp, display)
+    } else {
+      printf("%s<li>%s", pad, display)
+    }
+
+    if (child_count[child] > 0) {
+      printf("\n%s  <ul>\n", pad)
+      render(child, depth + 1)
+      printf("%s  </ul>\n", pad)
+      printf("%s</li>\n", pad)
+    } else {
+      printf("</li>\n")
+    }
+  }
+}
+
+BEGIN {
+  FS = "/"
+  pref = prefix
+  if (pref != "" && substr(pref, length(pref), 1) != "/") {
+    pref = pref "/"
+  }
+  pref_pattern = escape_regex(pref)
+}
+{
+  rel = $0
+  if (pref != "") {
+    sub("^" pref_pattern, "", rel)
+  }
+  base = rel
+  gsub(/\.md$/, "", base)
+
+  n = split(base, parts, "/")
+  full = ""
+  for (i = 1; i <= n; i++) {
+    parent = full
+    full = (full == "" ? parts[i] : full "/" parts[i])
+
+    if (!(full in seen)) {
+      seen[full] = 1
+      child_count[parent]++
+      children[parent, child_count[parent]] = full
+      name[full] = parts[i]
+    }
+
+    if (i == n) {
+      href[full] = rel
+    }
+  }
+}
+END {
+  print "<ul class=\"site-index\">"
+  render("", 1)
+  print "</ul>"
+}
diff --git a/tpl/style.css b/tpl/style.css
index f506c5a..b7ad6f6 100644
--- a/tpl/style.css
+++ b/tpl/style.css
@@ -67,6 +67,10 @@ img[src*="200px"]{width:200px;}
 .content th[align="right"]{text-align:right;}
 .content td[align="center"],
 .content th[align="center"]{text-align:center;}
+.site-index{list-style:none;margin:1.5rem 0;padding:0;}
+.site-index>li{margin:.35rem 0;}
+.site-index ul{list-style:none;margin:.35rem 0;padding-left:1.25rem;}
+.site-index a{font-weight:600;}
 .site-foot{
   margin-top:2rem;
   border-top:1px solid rgba(0,0,0,.08);