cloverleaf-larry/lib/nc-xlate.sh
Bryan Johnson a0502e2ec6 v0.4.2: operational layer — engine ctrl, tables CRUD, xlate viz, smat-diff, create-thread, tclgen
Seven new lib tools — covers the remaining Bryan-requested gaps.

lib/nc-engine.sh
  - Cloverleaf process control. Wraps shipped binaries (hcienginestop,
    hcienginerun, hcienginerestart, hciengineroutetest). Every action
    is Y/N confirmed AND journaled into engine-actions.tsv.
  - Subcommands: stop, start, bounce/restart, status, resend-ib,
    resend-ob, route-test, testxlate, tpstest.

lib/nc-status.sh
  - Runtime status, v1-modelled. Subcommands: sites, threads, not-up,
    connections, queued, raw. Auto-discovers hcienginestat / tstat /
    connstatus binaries; falls back to file-presence heuristics.

lib/nc-table.sh
  - Read+CRUD for .tbl lookup tables. Subcommands: list, show, pairs
    (→csv/tsv), lookup, reverse-lookup, add, delete, create, replace.
  - All modifications journal-backed. Composes csv-to-table /
    table-to-csv for format conversion.

lib/nc-xlate.sh
  - Visualize .xlt files. Parses the TCL nested-block ops format.
    Subcommands: list, show, ops (TSV), tree (ASCII flow), summary
    (counts + segments + tables touched), diff (cross-xlate).
  - Confirmed working against Epic_ADT_CodaMetrix.xlt: identified
    12 PATHCOPY + 1 COPY ops across MSH/EVN/PID/PV1/PV2/PD1/ZPD/ZPV/
    AL1/GT1/IN1/IN2.

lib/nc-smat-diff.sh
  - Cross-env smat content diff. Samples N msgs from each side,
    pairs by configurable HL7 field (default MSH.10 = control ID),
    hl7-diffs each pair with --ignore MSH.7. Outputs per-pair reports
    + master _summary.md with paired/A-only/B-only counts.

lib/nc-create-thread.sh
  - High-level: create a new protocol + optionally splice a route from
    an existing thread to the new one. Both writes journal-backed.
    Confirmed end-to-end: created to_metrics_test outbound + routed
    IB_ADT_muxS → to_metrics_test via journal entries 001+002.

lib/nc-tclgen.sh
  - TCL UPOC scaffolding from intent. Templates: tps-presc, tps-postsc,
    tps-iclkill, xlate-helper, trxid, ack, field-rewrite. Produces
    clean syntax-correct TCL ready to edit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 11:11:30 -07:00

156 lines
5.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# nc-xlate.sh — visualize and explore Cloverleaf xlate (.xlt) files.
# Parses the TCL nested-block format and renders operation flows.
#
# Subcommands:
# list [--site SITE] list .xlt files
# show <name> [--site SITE] dump the raw file
# ops <name> [--site SITE] list operations as TSV (op, in, out, err)
# tree <name> [--site SITE] ASCII tree by op type
# summary <name> [--site SITE] counts by operation + segments touched
# diff <name1> <name2> diff two xlates (semantic, sorted-by-op)
set -o pipefail
NC_SELF="$0"
die() { printf 'nc-xlate: %s\n' "$*" >&2; exit 1; }
locate_xlate() {
local name="$1" site="${2:-${HCISITE:-}}"
name="${name%.xlt}"
for d in "${HCIROOT:-}/$site/Xlate" "${HCIROOT:-}/$site/xlate" "${HCIROOT:-}/Xlate"; do
[ -f "$d/${name}.xlt" ] && { printf '%s\n' "$d/${name}.xlt"; return 0; }
done
return 1
}
cmd_list() {
local site="${HCISITE:-}"
while [ $# -gt 0 ]; do case "$1" in --site) shift; site="$1" ;; esac; shift; done
for d in "${HCIROOT:-}/$site/Xlate" "${HCIROOT:-}/$site/xlate"; do
[ -d "$d" ] || continue
find "$d" -maxdepth 1 -name '*.xlt' -type f 2>/dev/null | sort
done
}
cmd_show() {
local name="$1"; shift
local site="${HCISITE:-}"
while [ $# -gt 0 ]; do case "$1" in --site) shift; site="$1" ;; esac; shift; done
local f; f=$(locate_xlate "$name" "$site") || die "no such xlate: $name"
cat "$f"
}
# Parse the .xlt operations. Each top-level block looks like:
# { { OP <name> }
# { ERR <code> }
# { IN <path> }
# { OUT <path> }
# [ { TABLE <name> } ]
# [ ... ]
# }
# Emit TSV: op_num \t op \t in \t out \t err \t extra
parse_ops() {
local f="$1"
awk '
BEGIN { depth=0; in_block=0; n=0 }
/^end_prologue$/ { in_pre=0; next }
/^prologue$/ { in_pre=1; next }
in_pre { next }
/^[[:space:]]*\{ \{ OP / {
depth=1; in_block=1
op=""; in_path=""; out_path=""; err=""; extra=""
# First line has "{ { OP <name> }"
match($0, /\{ OP [A-Z]+ \}/)
if (RSTART) {
op = substr($0, RSTART+5, RLENGTH-7)
}
next
}
in_block {
# Track depth via gsub-of-self trick
no = gsub(/\{/, "{", $0)
nc = gsub(/\}/, "}", $0)
depth += no - nc
if (match($0, /\{ IN [^}]+\}/)) { in_path = substr($0, RSTART+5, RLENGTH-6); gsub(/[[:space:]]+$/, "", in_path) }
if (match($0, /\{ OUT [^}]+\}/)) { out_path = substr($0, RSTART+6, RLENGTH-7); gsub(/[[:space:]]+$/, "", out_path) }
if (match($0, /\{ ERR [^}]+\}/)) { err = substr($0, RSTART+6, RLENGTH-7); gsub(/[[:space:]]+$/, "", err) }
if (match($0, /\{ TABLE [^}]+\}/)) { extra = "TABLE=" substr($0, RSTART+8, RLENGTH-9); gsub(/[[:space:]]+$/, "", extra) }
if (depth == 0) {
n++
printf "%d\t%s\t%s\t%s\t%s\t%s\n", n, op, in_path, out_path, err, extra
in_block = 0
}
}
' "$f"
}
cmd_ops() {
local name="$1"; shift
local site="${HCISITE:-}"
while [ $# -gt 0 ]; do case "$1" in --site) shift; site="$1" ;; esac; shift; done
local f; f=$(locate_xlate "$name" "$site") || die "no such xlate: $name"
printf 'num\top\tin\tout\terr\textra\n'
parse_ops "$f"
}
cmd_tree() {
local name="$1"; shift
local site="${HCISITE:-}"
while [ $# -gt 0 ]; do case "$1" in --site) shift; site="$1" ;; esac; shift; done
local f; f=$(locate_xlate "$name" "$site") || die "no such xlate: $name"
printf 'Xlate: %s\n' "$name"
parse_ops "$f" | awk -F'\t' '
{
op=$2; in_path=$3; out_path=$4; extra=$6
if (in_path == "" && out_path == "") { printf " %d. %s\n", $1, op; next }
arrow = "→"
if (op == "MOVE") arrow = "↦"
if (op == "DELETE") arrow = "✗"
if (extra != "") arrow = "↦(" extra ")→"
printf " %2d. %-12s %-30s %s %s\n", $1, op, in_path, arrow, out_path
}
'
}
cmd_summary() {
local name="$1"; shift
local site="${HCISITE:-}"
while [ $# -gt 0 ]; do case "$1" in --site) shift; site="$1" ;; esac; shift; done
local f; f=$(locate_xlate "$name" "$site") || die "no such xlate: $name"
printf 'Xlate: %s (path: %s)\n\n' "$name" "$f"
printf 'Operations by type:\n'
parse_ops "$f" | awk -F'\t' 'NR>0 {c[$2]++} END {for (k in c) printf " %-12s %d\n", k, c[k]}' | sort -k2 -rn
printf '\nSegments touched (IN side):\n'
parse_ops "$f" | awk -F'\t' '{
if (match($3, /\.[A-Z][A-Z0-9]+\(/)) {
seg = substr($3, RSTART+1, RLENGTH-2)
c[seg]++
}
} END {for (k in c) printf " %-6s %d\n", k, c[k]}' | sort
printf '\nTables referenced:\n'
parse_ops "$f" | awk -F'\t' '$6 ~ /TABLE=/ { t=substr($6,7); print t }' | sort -u | sed 's/^/ /'
}
cmd_diff() {
local n1="$1" n2="$2"
local f1 f2
f1=$(locate_xlate "$n1") || die "no such xlate: $n1"
f2=$(locate_xlate "$n2") || die "no such xlate: $n2"
diff -u <(parse_ops "$f1" | sort -k2) <(parse_ops "$f2" | sort -k2)
}
SUB="${1:-list}"
case "$SUB" in
list) shift; cmd_list "$@" ;;
show) shift; [ $# -ge 1 ] || die "usage: show NAME"; cmd_show "$@" ;;
ops) shift; [ $# -ge 1 ] || die "usage: ops NAME"; cmd_ops "$@" ;;
tree) shift; [ $# -ge 1 ] || die "usage: tree NAME"; cmd_tree "$@" ;;
summary) shift; [ $# -ge 1 ] || die "usage: summary NAME"; cmd_summary "$@" ;;
diff) shift; [ $# -ge 2 ] || die "usage: diff NAME1 NAME2"; cmd_diff "$@" ;;
help|-h|--help) sed -n '2,15p' "$NC_SELF" ;;
*) die "unknown subcommand: $SUB" ;;
esac