Files
Senko-san 4108121984 feat(wizard): prompt for MusicBrainz/AcoustID contact email
Replace the hardcoded MUSICBRAINZ_USER_AGENT placeholder in env.template
with an optional MUSICBRAINZ_OWNER_EMAIL prompt in step_enrichment.
The backend now composes a valid User-Agent from app name + version +
this email (falling back to the project URL if left blank).
2026-06-11 00:40:00 +03:00

136 lines
5.4 KiB
Bash

# Pure-bash prompt helpers — no external TUI, no dependencies.
#
# Every prompt shows its default, re-asks on invalid input, and lets Ctrl+C
# abort cleanly (the trap is installed in deploy.sh). Results are returned via
# the REPLY-style globals documented on each function.
# -- colours (disabled if not a TTY or NO_COLOR set) -----------------------
if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then
C_RESET=$'\033[0m'; C_BOLD=$'\033[1m'; C_DIM=$'\033[2m'
C_CYAN=$'\033[36m'; C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m'; C_RED=$'\033[31m'
else
C_RESET=''; C_BOLD=''; C_DIM=''; C_CYAN=''; C_GREEN=''; C_YELLOW=''; C_RED=''
fi
ui_title() { printf '\n%s%s%s\n' "$C_BOLD$C_CYAN" "$1" "$C_RESET"; }
ui_info() { printf '%s\n' "$1"; }
ui_dim() { printf '%s%s%s\n' "$C_DIM" "$1" "$C_RESET"; }
ui_ok() { printf '%s✓ %s%s\n' "$C_GREEN" "$1" "$C_RESET"; }
ui_warn() { printf '%s! %s%s\n' "$C_YELLOW" "$1" "$C_RESET"; }
ui_err() { printf '%s✗ %s%s\n' "$C_RED" "$1" "$C_RESET" >&2; }
# ui_input PROMPT DEFAULT [VALIDATOR_FN]
# Sets UI_VALUE. VALIDATOR_FN (optional) receives the candidate value and
# returns 0 to accept; on rejection it should print why (to stderr).
ui_input() {
local prompt="$1" default="${2:-}" validator="${3:-}" ans
while true; do
if [[ -n "$default" ]]; then
read -r -p "$prompt [${C_DIM}${default}${C_RESET}]: " ans || exit 130
ans="${ans:-$default}"
else
read -r -p "$prompt: " ans || exit 130
fi
if [[ -n "$validator" ]]; then
if ! "$validator" "$ans"; then continue; fi
fi
UI_VALUE="$ans"
return 0
done
}
# ui_secret PROMPT — no echo. Sets UI_VALUE.
ui_secret() {
local prompt="$1" ans
read -r -s -p "$prompt: " ans || exit 130
echo
UI_VALUE="$ans"
}
# ui_yesno PROMPT DEFAULT(yes|no) — sets UI_BOOL to "yes"/"no", returns 0/1.
ui_yesno() {
local prompt="$1" default="${2:-yes}" ans hint
[[ "$default" == "yes" ]] && hint="[Y/n]" || hint="[y/N]"
while true; do
read -r -p "$prompt $hint: " ans || exit 130
ans="${ans:-$default}"
case "${ans,,}" in
y|yes|да|д) UI_BOOL="yes"; return 0 ;;
n|no|нет|н) UI_BOOL="no"; return 1 ;;
*) ui_warn "$(t invalid_input)" ;;
esac
done
}
# ui_select PROMPT "label1" "label2" ...
# Numbered single-choice menu. Sets UI_INDEX (1-based) and UI_VALUE (label).
ui_select() {
local prompt="$1"; shift
local -a opts=("$@")
local i ans
ui_info "$prompt"
for i in "${!opts[@]}"; do
printf ' %s%d%s) %s\n' "$C_CYAN" "$((i + 1))" "$C_RESET" "${opts[$i]}"
done
while true; do
read -r -p "> [${C_DIM}1${C_RESET}]: " ans || exit 130
ans="${ans:-1}"
if [[ "$ans" =~ ^[0-9]+$ ]] && ((ans >= 1 && ans <= ${#opts[@]})); then
UI_INDEX="$ans"
UI_VALUE="${opts[$((ans - 1))]}"
return 0
fi
ui_warn "$(t invalid_input)"
done
}
# ui_multiselect PROMPT "key1:label1:on" "key2:label2:off" "key3:label3:locked"
# Toggle items by number; 'locked' items cannot be turned off. Empty line
# confirms. Sets UI_SELECTED (space-separated keys that ended up on).
ui_multiselect() {
local prompt="$1"; shift
local -a keys=() labels=() states=() locks=()
local spec key label state
for spec in "$@"; do
key="${spec%%:*}"; spec="${spec#*:}"
label="${spec%%:*}"; state="${spec#*:}"
keys+=("$key"); labels+=("$label")
if [[ "$state" == "locked" ]]; then states+=("on"); locks+=("yes")
else states+=("$state"); locks+=("no"); fi
done
local i ans
while true; do
ui_info "$prompt"
for i in "${!keys[@]}"; do
local mark="[ ]"; [[ "${states[$i]}" == "on" ]] && mark="[x]"
local lock=""; [[ "${locks[$i]}" == "yes" ]] && lock=" ${C_DIM}(required)${C_RESET}"
printf ' %s%d%s) %s %s%s\n' "$C_CYAN" "$((i + 1))" "$C_RESET" "$mark" "${labels[$i]}" "$lock"
done
ui_dim "$(t enter_to_keep) — number toggles, Enter confirms"
read -r -p "> " ans || exit 130
[[ -z "$ans" ]] && break
if [[ "$ans" =~ ^[0-9]+$ ]] && ((ans >= 1 && ans <= ${#keys[@]})); then
local idx=$((ans - 1))
if [[ "${locks[$idx]}" == "yes" ]]; then
ui_warn "$(t backend_required)"
elif [[ "${states[$idx]}" == "on" ]]; then states[$idx]="off"
else states[$idx]="on"; fi
else
ui_warn "$(t invalid_input)"
fi
done
UI_SELECTED=""
for i in "${!keys[@]}"; do
[[ "${states[$i]}" == "on" ]] && UI_SELECTED+="${keys[$i]} "
done
UI_SELECTED="${UI_SELECTED% }"
}
# -- common validators (for ui_input) --------------------------------------
v_nonempty() { [[ -n "$1" ]] || { ui_warn "$(t invalid_input)"; return 1; }; }
v_port() { [[ "$1" =~ ^[0-9]+$ ]] && (($1 >= 1 && $1 <= 65535)) || { ui_warn "$(t invalid_input)"; return 1; }; }
v_url() { [[ "$1" =~ ^https?:// ]] || { ui_warn "$(t invalid_input)"; return 1; }; }
v_redis() { [[ "$1" =~ ^rediss?:// ]] || { ui_warn "$(t invalid_input)"; return 1; }; }
v_domain() { [[ "$1" =~ ^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]] || { ui_warn "$(t invalid_input)"; return 1; }; }
v_email_opt() { [[ -z "$1" || "$1" =~ ^[^[:space:]@]+@[^[:space:]@]+\.[a-zA-Z]{2,}$ ]] || { ui_warn "$(t invalid_input)"; return 1; }; }