92 lines
3 KiB
Bash
92 lines
3 KiB
Bash
|
|
#!/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
|