#!/usr/bin/env bash # Tab-completion for the `odysseus` umbrella + every `odysseus-*` CLI. # # Source from your shell rc: # source /path/to/odysseus-ui/scripts/_completion/odysseus.bash # # Or wire it once per machine: # sudo install -m 644 odysseus.bash /etc/bash_completion.d/odysseus # # What it does: # - On the first word after `odysseus`, complete with the list of # subcommands (`mail`, `calendar`, ...). # - On subsequent words, complete with the subcommand's first-token # subcommands (`list`, `show`, ...) which we cache by parsing the # tool's own --help output. Updates lazily; refresh by running # `_odysseus_refresh_cache`. # - Same completion works for the individual `odysseus-foo` scripts. _odysseus_scripts_dir() { # Resolve the scripts/ dir from the script that sources us. We assume # the user sourced the file directly out of scripts/_completion/. local self="${BASH_SOURCE[0]}" while [ -L "$self" ]; do self=$(readlink "$self"); done cd "$(dirname "$self")/.." && pwd } declare -A _ODYSSEUS_SUBS_CACHE=() _odysseus_refresh_cache() { local dir="$(_odysseus_scripts_dir)" _ODYSSEUS_SUBS_CACHE=() # Prefer the project venv's Python so deps (bcrypt, sqlalchemy, ...) # resolve. Falls back to system `python3` for container installs. local py="$dir/../venv/bin/python" [ -x "$py" ] || py="$(command -v python3)" local f for f in "$dir"/odysseus-*; do [ -x "$f" ] || continue case "$f" in *.bak|*.pyc|*.pre-*) continue ;; esac local name="$(basename "$f")" local sub="${name#odysseus-}" local help_out help_out=$("$py" "$f" --help 2>/dev/null) || continue local commands commands=$(echo "$help_out" | grep -oE '\{[a-z0-9_,-]+\}' | head -1 \ | tr -d '{}' | tr ',' ' ') _ODYSSEUS_SUBS_CACHE[$sub]="$commands" done } _odysseus_complete() { [ ${#_ODYSSEUS_SUBS_CACHE[@]} -eq 0 ] && _odysseus_refresh_cache local cur="${COMP_WORDS[COMP_CWORD]}" local cmd="${COMP_WORDS[0]}" # `odysseus ` → list every subcommand if [ "$cmd" = "odysseus" ]; then if [ "$COMP_CWORD" -eq 1 ]; then local subs="${!_ODYSSEUS_SUBS_CACHE[@]} help" COMPREPLY=($(compgen -W "$subs" -- "$cur")) return 0 fi # `odysseus foo ` — complete with foo's own subcommands local sub="${COMP_WORDS[1]}" # `odysseus help ` lists every subcommand if [ "$sub" = "help" ] && [ "$COMP_CWORD" -eq 2 ]; then COMPREPLY=($(compgen -W "${!_ODYSSEUS_SUBS_CACHE[*]}" -- "$cur")) return 0 fi if [ "$COMP_CWORD" -eq 2 ]; then COMPREPLY=($(compgen -W "${_ODYSSEUS_SUBS_CACHE[$sub]}" -- "$cur")) return 0 fi return 0 fi # Direct `odysseus-foo ` (no umbrella) local sub="${cmd#odysseus-}" if [ "$COMP_CWORD" -eq 1 ]; then COMPREPLY=($(compgen -W "${_ODYSSEUS_SUBS_CACHE[$sub]}" -- "$cur")) return 0 fi } # Register the completion for every odysseus-* script + the umbrella. complete -F _odysseus_complete odysseus for f in "$(_odysseus_scripts_dir)"/odysseus-*; do [ -x "$f" ] || continue case "$f" in *.bak|*.pyc|*.pre-*) continue ;; esac complete -F _odysseus_complete "$(basename "$f")" done