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>
230 lines
8.5 KiB
Bash
Executable File
230 lines
8.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# help — one-screen reference for ALL Cloverleaf-Larry short commands.
|
|
# Generated LIVE from the bin/ wrapper set + each tool's help block, so it can
|
|
# NEVER drift from what is actually installed on PATH. No static list.
|
|
# (Canonical command is `help`; `cheat` is a kept alias — same output.)
|
|
#
|
|
# help # the full table: command · what it does · one example
|
|
# help tbn # just the row(s) whose name contains 'tbn'
|
|
# help hl7 # filter — every command matching 'hl7'
|
|
# help -h # this help
|
|
#
|
|
# For each command shown, `help` derives:
|
|
# • the description — from the wrapper's own `# name — …` header line, or (for
|
|
# the thin nc-*/hl7-* pass-through wrappers) from line 1 of
|
|
# the underlying lib/<tool>.sh, whichever is more specific.
|
|
# • ONE real example — the first indented `# <cmd> …` example in the wrapper's
|
|
# help block; else the first `# Usage:` form of the backing
|
|
# lib tool, rewritten to the bare on-PATH command; else
|
|
# `<cmd> -h`.
|
|
# Run `<cmd> -h` (or `<cmd> --help`) on any row for the full, authoritative help.
|
|
set -o pipefail
|
|
|
|
# --- locate our own bin/ dir (follow one symlink level, like the wrappers) ---
|
|
_self="${BASH_SOURCE[0]}"; [ -L "$_self" ] && _self="$(readlink "$_self")"
|
|
_BINDIR="$(cd "$(dirname "$_self")" && pwd)"
|
|
# Reuse the shared lib resolver so the example-from-lib path finds lib/<tool>.sh.
|
|
[ -f "$_BINDIR/_nc_common.sh" ] && . "$_BINDIR/_nc_common.sh"
|
|
|
|
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
|
|
awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "${BASH_SOURCE[0]}"
|
|
exit 0
|
|
fi
|
|
|
|
_filter="${1:-}"
|
|
|
|
# v0.9.6: nc-* are backward-compat aliases hidden from the table, so a user who
|
|
# types `help nc-table` (old muscle memory) would otherwise get "no match".
|
|
# Translate an `nc-<x>` filter to the canonical short name it aliases so the
|
|
# right row still shows: nc-find → nfind, every other nc-<x> → <x>.
|
|
case "$_filter" in
|
|
nc-find) _filter="nfind" ;;
|
|
nc-paths) _filter="paths" ;;
|
|
nc-*) _filter="${_filter#nc-}" ;;
|
|
esac
|
|
|
|
# Resolve the lib/ dir once (best-effort; example-from-lib degrades gracefully).
|
|
_LIB=""
|
|
if command -v _nc_resolve_lib >/dev/null 2>&1; then
|
|
_LIB="$(_nc_resolve_lib 2>/dev/null || true)"
|
|
fi
|
|
|
|
# --- header line of a wrapper: the `# <name> — <desc>` comment (line 2) -------
|
|
# Returns the text after the em-dash separator. The codebase convention is a
|
|
# literal em-dash (—, U+2014) between the command name and its description; we
|
|
# split ONLY on that so hyphenated command names (csv-to-table, hl7-field) stay
|
|
# intact. A wrapper whose first comment has no em-dash yields no desc here (the
|
|
# caller then falls back to the backing lib tool's description).
|
|
_wrapper_desc() {
|
|
# LC_ALL=C → byte-oriented index()/substr() so the 3-byte em-dash (—) is
|
|
# skipped exactly (under a UTF-8 locale substr counts chars and over-skips).
|
|
LC_ALL=C awk '
|
|
NR==1 { next } # shebang
|
|
/^#/ {
|
|
line = $0
|
|
sub(/^# ?/, "", line)
|
|
i = index(line, "\xe2\x80\x94") # UTF-8 bytes for — (em-dash)
|
|
if (i > 0) {
|
|
rest = substr(line, i + 3)
|
|
sub(/^[[:space:]]+/, "", rest)
|
|
print rest
|
|
exit
|
|
}
|
|
}
|
|
!/^#/ { exit }
|
|
' "$1"
|
|
}
|
|
|
|
# Is this wrapper one of the thin pass-through wrappers whose own description is
|
|
# the generic boilerplate? If so we prefer the backing lib tool's description.
|
|
_is_boilerplate_desc() {
|
|
case "$1" in
|
|
*"direct, on-PATH wrapper for lib/"*) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
# Backing lib tool name for a wrapper (v0.9.6). The prefix-free short commands
|
|
# (table, nfind, …) no longer share a basename with their lib tool (table →
|
|
# lib/nc-table.sh), so we read the actual `lib/<tool>.sh` the wrapper execs from
|
|
# the wrapper body. Falls back to the command name (legacy 1:1 wrappers).
|
|
_backing_tool() {
|
|
local wf="$1" cmd="$2" t
|
|
t="$(LC_ALL=C sed -n 's#.*lib/\([A-Za-z0-9_-]\{1,\}\)\.sh.*#\1#p' "$wf" 2>/dev/null | head -1)"
|
|
[ -n "$t" ] && { printf '%s' "$t"; return 0; }
|
|
printf '%s' "$cmd"
|
|
}
|
|
|
|
# Description of the backing lib tool (line 1 after its `# tool.sh — `).
|
|
_lib_desc() {
|
|
local tool="$1" f="$_LIB/$1.sh"
|
|
[ -n "$_LIB" ] && [ -f "$f" ] || return 1
|
|
LC_ALL=C awk '
|
|
/^#/ {
|
|
line = $0; sub(/^# ?/, "", line)
|
|
i = index(line, "\xe2\x80\x94") # UTF-8 bytes for — (em-dash)
|
|
if (i > 0) {
|
|
rest = substr(line, i + 3)
|
|
sub(/^[[:space:]]+/, "", rest)
|
|
print rest; exit
|
|
}
|
|
}
|
|
!/^#/ { exit }
|
|
' "$f"
|
|
}
|
|
|
|
# First indented `# <cmd> …` example inside a wrapper's help block. The header
|
|
# line ("# <cmd> — …") is excluded by requiring (a) at least TWO spaces after the
|
|
# `#` (real example blocks are indented `# <cmd> …`) and (b) no em-dash on the
|
|
# line (the header carries the em-dash; example lines never do).
|
|
_wrapper_example() {
|
|
local cmd="$1"
|
|
LC_ALL=C awk -v cmd="$cmd" '
|
|
NR==1 { next }
|
|
/^#/ {
|
|
raw = $0
|
|
body = raw; sub(/^#/, "", body) # keep indentation
|
|
if (body !~ ("^[[:space:]][[:space:]]+" cmd "([[:space:]]|$)")) { next }
|
|
if (index(body, "\xe2\x80\x94") > 0) next # skip the header (has —)
|
|
sub(/^[[:space:]]+/, "", body)
|
|
sub(/[[:space:]]+#.*$/, "", body) # drop trailing "# comment"
|
|
print body; exit
|
|
}
|
|
!/^#/ { exit }
|
|
' "$2"
|
|
}
|
|
|
|
# First `# Usage:` form of the backing lib tool, rewritten to the bare command.
|
|
# e.g. " nc-find.sh --name PATTERN # …" -> "nc-find --name PATTERN"
|
|
_lib_example() {
|
|
local cmd="$1" tool="$2" f="$_LIB/$2.sh"
|
|
[ -n "$_LIB" ] && [ -f "$f" ] || return 1
|
|
LC_ALL=C awk -v tool="$tool" -v cmd="$cmd" '
|
|
/^#[[:space:]]*Usage:/ { inu=1; next }
|
|
inu && /^#/ {
|
|
line = $0; sub(/^#/, "", line)
|
|
if (line ~ /[^[:space:]]/) {
|
|
sub(/^[[:space:]]+/, "", line)
|
|
sub(/[[:space:]]+#.*$/, "", line) # drop trailing comment
|
|
if (line ~ ("^" tool "\\.sh")) {
|
|
sub("^" tool "\\.sh", cmd, line) # nc-find.sh … -> nc-find …
|
|
print line; exit
|
|
}
|
|
}
|
|
next
|
|
}
|
|
inu && !/^#/ { exit }
|
|
' "$f"
|
|
}
|
|
|
|
# --- gather the command list (skip helpers / sourced files / fetched bins) ---
|
|
_cmds=()
|
|
for _p in "$_BINDIR"/*; do
|
|
[ -f "$_p" ] && [ -x "$_p" ] || continue
|
|
_b="$(basename "$_p")"
|
|
case "$_b" in
|
|
# sourced helpers / fetched bins / the help command(s) themselves
|
|
_nc_common.sh|nc-completion.bash|help|cheat|jq|jq.exe) continue ;;
|
|
*.bash) continue ;;
|
|
# v0.9.6: nc-* are now backward-compat ALIASES of the prefix-free short
|
|
# commands (nc-table→table, nc-find→nfind, …). Hide the aliases from the
|
|
# table so each tool shows once, under its canonical short name. The user
|
|
# filter still finds them: `help nc-table` matches both the alias name and
|
|
# the (un-skipped) canonical row, but the row shown is the canonical one.
|
|
nc-*) continue ;;
|
|
esac
|
|
_cmds+=("$_b")
|
|
done
|
|
|
|
# Sort for stable output.
|
|
IFS=$'\n' _cmds=($(printf '%s\n' "${_cmds[@]}" | sort)); unset IFS
|
|
|
|
# --- build rows -------------------------------------------------------------
|
|
_rows=""
|
|
for _c in "${_cmds[@]}"; do
|
|
if [ -n "$_filter" ] && ! printf '%s' "$_c" | grep -qiF "$_filter"; then
|
|
continue
|
|
fi
|
|
_wf="$_BINDIR/$_c"
|
|
|
|
# backing lib tool (table -> nc-table, nfind -> nc-find, …; legacy 1:1 ok)
|
|
_tool="$(_backing_tool "$_wf" "$_c")"
|
|
|
|
# description
|
|
_desc="$(_wrapper_desc "$_wf")"
|
|
if [ -z "$_desc" ] || _is_boilerplate_desc "$_desc"; then
|
|
_ld="$(_lib_desc "$_tool")"
|
|
[ -n "$_ld" ] && _desc="$_ld"
|
|
fi
|
|
[ -z "$_desc" ] && _desc="(no description)"
|
|
# collapse whitespace, trim to one clause for the table
|
|
_desc="$(printf '%s' "$_desc" | tr -s '[:space:]' ' ' | sed 's/^ *//; s/ *$//')"
|
|
|
|
# example
|
|
_ex="$(_wrapper_example "$_c" "$_wf")"
|
|
if [ -z "$_ex" ]; then
|
|
_ex="$(_lib_example "$_c" "$_tool")"
|
|
fi
|
|
[ -z "$_ex" ] && _ex="$_c -h"
|
|
_ex="$(printf '%s' "$_ex" | tr -s '[:space:]' ' ' | sed 's/^ *//; s/ *$//')"
|
|
|
|
_rows="${_rows}${_c}"$'\t'"${_desc}"$'\t'"${_ex}"$'\n'
|
|
done
|
|
|
|
if [ -z "$_rows" ]; then
|
|
echo "help: no commands match '${_filter}'" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# --- render: aligned three-column table -------------------------------------
|
|
printf 'Cloverleaf-Larry short commands (run <cmd> -h for full help)\n\n'
|
|
printf '%s' "$_rows" | awk -F'\t' '
|
|
{ c[NR]=$1; d[NR]=$2; e[NR]=$3; n=NR; if (length($1)>w) w=length($1) }
|
|
END {
|
|
for (i=1;i<=n;i++) {
|
|
printf " %-*s %s\n", w, c[i], d[i]
|
|
printf " %-*s e.g. %s\n", w, "", e[i]
|
|
}
|
|
}
|
|
'
|