my-homelab-configs/scripts/render-docs

128 lines
3.3 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
INVENTORY_FILE="${REPO_ROOT}/homelab.yml"
README_TEMPLATE="${REPO_ROOT}/README.md.tmpl"
README_OUTPUT="${REPO_ROOT}/README.md"
usage() {
cat <<'USAGE'
Usage: scripts/render-docs [--check]
Render generated Markdown docs from repo-local templates.
Options:
--check fail if generated docs differ from committed output
USAGE
}
read_inventory_value() {
local path="$1"
python3 - "${INVENTORY_FILE}" "${path}" <<'PY'
import re
import sys
inventory_file, requested_path = sys.argv[1:3]
stack = []
values = {}
pattern = re.compile(r"^(\s*)([A-Za-z_][A-Za-z0-9_]*):(?:\s*(.*?))?\s*$")
with open(inventory_file, encoding="utf-8") as handle:
for raw_line in handle:
if not raw_line.strip() or raw_line.lstrip().startswith("#") or raw_line.lstrip().startswith("- "):
continue
match = pattern.match(raw_line.rstrip("\n"))
if not match:
continue
indent = len(match.group(1))
key = match.group(2)
value = (match.group(3) or "").strip()
while stack and stack[-1][0] >= indent:
stack.pop()
current_path = ".".join([item[1] for item in stack] + [key])
if value == "":
stack.append((indent, key))
continue
if " #" in value:
value = value.split(" #", 1)[0].strip()
values[current_path] = value.strip("\"'")
value = values.get(requested_path)
if not value:
raise SystemExit(1)
print(value)
PY
}
render_readme() {
local main_script="$1"
if grep -Eo '{{[[:space:]]*[A-Za-z_][A-Za-z0-9_]*[[:space:]]*}}' "${README_TEMPLATE}" |
grep -Evq '{{[[:space:]]*main_script[[:space:]]*}}'; then
printf 'Unsupported template variable in %s\n' "${README_TEMPLATE}" >&2
return 1
fi
sed "s/{{[[:space:]]*main_script[[:space:]]*}}/${main_script}/g" "${README_TEMPLATE}"
}
main() {
local mode="render"
local main_script
local rendered
case "${1:-}" in
"")
;;
--check)
mode="check"
;;
-h | --help)
usage
return 0
;;
*)
usage >&2
return 2
;;
esac
if [[ ! -s "${INVENTORY_FILE}" ]]; then
printf 'Missing homelab inventory: %s\n' "${INVENTORY_FILE}" >&2
return 1
fi
if [[ ! -s "${README_TEMPLATE}" ]]; then
printf 'Missing README template: %s\n' "${README_TEMPLATE}" >&2
return 1
fi
main_script="$(read_inventory_value metadata.main_script)"
if [[ -z "${main_script}" ]]; then
printf 'homelab.yml metadata.main_script must not be empty\n' >&2
return 1
fi
rendered="$(mktemp)"
render_readme "${main_script}" >"${rendered}"
if [[ "${mode}" == "check" ]]; then
if ! cmp -s "${rendered}" "${README_OUTPUT}"; then
printf 'Generated README.md is stale. Run scripts/render-docs.\n' >&2
diff -u "${README_OUTPUT}" "${rendered}" || true
rm -f "${rendered}"
return 1
fi
printf 'docs are current\n'
rm -f "${rendered}"
return 0
fi
mv "${rendered}" "${README_OUTPUT}"
printf 'Rendered %s from %s\n' "${README_OUTPUT}" "${README_TEMPLATE}"
}
main "$@"