diff --git a/stacks.sh b/stacks.sh index a39f5c5..92732db 100755 --- a/stacks.sh +++ b/stacks.sh @@ -726,6 +726,7 @@ REDIS # ─── Shell Completion ───────────────────────────────────────────────────────── _completion_bash() { + local stacks_init="$1" # pre-rendered stacks array initialisation block cat << 'EOF' # stacks.sh bash completion — source this file or place in /etc/bash_completion.d/ _stacks_complete() { @@ -736,19 +737,10 @@ _stacks_complete() { prev="${COMP_WORDS[COMP_CWORD-1]}" } - # Locate the stacks dir from the invoked script path local script="${COMP_WORDS[0]}" - local stacks_dir - stacks_dir="$(cd "$(dirname "$(realpath "$script" 2>/dev/null || echo "$script")")" 2>/dev/null && pwd)" - - local -a stacks=() - if [[ -d "$stacks_dir" ]]; then - for d in "$stacks_dir"/*/; do - local n; n="$(basename "$d")" - [[ "$n" == _* ]] && continue - [[ -f "$d/compose.yml" ]] && stacks+=("$n") - done - fi +EOF + echo "$stacks_init" + cat << 'EOF' local commands="ls ps up down restart pull logs exec run config images top stats compose update backup new completion version help" local update_subs="install uninstall run status logs next" @@ -758,7 +750,7 @@ _stacks_complete() { case "${COMP_WORDS[1]}" in update) COMPREPLY=( $(compgen -W "$update_subs" -- "$cur") ); return ;; backup) COMPREPLY=( $(compgen -W "$backup_subs" -- "$cur") ); return ;; - completion) COMPREPLY=( $(compgen -W "bash zsh --install" -- "$cur") ); return ;; + completion) COMPREPLY=( $(compgen -W "bash zsh --install --static" -- "$cur") ); return ;; new) case "$prev" in --db) COMPREPLY=( $(compgen -W "postgres mysql mariadb" -- "$cur") ); return ;; @@ -803,22 +795,15 @@ EOF } _completion_zsh() { + local stacks_init="$1" # pre-rendered stacks array initialisation block cat << 'EOF' #compdef stacks.sh stacks _stacks() { local script="${words[1]}" - local stacks_dir - stacks_dir="$(cd "$(dirname "$(realpath "$script" 2>/dev/null || echo "$script")")" 2>/dev/null && pwd)" - - local -a stacks=() - if [[ -d "$stacks_dir" ]]; then - for d in "$stacks_dir"/*/; do - local n="${d:t}" - [[ "$n" == _* ]] && continue - [[ -f "${d}compose.yml" ]] && stacks+=("$n") - done - fi +EOF + echo "$stacks_init" + cat << 'EOF' local -a commands=( 'ls:List all stacks with live status' @@ -916,6 +901,7 @@ _stacks() { completion) _arguments \ '--install[Install the completion file]' \ + '--static[Bake current stack list into completion script]' \ '1:shell:(bash zsh)' ;; esac @@ -928,13 +914,14 @@ EOF } cmd_completion() { - local shell="" install=false + local shell="" install=false static=false for arg in "$@"; do case "$arg" in bash|zsh) shell="$arg" ;; --install) install=true ;; - *) die "Unknown argument: $arg (bash|zsh [--install])" ;; + --static) static=true ;; + *) die "Unknown argument: $arg (bash|zsh [--install] [--static])" ;; esac done @@ -944,10 +931,52 @@ cmd_completion() { warn "No shell specified, defaulting to: $shell" fi + # Build the stacks array initialisation block — either dynamic (runtime discovery) + # or static (current list baked in at generation time). + local bash_stacks_init zsh_stacks_init + + if $static; then + local -a cur_stacks=() + mapfile -t cur_stacks < <(list_all_stacks) + [[ ${#cur_stacks[@]} -eq 0 ]] && die "No stacks found to bake into completion" + local generated_at + generated_at="$(date '+%Y-%m-%d')" + bash_stacks_init=" # Stacks baked in at generation time (${generated_at}) — rerun to update + local -a stacks=(${cur_stacks[*]})" + zsh_stacks_init=" # Stacks baked in at generation time (${generated_at}) — rerun to update + local -a stacks=(${cur_stacks[*]})" + info "Baking ${#cur_stacks[@]} stacks: ${cur_stacks[*]}" + else + bash_stacks_init=' # Dynamically discover stacks from the script location at completion time + local stacks_dir + stacks_dir="$(cd "$(dirname "$(realpath "$script" 2>/dev/null || echo "$script")")" 2>/dev/null && pwd)" + + local -a stacks=() + if [[ -d "$stacks_dir" ]]; then + for d in "$stacks_dir"/*/; do + local n; n="$(basename "$d")" + [[ "$n" == _* ]] && continue + [[ -f "$d/compose.yml" ]] && stacks+=("$n") + done + fi' + zsh_stacks_init=' # Dynamically discover stacks from the script location at completion time + local stacks_dir + stacks_dir="$(cd "$(dirname "$(realpath "$script" 2>/dev/null || echo "$script")")" 2>/dev/null && pwd)" + + local -a stacks=() + if [[ -d "$stacks_dir" ]]; then + for d in "$stacks_dir"/*/; do + local n="${d:t}" + [[ "$n" == _* ]] && continue + [[ -f "${d}compose.yml" ]] && stacks+=("$n") + done + fi' + fi + local content case "$shell" in - bash) content="$(_completion_bash)" ;; - zsh) content="$(_completion_zsh)" ;; + bash) content="$(_completion_bash "$bash_stacks_init")" ;; + zsh) content="$(_completion_zsh "$zsh_stacks_init")" ;; *) die "Unsupported shell: $shell (bash|zsh)" ;; esac @@ -1042,7 +1071,8 @@ ${BOLD}UTILITIES${RESET} --db postgres|mysql|mariadb Add a database service --redis Add a Redis service --no-traefik Expose port instead of Traefik labels - ${CYAN}completion${RESET} bash|zsh [--install] Generate/install shell completion + ${CYAN}completion${RESET} bash|zsh [--install] [--static] Generate/install shell completion + --static Bake current stack list in (no runtime discovery) ${CYAN}version${RESET} Print version ${CYAN}help${RESET} Show this help