#!/usr/bin/env bash# Bash completions for every project script: install, uninstall or render the completion file.# ============================================================================================## Installs or removes bash tab-completions for every project script.## scripts/bash_completions.sh [install|uninstall|render]## Each script is completed with what it actually accepts — read off its own argument# parser, so keep the tables below in step when a script grows an action or a flag:## _EULER_ACTIONS one action word, in first position (deploy | remove | status …)# _EULER_FLAGS --flags, anywhere on the line# a function when the arguments are not a fixed list: files, directories, or# collaborator slugs (the members of the euler-user group)## A script with no arguments is registered with an empty list, so <Tab> offers nothing# rather than filenames. scripts/setup/authoring_env.sh is not registered: it is a# library the setup scripts source, never run.## Every script is registered three ways — its absolute path, `scripts/…` and# `./scripts/…` — since `complete` matches the command word exactly. (`bash scripts/…`# runs bash, not the script, and so is not completed.)## `install` (re)writes /etc/bash_completion.d/euler-scripts, so re-running it after a# change is how the change arrives. `render` prints that file instead — no sudo.## Author: Vikas Munshi <vikas.munshi@gmail.com># Copyright (c) 2024. All rights reserved.# Licensed under the MIT License.set-euopipefailSCRIPT_DIR="$(dirname"$(readlink-f"$0")")"PROJECT_ROOT="$(dirname"${SCRIPT_DIR}")"COMPLETION_FILE="/etc/bash_completion.d/euler-scripts"# Every script that gets a completion, repo-relative, paired with its completer.REGISTERED=("scripts/bash_completions.sh:_euler_words""scripts/c/compile.sh:_euler_compile""scripts/fixes/fix_pycharm_dimmed_issue.sh:_euler_words""scripts/git/audit.sh:_euler_words""scripts/git/configure-identity.sh:_euler_words""scripts/git/publish.sh:_euler_publish""scripts/git/status.sh:_euler_words""scripts/git/sync.sh:_euler_words""scripts/linters/check.sh:_euler_dirs""scripts/ops/release.sh:_euler_ops_release""scripts/ops/reset-user.sh:_euler_reset_user""scripts/pip/upgrade.sh:_euler_words""scripts/setup/auth.sh:_euler_words""scripts/setup/chrome.sh:_euler_words""scripts/setup/claude_code.sh:_euler_words""scripts/setup/ddns.sh:_euler_words""scripts/setup/dev_env.sh:_euler_dev_env""scripts/setup/egress.sh:_euler_words""scripts/setup/euler-ddns.sh:_euler_words""scripts/setup/firewall.sh:_euler_words""scripts/setup/frontend.sh:_euler_words""scripts/setup/githooks.sh:_euler_words""scripts/setup/msg.sh:_euler_words""scripts/setup/nodejs.sh:_euler_words""scripts/setup/primesieve_numpy_extension.sh:_euler_words""scripts/setup/pycharm_service.sh:_euler_words""scripts/setup/site.sh:_euler_words""scripts/setup/smtp.sh:_euler_words""scripts/setup/upgrade_service.sh:_euler_words""scripts/setup/user.sh:_euler_user""scripts/setup/venv.sh:_euler_words""scripts/version/release.sh:_euler_words")usage(){echo"Usage: $0 [install|uninstall|render]"echoecho" install Install (or refresh) bash completions for all project scripts (default)"echo" uninstall Remove bash completions"echo" render Print the completion file instead of installing it (no sudo)"}check_can_sudo(){if!command-vsudo&>/dev/null;thenecho"Error: sudo is not installed or not found in PATH">&2return1fiif!sudo-v2>/dev/null;thenecho"Error: current user does not have sudo privileges">&2return1fi}# render — the completion file, on stdout.render(){# The completers themselves: single-quoted heredoc, nothing expanded here.cat<<'STATIC_EOF'# Bash completions for euler project scripts# Generated by scripts/bash_completions.sh — edit that, then re-run its `install`.# One action word, offered in first position only. Keyed by the repo-relative path.declare -gA _EULER_ACTIONS=( [scripts/bash_completions.sh]="install uninstall render help --help -h" [scripts/setup/auth.sh]="deploy upgrade redeploy remove status help --help -h" [scripts/setup/chrome.sh]="install install-script uninstall help --help -h" [scripts/setup/claude_code.sh]="install update uninstall status help --help -h" [scripts/setup/ddns.sh]="deploy update status remove help --help -h" [scripts/setup/egress.sh]="deploy remove upgrade reload status help --help -h" [scripts/setup/euler-ddns.sh]="update show help --help -h" [scripts/setup/firewall.sh]="deploy remove reload render status help --help -h" [scripts/setup/frontend.sh]="deploy remove upgrade redeploy renew reload status help --help -h" [scripts/setup/msg.sh]="deploy upgrade redeploy remove status help --help -h" [scripts/setup/nodejs.sh]="install uninstall status help --help -h" [scripts/setup/primesieve_numpy_extension.sh]="install check uninstall help --help -h" [scripts/setup/site.sh]="deploy upgrade redeploy remove status help --help -h" [scripts/setup/smtp.sh]="deploy upgrade remove status test help --help -h" [scripts/setup/upgrade_service.sh]="install uninstall --help -h" [scripts/setup/venv.sh]="deploy remove redeploy clean status help --help -h")# Flags, offered anywhere on the line (an action script may list some too).declare -gA _EULER_FLAGS=( [scripts/git/audit.sh]="--summary" [scripts/git/status.sh]="--summary" [scripts/git/sync.sh]="--dry-run" [scripts/setup/githooks.sh]="install uninstall status cleanup --force help --help -h" [scripts/setup/pycharm_service.sh]="start service stop kill --no-color --help -h" [scripts/version/release.sh]="--dry-run --no-push" [scripts/fixes/fix_pycharm_dimmed_issue.sh]="" [scripts/git/configure-identity.sh]="")# The repo-relative key for the command word, however it was typed._euler_key() { printf 'scripts/%s' "${1##*scripts/}"; }# Collaborator slugs: the members of euler-user (empty on a host without the web stack)._euler_slugs() { getent group euler-user 2>/dev/null | cut -d: -f4 | tr ',' ' '; }_euler_words() { local cur key cur="${COMP_WORDS[COMP_CWORD]}" key="$(_euler_key "$1")" COMPREPLY=() if [[ -n "${_EULER_ACTIONS[${key}]+set}" ]]; then (( COMP_CWORD == 1 )) && COMPREPLY=($(compgen -W "${_EULER_ACTIONS[${key}]}" -- "${cur}")) elif [[ -n "${_EULER_FLAGS[${key}]+set}" ]]; then COMPREPLY=($(compgen -W "${_EULER_FLAGS[${key}]}" -- "${cur}")) else COMPREPLY=($(compgen -f -- "${cur}")) # unlisted (pip/upgrade.sh): plain files fi}_euler_dirs() { COMPREPLY=($(compgen -d -- "${COMP_WORDS[COMP_CWORD]}"))}_euler_publish() { local cur="${COMP_WORDS[COMP_CWORD]}" if [[ "${cur}" == -* ]]; then COMPREPLY=($(compgen -W "--dry-run --help -h" -- "${cur}")) else COMPREPLY=($(compgen -d -- "${cur}")) fi}_euler_compile() { local cur="${COMP_WORDS[COMP_CWORD]}" if [[ "${cur}" == -* ]]; then COMPREPLY=($(compgen -W "--clean" -- "${cur}")) elif (( COMP_CWORD == 1 )); then COMPREPLY=($(compgen -d -S / -- "${cur}") $(compgen -f -X '!*.c' -- "${cur}")) # A directory is a step on the way to the file: no space after it, so <Tab> goes on. [[ ${#COMPREPLY[@]} -eq 1 && "${COMPREPLY[0]}" == */ ]] && compopt -o nospace 2>/dev/null fi}_euler_ops_release() { local cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" if [[ "${prev}" == "--log" ]]; then COMPREPLY=($(compgen -f -- "${cur}")) else COMPREPLY=($(compgen -W "--dry-run --yes -y --skip-updates --no-deploy --no-follow --no-tmux --log --help -h" -- "${cur}")) fi}_euler_reset_user() { local cur="${COMP_WORDS[COMP_CWORD]}" if [[ "${cur}" == -* ]]; then COMPREPLY=($(compgen -W "--dry-run --yes -y --no-filter --help -h" -- "${cur}")) else COMPREPLY=($(compgen -W "$(_euler_slugs)" -- "${cur}")) fi}_euler_user() { local cur="${COMP_WORDS[COMP_CWORD]}" if (( COMP_CWORD == 1 )); then COMPREPLY=($(compgen -W "deploy upgrade redeploy remove provision deprovision purge-legacy status help --help -h" -- "${cur}")) return fi case "${COMP_WORDS[1]}" in status|deprovision) (( COMP_CWORD == 2 )) && COMPREPLY=($(compgen -W "$(_euler_slugs)" -- "${cur}")) ;; provision) # provision <slug> <email> <profile> (( COMP_CWORD == 4 )) && COMPREPLY=($(compgen -W "reader contributor maintainer admin" -- "${cur}")) ;; *) COMPREPLY=() ;; esac}_euler_dev_env() { local cur prev cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" local commands="install uninstall status" local options="--help --dry-run" local targets="all c lua primesieve ruby python" # Check whether a command word already appears in the line local has_command="" local word for word in "${COMP_WORDS[@]:1:COMP_CWORD-1}"; do case "${word}" in install|uninstall|status) has_command="${word}" ;; esac done if [[ "${cur}" == -* ]]; then COMPREPLY=($(compgen -W "${options}" -- "${cur}")) elif [[ -n "${has_command}" ]]; then COMPREPLY=($(compgen -W "${targets}" -- "${cur}")) elif [[ "${prev}" == "--dry-run" ]]; then COMPREPLY=($(compgen -W "${commands}" -- "${cur}")) else COMPREPLY=($(compgen -W "${commands} ${options}" -- "${cur}")) fi}STATIC_EOF# The registrations, with this checkout's root embedded: absolute, relative, ./relative.localentrypathfnprefixforprefixin"${PROJECT_ROOT}/""""./";doecho""forentryin"${REGISTERED[@]}";dopath="${entry%%:*}"fn="${entry##*:}"printf"complete -F %-20s '%s%s'\n""${fn}""${prefix}""${path}"donedone}install(){check_can_sudo||return1localverb='installed'[[-f"${COMPLETION_FILE}"]]&&verb='refreshed'render|sudotee"${COMPLETION_FILE}">/dev/nullecho"Bash completions ${verb} at ${COMPLETION_FILE} (${#REGISTERED[@]} scripts)"echo"Run 'source ${COMPLETION_FILE}' to activate in the current shell"}uninstall(){check_can_sudo||return1if[-f"${COMPLETION_FILE}"];thensudorm-f"${COMPLETION_FILE}"echo"Bash completions removed from ${COMPLETION_FILE}"elseecho"Bash completions are not installed at ${COMPLETION_FILE}"fi}if[["${BASH_SOURCE[0]}"=="${0}"]];thenACTION="${1:-install}"case"${ACTION}"ininstall)install;;uninstall)uninstall;;render)render;;-h|--help|help)usage;;*)echo"Unknown action: ${ACTION}"usageexit1;;esacfi