elsa-core/scripts/adr/generate-toc.sh

92 lines
3 KiB
Bash
Raw Normal View History

#!/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 '<!-- Generated by scripts/adr/generate-toc.sh. Do not edit by hand. -->'
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