11b4a679a8
gates / consistency-and-conventions (push) Successful in 20s
bank.env is line-oriented (name|description|command) so commands with real newlines spanned records: bank_load truncated the command to its first line and the remaining script lines became bogus entries. The bank_get+cut -f3 retrieval path also truncated at embedded tabs. - lib/bank-lib.sh: v2 format escapes backslash->\\ and newline->\\n in the command field, writes # BANK_VERSION: 2; bank_load decodes with printf %b only for v2 files, so existing v1 files load raw (backward compatible, verified against the real ts-google entry). - bin/pos-bank: cmd_show/cmd_run/cmd_edit now read fields from the BANK_* arrays via bank_find instead of bank_get+cut. - tests/t-bank.sh: +13 checks (71 total) - multiline round-trip exact bytes, literal backslash-n, v1 raw-backslash compat, v1+re-save byte-identical, CLI show/run full script.
184 lines
6.8 KiB
Bash
184 lines
6.8 KiB
Bash
#!/usr/bin/env bash
|
|
# lib/bank-lib.sh — shared storage helpers for the Command Bank.
|
|
# Sourced by bin/pos-bank. Uses err() from lib/common.sh.
|
|
#
|
|
# Storage: ~/.config/linux_post_install/bank.env
|
|
# Format: name|description|command
|
|
# v2: commands with real newlines are saved with \\ (backslash) and \n
|
|
# (newline) escapes; files written by v1 have no marker and load raw.
|
|
#
|
|
# Contracts:
|
|
# * Defines ONLY bank_* functions — sourcing never clobbers a tool's helpers.
|
|
# * Requires common.sh to be sourced by the CALLER.
|
|
# * NEVER exits — return codes only.
|
|
# * Performs NO interactive prompts (those stay in bin/pos-bank).
|
|
|
|
CONFIG_DIR="${CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/linux_post_install}"
|
|
BANK_FILE="${BANK_FILE:-${CONFIG_DIR}/bank.env}"
|
|
|
|
# ── Load ────────────────────────────────────────────────────────
|
|
# Populate parallel arrays: BANK_NAMES, BANK_DESCS, BANK_CMDS.
|
|
bank_load() {
|
|
BANK_NAMES=(); BANK_DESCS=(); BANK_CMDS=()
|
|
[ -f "$BANK_FILE" ] || return 0
|
|
|
|
local line name desc cmd v2=0
|
|
while IFS= read -r line; do
|
|
[[ "$line" =~ ^[[:space:]]*# ]] && { [[ "$line" == *"BANK_VERSION: 2"* ]] && v2=1; continue; }
|
|
[[ -z "${line// /}" ]] && continue
|
|
IFS='|' read -r name desc cmd <<< "$line"
|
|
[[ -z "$name" ]] && continue
|
|
[ "$v2" -eq 1 ] && cmd="$(printf '%b' "$cmd")"
|
|
BANK_NAMES+=("$name")
|
|
BANK_DESCS+=("${desc:-}")
|
|
BANK_CMDS+=("${cmd:-}")
|
|
done < "$BANK_FILE"
|
|
}
|
|
|
|
# ── Save ────────────────────────────────────────────────────────
|
|
# Atomic overwrite from parallel arrays.
|
|
bank_save() {
|
|
local dir
|
|
dir="$(dirname "$BANK_FILE")"
|
|
mkdir -p "$dir"
|
|
local tmp
|
|
tmp="$(mktemp "${dir}/.bank.XXXXXX")"
|
|
{
|
|
printf '%s\n' "# Command Bank — managed by pos bank (do not hand-edit)"
|
|
printf '%s\n' "# Format: name|description|command (\\n = escaped newline in command)"
|
|
printf '%s\n' "# BANK_VERSION: 2"
|
|
local i
|
|
for ((i = 0; i < ${#BANK_NAMES[@]}; i++)); do
|
|
local cmd="${BANK_CMDS[$i]}"
|
|
# Escape: every backslash → \\, every real newline → \n (keeps one record per physical line)
|
|
cmd="${cmd//\\/\\\\}"
|
|
cmd="${cmd//$'\n'/\\n}"
|
|
printf '%s|%s|%s\n' "${BANK_NAMES[$i]}" "${BANK_DESCS[$i]}" "$cmd"
|
|
done
|
|
} > "$tmp"
|
|
chmod 600 "$tmp"
|
|
mv "$tmp" "$BANK_FILE"
|
|
}
|
|
|
|
# ── Add ─────────────────────────────────────────────────────────
|
|
bank_add() {
|
|
local name="$1" desc="$2" cmd="$3"
|
|
bank_load
|
|
local i
|
|
for ((i = 0; i < ${#BANK_NAMES[@]}; i++)); do
|
|
[[ "${BANK_NAMES[$i]}" == "$name" ]] && return 1
|
|
done
|
|
BANK_NAMES+=("$name")
|
|
BANK_DESCS+=("$desc")
|
|
BANK_CMDS+=("$cmd")
|
|
bank_save
|
|
}
|
|
|
|
# ── Remove ──────────────────────────────────────────────────────
|
|
bank_remove() {
|
|
local name="$1"
|
|
bank_load
|
|
local found=0 i
|
|
for ((i = 0; i < ${#BANK_NAMES[@]}; i++)); do
|
|
if [[ "${BANK_NAMES[$i]}" == "$name" ]]; then
|
|
unset 'BANK_NAMES[i]'
|
|
unset 'BANK_DESCS[i]'
|
|
unset 'BANK_CMDS[i]'
|
|
found=1
|
|
break
|
|
fi
|
|
done
|
|
[[ "$found" -eq 1 ]] || return 1
|
|
# Re-index arrays (unset leaves gaps)
|
|
BANK_NAMES=("${BANK_NAMES[@]}")
|
|
BANK_DESCS=("${BANK_DESCS[@]}")
|
|
BANK_CMDS=("${BANK_CMDS[@]}")
|
|
bank_save
|
|
}
|
|
|
|
# ── Update ──────────────────────────────────────────────────────
|
|
bank_update() {
|
|
local name="$1" desc="$2" cmd="$3"
|
|
bank_load
|
|
local i
|
|
for ((i = 0; i < ${#BANK_NAMES[@]}; i++)); do
|
|
if [[ "${BANK_NAMES[$i]}" == "$name" ]]; then
|
|
BANK_DESCS[$i]="$desc"
|
|
BANK_CMDS[$i]="$cmd"
|
|
bank_save
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# ── Find (by name) ──────────────────────────────────────────────
|
|
# Sets BANK_IDX. Returns 0 if found, 1 if not.
|
|
bank_find() {
|
|
local name="$1"
|
|
bank_load
|
|
local i
|
|
for ((i = 0; i < ${#BANK_NAMES[@]}; i++)); do
|
|
if [[ "${BANK_NAMES[$i]}" == "$name" ]]; then
|
|
BANK_IDX=$i
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# ── Get (by name) ───────────────────────────────────────────────
|
|
# Outputs: name\tdescription\tcommand (tab-separated).
|
|
bank_get() {
|
|
local name="$1"
|
|
bank_find "$name" || return 1
|
|
printf '%s\t%s\t%s\n' "${BANK_NAMES[$BANK_IDX]}" "${BANK_DESCS[$BANK_IDX]}" "${BANK_CMDS[$BANK_IDX]}"
|
|
}
|
|
|
|
# ── List names ──────────────────────────────────────────────────
|
|
bank_list_names() {
|
|
bank_load
|
|
local i
|
|
for ((i = 0; i < ${#BANK_NAMES[@]}; i++)); do
|
|
printf '%s\n' "${BANK_NAMES[$i]}"
|
|
done
|
|
}
|
|
|
|
# ── Count ───────────────────────────────────────────────────────
|
|
bank_count() {
|
|
bank_load
|
|
echo "${#BANK_NAMES[@]}"
|
|
}
|
|
|
|
# ── Validate name ───────────────────────────────────────────────
|
|
bank_valid_name() {
|
|
[[ "$1" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]]
|
|
}
|
|
|
|
# ── Extract {param} names from a command template ───────────────
|
|
_extract_params() {
|
|
local cmd="$1"
|
|
grep -oE '\{[a-zA-Z_][a-zA-Z0-9_]*\}' <<< "$cmd" 2>/dev/null | \
|
|
sed 's/[{}]//g' | \
|
|
awk '!seen[$0]++' || true
|
|
}
|
|
|
|
# ── Substitute {param} with quoted values ───────────────────────
|
|
# Usage: _substitute_params "cmd template" "key1=val1" "key2=val2" ...
|
|
# Uses awk to build quoted replacements safely, avoiding bash quote-nesting.
|
|
_substitute_params() {
|
|
local cmd="$1"; shift
|
|
# Build a sed expression: for each key=val, replace {key} with "val"
|
|
local sed_expr=""
|
|
while [ $# -gt 0 ]; do
|
|
local key="${1%%=*}"
|
|
local val="${1#*=}"
|
|
# Escape sed special chars in the value
|
|
val="${val//\\/\\\\}"
|
|
val="${val//\//\\/}"
|
|
sed_expr+="s/{${key}}/\"${val}\"/g; "
|
|
shift
|
|
done
|
|
[ -n "$sed_expr" ] && printf '%s' "$cmd" | sed "$sed_expr" || printf '%s' "$cmd"
|
|
}
|