218 lines
6.1 KiB
Bash
218 lines
6.1 KiB
Bash
#!/usr/bin/env bash
|
|
# Interactive per-variable prompting.
|
|
# Source this file; do not execute it directly.
|
|
# Requires colors.sh and validate.sh to be sourced first.
|
|
|
|
# _prompt_enum <var_name> <options_csv> <default>
|
|
# Prints a numbered menu and returns the chosen value via stdout.
|
|
_prompt_enum() {
|
|
local var_name="$1"
|
|
local options_csv="$2"
|
|
local default="$3"
|
|
|
|
local -a opts
|
|
local IFS=','
|
|
read -ra opts <<< "$options_csv"
|
|
unset IFS
|
|
|
|
local default_idx=0
|
|
local i
|
|
for i in "${!opts[@]}"; do
|
|
printf " %d) %s" $((i + 1)) "${opts[$i]}" >&2
|
|
if [[ "${opts[$i]}" == "$default" ]]; then
|
|
printf " ${DIM}(default)${NC}" >&2
|
|
default_idx=$((i + 1))
|
|
fi
|
|
printf "\n" >&2
|
|
done
|
|
|
|
local choice value
|
|
while true; do
|
|
if [[ $default_idx -gt 0 ]]; then
|
|
read -r -p " Choice [1-${#opts[@]}, default ${default_idx}]: " choice >&2
|
|
else
|
|
read -r -p " Choice [1-${#opts[@]}]: " choice >&2
|
|
fi
|
|
|
|
# Empty input → use default
|
|
if [[ -z "$choice" && $default_idx -gt 0 ]]; then
|
|
echo "${opts[$((default_idx - 1))]}"
|
|
return 0
|
|
fi
|
|
|
|
# Numeric selection
|
|
if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#opts[@]} )); then
|
|
echo "${opts[$((choice - 1))]}"
|
|
return 0
|
|
fi
|
|
|
|
# Direct string match
|
|
local opt
|
|
for opt in "${opts[@]}"; do
|
|
if [[ "$choice" == "$opt" ]]; then
|
|
echo "$opt"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
printf "${RED} Invalid choice. Enter a number between 1 and %d.${NC}\n" "${#opts[@]}" >&2
|
|
done
|
|
}
|
|
|
|
# _prompt_bool <default>
|
|
# Prompts y/n and returns canonical "true" or "false" via stdout.
|
|
_prompt_bool() {
|
|
local default="$1"
|
|
local hint
|
|
|
|
case "${default,,}" in
|
|
true|yes|1|y) hint="Y/n" ;;
|
|
false|no|0|n) hint="y/N" ;;
|
|
*) hint="y/n" ;;
|
|
esac
|
|
|
|
local input
|
|
while true; do
|
|
read -r -p " Enable? [${hint}]: " input >&2
|
|
if [[ -z "$input" && -n "$default" ]]; then
|
|
normalize_bool "$default"
|
|
return 0
|
|
fi
|
|
case "${input,,}" in
|
|
y|yes|true|1) echo "true"; return 0 ;;
|
|
n|no|false|0) echo "false"; return 0 ;;
|
|
*) printf "${RED} Enter y or n.${NC}\n" >&2 ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# _prompt_secret <var_name> <default>
|
|
# Reads a secret (hidden) value, with optional confirmation, via stdout.
|
|
_prompt_secret() {
|
|
local var_name="$1"
|
|
local default="$2"
|
|
local value confirm
|
|
|
|
while true; do
|
|
if [[ -n "$default" ]]; then
|
|
read -r -s -p " Value (leave blank to keep default): " value >&2
|
|
else
|
|
read -r -s -p " Value: " value >&2
|
|
fi
|
|
printf "\n" >&2
|
|
|
|
if [[ -z "$value" && -n "$default" ]]; then
|
|
echo "$default"
|
|
return 0
|
|
fi
|
|
|
|
if [[ -n "$value" ]]; then
|
|
read -r -s -p " Confirm value: " confirm >&2
|
|
printf "\n" >&2
|
|
if [[ "$value" == "$confirm" ]]; then
|
|
echo "$value"
|
|
return 0
|
|
fi
|
|
printf "${RED} Values do not match. Try again.${NC}\n" >&2
|
|
else
|
|
printf "${RED} Value cannot be empty for a secret field with no default.${NC}\n" >&2
|
|
fi
|
|
done
|
|
}
|
|
|
|
# prompt_variable <var_name>
|
|
# Interactively prompts the user for a value and stores it in ENV_VALUE_<var_name>.
|
|
prompt_variable() {
|
|
local var_name="$1"
|
|
|
|
# Dereference metadata
|
|
local desc_var="ENV_DESC_${var_name}"
|
|
local type_var="ENV_TYPE_${var_name}"
|
|
local required_var="ENV_REQUIRED_${var_name}"
|
|
local default_var="ENV_DEFAULT_${var_name}"
|
|
|
|
local desc="${!desc_var}"
|
|
local type="${!type_var}"
|
|
local required="${!required_var}"
|
|
local default="${!default_var}"
|
|
|
|
# ---- Header ----
|
|
printf "\n" >&2
|
|
printf "${BOLD}${CYAN}%s${NC}" "$var_name" >&2
|
|
if [[ "$required" == "true" ]]; then
|
|
printf " ${RED}(required)${NC}" >&2
|
|
else
|
|
printf " ${DIM}(optional)${NC}" >&2
|
|
fi
|
|
if [[ -n "$type" && "$type" != "string" ]]; then
|
|
printf " ${DIM}[%s]${NC}" "$type" >&2
|
|
fi
|
|
printf "\n" >&2
|
|
|
|
if [[ -n "$desc" ]]; then
|
|
printf " ${DIM}%s${NC}\n" "$desc" >&2
|
|
fi
|
|
|
|
# ---- Type-specific prompt ----
|
|
local value
|
|
|
|
case "$type" in
|
|
bool)
|
|
value="$(_prompt_bool "$default")"
|
|
;;
|
|
|
|
secret)
|
|
value="$(_prompt_secret "$var_name" "$default")"
|
|
;;
|
|
|
|
enum:*)
|
|
local options="${type#enum:}"
|
|
printf " ${DIM}Options:${NC}\n" >&2
|
|
value="$(_prompt_enum "$var_name" "$options" "$default")"
|
|
;;
|
|
|
|
*)
|
|
# Generic text input (string, number, url, email)
|
|
local prompt_str
|
|
if [[ -n "$default" ]]; then
|
|
prompt_str=" Value (default: ${default}): "
|
|
else
|
|
prompt_str=" Value: "
|
|
fi
|
|
|
|
while true; do
|
|
read -r -p "$prompt_str" value >&2
|
|
if [[ -z "$value" ]]; then
|
|
value="$default"
|
|
fi
|
|
|
|
if validate_value "$var_name" "$value" "$type" "$required"; then
|
|
break
|
|
fi
|
|
done
|
|
;;
|
|
esac
|
|
|
|
# For bool/enum/secret we still run the required check after collecting value
|
|
if [[ "$type" == "bool" || "$type" == "secret" || "$type" == enum:* ]]; then
|
|
while ! validate_value "$var_name" "$value" "$type" "$required"; do
|
|
case "$type" in
|
|
bool) value="$(_prompt_bool "$default")" ;;
|
|
secret) value="$(_prompt_secret "$var_name" "$default")" ;;
|
|
enum:*) value="$(_prompt_enum "$var_name" "${type#enum:}" "$default")" ;;
|
|
esac
|
|
done
|
|
fi
|
|
|
|
printf -v "ENV_VALUE_${var_name}" '%s' "$value"
|
|
}
|
|
|
|
# prompt_all_variables
|
|
# Iterates over ENV_VARS and prompts for each one.
|
|
prompt_all_variables() {
|
|
local var_name
|
|
for var_name in "${ENV_VARS[@]}"; do
|
|
prompt_variable "$var_name"
|
|
done
|
|
}
|