#!/usr/bin/env bash set -e # Generator tolerates "no matches" exit codes from grep | sort. # pipefail and -e off; -u stays. set -u # nomarchy-docs-scripts # # Regenerates docs/SCRIPTS.md from the repo state. Produces: # 1. Header + status legend + regen instructions. # 2. Table of every nomarchy-* script (location, callers, status). # 3. Table of every menu entry in features/scripts/utils/nomarchy-menu # (submenu, label, target command, status). # 4. Snapshot list of orphaned references (called somewhere, no script). # # Status heuristic in Phase A: # kept — file exists AND is called from at least one *.nix / *.conf / # shell file outside its own directory. # unused? — file exists but no caller found. Phase B decides delete-dead # vs intentional public API. # missing — referenced but no file. Phase B decides port-from-omarchy # vs delete-dead vs stub-with-notify. # # nomarchy-docs-scripts # write to stdout # nomarchy-docs-scripts --out docs/SCRIPTS.md repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" cd "$repo_root" # --- Inventory ------------------------------------------------------------- # Where scripts live, in render order. declare -A loc_label=( ["features/scripts/utils"]="features/scripts/utils" ["core/system/scripts"]="core/system/scripts" ["themes/engine/scripts"]="themes/engine/scripts" ) script_dirs=(features/scripts/utils core/system/scripts themes/engine/scripts) # Build name → location map (associative array of basename → repo-relative dir). declare -A script_loc for dir in "${script_dirs[@]}"; do [[ -d "$dir" ]] || continue while IFS= read -r f; do script_loc["$(basename "$f")"]="$dir" done < <(find "$dir" -maxdepth 1 -type f -name 'nomarchy-*') done # Find every nomarchy-* token referenced anywhere outside the script dirs. # (We exclude the script files themselves so they don't list themselves as # their own caller.) # File types we search for references. *.md catches docs and README; # branding/hook/extension files have varied or no extensions. # *.lua catches elephant providers; *.ini catches mako on-button-* hooks; # *.desktop catches MimeType-registered URL handlers. grep_includes=( --include='*.nix' --include='*.conf' --include='*.sh' --include='*.md' --include='nomarchy-*' --include='*.jsonc' --include='*.json' --include='*.toml' --include='*.ini' --include='*.lua' --include='*.desktop' --include='*.txt' --include='*.sample' ) search_dirs=(core features themes installer hosts bin lib README.md) # Files whose mentions of nomarchy-* are documentation about the scripts, # not real callers. Excluded from caller discovery so they don't promote # every script to `kept`. self_refs=(docs/SCRIPTS.md docs/ROADMAP.md docs/AGENT.md) ref_files_per_cmd() { local cmd="$1" local self_pattern self_pattern=$(IFS='|'; echo "${self_refs[*]}") grep -rlE "\\b${cmd}\\b" \ "${grep_includes[@]}" \ "${search_dirs[@]}" 2>/dev/null \ | grep -vE "^(features/scripts/utils|core/system/scripts|themes/engine/scripts)/${cmd}$" \ | grep -vE "^(${self_pattern})$" \ | sort -u } # All distinct nomarchy-* tokens we see anywhere in the repo. # Final char must be alphanumeric — dropping trailing-dash matches like # `nomarchy-pkg-` that come from glob references (`for c in nomarchy-pkg-*`). # Restrict to grep_includes so binaries / tmpfiles don't pollute the set. # The middle `grep -vE` drops lines where `nomarchy-*` is a derivation / # tmp file / sudoers basename / systemd unit / flake output / docker # container identifier — not a shell invocation — so they don't show up # as fake "missing" references. all_refs=$(grep -rhE 'nomarchy-[a-z0-9]([a-z0-9-]*[a-z0-9])?' \ "${grep_includes[@]}" \ "${search_dirs[@]}" 2>/dev/null \ | grep -vE \ -e '(pname|name)[[:space:]]*=[[:space:]]*"nomarchy-' \ -e '/tmp/nomarchy-' \ -e '/etc/sudoers\.d/[^"[:space:]]*nomarchy-' \ -e 'nixosConfigurations\.nomarchy-' \ -e 'packages\.[^.]+\.nomarchy-' \ -e '\./result/bin/run-nomarchy-' \ -e 'mktemp[[:space:]]+[^|]*-t[[:space:]]+nomarchy-' \ -e '(TIMER_NAME|NOPASSWD_FILE|UNIT_NAME)=.*nomarchy-' \ -e 'docker[[:space:]]+[^|]*nomarchy-' \ | grep -oE 'nomarchy-[a-z0-9]([a-z0-9-]*[a-z0-9])?' \ | grep -vE '^(nomarchy-plymouth|nomarchy-sddm-theme|nomarchy-live|nomarchy-rev|nomarchy-windows)$' \ | sort -u) # The token-level denylist above covers identifiers whose ambiguity survives # the line filter: `nomarchy-plymouth` / `nomarchy-sddm-theme` are Nix # derivation names referenced as bare idents in `[...]` lists, # `nomarchy-live` is an ISO label that shows up in comments, `nomarchy-rev` # is `/etc/nomarchy-rev` (a file written by the ISO), and # `nomarchy-windows` is a docker container name in compose heredocs. # --- Render: header -------------------------------------------------------- main() { cat <<'HEADER' # Nomarchy Script & Menu Audit Auto-generated table for [Pillar 3 of the roadmap](ROADMAP.md#3-pillar-script--menu-audit). **Do not edit by hand.** Regenerate after script or menu changes: ```bash ./bin/utils/nomarchy-docs-scripts --out docs/SCRIPTS.md ``` The status column uses a Phase A heuristic — `kept` / `unused?` / `missing`. Phase B (per-batch PRs) refines those into `port-from-omarchy`, `delete-dead`, or `stub-with-notify` and updates the rows. ## Status legend - `kept` — script exists and is called from somewhere outside its own directory. - `unused?` — script exists but no caller was found. Could be dead, could be intentional public API. Phase B triage decides. - `missing` — referenced from code but no script file exists. Phase B triage decides whether to port from Omarchy upstream, delete the caller, or stub with `notify-send`. - `port-from-omarchy` — Phase B verdict: lift the upstream Omarchy script, rewrite for NixOS paths. - `delete-dead` — Phase B verdict: remove and update callers. - `stub-with-notify` — Phase B verdict: temporary `notify-send` stub. HEADER # --- Render: scripts table ---------------------------------------------- printf '## Scripts (%d)\n\n' "${#script_loc[@]}" printf '| Script | Location | Callers | Status | Notes |\n' printf '| --- | --- | --- | --- | --- |\n' # Sort scripts by name. for name in $(printf '%s\n' "${!script_loc[@]}" | sort); do local dir="${script_loc[$name]}" local callers status callers_str callers=$(ref_files_per_cmd "$name") if [[ -z "$callers" ]]; then status='`unused?`' callers_str='—' else status='`kept`' # Trim caller list to 2 entries + count. local count count=$(printf '%s\n' "$callers" | wc -l) if (( count > 2 )); then callers_str=$(printf '%s\n' "$callers" | head -2 | paste -sd, -) callers_str="$callers_str, +$((count - 2)) more" else callers_str=$(printf '%s\n' "$callers" | paste -sd, -) fi fi printf '| `%s` | `%s` | %s | %s | |\n' \ "$name" "$dir" "$callers_str" "$status" done echo # --- Render: missing references ----------------------------------------- printf '## Missing references\n\n' printf 'Tokens grepped from `core/`, `features/`, `themes/`, `installer/`, `hosts/`, `bin/`, `lib/` that have no matching script file.\n\n' printf '| Token | Referenced in | Status |\n' printf '| --- | --- | --- |\n' while IFS= read -r token; do [[ -z "$token" ]] && continue [[ -n "${script_loc[$token]:-}" ]] && continue local refs self_pattern=$(IFS='|'; echo "${self_refs[*]}") refs=$(grep -rlE "\\b${token}\\b" \ "${grep_includes[@]}" \ "${search_dirs[@]}" 2>/dev/null \ | grep -vE "^(${self_pattern})$" \ | sort -u) [[ -z "$refs" ]] && continue local count refs_str count=$(printf '%s\n' "$refs" | wc -l) if (( count > 2 )); then refs_str=$(printf '%s\n' "$refs" | head -2 | paste -sd, -) refs_str="$refs_str, +$((count - 2)) more" else refs_str=$(printf '%s\n' "$refs" | paste -sd, -) fi printf '| `%s` | %s | `missing` |\n' "$token" "$refs_str" done <<<"$all_refs" echo # --- Render: menu items ------------------------------------------------- printf '## Menu items\n\n' printf 'Walked from `features/scripts/utils/nomarchy-menu`. Each `case` arm in a `show_*_menu` function becomes one row.\n\n' printf '| Submenu | Entry | Calls | Status |\n' printf '| --- | --- | --- | --- |\n' awk ' /^show_[a-z_]+_menu\(\) {/ { sub(/\(\) {/, ""); current=$1; in_func=1; next } /^[a-z_]+\(\) {/ && !/^show_/ { current=""; in_func=0; next } /^}$/ { current=""; in_func=0; next } !in_func { next } /^ case \$\(menu / { # extract the menu title between the first pair of double quotes match($0, /menu "[^"]+" "[^"]+"/); if (RSTART == 0) next; title=substr($0, RSTART, RLENGTH); # second quoted string is the option list n=split(title, parts, "\""); title=parts[2]; options=parts[4]; # split options on \n split(options, opts, "\\\\n"); pending_submenu=current; pending_title=title; for (i=1;i<=length(opts);i++) pending_opts[i]=opts[i]; pending_count=length(opts); next } /^ \*[A-Za-z]/ { # case arm — extract pattern between the first * and the closing ) match($0, /\*[^)]*\)/); if (RSTART == 0) next; arm=substr($0, RSTART, RLENGTH); gsub(/[*)]/, "", arm); gsub(/^[[:space:]]+|[[:space:]]+$/, "", arm); # action follows the ) rest=substr($0, RSTART+RLENGTH); sub(/^[[:space:]]+/, "", rest); sub(/[[:space:]]*;;[[:space:]]*$/, "", rest); # match the first nomarchy-* token in the action cmd="" if (match(rest, /nomarchy-[a-z0-9-]+/)) { cmd=substr(rest, RSTART, RLENGTH); } printf "%s|%s|%s\n", pending_submenu, arm, cmd; } ' features/scripts/utils/nomarchy-menu > /tmp/nomarchy-menu-rows.$$ while IFS='|' read -r submenu entry cmd; do [[ -z "$entry" ]] && continue [[ "$entry" =~ ^\) ]] && continue status='`kept`' if [[ -n "$cmd" ]]; then if [[ -z "${script_loc[$cmd]:-}" ]]; then status='`missing`' fi else cmd='_(inline)_' fi printf '| `%s` | %s | `%s` | %s |\n' "$submenu" "$entry" "$cmd" "$status" done < /tmp/nomarchy-menu-rows.$$ rm -f /tmp/nomarchy-menu-rows.$$ echo } out="" if [[ "${1:-}" == "--out" ]]; then out="${2:?--out needs a path}"; shift 2 fi if [[ -n "$out" ]]; then main >"$out" else main fi