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>
224 lines
7.5 KiB
Bash
Executable File
224 lines
7.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# nc-table.sh — read and modify Cloverleaf lookup tables (.tbl files).
|
|
# Every modification goes through the journal (snapshot + diff + atomic write).
|
|
#
|
|
# Subcommands:
|
|
# list [--site SITE] list .tbl files
|
|
# show <name> [--site SITE] dump the file
|
|
# pairs <name> [--site SITE] [--format csv|tsv] input→output pairs only
|
|
# lookup <name> <input> find output for input
|
|
# reverse-lookup <name> <output> find input(s) for output
|
|
# add <name> <input> <output> [--site SITE] add or update a row (journaled)
|
|
# delete <name> <input> [--site SITE] delete a row by input (journaled)
|
|
# create <name> --from-csv FILE [opts] create a new table from CSV
|
|
# replace <name> --from-csv FILE [opts] replace contents (journaled)
|
|
#
|
|
# Find tables at: $HCISITEDIR/tables/<name>.tbl (or $HCIROOT/Tables/ shared)
|
|
set -o pipefail
|
|
|
|
NC_SELF="$0"
|
|
LIB_DIR="$(cd "$(dirname "$NC_SELF")" && pwd)"
|
|
JOURNAL="$LIB_DIR/journal.sh"
|
|
C2T="$LIB_DIR/csv-to-table.sh"
|
|
T2C="$LIB_DIR/table-to-csv.sh"
|
|
|
|
die() { printf 'nc-table: %s\n' "$*" >&2; exit 1; }
|
|
|
|
[ -f "$JOURNAL" ] && . "$JOURNAL"
|
|
|
|
locate_table() {
|
|
local name="$1" site="${2:-${HCISITE:-}}"
|
|
# Strip .tbl if user gave it
|
|
name="${name%.tbl}"
|
|
for d in \
|
|
"${HCIROOT:-}/$site/tables" \
|
|
"${HCIROOT:-}/$site/Tables" \
|
|
"${HCIROOT:-}/Tables"; do
|
|
[ -f "$d/${name}.tbl" ] && { printf '%s\n' "$d/${name}.tbl"; 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/tables" "${HCIROOT:-}/$site/Tables" "${HCIROOT:-}/Tables"; do
|
|
[ -d "$d" ] || continue
|
|
find "$d" -maxdepth 1 -name '*.tbl' -type f 2>/dev/null | sort
|
|
done | sort -u
|
|
}
|
|
|
|
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_table "$name" "$site") || die "no such table: $name"
|
|
cat "$f"
|
|
}
|
|
|
|
cmd_pairs() {
|
|
local name="$1"; shift
|
|
local site="${HCISITE:-}"
|
|
local fmt="csv"
|
|
while [ $# -gt 0 ]; do case "$1" in
|
|
--site) shift; site="$1" ;;
|
|
--format) shift; fmt="$1" ;;
|
|
esac; shift; done
|
|
local f; f=$(locate_table "$name" "$site") || die "no such table: $name"
|
|
case "$fmt" in
|
|
csv) "$T2C" --with-header "$f" ;;
|
|
tsv) "$T2C" --with-header --delim $'\t' "$f" ;;
|
|
*) die "bad --format" ;;
|
|
esac
|
|
}
|
|
|
|
cmd_lookup() {
|
|
local name="$1" input="$2"
|
|
local site="${HCISITE:-}"
|
|
local f; f=$(locate_table "$name" "$site") || die "no such table: $name"
|
|
"$T2C" "$f" | awk -F',' -v target="$input" '
|
|
BEGIN { found=0 }
|
|
{
|
|
# CSV unquote (basic)
|
|
v=$1; gsub(/^"|"$/, "", v); gsub(/""/, "\"", v)
|
|
if (v == target) { o=$2; gsub(/^"|"$/, "", o); gsub(/""/, "\"", o); print o; found=1; exit }
|
|
}
|
|
END { if (!found) exit 1 }
|
|
'
|
|
}
|
|
|
|
cmd_reverse_lookup() {
|
|
local name="$1" output="$2"
|
|
local site="${HCISITE:-}"
|
|
local f; f=$(locate_table "$name" "$site") || die "no such table: $name"
|
|
"$T2C" "$f" | awk -F',' -v target="$output" '
|
|
{
|
|
v=$2; gsub(/^"|"$/, "", v); gsub(/""/, "\"", v)
|
|
if (v == target) { i=$1; gsub(/^"|"$/, "", i); gsub(/""/, "\"", i); print i }
|
|
}
|
|
'
|
|
}
|
|
|
|
# Modification helpers (journal-backed)
|
|
modify_via_csv() {
|
|
local name="$1" csv_path="$2" site="$3" mode="$4" # mode = add|delete|replace|create
|
|
local action="$mode-table"
|
|
local target
|
|
|
|
# Determine target path
|
|
if [ "$mode" = "create" ]; then
|
|
[ -n "$site" ] || die "create: --site required"
|
|
[ -d "${HCIROOT}/$site/tables" ] || mkdir -p "${HCIROOT}/$site/tables"
|
|
target="${HCIROOT}/$site/tables/${name%.tbl}.tbl"
|
|
[ ! -e "$target" ] || die "table already exists: $target (use replace if you want to overwrite)"
|
|
else
|
|
target=$(locate_table "$name" "$site") || die "no such table: $name"
|
|
fi
|
|
|
|
local new; new=$(mktemp)
|
|
"$C2T" --has-header --out "$new" < "$csv_path" 2>/dev/null \
|
|
|| "$C2T" --has-header "$csv_path" > "$new"
|
|
|
|
if declare -f journal_write >/dev/null 2>&1; then
|
|
journal_write "$target" "$new"
|
|
else
|
|
# No journal — direct write with simple backup
|
|
[ -f "$target" ] && cp -p "$target" "${target}.larry-bak.$(date +%s)"
|
|
mv "$new" "$target"
|
|
echo "(no journal available; backup at ${target}.larry-bak.<ts>)"
|
|
fi
|
|
rm -f "$new"
|
|
}
|
|
|
|
cmd_add() {
|
|
local name="$1" input="$2" output="$3"; shift 3
|
|
local site="${HCISITE:-}"
|
|
while [ $# -gt 0 ]; do case "$1" in --site) shift; site="$1" ;; esac; shift; done
|
|
[ -n "$name" ] && [ -n "$input" ] && [ -n "$output" ] || die "usage: add NAME INPUT OUTPUT"
|
|
local f; f=$(locate_table "$name" "$site") || die "no such table: $name"
|
|
|
|
# Read current pairs, replace existing input row or append, then rewrite
|
|
local tmp_csv; tmp_csv=$(mktemp)
|
|
{
|
|
"$T2C" --with-header "$f"
|
|
# If the new pair wasn't already in the existing data, it'll be added below
|
|
} > "$tmp_csv"
|
|
|
|
local awk_script='
|
|
BEGIN { added=0 }
|
|
NR==1 { print; next }
|
|
{
|
|
n=split($0, c, ",")
|
|
v=c[1]; gsub(/^"|"$/, "", v); gsub(/""/, "\"", v)
|
|
if (v == NEW_IN) {
|
|
printf "%s,%s\n", NEW_IN, NEW_OUT
|
|
added=1
|
|
} else { print }
|
|
}
|
|
END { if (!added) printf "%s,%s\n", NEW_IN, NEW_OUT }
|
|
'
|
|
local newcsv; newcsv=$(mktemp)
|
|
awk -v NEW_IN="$input" -v NEW_OUT="$output" "$awk_script" "$tmp_csv" > "$newcsv"
|
|
modify_via_csv "$name" "$newcsv" "$site" "add"
|
|
rm -f "$tmp_csv" "$newcsv"
|
|
}
|
|
|
|
cmd_delete() {
|
|
local name="$1" input="$2"; shift 2
|
|
local site="${HCISITE:-}"
|
|
while [ $# -gt 0 ]; do case "$1" in --site) shift; site="$1" ;; esac; shift; done
|
|
[ -n "$name" ] && [ -n "$input" ] || die "usage: delete NAME INPUT"
|
|
local f; f=$(locate_table "$name" "$site") || die "no such table: $name"
|
|
local tmp_csv; tmp_csv=$(mktemp)
|
|
"$T2C" --with-header "$f" \
|
|
| awk -F',' -v IN="$input" '
|
|
NR==1 { print; next }
|
|
{
|
|
v=$1; gsub(/^"|"$/, "", v); gsub(/""/, "\"", v)
|
|
if (v == IN) next
|
|
print
|
|
}
|
|
' > "$tmp_csv"
|
|
modify_via_csv "$name" "$tmp_csv" "$site" "delete"
|
|
rm -f "$tmp_csv"
|
|
}
|
|
|
|
cmd_create() {
|
|
local name="$1"; shift
|
|
local from_csv=""
|
|
local site="${HCISITE:-}"
|
|
while [ $# -gt 0 ]; do case "$1" in
|
|
--from-csv) shift; from_csv="$1" ;;
|
|
--site) shift; site="$1" ;;
|
|
esac; shift; done
|
|
[ -f "$from_csv" ] || die "create: --from-csv FILE required"
|
|
modify_via_csv "$name" "$from_csv" "$site" "create"
|
|
}
|
|
|
|
cmd_replace() {
|
|
local name="$1"; shift
|
|
local from_csv=""
|
|
local site="${HCISITE:-}"
|
|
while [ $# -gt 0 ]; do case "$1" in
|
|
--from-csv) shift; from_csv="$1" ;;
|
|
--site) shift; site="$1" ;;
|
|
esac; shift; done
|
|
[ -f "$from_csv" ] || die "replace: --from-csv FILE required"
|
|
modify_via_csv "$name" "$from_csv" "$site" "replace"
|
|
}
|
|
|
|
SUB="${1:-list}"
|
|
case "$SUB" in
|
|
list) shift; cmd_list "$@" ;;
|
|
show) shift; [ $# -ge 1 ] || die "usage: show NAME"; cmd_show "$@" ;;
|
|
pairs) shift; [ $# -ge 1 ] || die "usage: pairs NAME"; cmd_pairs "$@" ;;
|
|
lookup) shift; [ $# -ge 2 ] || die "usage: lookup NAME INPUT"; cmd_lookup "$@" ;;
|
|
reverse-lookup) shift; [ $# -ge 2 ] || die "usage: reverse-lookup NAME OUTPUT"; cmd_reverse_lookup "$@" ;;
|
|
add) shift; cmd_add "$@" ;;
|
|
delete) shift; cmd_delete "$@" ;;
|
|
create) shift; cmd_create "$@" ;;
|
|
replace) shift; cmd_replace "$@" ;;
|
|
help|-h|--help) sed -n '2,20p' "$NC_SELF" ;;
|
|
*) die "unknown subcommand: $SUB" ;;
|
|
esac
|