389 lines
14 KiB
Bash
389 lines
14 KiB
Bash
#!/bin/bash
|
|
#
|
|
# Bash completion generated for '{{name}}' at {{date}}.
|
|
#
|
|
# The original template lives here:
|
|
# https://github.com/trentm/node-dashdash/blob/master/etc/dashdash.bash_completion.in
|
|
#
|
|
|
|
#
|
|
# Copyright 2016 Trent Mick
|
|
# Copyright 2016 Joyent, Inc.
|
|
#
|
|
#
|
|
# A generic Bash completion driver script.
|
|
#
|
|
# This is meant to provide a re-usable chunk of Bash to use for
|
|
# "etc/bash_completion.d/" files for individual tools. Only the "Configuration"
|
|
# section with tool-specific info need differ. Features:
|
|
#
|
|
# - support for short and long opts
|
|
# - support for knowing which options take arguments
|
|
# - support for subcommands (e.g. 'git log <TAB>' to show just options for the
|
|
# log subcommand)
|
|
# - does the right thing with "--" to stop options
|
|
# - custom optarg and arg types for custom completions
|
|
# - (TODO) support for shells other than Bash (tcsh, zsh, fish?, etc.)
|
|
#
|
|
#
|
|
# Examples/design:
|
|
#
|
|
# 1. Bash "default" completion. By default Bash's 'complete -o default' is
|
|
# enabled. That means when there are no completions (e.g. if no opts match
|
|
# the current word), then you'll get Bash's default completion. Most notably
|
|
# that means you get filename completion. E.g.:
|
|
# $ tool ./<TAB>
|
|
# $ tool READ<TAB>
|
|
#
|
|
# 2. all opts and subcmds:
|
|
# $ tool <TAB>
|
|
# $ tool -v <TAB> # assuming '-v' doesn't take an arg
|
|
# $ tool -<TAB> # matching opts
|
|
# $ git lo<TAB> # matching subcmds
|
|
#
|
|
# Long opt completions are given *without* the '=', i.e. we prefer space
|
|
# separated because that's easier for good completions.
|
|
#
|
|
# 3. long opt arg with '='
|
|
# $ tool --file=<TAB>
|
|
# $ tool --file=./d<TAB>
|
|
# We maintain the "--file=" prefix. Limitation: With the attached prefix
|
|
# the 'complete -o filenames' doesn't know to do dirname '/' suffixing. Meh.
|
|
#
|
|
# 4. envvars:
|
|
# $ tool $<TAB>
|
|
# $ tool $P<TAB>
|
|
# Limitation: Currently only getting exported vars, so we miss "PS1" and
|
|
# others.
|
|
#
|
|
# 5. Defer to other completion in a subshell:
|
|
# $ tool --file $(cat ./<TAB>
|
|
# We get this from 'complete -o default ...'.
|
|
#
|
|
# 6. Custom completion types from a provided bash function.
|
|
# $ tool --profile <TAB> # complete available "profiles"
|
|
#
|
|
#
|
|
# Dev Notes:
|
|
# - compgen notes, from http://unix.stackexchange.com/questions/151118/understand-compgen-builtin-command
|
|
# - https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion-Builtins.html
|
|
#
|
|
|
|
|
|
# Debugging this completion:
|
|
# 1. Uncomment the "_{{name}}_log_file=..." line.
|
|
# 2. 'tail -f /var/tmp/dashdash-completion.log' in one terminal.
|
|
# 3. Re-source this bash completion file.
|
|
#_{{name}}_log=/var/tmp/dashdash-completion.log
|
|
|
|
function _{{name}}_completer {
|
|
|
|
# ---- cmd definition
|
|
|
|
{{spec}}
|
|
|
|
|
|
# ---- locals
|
|
|
|
declare -a argv
|
|
|
|
|
|
# ---- support functions
|
|
|
|
function trace {
|
|
[[ -n "$_{{name}}_log" ]] && echo "$*" >&2
|
|
}
|
|
|
|
function _dashdash_complete {
|
|
local idx context
|
|
idx=$1
|
|
context=$2
|
|
|
|
local shortopts longopts optargs subcmds allsubcmds argtypes
|
|
shortopts="$(eval "echo \${cmd${context}_shortopts}")"
|
|
longopts="$(eval "echo \${cmd${context}_longopts}")"
|
|
optargs="$(eval "echo \${cmd${context}_optargs}")"
|
|
subcmds="$(eval "echo \${cmd${context}_subcmds}")"
|
|
allsubcmds="$(eval "echo \${cmd${context}_allsubcmds}")"
|
|
IFS=', ' read -r -a argtypes <<< "$(eval "echo \${cmd${context}_argtypes}")"
|
|
|
|
trace ""
|
|
trace "_dashdash_complete(idx=$idx, context=$context)"
|
|
trace " shortopts: $shortopts"
|
|
trace " longopts: $longopts"
|
|
trace " optargs: $optargs"
|
|
trace " subcmds: $subcmds"
|
|
trace " allsubcmds: $allsubcmds"
|
|
|
|
# Get 'state' of option parsing at this COMP_POINT.
|
|
# Copying "dashdash.js#parse()" behaviour here.
|
|
local state=
|
|
local nargs=0
|
|
local i=$idx
|
|
local argtype
|
|
local optname
|
|
local prefix
|
|
local word
|
|
local dashdashseen=
|
|
while [[ $i -lt $len && $i -le $COMP_CWORD ]]; do
|
|
argtype=
|
|
optname=
|
|
prefix=
|
|
word=
|
|
|
|
arg=${argv[$i]}
|
|
trace " consider argv[$i]: '$arg'"
|
|
|
|
if [[ "$arg" == "--" && $i -lt $COMP_CWORD ]]; then
|
|
trace " dashdash seen"
|
|
dashdashseen=yes
|
|
state=arg
|
|
word=$arg
|
|
elif [[ -z "$dashdashseen" && "${arg:0:2}" == "--" ]]; then
|
|
arg=${arg:2}
|
|
if [[ "$arg" == *"="* ]]; then
|
|
optname=${arg%%=*}
|
|
val=${arg##*=}
|
|
trace " long opt: optname='$optname' val='$val'"
|
|
state=arg
|
|
argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1)
|
|
word=$val
|
|
prefix="--$optname="
|
|
else
|
|
optname=$arg
|
|
val=
|
|
trace " long opt: optname='$optname'"
|
|
state=longopt
|
|
word=--$optname
|
|
|
|
if [[ "$optargs" == *"-$optname="* && $i -lt $COMP_CWORD ]]; then
|
|
i=$(( $i + 1 ))
|
|
state=arg
|
|
argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1)
|
|
word=${argv[$i]}
|
|
trace " takes arg (consume argv[$i], word='$word')"
|
|
fi
|
|
fi
|
|
elif [[ -z "$dashdashseen" && "${arg:0:1}" == "-" ]]; then
|
|
trace " short opt group"
|
|
state=shortopt
|
|
word=$arg
|
|
|
|
local j=1
|
|
while [[ $j -lt ${#arg} ]]; do
|
|
optname=${arg:$j:1}
|
|
trace " consider index $j: optname '$optname'"
|
|
|
|
if [[ "$optargs" == *"-$optname="* ]]; then
|
|
argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1)
|
|
if [[ $(( $j + 1 )) -lt ${#arg} ]]; then
|
|
state=arg
|
|
word=${arg:$(( $j + 1 ))}
|
|
trace " takes arg (rest of this arg, word='$word', argtype='$argtype')"
|
|
elif [[ $i -lt $COMP_CWORD ]]; then
|
|
state=arg
|
|
i=$(( $i + 1 ))
|
|
word=${argv[$i]}
|
|
trace " takes arg (word='$word', argtype='$argtype')"
|
|
fi
|
|
break
|
|
fi
|
|
|
|
j=$(( $j + 1 ))
|
|
done
|
|
elif [[ $i -lt $COMP_CWORD && -n "$arg" ]] && $(echo "$allsubcmds" | grep -w "$arg" >/dev/null); then
|
|
trace " complete subcmd: recurse _dashdash_complete"
|
|
_dashdash_complete $(( $i + 1 )) "${context}__${arg/-/_}"
|
|
return
|
|
else
|
|
trace " not an opt or a complete subcmd"
|
|
state=arg
|
|
word=$arg
|
|
nargs=$(( $nargs + 1 ))
|
|
if [[ ${#argtypes[@]} -gt 0 ]]; then
|
|
argtype="${argtypes[$(( $nargs - 1 ))]}"
|
|
if [[ -z "$argtype" ]]; then
|
|
# If we have more args than argtypes, we use the
|
|
# last type.
|
|
argtype="${argtypes[@]: -1:1}"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
trace " state=$state prefix='$prefix' word='$word'"
|
|
i=$(( $i + 1 ))
|
|
done
|
|
|
|
trace " parsed: state=$state optname='$optname' argtype='$argtype' prefix='$prefix' word='$word' dashdashseen=$dashdashseen"
|
|
local compgen_opts=
|
|
if [[ -n "$prefix" ]]; then
|
|
compgen_opts="$compgen_opts -P $prefix"
|
|
fi
|
|
|
|
case $state in
|
|
shortopt)
|
|
compgen $compgen_opts -W "$shortopts $longopts" -- "$word"
|
|
;;
|
|
longopt)
|
|
compgen $compgen_opts -W "$longopts" -- "$word"
|
|
;;
|
|
arg)
|
|
# If we don't know what completion to do, then emit nothing. We
|
|
# expect that we are running with:
|
|
# complete -o default ...
|
|
# where "default" means: "Use Readline's default completion if
|
|
# the compspec generates no matches." This gives us the good filename
|
|
# completion, completion in subshells/backticks.
|
|
#
|
|
# We cannot support an argtype="directory" because
|
|
# compgen -S '/' -A directory -- "$word"
|
|
# doesn't give a satisfying result. It doesn't stop at the trailing '/'
|
|
# so you cannot descend into dirs.
|
|
if [[ "${word:0:1}" == '$' ]]; then
|
|
# By default, Bash will complete '$<TAB>' to all envvars. Apparently
|
|
# 'complete -o default' does *not* give us that. The following
|
|
# gets *close* to the same completions: '-A export' misses envvars
|
|
# like "PS1".
|
|
trace " completing envvars"
|
|
compgen $compgen_opts -P '$' -A export -- "${word:1}"
|
|
elif [[ -z "$argtype" ]]; then
|
|
# Only include opts in completions if $word is not empty.
|
|
# This is to avoid completing the leading '-', which foils
|
|
# using 'default' completion.
|
|
if [[ -n "$dashdashseen" ]]; then
|
|
trace " completing subcmds, if any (no argtype, dashdash seen)"
|
|
compgen $compgen_opts -W "$subcmds" -- "$word"
|
|
elif [[ -z "$word" ]]; then
|
|
trace " completing subcmds, if any (no argtype, empty word)"
|
|
compgen $compgen_opts -W "$subcmds" -- "$word"
|
|
else
|
|
trace " completing opts & subcmds (no argtype)"
|
|
compgen $compgen_opts -W "$shortopts $longopts $subcmds" -- "$word"
|
|
fi
|
|
elif [[ $argtype == "none" ]]; then
|
|
# We want *no* completions, i.e. some way to get the active
|
|
# 'complete -o default' to not do filename completion.
|
|
trace " completing 'none' (hack to imply no completions)"
|
|
echo "##-no-completion- -results-##"
|
|
elif [[ $argtype == "file" ]]; then
|
|
# 'complete -o default' gives the best filename completion, at least
|
|
# on Mac.
|
|
trace " completing 'file' (let 'complete -o default' handle it)"
|
|
echo ""
|
|
elif ! type complete_$argtype 2>/dev/null >/dev/null; then
|
|
trace " completing '$argtype' (fallback to default b/c complete_$argtype is unknown)"
|
|
echo ""
|
|
else
|
|
trace " completing custom '$argtype'"
|
|
completions=$(complete_$argtype "$word")
|
|
if [[ -z "$completions" ]]; then
|
|
trace " no custom '$argtype' completions"
|
|
# These are in ascii and "dictionary" order so they sort
|
|
# correctly.
|
|
echo "##-no-completion- -results-##"
|
|
else
|
|
echo $completions
|
|
fi
|
|
fi
|
|
;;
|
|
*)
|
|
trace " unknown state: $state"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
|
|
trace ""
|
|
trace "-- $(date)"
|
|
#trace "\$IFS: '$IFS'"
|
|
#trace "\$@: '$@'"
|
|
#trace "COMP_WORDBREAKS: '$COMP_WORDBREAKS'"
|
|
trace "COMP_CWORD: '$COMP_CWORD'"
|
|
trace "COMP_LINE: '$COMP_LINE'"
|
|
trace "COMP_POINT: $COMP_POINT"
|
|
|
|
# Guard against negative COMP_CWORD. This is a Bash bug at least on
|
|
# Mac 10.10.4's bash. See
|
|
# <https://lists.gnu.org/archive/html/bug-bash/2009-07/msg00125.html>.
|
|
if [[ $COMP_CWORD -lt 0 ]]; then
|
|
trace "abort on negative COMP_CWORD"
|
|
exit 1;
|
|
fi
|
|
|
|
# I don't know how to do array manip on argv vars,
|
|
# so copy over to argv array to work on them.
|
|
shift # the leading '--'
|
|
i=0
|
|
len=$#
|
|
while [[ $# -gt 0 ]]; do
|
|
argv[$i]=$1
|
|
shift;
|
|
i=$(( $i + 1 ))
|
|
done
|
|
trace "argv: '${argv[@]}'"
|
|
trace "argv[COMP_CWORD-1]: '${argv[$(( $COMP_CWORD - 1 ))]}'"
|
|
trace "argv[COMP_CWORD]: '${argv[$COMP_CWORD]}'"
|
|
trace "argv len: '$len'"
|
|
|
|
_dashdash_complete 1 ""
|
|
}
|
|
|
|
|
|
# ---- mainline
|
|
|
|
# Note: This if-block to help work with 'compdef' and 'compctl' is
|
|
# adapted from 'npm completion'.
|
|
if type complete &>/dev/null; then
|
|
function _{{name}}_completion {
|
|
local _log_file=/dev/null
|
|
[[ -z "$_{{name}}_log" ]] || _log_file="$_{{name}}_log"
|
|
COMPREPLY=($(COMP_CWORD="$COMP_CWORD" \
|
|
COMP_LINE="$COMP_LINE" \
|
|
COMP_POINT="$COMP_POINT" \
|
|
_{{name}}_completer -- "${COMP_WORDS[@]}" \
|
|
2>$_log_file)) || return $?
|
|
}
|
|
complete -o default -F _{{name}}_completion {{name}}
|
|
elif type compdef &>/dev/null; then
|
|
function _{{name}}_completion {
|
|
local _log_file=/dev/null
|
|
[[ -z "$_{{name}}_log" ]] || _log_file="$_{{name}}_log"
|
|
compadd -- $(COMP_CWORD=$((CURRENT-1)) \
|
|
COMP_LINE=$BUFFER \
|
|
COMP_POINT=0 \
|
|
_{{name}}_completer -- "${words[@]}" \
|
|
2>$_log_file)
|
|
}
|
|
compdef _{{name}}_completion {{name}}
|
|
elif type compctl &>/dev/null; then
|
|
function _{{name}}_completion {
|
|
local cword line point words si
|
|
read -Ac words
|
|
read -cn cword
|
|
let cword-=1
|
|
read -l line
|
|
read -ln point
|
|
local _log_file=/dev/null
|
|
[[ -z "$_{{name}}_log" ]] || _log_file="$_{{name}}_log"
|
|
reply=($(COMP_CWORD="$cword" \
|
|
COMP_LINE="$line" \
|
|
COMP_POINT="$point" \
|
|
_{{name}}_completer -- "${words[@]}" \
|
|
2>$_log_file)) || return $?
|
|
}
|
|
compctl -K _{{name}}_completion {{name}}
|
|
fi
|
|
|
|
|
|
##
|
|
## This is a Bash completion file for the '{{name}}' command. You can install
|
|
## with either:
|
|
##
|
|
## cp FILE /usr/local/etc/bash_completion.d/{{name}} # Mac
|
|
## cp FILE /etc/bash_completion.d/{{name}} # Linux
|
|
##
|
|
## or:
|
|
##
|
|
## cp FILE > ~/.{{name}}.completion
|
|
## echo "source ~/.{{name}}.completion" >> ~/.bashrc
|
|
## |