#!/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" TEMPLATES_DIR="${REPO_ROOT}/docs/templates" DOCS_DIR="${REPO_ROOT}/docs" 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_template() { local template_path="$1" local output_path="$2" local main_script="$3" if [[ ! -f "$template_path" ]]; then return 1 fi sed "s/{{[[:space:]]*main_script[[:space:]]*}}/${main_script}/g" "$template_path" > "$output_path" } main() { local mode="render" local main_script local rendered_tmp 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 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 # 1. Render README rendered_tmp=$(mktemp) render_template "${README_TEMPLATE}" "$rendered_tmp" "${main_script}" if [[ "${mode}" == "check" ]]; then if ! cmp -s "$rendered_tmp" "${README_OUTPUT}"; then printf 'Generated README.md is stale.\n' >&2 diff -u "${README_OUTPUT}" "$rendered_tmp" || true rm -f "$rendered_tmp" return 1 fi else mv "$rendered_tmp" "${README_OUTPUT}" fi rm -f "$rendered_tmp" # 2. Render Modular Docs for tmpl in "${TEMPLATES_DIR}"/*.tmpl; do [[ -e "$tmpl" ]] || continue local filename local output_file filename=$(basename "$tmpl" .md.tmpl) output_file="${DOCS_DIR}/${filename}.md" if [[ "${mode}" == "check" ]]; then rendered_tmp=$(mktemp) render_template "$tmpl" "$rendered_tmp" "${main_script}" if ! cmp -s "$rendered_tmp" "$output_file"; then printf 'Generated %s is stale.\n' "$output_file" >&2 diff -u "$output_file" "$rendered_tmp" || true rm -f "$rendered_tmp" return 1 fi rm -f "$rendered_tmp" else render_template "$tmpl" "$output_file" "${main_script}" fi done # 3. Render Service Catalog if [[ "${mode}" == "check" ]]; then "${REPO_ROOT}/scripts/render-service-catalog" --check else "${REPO_ROOT}/scripts/render-service-catalog" fi if [[ "${mode}" == "render" ]]; then printf 'Successfully rendered all documentation from templates.\n' else printf 'All docs are current.\n' fi } main "$@"