# 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[@]}"