#!/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/.sh, whichever is more specific. # • ONE real example — the first indented `# …` 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 # ` -h`. # Run ` -h` (or ` --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/.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-` filter to the canonical short name it aliases so the # right row still shows: nc-find → nfind, every other nc-. 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 `# ` 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/.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 `# …` example inside a wrapper's help block. The header # line ("# — …") is excluded by requiring (a) at least TWO spaces after the # `#` (real example blocks are indented `# …`) 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 -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] } } '