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) # Bash completion script for brew(1)
# #
# To use, edit your .bashrc and add: # To use, add the following to your .bashrc:
# source `brew --prefix`/Library/Contributions/brew_bash_completion.sh #
# . $(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 # The __brew_ps1() function can be used to annotate your PS1 with
# Homebrew debugging information; it behaves similarly to the __git_ps1() # Homebrew debugging information; it behaves similarly to the __git_ps1()
# function provided by the git's bash completion script. # function provided by the git's bash completion script.
# #
# For example, the prompt string # For example, the prompt string
#
# PS1='\u@\h \W $(__brew_ps1 "(%s)") $' # PS1='\u@\h \W $(__brew_ps1 "(%s)") $'
# #
# would result in a prompt like # would result in a prompt like
#
# user@hostname cwd $ # user@hostname cwd $
# #
# but if you are currently engaged in an interactive or debug install, # 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 # (i.e., you invoked `brew install` with either '-i' or '-d'), then the
# prompt would look like # 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 # You can customize the output string, e.g. $(__brew_ps1 "[%s]") would
# output "[formula_name|DEBUG]". The default (if you do not provide a # output "[<formula_name>|DEBUG]". The default (if you do not provide a
# format argument) is to print "(formula_name|DEBUG)" prefixed with a # format argument) is to print "(<formula_name>|DEBUG)" prefixed with a
# single space. # 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 # Find the previous non-switch word
[[ ${COMP_CWORD} -eq 1 ]] && { __brewcomp_prev ()
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 local idx=$((COMP_CWORD - 1))
outdated prune remove search test uninstall unlink update upgrade uses versions" local prv="${COMP_WORDS[idx]}"
local ext=$(\ls $(brew --repository)/Library/Contributions/examples 2> /dev/null | while [[ $prv == -* ]]; do
sed -e "s/\.rb//g" -e "s/brew-//g") idx=$((--idx))
COMPREPLY=( $(compgen -W "${actions} ${ext}" -- ${cur}) ) prv="${COMP_WORDS[idx]}"
return done
} echo "$prv"
}
# some flags take arguments __brewcomp ()
# kind of pointless to use a case statement here, but it's cleaner {
# than a bunch of string comparisons and leaves room for future # break $1 on space, tab, and newline characters,
# expansion # and turn it into a newline separated list of words
case "${COMP_WORDS[1]}" in local list s sep=$'\n' IFS=$' '$'\t'$'\n'
# flags that take a formula local cur="${COMP_WORDS[COMP_CWORD]}"
--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
# Find the previous non-switch word for s in $1; do
local prev_index=$((COMP_CWORD - 1)) __brewcomp_words_include "$s" && continue
local prev="${COMP_WORDS[prev_index]}" list="$list$s$sep"
while [[ $prev == -* ]]; do done
prev_index=$((--prev_index))
prev="${COMP_WORDS[prev_index]}"
done
# handle subcommand options IFS=$sep
if [[ "$cur" == --* ]]; then COMPREPLY=($(compgen -W "$list" -- "$cur"))
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"))
# options that make sense with '--interactive' # Don't use __brewcomp() in any of the __brew_complete_foo functions, as
if [[ "${COMP_WORDS[*]}" =~ "--interactive" ]]; then # it is too slow and is not worth it just for duplicate elimination.
opts=(--force --git --use-clang --use-gcc --use-llvm --HEAD --devel) __brew_complete_formulae ()
fi {
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 __brew_complete_installed ()
[[ "${COMP_WORDS[*]}" =~ "$o" ]] || echo "$o" {
done local cur="${COMP_WORDS[COMP_CWORD]}"
) local inst=$(\ls $(brew --cellar))
COMPREPLY=( $(compgen -W "$opts" -- ${cur}) ) COMPREPLY=($(compgen -W "$inst" -- "$cur"))
return }
;;
list|ls)
local opts=$(
local opts=(--unbrewed --verbose --versions)
# the three options for list are mutually exclusive __brew_complete_outdated ()
for o in ${opts[*]}; do {
if [[ "${COMP_WORDS[*]}" =~ "$o" ]]; then local cur="${COMP_WORDS[COMP_CWORD]}"
opts=() local od=$(brew outdated --quiet)
else COMPREPLY=($(compgen -W "$od" -- "$cur"))
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
# find the index of the *first* non-switch word _brew_cleanup ()
# we can use this to allow completion for multiple formula arguments {
local cmd_index=1 local cur="${COMP_WORDS[COMP_CWORD]}"
while [[ ${COMP_WORDS[cmd_index]} == -* ]]; do case "$cur" in
cmd_index=$((++cmd_index)) --*)
done __brewcomp "--force"
return
;;
esac
__brew_complete_installed
}
case "${COMP_WORDS[cmd_index]}" in _brew_create ()
# Commands that take a formula {
audit|cat|deps|edit|fetch|home|homepage|info|install|log|missing|options|uses|versions) local cur="${COMP_WORDS[COMP_CWORD]}"
local ff=$(\ls $(brew --repository)/Library/Formula 2> /dev/null | sed "s/\.rb//g") case "$cur" in
local af=$(\ls $(brew --repository)/Library/Aliases 2> /dev/null | sed "s/\.rb//g") --*)
COMPREPLY=( $(compgen -W "${ff} ${af}" -- ${cur}) ) __brewcomp "--autotools --cmake --no-fetch"
return return
;; ;;
# Commands that take an existing brew esac
abv|cleanup|link|list|ln|ls|remove|rm|test|uninstall|unlink) }
COMPREPLY=( $(compgen -W "$(\ls $(brew --cellar))" -- ${cur}) )
return _brew_deps ()
;; {
# Commands that take an outdated brew local cur="${COMP_WORDS[COMP_CWORD]}"
upgrade) case "$cur" in
COMPREPLY=( $(compgen -W "$(brew outdated --quiet)" -- ${cur}) ) --*)
return __brewcomp "--1 --all --tree"
;; return
esac ;;
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 () __brew_ps1 ()
@ -239,4 +338,94 @@ __brew_ps1 ()
printf "${1:- (%s)}" "$HOMEBREW_DEBUG_INSTALL|DEBUG" 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