|
#!/bin/bash |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function _{{name}}_completer { |
|
|
|
|
|
|
|
{{spec}} |
|
|
|
|
|
|
|
|
|
declare -a argv |
|
|
|
|
|
|
|
|
|
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" |
|
|
|
|
|
|
|
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 |
|
|
|
|
|
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 [[ "${word:0:1}" == '$' ]]; then |
|
|
|
|
|
|
|
|
|
trace " completing envvars" |
|
compgen $compgen_opts -P '$' -A export -- "${word:1}" |
|
elif [[ -z "$argtype" ]]; then |
|
|
|
|
|
|
|
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 |
|
|
|
|
|
trace " completing 'none' (hack to imply no completions)" |
|
echo "##-no-completion- -results-##" |
|
elif [[ $argtype == "file" ]]; then |
|
|
|
|
|
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" |
|
|
|
|
|
echo "##-no-completion- -results-##" |
|
else |
|
echo $completions |
|
fi |
|
fi |
|
;; |
|
*) |
|
trace " unknown state: $state" |
|
;; |
|
esac |
|
} |
|
|
|
|
|
trace "" |
|
trace "-- $(date)" |
|
|
|
|
|
|
|
trace "COMP_CWORD: '$COMP_CWORD'" |
|
trace "COMP_LINE: '$COMP_LINE'" |
|
trace "COMP_POINT: $COMP_POINT" |
|
|
|
|
|
|
|
|
|
if [[ $COMP_CWORD -lt 0 ]]; then |
|
trace "abort on negative COMP_CWORD" |
|
exit 1; |
|
fi |
|
|
|
|
|
|
|
shift |
|
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 "" |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|