Refactor the bash completion script

The script was lacking structure and had grown a number of one-off hacks
that would be better as reusable functions. So,

 - give each subcommand that has completions it's own function
 - move completion of formulae, installed brews, and outdated brews into
   reusable functions
 - introduce a general __brewcomp() function that takes a string of tab,
   space, and/or newline separated items and converts all seperators to
   newlines, and then generates a reply with compgen().

These changes should allow for easier addition of new features in the
future.

As a bonus, completion for `brew log` will include git-log options if
the git completion script is also loaded.

_brew_to_completion() is kept around for compatiblity.

Signed-off-by: Jack Nagel <jacknagel@gmail.com>
This commit is contained in:
Jack Nagel 2012-01-21 00:08:35 -06:00
parent ab19242d04
commit ba69e17073

View File

@ -1,236 +1,335 @@
# Bash completion script for brew(1)
#
# To use, edit your .bashrc and add:
# source `brew --prefix`/Library/Contributions/brew_bash_completion.sh
# To use, add the following to your .bashrc:
#
# . $(brew --repository)/Library/Contributions/brew_bash_completion.sh
#
# Alternatively, if you have installed the bash-completion package,
# you can create a symlink to this file in one of the following directories:
#
# $(brew --prefix)/etc/bash_completion.d
# $(brew --prefix)/share/bash-completion/completions
#
# and bash-completion will source it automatically.
#
# The __brew_ps1() function can be used to annotate your PS1 with
# Homebrew debugging information; it behaves similarly to the __git_ps1()
# function provided by the git's bash completion script.
#
# For example, the prompt string
#
# PS1='\u@\h \W $(__brew_ps1 "(%s)") $'
#
# would result in a prompt like
#
# user@hostname cwd $
#
# but if you are currently engaged in an interactive or debug install,
# (i.e., you invoked `brew install` with either '-i' or '-d'), then the
# prompt would look like
# user@hostname cwd (formula_name|DEBUG) $
#
# user@hostname cwd (<formula_name>|DEBUG) $
#
# You can customize the output string, e.g. $(__brew_ps1 "[%s]") would
# output "[formula_name|DEBUG]". The default (if you do not provide a
# format argument) is to print "(formula_name|DEBUG)" prefixed with a
# output "[<formula_name>|DEBUG]". The default (if you do not provide a
# format argument) is to print "(<formula_name>|DEBUG)" prefixed with a
# single space.
_brew_to_completion()
__brewcomp_words_include ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
local i=1
while [[ $i -lt $COMP_CWORD ]]; do
if [[ "${COMP_WORDS[i]}" = "$1" ]]; then
return 0
fi
i=$((++i))
done
return 1
}
# Subcommand list
[[ ${COMP_CWORD} -eq 1 ]] && {
local actions="--cache --cellar --config --env --prefix --repository audit cat cleanup
configure create deps diy doctor edit fetch help home info install link list log options
outdated prune remove search test uninstall unlink update upgrade uses versions"
local ext=$(\ls $(brew --repository)/Library/Contributions/examples 2> /dev/null |
sed -e "s/\.rb//g" -e "s/brew-//g")
COMPREPLY=( $(compgen -W "${actions} ${ext}" -- ${cur}) )
return
}
# Find the previous non-switch word
__brewcomp_prev ()
{
local idx=$((COMP_CWORD - 1))
local prv="${COMP_WORDS[idx]}"
while [[ $prv == -* ]]; do
idx=$((--idx))
prv="${COMP_WORDS[idx]}"
done
echo "$prv"
}
# some flags take arguments
# kind of pointless to use a case statement here, but it's cleaner
# than a bunch of string comparisons and leaves room for future
# expansion
case "${COMP_WORDS[1]}" in
# flags that take a formula
--cache|--cellar|--prefix)
local ff=$(\ls $(brew --repository)/Library/Formula 2> /dev/null | sed "s/\.rb//g")
local af=$(\ls $(brew --repository)/Library/Aliases 2> /dev/null | sed "s/\.rb//g")
COMPREPLY=( $(compgen -W "${ff} ${af}" -- ${cur}) )
return
;;
esac
__brewcomp ()
{
# break $1 on space, tab, and newline characters,
# and turn it into a newline separated list of words
local list s sep=$'\n' IFS=$' '$'\t'$'\n'
local cur="${COMP_WORDS[COMP_CWORD]}"
# Find the previous non-switch word
local prev_index=$((COMP_CWORD - 1))
local prev="${COMP_WORDS[prev_index]}"
while [[ $prev == -* ]]; do
prev_index=$((--prev_index))
prev="${COMP_WORDS[prev_index]}"
done
for s in $1; do
__brewcomp_words_include "$s" && continue
list="$list$s$sep"
done
# handle subcommand options
if [[ "$cur" == --* ]]; then
case "${COMP_WORDS[1]}" in
cleanup)
local opts=$([[ "${COMP_WORDS[*]}" =~ "--force" ]] || echo "--force")
COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
return
;;
create)
local opts=$(
local opts=(--autotools --cmake --no-fetch)
for o in ${opts[*]}; do
[[ "${COMP_WORDS[*]}" =~ "$o" ]] || echo "$o"
done
)
COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
return
;;
deps)
local opts=$(
local opts=(--1 --all --tree)
for o in ${opts[*]}; do
[[ "${COMP_WORDS[*]}" =~ "$o" ]] || echo "$o"
done
)
COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
return
;;
diy|configure)
local opts=$(
local opts=(--set-version --set-name)
for o in ${opts[*]}; do
[[ "${COMP_WORDS[*]}" =~ "$o" ]] || echo "$o"
done
)
COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
return
;;
fetch)
local opts=$(
local opts=(--deps --force --HEAD)
for o in ${opts[*]}; do
[[ "${COMP_WORDS[*]}" =~ "$o" ]] || echo "$o"
done
)
COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
return
;;
info|abv)
local opts=$(
local opts=(--all --github)
for o in ${opts[*]}; do
[[ "${COMP_WORDS[*]}" =~ "$o" ]] || echo "$o"
done
)
COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
return
;;
install)
local opts=$(
local opts=(--force --verbose --debug --use-clang --use-gcc
--use-llvm --ignore-dependencies --build-from-source --HEAD
--interactive --fresh --devel $(brew options --compact "$prev"))
IFS=$sep
COMPREPLY=($(compgen -W "$list" -- "$cur"))
}
# options that make sense with '--interactive'
if [[ "${COMP_WORDS[*]}" =~ "--interactive" ]]; then
opts=(--force --git --use-clang --use-gcc --use-llvm --HEAD --devel)
fi
# Don't use __brewcomp() in any of the __brew_complete_foo functions, as
# it is too slow and is not worth it just for duplicate elimination.
__brew_complete_formulae ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
local ff=$(\ls $(brew --repository)/Library/Formula 2>/dev/null | sed 's/\.rb//g')
local af=$(\ls $(brew --repository)/Library/Aliases 2>/dev/null | sed 's/\.rb//g')
COMPREPLY=($(compgen -W "$ff $af" -- "$cur"))
}
for o in ${opts[*]}; do
[[ "${COMP_WORDS[*]}" =~ "$o" ]] || echo "$o"
done
)
COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
return
;;
list|ls)
local opts=$(
local opts=(--unbrewed --verbose --versions)
__brew_complete_installed ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
local inst=$(\ls $(brew --cellar))
COMPREPLY=($(compgen -W "$inst" -- "$cur"))
}
# the three options for list are mutually exclusive
for o in ${opts[*]}; do
if [[ "${COMP_WORDS[*]}" =~ "$o" ]]; then
opts=()
else
echo "$o"
fi
done
)
COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
return
;;
options)
local opts=$(
local opts=(--all --compact --installed)
for o in ${opts[*]}; do
[[ "${COMP_WORDS[*]}" =~ "$o" ]] || echo "$o"
done
)
COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
return
;;
outdated)
local opts=$([[ "${COMP_WORDS[*]}" =~ "--quiet" ]] || echo "--quiet")
COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
return
;;
search|-S)
local opts=$(
local opts=(--fink --macports)
for o in ${opts[*]}; do
[[ "${COMP_WORDS[*]}" =~ "$o" ]] || echo "$o"
done
)
COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
return
;;
uninstall|remove|rm)
local opts=$([[ "${COMP_WORDS[*]}" =~ "--force" ]] || echo "--force")
COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
return
;;
update)
local opts=$(
local opts=(--rebase --verbose)
for o in ${opts[*]}; do
[[ "${COMP_WORDS[*]}" =~ "$o" ]] || echo "$o"
done
)
COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
return
;;
uses)
local opts=$([[ "${COMP_WORDS[*]}" =~ "--installed" ]] || echo "--installed")
COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
return
;;
versions)
local opts=$([[ "${COMP_WORDS[*]}" =~ "--compact" ]] || echo "--compact")
COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
return
;;
esac
fi
__brew_complete_outdated ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
local od=$(brew outdated --quiet)
COMPREPLY=($(compgen -W "$od" -- "$cur"))
}
# find the index of the *first* non-switch word
# we can use this to allow completion for multiple formula arguments
local cmd_index=1
while [[ ${COMP_WORDS[cmd_index]} == -* ]]; do
cmd_index=$((++cmd_index))
done
_brew_cleanup ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__brewcomp "--force"
return
;;
esac
__brew_complete_installed
}
case "${COMP_WORDS[cmd_index]}" in
# Commands that take a formula
audit|cat|deps|edit|fetch|home|homepage|info|install|log|missing|options|uses|versions)
local ff=$(\ls $(brew --repository)/Library/Formula 2> /dev/null | sed "s/\.rb//g")
local af=$(\ls $(brew --repository)/Library/Aliases 2> /dev/null | sed "s/\.rb//g")
COMPREPLY=( $(compgen -W "${ff} ${af}" -- ${cur}) )
return
;;
# Commands that take an existing brew
abv|cleanup|link|list|ln|ls|remove|rm|test|uninstall|unlink)
COMPREPLY=( $(compgen -W "$(\ls $(brew --cellar))" -- ${cur}) )
return
;;
# Commands that take an outdated brew
upgrade)
COMPREPLY=( $(compgen -W "$(brew outdated --quiet)" -- ${cur}) )
return
;;
esac
_brew_create ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__brewcomp "--autotools --cmake --no-fetch"
return
;;
esac
}
_brew_deps ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__brewcomp "--1 --all --tree"
return
;;
esac
__brew_complete_formulae
}
_brew_diy ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__brewcomp "--set-name --set-version"
return
;;
esac
}
_brew_fetch ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__brewcomp "--deps --force --HEAD"
return
;;
esac
__brew_complete_formulae
}
_brew_info ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__brewcomp "--all --github"
return
;;
esac
__brew_complete_formulae
}
_brew_install ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
local prv=$(__brewcomp_prev)
case "$cur" in
--*)
if __brewcomp_words_include "--interactive"; then
__brewcomp "
--devel
--force
--git
--HEAD
--use-clang
--use-gcc
--use-llvm
"
else
__brewcomp "
--build-from-source
--debug
--devel
--force
--fresh
--HEAD
--ignore-dependencies
--interactive
--use-clang
--use-gcc
--use-llvm
--verbose
$(brew options --compact "$prv")
"
fi
return
;;
esac
__brew_complete_formulae
}
_brew_list ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
# options to brew-list are mutually exclusive
if __brewcomp_words_include "--unbrewed"; then
return
elif __brewcomp_words_include "--verbose"; then
return
elif __brewcomp_words_include "--versions"; then
return
else
__brewcomp "--unbrewed --verbose --versions"
return
fi
;;
esac
__brew_complete_installed
}
_brew_log ()
{
# if git-completion is loaded, then we complete git-log options
declare -F _git_log >/dev/null || return
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__brewcomp "
$__git_log_common_options
$__git_log_shortlog_options
$__git_log_gitk_options
$__git_diff_common_options
--walk-reflogs --graph --decorate
--abbrev-commit --oneline --reverse
"
return
;;
esac
__brew_complete_formulae
}
_brew_options ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__brewcomp "--all --compact --installed"
return
;;
esac
__brew_complete_formulae
}
_brew_outdated ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__brewcomp "--quiet"
return
;;
esac
}
_brew_search ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__brewcomp "--fink --macports"
return
;;
esac
}
_brew_uninstall ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__brewcomp "--force"
return
;;
esac
__brew_complete_installed
}
_brew_update ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__brewcomp "--rebase --verbose"
return
;;
esac
}
_brew_uses ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__brewcomp "--installed"
return
;;
esac
__brew_complete_formulae
}
_brew_versions ()
{
local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__brewcomp "--compact"
return
;;
esac
__brew_complete_formulae
}
__brew_ps1 ()
@ -239,4 +338,94 @@ __brew_ps1 ()
printf "${1:- (%s)}" "$HOMEBREW_DEBUG_INSTALL|DEBUG"
}
complete -o bashdefault -o default -F _brew_to_completion brew
_brew ()
{
local i=1 cmd
# find the subcommand
while [[ $i -lt $COMP_CWORD ]]; do
local s="${COMP_WORDS[i]}"
case "$s" in
--*) cmd="$s"
break
;;
-*) ;;
*) cmd="$s"
break
;;
esac
i=$((++i))
done
if [[ $i -eq $COMP_CWORD ]]; then
local ext=$(\ls $(brew --repository)/Library/Contributions/examples \
2>/dev/null | sed -e "s/\.rb//g" -e "s/brew-//g")
__brewcomp "
--cache --cellar --config
--env --prefix --repository
audit
cat
cleanup
create
deps
diy configure
doctor
edit
fetch
help
home
info abv
install
link ln
list ls
log
options
outdated
prune
search
test
uninstall remove rm
unlink
update
upgrade
uses
versions
$ext
"
return
fi
# subcommands have their own completion functions
case "$cmd" in
--cache|--cellar|--prefix)
__brew_complete_formulae ;;
audit|cat|edit|home) __brew_complete_formulae ;;
link|ln|test|unlink) __brew_complete_installed ;;
upgrade) __brew_complete_outdated ;;
cleanup) _brew_cleanup ;;
create) _brew_create ;;
deps) _brew_deps ;;
diy|configure) _brew_diy ;;
fetch) _brew_fetch ;;
info|abv) _brew_info ;;
install) _brew_install ;;
list|ls) _brew_list ;;
log) _brew_log ;;
options) _brew_options ;;
outdated) _brew_outdated ;;
search|-S) _brew_search ;;
uninstall|remove|rm) _brew_uninstall ;;
update) _brew_update ;;
uses) _brew_uses ;;
versions) _brew_versions ;;
*) ;;
esac
}
# keep around for compatibility
_brew_to_completion ()
{
_brew
}
complete -o bashdefault -o default -F _brew brew