#!/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 # 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 # 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 # 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 # Interactively prompts the user for a value and stores it in ENV_VALUE_. 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 }