123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- # shellcheck shell=bash
- #
- # Copyright (C) 2017 Canonical Ltd
- #
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License version 3 as
- # published by the Free Software Foundation.
- #
- # 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, see <http://www.gnu.org/licenses/>.
- # etelpmoc is the reverse of complete: it de-serialises the tab completion
- # request into the appropriate environment variables expected by the tab
- # completion tools, performs whatever action is wanted, and serialises the
- # result. It accomplishes this by having functions override the builtin
- # completion commands.
- #
- # this always runs "inside", in the same environment you get when doing "snap
- # run --shell", and snap-exec is the one setting the first argument to the
- # completion script set in the snap. The rest of the arguments come through
- # from snap-run --command=complete <snap> <args...>
- _die() {
- echo "$*" >&2
- exit 1
- }
- if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then
- _die "ERROR: this is meant to be run, not sourced."
- fi
- if [[ "${#@}" -lt 8 ]]; then
- _die "USAGE: $0 <script> <COMP_TYPE> <COMP_KEY> <COMP_POINT> <COMP_CWORD> <COMP_WORDBREAKS> <COMP_LINE> cmd [args...]"
- fi
- # De-serialize the command line arguments and populate tab completion environment
- _compscript="$1"
- shift
- COMP_TYPE="$1"
- shift
- COMP_KEY="$1"
- shift
- COMP_POINT="$1"
- shift
- COMP_CWORD="$1"
- shift
- COMP_WORDBREAKS="$1"
- shift
- # duplication, but whitespace is eaten and that throws off COMP_POINT
- COMP_LINE="$1"
- shift
- # rest of the args is the command itself
- COMP_WORDS=("$@")
- COMPREPLY=()
- if [[ ! "$_compscript" ]]; then
- _die "ERROR: completion script filename can't be empty"
- fi
- if [[ ! -f "$_compscript" ]]; then
- _die "ERROR: completion script does not exist"
- fi
- # Source the bash-completion library functions and common completion setup
- # shellcheck disable=SC1091
- . /usr/share/bash-completion/bash_completion
- # Now source the snap's 'completer' script itself
- # shellcheck disable=SC1090
- . "$_compscript"
- # _compopts is an associative array, which keys are options. The options are
- # described in bash(1)'s description of the -o option to the "complete"
- # builtin, and they affect how the completion options are presented to the user
- # (e.g. adding a slash for directories, whether to add a space after the
- # completion, etc). These need setting in the user's environment so need
- # serializing separately from the completions themselves.
- declare -A _compopts
- # wrap compgen, setting _compopts for any options given.
- # (as these options need handling separately from the completions)
- compgen() {
- local opt
- while getopts :o: opt; do
- case "$opt" in
- o)
- _compopts["$OPTARG"]=1
- ;;
- *)
- # Do nothing, explicitly. This silences shellcheck's detector
- # of unhandled command line options.
- ;;
- esac
- done
- builtin compgen "$@"
- }
- # compopt replaces the original compopt with one that just sets/unsets entries
- # in _compopts
- compopt() {
- local i
- for ((i=0; i<$#; i++)); do
- # in bash, ${!x} does variable indirection. Thus if x=1, ${!x} becomes $1.
- case "${!i}" in
- -o)
- ((i++))
- _compopts[${!i}]=1
- ;;
- +o)
- ((i++))
- unset _compopts[${!i}]
- ;;
- esac
- done
- }
- _compfunc="_minimal"
- _compact=""
- # this is a lot more complicated than it should be, but it's how you
- # get the result of 'complete -p "$1"' into an array, splitting it as
- # the shell would.
- readarray -t _comp < <(xargs -n1 < <(complete -p "$1") )
- # _comp is now an array of the appropriate 'complete' invocation, word-split as
- # the shell would, so we can now inspect it with getopts to determine the
- # appropriate completion action.
- # Unfortunately shellcheck doesn't know about readarray:
- # shellcheck disable=SC2154
- if [[ "${_comp[*]}" ]]; then
- while getopts :abcdefgjksuvA:C:W:o:F: opt "${_comp[@]:1}"; do
- case "$opt" in
- a)
- _compact="alias"
- ;;
- b)
- _compact="builtin"
- ;;
- c)
- _compact="command"
- ;;
- d)
- _compact="directory"
- ;;
- e)
- _compact="export"
- ;;
- f)
- _compact="file"
- ;;
- g)
- _compact="group"
- ;;
- j)
- _compact="job"
- ;;
- k)
- _compact="keyword"
- ;;
- s)
- _compact="service"
- ;;
- u)
- _compact="user"
- ;;
- v)
- _compact="variable"
- ;;
- A)
- _compact="$OPTARG"
- ;;
- o)
- _compopts["$OPTARG"]=1
- ;;
- C|F)
- _compfunc="$OPTARG"
- ;;
- W)
- readarray -t COMPREPLY < <( builtin compgen -W "$OPTARG" -- "${COMP_WORDS[COMP_CWORD]}" )
- _compfunc=""
- ;;
- *)
- # P, G, S, and X are not supported yet
- _die "ERROR: unknown option -$OPTARG"
- ;;
- esac
- done
- fi
- _bounce=""
- case "$_compact" in
- # these are for completing things that'll be interpreted by the
- # "outside" bash, so send them back to be completed there.
- "alias"|"export"|"job"|"variable")
- _bounce="$_compact"
- ;;
- esac
- if [ ! "$_bounce" ]; then
- if [ "$_compact" ]; then
- readarray -t COMPREPLY < <( builtin compgen -A "$_compact" -- "${COMP_WORDS[COMP_CWORD]}" )
- elif [ "$_compfunc" ]; then
- # execute completion function (or the command if -C)
- # from https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html:
- # When the function or command is invoked, the first argument ($1) is
- # the name of the command whose arguments are being completed, the
- # second argument ($2) is the word being completed, and the third
- # argument ($3) is the word preceding the word being completed on the
- # current command line.
- # that's "$1" "${COMP_WORDS[COMP_CWORD]}" and "${COMP_WORDS[COMP_CWORD-1]}"
- # (probably)
- $_compfunc "$1" "${COMP_WORDS[COMP_CWORD]}" "${COMP_WORDS[COMP_CWORD-1]}"
- fi
- fi
- # print completions to stdout
- echo "${!_compopts[@]}"
- echo "$_bounce"
- echo ""
- printf "%s\\n" "${COMPREPLY[@]}"
|