Skip to content

Instantly share code, notes, and snippets.

@YodasWs
Created May 30, 2017 19:40
Show Gist options
  • Save YodasWs/79585bbd4c4ef269377039dcede67731 to your computer and use it in GitHub Desktop.
Save YodasWs/79585bbd4c4ef269377039dcede67731 to your computer and use it in GitHub Desktop.
Generalized Bash completion script for several different CLI utils
#!bash
#
# bash/zsh completion for many dev utilities
#
# Forked from gulp-completion.sh
# Copyright (C) 2006,2007 Shawn O. Pearce <[email protected]>
# Conceptually based on gitcompletion (http://gitweb.hawaga.org.uk/).
#
# Copyright © 2016,2017 Samuel B Grundman <[email protected]>
# Released under Creative Commons BY-NC-SA 4.0 License
# (https://creativecommons.org/licenses/by-nc-sa/4.0/)
#
# The contained completion routines provide support for completing:
#
# *) npm 'subcommands'
# *) common --long-options
#
# To use these routines:
#
# 1) Copy this file to somewhere (e.g. ~/.completion.sh).
# 2) Add the following line to your .bashrc/.zshrc:
# source ~/.completion.sh
if [[ -n ${ZSH_VERSION-} ]]; then
autoload -U +X bashcompinit && bashcompinit
fi
case "$COMP_WORDBREAKS" in
*:*) : great ;;
*) COMP_WORDBREAKS="$COMP_WORDBREAKS:"
esac
if [ -z "$(type -t __comp_1)" ] || [ "$(type -t __comp_1)" != "function" ]; then
__comp_1 ()
{
local c IFS=$' \t\n'
for c in $1; do
c="$c$2"
case $c in
--*=*|*.) ;;
*) c="$c " ;;
esac
printf '%s\n' "$c"
done
}
fi
# The following function is based on code from:
#
# bash_completion - programmable completion functions for bash 3.2+
#
# Copyright © 2006-2008, Ian Macdonald <[email protected]>
# © 2009-2010, Bash Completion Maintainers
# <[email protected]>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# The latest version of this software can be obtained here:
#
# http://bash-completion.alioth.debian.org/
#
# RELEASE: 2.x
# This function can be used to access a tokenized list of words
# on the command line:
#
# __reassemble_comp_words_by_ref '=:'
# if test "${words_[cword_-1]}" = -w
# then
# ...
# fi
#
# The argument should be a collection of characters from the list of
# word completion separators (COMP_WORDBREAKS) to treat as ordinary
# characters.
#
# This is roughly equivalent to going back in time and setting
# COMP_WORDBREAKS to exclude those characters. The intent is to
# make option types like --date=<type> and <rev>:<path> easy to
# recognize by treating each shell word as a single token.
#
# It is best not to set COMP_WORDBREAKS directly because the value is
# shared with other completion scripts. By the time the completion
# function gets called, COMP_WORDS has already been populated so local
# changes to COMP_WORDBREAKS have no effect.
#
# Output: words_, cword_, cur_.
if [ -z "$(type -t __reassemble_comp_words_by_ref)" ] || [ "$(type -t __reassemble_comp_words_by_ref)" != "function" ]; then
__reassemble_comp_words_by_ref()
{
local exclude i j first
# Which word separators to exclude?
exclude="${1//[^$COMP_WORDBREAKS]}"
cword_=$COMP_CWORD
if [ -z "$exclude" ]; then
words_=("${COMP_WORDS[@]}")
return
fi
# List of word completion separators has shrunk;
# re-assemble words to complete.
for ((i=0, j=0; i < ${#COMP_WORDS[@]}; i++, j++)); do
# Append each nonempty word consisting of just
# word separator characters to the current word.
first=t
while
[ $i -gt 0 ] &&
[ -n "${COMP_WORDS[$i]}" ] &&
# word consists of excluded word separators
[ "${COMP_WORDS[$i]//[^$exclude]}" = "${COMP_WORDS[$i]}" ]
do
# Attach to the previous token,
# unless the previous token is the command name.
if [ $j -ge 2 ] && [ -n "$first" ]; then
((j--))
fi
first=
words_[$j]=${words_[j]}${COMP_WORDS[i]}
if [ $i = $COMP_CWORD ]; then
cword_=$j
fi
if (($i < ${#COMP_WORDS[@]} - 1)); then
((i++))
else
# Done.
return
fi
done
words_[$j]=${words_[j]}${COMP_WORDS[i]}
if [ $i = $COMP_CWORD ]; then
cword_=$j
fi
done
}
fi
if ! type _get_comp_words_by_ref >/dev/null 2>&1; then
if [[ -z ${ZSH_VERSION:+set} ]]; then
_get_comp_words_by_ref ()
{
local exclude cur_ words_ cword_
if [ "$1" = "-n" ]; then
exclude=$2
shift 2
fi
__reassemble_comp_words_by_ref "$exclude"
cur_=${words_[cword_]}
while [ $# -gt 0 ]; do
case "$1" in
cur)
cur=$cur_
;;
prev)
prev=${words_[$cword_-1]}
;;
words)
words=("${words_[@]}")
;;
cword)
cword=$cword_
;;
esac
shift
done
}
else
_get_comp_words_by_ref ()
{
while [ $# -gt 0 ]; do
case "$1" in
cur)
cur=${COMP_WORDS[COMP_CWORD]}
;;
prev)
prev=${COMP_WORDS[COMP_CWORD-1]}
;;
words)
words=("${COMP_WORDS[@]}")
;;
cword)
cword=$COMP_CWORD
;;
-n)
# assume COMP_WORDBREAKS is already set sanely
shift
;;
esac
shift
done
}
fi
fi
# Generates completion reply with compgen, appending a space to possible
# completion words, if necessary.
# It accepts 1 to 4 arguments:
# 1: List of possible completion words.
# 2: A prefix to be added to each possible completion word (optional).
# 3: Generate possible completion matches for this word (optional).
# 4: A suffix to be appended to each possible completion word (optional).
if [ -z "$(type -t __comp)" ] || [ "$(type -t __comp)" != "function" ]; then
__comp ()
{
local cur_="${3-$cur}"
case "$cur_" in
--*=)
COMPREPLY=()
;;
*)
local IFS=$'\n'
COMPREPLY=($(compgen -P "${2-}" \
-W "$(__comp_1 "${1-}" "${4-}")" \
-- "$cur_"))
;;
esac
}
fi
# Generates completion reply with compgen from newline-separated possible
# completion words by appending a space to all of them.
# It accepts 1 to 4 arguments:
# 1: List of possible completion words, separated by a single newline.
# 2: A prefix to be added to each possible completion word (optional).
# 3: Generate possible completion matches for this word (optional).
# 4: A suffix to be appended to each possible completion word instead of
# the default space (optional). If specified but empty, nothing is
# appended.
if [ -z "$(type -t __nl)" ] || [ "$(type -t __nl)" != "function" ]; then
__nl ()
{
local IFS=$'\n'
COMPREPLY=($(compgen -P "${2-}" -S "${4- }" -W "$1" -- "${3-$cur}"))
}
fi
# npm Command Expansions
_npm_install ()
{
if [[ "$cur" =~ "--" ]] || [ -z "$prev" ] || [ "$prev" == "$command" ]; then
__comp "--save --save-dev --global"
fi
}
__npm_list_all_commands ()
{
local i IFS=" "$'\n'
echo "access adduser bin bugs c cache completion config
ddp dedupe deprecate dist-tag docs edit explore get
help help-search i init install install-test it link
list ln logout ls outdated owner pack ping prefix
prune publish rb rebuild repo restart root run
run-script s se search set shrinkwrap star stars
start stop t tag team test tst un uninstall
unpublish unstar up update v version view whoami"
}
# Bower Commands
__bower_list_all_commands ()
{
local i IFS=" "$'\n'
echo "install"
}
__all_commands=
__compute_all_commands ()
{
test -n "$__{$1}_all_commands" ||
local temp=$(__{$1}_all_commands=$(__${1}_list_all_commands))
}
__list_porcelain_commands ()
{
local i IFS=" "$'\n'
__compute_all_commands $1
local temp=$(__{$1}_all_commands)
for i in temp
do
case $i in
*) echo $i;;
esac
done
}
__porcelain_commands=
__compute_porcelain_commands ()
{
__compute_all_commands $1
test -n "$__${1}_porcelain_commands" ||
local temp=$(__{$1}_procelain_commands=$(__list_procelain_commands $1))
}
if [ -z "$(type -t __has_doubledash)" ] || [ "$(type -t __has_doubledash)" != "function" ]; then
__has_doubledash ()
{
local c=1
while [ $c -lt $cword ]; do
if [ "--" = "${words[c]}" ]; then
return 0
fi
((c++))
done
return 1
}
fi
## Main npm Expansion
__npm_main ()
{
local i c=1 command
while [ $c -lt $cword ]; do
i="${words[c]}"
case "$i" in
--help) command="help"; break ;;
-c) c=$((++c)) ;;
-*) ;;
*) command="$i"; break ;;
esac
((c++))
done
__main npm
}
__main ()
{
local command
if [ -z "$command" ]; then
case "$cur" in
*) __compute_porcelain_commands $1
__comp "$__${1}_porcelain_commands" ;;
esac
return
fi
local completion_func="_${1}_${command//-/_}"
declare -f $completion_func >/dev/null && $completion_func && return
}
__func_wrap ()
{
if [[ -n ${ZSH_VERSION-} ]]; then
emulate -L bash
setopt KSH_TYPESET
# workaround zsh's bug that leaves 'words' as a special
# variable in versions < 4.3.12
typeset -h words
# workaround zsh's bug that quotes spaces in the COMPREPLY
# array if IFS doesn't contain spaces.
typeset -h IFS
fi
local cur words cword prev
_get_comp_words_by_ref -n =: cur words cword prev
$1
}
# Setup completion for certain functions defined above by setting common
# variables and workarounds.
# This is NOT a public function; use at your own risk.
__complete ()
{
local main="__${1}_main"
if [ -z "$(type -t $main)" ] || [ "$(type -t $main)" != "function" ]; then
main="__main"
fi
local wrapper="__${1}_wrap${main}"
eval "$wrapper () { __func_wrap $main ; }"
complete -o bashdefault -o default -o nospace -F $wrapper $1 2>/dev/null \
|| complete -o default -o nospace -F $wrapper $1
}
__complete npm
__complete bower
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment