#!/usr/bin/env bash # # Regenerates doc/adr/toc.md from the ADR files themselves. # # The index is generated, never hand-edited: it drifted from the documents it indexes precisely because # every ADR merge asked a human to retype it. Run with --check to verify it is current without writing. # # scripts/adr/generate-toc.sh # rewrite doc/adr/toc.md # scripts/adr/generate-toc.sh --check # exit 1 if doc/adr/toc.md is out of date # set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" adr_dir="$repo_root/doc/adr" toc_path="$adr_dir/toc.md" check_only=false if [[ "${1:-}" == "--check" ]]; then check_only=true elif [[ $# -gt 0 ]]; then echo "usage: $(basename "$0") [--check]" >&2 exit 2 fi # The heading is the source of truth for the title. A legacy "NN. " prefix is stripped so the identifier # is rendered once, from the filename, rather than depending on whether an author remembered to type it. title_of() { local heading heading="$(grep -m 1 '^# ' "$1" || true)" [[ -n "$heading" ]] || return 1 heading="${heading#\# }" sed -E 's/^[0-9]+\.[[:space:]]+//' <<<"$heading" } entries=() add_entry() { local file="$1" prefix="$2" title if ! title="$(title_of "$file")"; then echo "error: $(basename "$file") has no '# ' heading to take a title from." >&2 exit 1 fi entries+=("$(printf '* [%s. %s](%s)' "$prefix" "$title" "$(basename "$file")")") } numbered=() dated=() while IFS= read -r file; do case "$(basename "$file")" in toc.md) continue ;; # Dated first: a date also opens with four digits, so testing NNNN- first would swallow it. [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-*.md) dated+=("$file") ;; [0-9][0-9][0-9][0-9]-*.md) numbered+=("$file") ;; *) echo "error: $(basename "$file") matches neither the NNNN- nor the YYYY-MM-DD- naming convention." >&2 exit 1 ;; esac done < <(LC_ALL=C find "$adr_dir" -maxdepth 1 -name '*.md' | LC_ALL=C sort) # Numbered records first, then dated ones: every dated record postdates every numbered one, so one flat # list stays chronological across the change of convention. for file in ${numbered[@]+"${numbered[@]}"}; do identifier="$(basename "$file")" identifier="${identifier%%-*}" # 10# so a zero-padded identifier is never read as octal. add_entry "$file" "$((10#$identifier))" done for file in ${dated[@]+"${dated[@]}"}; do identifier="$(basename "$file")" add_entry "$file" "${identifier:0:10}" done generated="$( echo '# Architecture Decision Records' echo echo '' echo printf '%s\n' ${entries[@]+"${entries[@]}"} )" if [[ "$check_only" == true ]]; then if ! diff -u "$toc_path" <(printf '%s\n' "$generated"); then echo "doc/adr/toc.md is out of date. Run scripts/adr/generate-toc.sh and commit the result." >&2 exit 1 fi echo "doc/adr/toc.md is up to date." else printf '%s\n' "$generated" > "$toc_path" echo "Wrote doc/adr/toc.md." fi