- Fix bash arithmetic crash on MobaXterm/Cygwin: $(date +%s) was returning CR-tainted values landing in $(( )) operands - Mouse mode off by default; opt in via LARRY_MOUSE=1 or /mouse on - Comprehensive CR-safety sweep across lib/*.sh and larry.sh — every command-substitution result, file read, and user input that feeds an arithmetic context, case dispatcher, or path/header is now CR-stripped at the source New shared helper lib/cygwin-safe.sh defines three primitives: coerce_int VAL [DEFAULT] — for arithmetic / integer-test operands strip_cr VAL — for case patterns, regex tests, paths, headers read_clean VAR [PROMPT] — read -r wrapper that strips CR pre-assign Hardened call sites (14 files, 60+ patch points): - larry.sh: status-line date/tput, 3 y/N approvals, auth menu, API key - lib/oauth.sh: cmd_login + cmd_refresh date+%s captures - lib/nc-engine.sh: 5 y/N action prompts + find|wc arithmetic - lib/nc-msgs.sh: parse_time_ms (4 date sites) + meta-TSV time + MSG_COUNT - lib/nc-regression.sh: tr|wc count + hl7-diff ?-fallback arithmetic - lib/nc-smat-diff.sh: A_COUNT/B_COUNT/DIFFS_TOTAL - lib/nc-insert-protocol.sh: every awk-emitted line number → head/tail math - lib/journal.sh: _next_seq wc -l arithmetic - lib/lessons.sh: _next_id/_count + 2 y/N prompts - lib/hl7-sanitize.sh: cmd_count + clear-table y/N - lib/ssh-helper.sh: 4 local+remote wc -c integer compares - lib/nc-find.sh, lib/nc-table.sh, lib/nc-document.sh, larry-rollback.sh Reproduces the exact error Bryan hit: bash: ...: arithmetic syntax error: invalid arithmetic operator (error token is "") lib/cygwin-safe.sh added to MANIFEST so it auto-syncs on next launch. Co-Authored-By: Clover (Claude Opus 4.7) <noreply@anthropic.com>
235 lines
9.4 KiB
Bash
Executable File
235 lines
9.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# nc-document.sh — generate a v3 native markdown knowledge entry for a Cloverleaf
|
|
# subsystem identified by a name pattern. Walks every NetConfig under $HCIROOT
|
|
# (or a passed-in list), gathers config + flow + xlates + tclprocs, composes a
|
|
# markdown doc with placeholder context sections for humans to fill.
|
|
#
|
|
# Usage:
|
|
# nc-document.sh --name <pattern> [options]
|
|
#
|
|
# --name PATTERN case-insensitive substring/regex to match protocol names
|
|
# --hciroot DIR defaults to $HCIROOT
|
|
# --netconfigs PATHS colon-separated explicit NetConfig list (overrides --hciroot scan)
|
|
# --out PATH output markdown path (default: stdout)
|
|
# --title TITLE doc title (default: derived from --name)
|
|
# --poc-vendor TXT Vendor POC content
|
|
# --poc-internal TXT Internal Owner content
|
|
# --status TXT e.g. production / test / decommissioning
|
|
# --escalation TXT Escalation path text
|
|
# --open-items TXT Open items text (bulleted by you if multi-line)
|
|
# --notes TXT freeform additional notes
|
|
#
|
|
# Any --poc/-status/--escalation/--open-items/--notes that you OMIT becomes an
|
|
# empty placeholder section in the doc, ready for someone to fill.
|
|
set -u
|
|
set -o pipefail
|
|
|
|
NC_SELF="$0"
|
|
LIB_DIR="$(cd "$(dirname "$NC_SELF")" && pwd)"
|
|
NCP="$LIB_DIR/nc-parse.sh"
|
|
NCI="$LIB_DIR/nc-inbound.sh"
|
|
|
|
die() { printf 'nc-document: %s\n' "$*" >&2; exit 1; }
|
|
|
|
PATTERN=""
|
|
HCIROOT_OVERRIDE=""
|
|
NETCONFIGS_OVERRIDE=""
|
|
OUT=""
|
|
TITLE=""
|
|
POC_VENDOR=""
|
|
POC_INTERNAL=""
|
|
STATUS=""
|
|
ESCALATION=""
|
|
OPEN_ITEMS=""
|
|
NOTES=""
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--name) shift; PATTERN="$1" ;;
|
|
--hciroot) shift; HCIROOT_OVERRIDE="$1" ;;
|
|
--netconfigs) shift; NETCONFIGS_OVERRIDE="$1" ;;
|
|
--out) shift; OUT="$1" ;;
|
|
--title) shift; TITLE="$1" ;;
|
|
--poc-vendor) shift; POC_VENDOR="$1" ;;
|
|
--poc-internal) shift; POC_INTERNAL="$1" ;;
|
|
--status) shift; STATUS="$1" ;;
|
|
--escalation) shift; ESCALATION="$1" ;;
|
|
--open-items) shift; OPEN_ITEMS="$1" ;;
|
|
--notes) shift; NOTES="$1" ;;
|
|
-h|--help) sed -n '2,25p' "$NC_SELF"; exit 0 ;;
|
|
-*) die "unknown flag: $1" ;;
|
|
*) die "extra arg: $1" ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
[ -n "$PATTERN" ] || die "missing --name PATTERN"
|
|
[ -z "$TITLE" ] && TITLE="$(printf '%s' "$PATTERN" | tr '[:upper:]' '[:lower:]')"
|
|
|
|
# Determine the NetConfig list
|
|
NCONFIGS=()
|
|
if [ -n "$NETCONFIGS_OVERRIDE" ]; then
|
|
IFS=':' read -ra NCONFIGS <<< "$NETCONFIGS_OVERRIDE"
|
|
else
|
|
ROOT="${HCIROOT_OVERRIDE:-${HCIROOT:-}}"
|
|
[ -n "$ROOT" ] || die "no \$HCIROOT and no --hciroot; pass one or set the env var"
|
|
[ -d "$ROOT" ] || die "hciroot not a directory: $ROOT"
|
|
while IFS= read -r nc; do
|
|
NCONFIGS+=("$nc")
|
|
done < <(find "$ROOT" -maxdepth 2 -name NetConfig -type f 2>/dev/null)
|
|
fi
|
|
[ ${#NCONFIGS[@]} -gt 0 ] || die "no NetConfig files found"
|
|
|
|
# Emit to OUT or stdout
|
|
out_target() {
|
|
if [ -n "$OUT" ]; then
|
|
mkdir -p "$(dirname "$OUT")" 2>/dev/null
|
|
cat > "$OUT"
|
|
else
|
|
cat
|
|
fi
|
|
}
|
|
|
|
# Gather all matching protocols across all NetConfigs
|
|
declare -a MATCHES
|
|
for nc in "${NCONFIGS[@]}"; do
|
|
site=$(basename "$(dirname "$nc")")
|
|
while IFS= read -r prot; do
|
|
[ -z "$prot" ] && continue
|
|
MATCHES+=("$site|$nc|$prot")
|
|
done < <("$NCP" list-protocols "$nc" 2>/dev/null | grep -i -- "$PATTERN" || true)
|
|
done
|
|
|
|
if [ ${#MATCHES[@]} -eq 0 ]; then
|
|
printf 'No protocols matching "%s" found in %d NetConfig(s).\n' "$PATTERN" "${#NCONFIGS[@]}" >&2
|
|
exit 2
|
|
fi
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
# Compose markdown
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
{
|
|
printf '# %s — Cloverleaf System Knowledge Entry\n\n' "$TITLE"
|
|
printf '_Auto-generated by Larry-Anywhere v3 nc-document.sh on %s. Auto-derived facts are below; context fields are for humans to fill or refine._\n\n' "$(date -Iseconds 2>/dev/null || date)"
|
|
|
|
printf '## Context\n\n'
|
|
printf -- '- **Vendor POC:** %s\n' "${POC_VENDOR:-_(unfilled — add vendor contact name + email/phone)_}"
|
|
printf -- '- **Internal Owner:** %s\n' "${POC_INTERNAL:-_(unfilled — add the internal owner / engineer)_}"
|
|
printf -- '- **Status:** %s\n' "${STATUS:-_(unfilled — production / test / decommissioning / on hold)_}"
|
|
printf -- '- **Escalation:** %s\n' "${ESCALATION:-_(unfilled — on-call path, ticket queue, etc.)_}"
|
|
printf '\n### Open items\n'
|
|
if [ -n "$OPEN_ITEMS" ]; then
|
|
printf '%s\n\n' "$OPEN_ITEMS"
|
|
else
|
|
printf '_(unfilled — add open items / known issues / pending work)_\n\n'
|
|
fi
|
|
printf '### Notes\n'
|
|
if [ -n "$NOTES" ]; then
|
|
printf '%s\n\n' "$NOTES"
|
|
else
|
|
printf '_(unfilled — add any free-form context)_\n\n'
|
|
fi
|
|
|
|
# ─── Threads inventory ───
|
|
# v0.7.5: tr -cd '0-9' instead of tr -d ' ' — Cygwin wc.exe CR-taint would
|
|
# otherwise crash `printf '%d'` with "invalid number".
|
|
printf '## Threads (%d matched in %d site(s))\n\n' "${#MATCHES[@]}" "$(printf '%s\n' "${MATCHES[@]}" | awk -F'|' '{print $1}' | sort -u | wc -l | tr -cd '0-9')"
|
|
printf '| Site | Thread | Process | Direction | Port | Host | Type |\n'
|
|
printf '|---|---|---|---|---|---|---|\n'
|
|
for line in "${MATCHES[@]}"; do
|
|
IFS='|' read -r site nc prot <<< "$line"
|
|
pname=$("$NCP" protocol-field "$nc" "$prot" PROCESSNAME 2>/dev/null | head -1)
|
|
obib=$("$NCP" protocol-field "$nc" "$prot" OBWORKASIB 2>/dev/null | head -1)
|
|
outonly=$("$NCP" protocol-field "$nc" "$prot" OUTBOUNDONLY 2>/dev/null | head -1)
|
|
ptype=$("$NCP" protocol-nested "$nc" "$prot" PROTOCOL.TYPE 2>/dev/null | head -1)
|
|
phost=$("$NCP" protocol-nested "$nc" "$prot" PROTOCOL.HOST 2>/dev/null | head -1)
|
|
pport=$("$NCP" protocol-nested "$nc" "$prot" PROTOCOL.PORT 2>/dev/null | head -1)
|
|
isserver=$("$NCP" protocol-nested "$nc" "$prot" PROTOCOL.ISSERVER 2>/dev/null | head -1)
|
|
|
|
direction="?"
|
|
[ "$isserver" = "1" ] && direction="inbound (TCP listener)"
|
|
[ "$obib" = "1" ] && [ "$direction" = "?" ] && direction="inbound (ICL/file)"
|
|
[ "$outonly" = "1" ] && [ "$direction" = "?" ] && direction="outbound"
|
|
|
|
phost_clean=$(printf '%s' "$phost" | sed 's/^{}$//')
|
|
pport_clean=$(printf '%s' "$pport" | sed 's/^{}$//')
|
|
|
|
printf '| `%s` | `%s` | `%s` | %s | %s | %s | %s |\n' \
|
|
"$site" "$prot" "${pname:-?}" "$direction" "${pport_clean:-—}" "${phost_clean:-—}" "${ptype:-?}"
|
|
done
|
|
printf '\n'
|
|
|
|
# ─── Per-thread detail ───
|
|
for line in "${MATCHES[@]}"; do
|
|
IFS='|' read -r site nc prot <<< "$line"
|
|
printf '## `%s` (site: `%s`)\n\n' "$prot" "$site"
|
|
|
|
printf '### Sources (what feeds this thread)\n\n'
|
|
sources=$("$NCP" sources "$nc" "$prot" 2>/dev/null)
|
|
if [ -n "$sources" ]; then
|
|
printf '%s\n' "$sources" | awk '{print "- `" $0 "`"}'
|
|
else
|
|
printf '_(none found in `%s`; may be fed via TCP from outside, or from another site via ICL)_\n' "$site"
|
|
fi
|
|
printf '\n'
|
|
|
|
printf '### Destinations (where this thread routes to)\n\n'
|
|
dests=$("$NCP" destinations "$nc" "$prot" 2>/dev/null)
|
|
if [ -n "$dests" ]; then
|
|
printf '%s\n' "$dests" | awk '{print "- `" $0 "`"}'
|
|
else
|
|
printf '_(no DEST entries in DATAXLATE block)_\n'
|
|
fi
|
|
printf '\n'
|
|
|
|
printf '### Xlates referenced\n\n'
|
|
xlates=$("$NCP" xlate-refs "$nc" "$prot" 2>/dev/null)
|
|
if [ -n "$xlates" ]; then
|
|
printf '%s\n' "$xlates" | awk -v site="$site" '{print "- `" site "/Xlate/" $0 "`"}'
|
|
else
|
|
printf '_(no xlates — pass-through or raw routing only)_\n'
|
|
fi
|
|
printf '\n'
|
|
|
|
printf '### TCL procs referenced\n\n'
|
|
tcls=$("$NCP" tclproc-refs "$nc" "$prot" 2>/dev/null)
|
|
if [ -n "$tcls" ]; then
|
|
printf '%s\n' "$tcls" | awk -v site="$site" '{print "- `" site "/tclprocs/" $0 ".tcl`"}'
|
|
else
|
|
printf '_(no TCL procs referenced)_\n'
|
|
fi
|
|
printf '\n'
|
|
done
|
|
|
|
# ─── Sources outside the matched set (the "fed by" landscape) ───
|
|
printf '## Adjacent threads (the network this subsystem talks to)\n\n'
|
|
printf '_All threads that **either feed** matched threads **or are fed by** matched threads. These are the immediate operational neighbors._\n\n'
|
|
printf '| Site | Thread | Relationship to matched set |\n'
|
|
printf '|---|---|---|\n'
|
|
declare -A SEEN
|
|
for line in "${MATCHES[@]}"; do
|
|
IFS='|' read -r site nc prot <<< "$line"
|
|
while IFS= read -r src; do
|
|
key="$site|$src"
|
|
[ -n "${SEEN[$key]:-}" ] && continue
|
|
SEEN[$key]=1
|
|
printf '| `%s` | `%s` | feeds `%s` |\n' "$site" "$src" "$prot"
|
|
done < <("$NCP" sources "$nc" "$prot" 2>/dev/null)
|
|
while IFS= read -r dst; do
|
|
key="$site|$dst"
|
|
[ -n "${SEEN[$key]:-}" ] && continue
|
|
SEEN[$key]=1
|
|
printf '| `%s` | `%s` | receives from `%s` |\n' "$site" "$dst" "$prot"
|
|
done < <("$NCP" destinations "$nc" "$prot" 2>/dev/null)
|
|
done
|
|
printf '\n'
|
|
|
|
# ─── Footer ───
|
|
printf '---\n\n'
|
|
printf '_Generated: %s · NetConfigs scanned: %d · Pattern: `%s`_\n' \
|
|
"$(date -Iseconds 2>/dev/null || date)" "${#NCONFIGS[@]}" "$PATTERN"
|
|
} | out_target
|
|
|
|
[ -n "$OUT" ] && printf 'nc-document: wrote %s (%d matched threads across %d site(s))\n' \
|
|
"$OUT" "${#MATCHES[@]}" "$(printf '%s\n' "${MATCHES[@]}" | awk -F'|' '{print $1}' | sort -u | wc -l | tr -cd '0-9')" >&2
|