cloverleaf-larry/bin/nc-completion.bash
Clover 400398ca7d v0.9.6: help canonical + prefix-free short commands (nc-find→nfind guard) + HCISITEDIR auto-init
Hands-on ergonomics for Bryan's Gundersen testing. All three changes are
backward-compatible — every old name still works.

1. `help` is now the canonical reference command (live, never-drifts table
   from bin/ + each tool's help block). `cheat` kept as a thin alias.

2. Prefix-free short commands: nc-table→table, nc-parse→parse, nc-msgs→msgs,
   nc-status→status, nc-engine→engine, nc-xlate→xlate, nc-inbound→inbound, plus
   the write tools (create-thread, set-field, insert-protocol, make-jump,
   provision-jumps, tclgen, document, revisions, diff-interface, smat-diff,
   regression). COLLISION GUARD: nc-find→`nfind` (NOT `find` — would shadow the
   system find on PATH); nc-paths keeps `paths`. Every nc-* name retained as a
   backward-compat alias. Tab-completion + the help/cheat table updated.

3. HCISITEDIR auto-init in the shared preflight (bin/_nc_common.sh): HCIROOT +
   HCISITE still required, but $HCIROOT/$HCISITE is created if missing rather
   than erroring. Conservative + idempotent; respects an operator-set HCISITEDIR.

VERSION→0.9.6, MANIFEST regenerated (--check clean), bash -n clean.

Co-Authored-By: Clover (Claude Opus 4.8) <noreply@anthropic.com>
2026-06-08 20:21:32 -07:00

193 lines
8.2 KiB
Bash

# nc-completion.bash — dynamic bash tab-completion for the Cloverleaf-Larry
# short commands. Source this file (the installer adds it to your shell rc):
#
# source /path/to/cloverleaf-larry/bin/nc-completion.bash
#
# THREE LEVELS, all enumerated LIVE off the NetConfig site tree under $HCIROOT
# (never a static list — it reflects exactly what the tools see right now):
#
# 1. COMMAND NAMES — `tb<TAB>` → tbn tbp tbh tbpr ; `nc-<TAB>` → nc-find …
# 2. SITE NAMES — `tbn <TAB>` → adt ancout codamx epic …
# 3. THREAD NAMES — `paths <TAB>`, `route_test --source-thread <TAB>`,
# `where <TAB>` → IB_ADT_epic MUX_ADT_ancout …
#
# Sites/threads are read through the SAME parser the tools use (lib/nc-parse.sh
# via bin/_nc_common.sh), so completion can never drift from reality. Results
# are cached per (HCIROOT, mtime-of-tree) for the life of the shell so repeated
# TABs are instant; the cache invalidates automatically when a NetConfig under
# $HCIROOT changes.
# Locate _nc_common.sh relative to THIS completion file (works for both the repo
# layout and an installed copy). Sourced once at load.
if [ -z "${_NC_COMPLETION_COMMON:-}" ]; then
_ncc_self="${BASH_SOURCE[0]}"
_NC_COMPLETION_COMMON="$(cd "$(dirname "$_ncc_self")" 2>/dev/null && pwd)/_nc_common.sh"
unset _ncc_self
fi
# --- cache key = HCIROOT + newest NetConfig mtime (cheap staleness check) ----
_nc_comp_cachekey() {
local root="${HCIROOT:-}"
[ -n "$root" ] && [ -d "$root" ] || { printf 'none'; return; }
local newest
newest="$(find "$root" -maxdepth 2 -name NetConfig -printf '%T@\n' 2>/dev/null | sort -rn | head -1)"
printf '%s@%s' "$root" "${newest:-0}"
}
# --- live site list (cached) -------------------------------------------------
_NC_COMP_SITES_KEY=""; _NC_COMP_SITES=""
_nc_comp_sites() {
local key; key="$(_nc_comp_cachekey)"
if [ "$key" != "$_NC_COMP_SITES_KEY" ]; then
[ -f "$_NC_COMPLETION_COMMON" ] && . "$_NC_COMPLETION_COMMON"
_NC_COMP_SITES="$(_nc_sites 2>/dev/null | tr '\n' ' ')"
_NC_COMP_SITES_KEY="$key"
fi
printf '%s' "$_NC_COMP_SITES"
}
# --- live thread list (cached) -----------------------------------------------
_NC_COMP_THREADS_KEY=""; _NC_COMP_THREADS=""
_nc_comp_threads() {
local key; key="$(_nc_comp_cachekey)"
if [ "$key" != "$_NC_COMP_THREADS_KEY" ]; then
[ -f "$_NC_COMPLETION_COMMON" ] && . "$_NC_COMPLETION_COMMON"
_NC_COMP_THREADS="$(_nc_threads 2>/dev/null | tr '\n' ' ')"
_NC_COMP_THREADS_KEY="$key"
fi
printf '%s' "$_NC_COMP_THREADS"
}
# --- generic completer: offer sites + threads (union) for a name argument ----
# Most short commands take "a site OR a thread"; offering both satisfies every
# documented level (tbn→sites, paths/where→threads) without over-fitting.
_nc_complete_names() {
local cur="${COMP_WORDS[COMP_CWORD]}"
local pool="$(_nc_comp_sites) $(_nc_comp_threads)"
COMPREPLY=( $(compgen -W "$pool" -- "$cur") )
}
# --- threads-only completer (route_test --source-thread, paths, where) -------
_nc_complete_threads_only() {
local cur="${COMP_WORDS[COMP_CWORD]}"
COMPREPLY=( $(compgen -W "$(_nc_comp_threads)" -- "$cur") )
}
# --- sites-only completer ----------------------------------------------------
_nc_complete_sites_only() {
local cur="${COMP_WORDS[COMP_CWORD]}"
COMPREPLY=( $(compgen -W "$(_nc_comp_sites)" -- "$cur") )
}
# --- per-command completers ---------------------------------------------------
# tbn: thread-name search, but a site name is the natural first thing operators
# type (tbn adt) — so offer the union (sites + threads). Same for tbh/tbpr.
_nc_complete_tbn() { _nc_complete_names; }
_nc_complete_tbh() { _nc_complete_names; }
_nc_complete_tbpr() {
# process-name search → no live process index; fall back to threads+sites.
_nc_complete_names
}
# tbp: v1 "thread by port". The task wants this to surface thread names on TAB,
# so we complete against threads (the operator can still type a bare port).
_nc_complete_tbp() { _nc_complete_threads_only; }
# where: positional <thread>.
_nc_complete_where() { _nc_complete_threads_only; }
# paths: first arg <thread>, optional 2nd arg <site>, then flags.
_nc_complete_paths() {
local cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}"
case "$cur" in
-*) COMPREPLY=( $(compgen -W "--up --down --site-only --all --site --format --hciroot" -- "$cur") ); return ;;
esac
case "$prev" in
--site) _nc_complete_sites_only; return ;;
--format) COMPREPLY=( $(compgen -W "v1 table tsv jsonl nodes" -- "$cur") ); return ;;
esac
# 1st positional → thread; 2nd positional → site. Count prior non-flag words.
local i nonflag=0
for ((i=1; i<COMP_CWORD; i++)); do
case "${COMP_WORDS[i]}" in -*) ;; *) ((nonflag++)) ;; esac
done
if [ "$nonflag" -eq 0 ]; then _nc_complete_threads_only
elif [ "$nonflag" -eq 1 ]; then _nc_complete_sites_only
else _nc_complete_names; fi
}
# route_test: <thread> <file>, or --source-thread <thread> [--file <file>].
_nc_complete_route_test() {
local cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}"
case "$prev" in
--source-thread|--thread) _nc_complete_threads_only; return ;;
--file) COMPREPLY=( $(compgen -f -- "$cur") ); return ;;
esac
case "$cur" in
-*) COMPREPLY=( $(compgen -W "--source-thread --file --dry-run" -- "$cur") ); return ;;
esac
# bare first positional → thread; subsequent → file path.
local i nonflag=0
for ((i=1; i<COMP_CWORD; i++)); do
case "${COMP_WORDS[i]}" in --source-thread|--thread|--file) ((i++)) ;; -*) ;; *) ((nonflag++)) ;; esac
done
if [ "$nonflag" -eq 0 ]; then _nc_complete_threads_only
else COMPREPLY=( $(compgen -f -- "$cur") ); fi
}
# nc-find: mode-aware (after --name/--where/etc, offer names; after --port, nothing).
_nc_complete_nc_find() {
local cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}"
case "$prev" in
--name|--where) _nc_complete_names; return ;;
--process) _nc_complete_threads_only; return ;;
--format) COMPREPLY=( $(compgen -W "table tsv jsonl" -- "$cur") ); return ;;
--hciroot) COMPREPLY=( $(compgen -d -- "$cur") ); return ;;
esac
COMPREPLY=( $(compgen -W "--name --port --host --process --where --xlate --tclproc --format --hciroot --case-sensitive" -- "$cur") )
}
# nc-paths shares the paths completer.
_nc_complete_nc_paths() { _nc_complete_paths; }
# nfind is the prefix-free name for nc-find (NOT `find` — that would shadow the
# system find on PATH). Same mode-aware completer.
_nc_complete_nfind() { _nc_complete_nc_find; }
# The remaining prefix-free short commands (v0.9.6: table, parse, status, …) take
# "a site OR a thread" as their natural first argument, so the union completer is
# the right default. (Their nc-* aliases get the SAME completer below.)
_nc_complete_table() { _nc_complete_names; }
_nc_complete_parse() { _nc_complete_names; }
_nc_complete_msgs() { _nc_complete_threads_only; }
_nc_complete_status() { _nc_complete_names; }
_nc_complete_engine() { _nc_complete_names; }
_nc_complete_xlate() { _nc_complete_names; }
# Register. Canonical prefix-free short commands first (the headline ergonomics);
# then the kept nc-* aliases get the SAME completer so muscle memory still tabs.
complete -F _nc_complete_tbn tbn
complete -F _nc_complete_tbp tbp
complete -F _nc_complete_tbh tbh
complete -F _nc_complete_tbpr tbpr
complete -F _nc_complete_where where
complete -F _nc_complete_paths paths
complete -F _nc_complete_route_test route_test
complete -F _nc_complete_nfind nfind
complete -F _nc_complete_table table
complete -F _nc_complete_parse parse
complete -F _nc_complete_msgs msgs
complete -F _nc_complete_status status
complete -F _nc_complete_engine engine
complete -F _nc_complete_xlate xlate
# Backward-compat aliases — same completer as their canonical short command.
complete -F _nc_complete_nc_find nc-find
complete -F _nc_complete_nc_paths nc-paths
complete -F _nc_complete_table nc-table
complete -F _nc_complete_parse nc-parse
complete -F _nc_complete_msgs nc-msgs
complete -F _nc_complete_status nc-status
complete -F _nc_complete_engine nc-engine
complete -F _nc_complete_xlate nc-xlate