#!/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 [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