etelpmoc.sh 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. # shellcheck shell=bash
  2. #
  3. # Copyright (C) 2017 Canonical Ltd
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License version 3 as
  7. # published by the Free Software Foundation.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. # etelpmoc is the reverse of complete: it de-serialises the tab completion
  17. # request into the appropriate environment variables expected by the tab
  18. # completion tools, performs whatever action is wanted, and serialises the
  19. # result. It accomplishes this by having functions override the builtin
  20. # completion commands.
  21. #
  22. # this always runs "inside", in the same environment you get when doing "snap
  23. # run --shell", and snap-exec is the one setting the first argument to the
  24. # completion script set in the snap. The rest of the arguments come through
  25. # from snap-run --command=complete <snap> <args...>
  26. _die() {
  27. echo "$*" >&2
  28. exit 1
  29. }
  30. if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then
  31. _die "ERROR: this is meant to be run, not sourced."
  32. fi
  33. if [[ "${#@}" -lt 8 ]]; then
  34. _die "USAGE: $0 <script> <COMP_TYPE> <COMP_KEY> <COMP_POINT> <COMP_CWORD> <COMP_WORDBREAKS> <COMP_LINE> cmd [args...]"
  35. fi
  36. # De-serialize the command line arguments and populate tab completion environment
  37. _compscript="$1"
  38. shift
  39. COMP_TYPE="$1"
  40. shift
  41. COMP_KEY="$1"
  42. shift
  43. COMP_POINT="$1"
  44. shift
  45. COMP_CWORD="$1"
  46. shift
  47. COMP_WORDBREAKS="$1"
  48. shift
  49. # duplication, but whitespace is eaten and that throws off COMP_POINT
  50. COMP_LINE="$1"
  51. shift
  52. # rest of the args is the command itself
  53. COMP_WORDS=("$@")
  54. COMPREPLY=()
  55. if [[ ! "$_compscript" ]]; then
  56. _die "ERROR: completion script filename can't be empty"
  57. fi
  58. if [[ ! -f "$_compscript" ]]; then
  59. _die "ERROR: completion script does not exist"
  60. fi
  61. # Source the bash-completion library functions and common completion setup
  62. # shellcheck disable=SC1091
  63. . /usr/share/bash-completion/bash_completion
  64. # Now source the snap's 'completer' script itself
  65. # shellcheck disable=SC1090
  66. . "$_compscript"
  67. # _compopts is an associative array, which keys are options. The options are
  68. # described in bash(1)'s description of the -o option to the "complete"
  69. # builtin, and they affect how the completion options are presented to the user
  70. # (e.g. adding a slash for directories, whether to add a space after the
  71. # completion, etc). These need setting in the user's environment so need
  72. # serializing separately from the completions themselves.
  73. declare -A _compopts
  74. # wrap compgen, setting _compopts for any options given.
  75. # (as these options need handling separately from the completions)
  76. compgen() {
  77. local opt
  78. while getopts :o: opt; do
  79. case "$opt" in
  80. o)
  81. _compopts["$OPTARG"]=1
  82. ;;
  83. *)
  84. # Do nothing, explicitly. This silences shellcheck's detector
  85. # of unhandled command line options.
  86. ;;
  87. esac
  88. done
  89. builtin compgen "$@"
  90. }
  91. # compopt replaces the original compopt with one that just sets/unsets entries
  92. # in _compopts
  93. compopt() {
  94. local i
  95. for ((i=0; i<$#; i++)); do
  96. # in bash, ${!x} does variable indirection. Thus if x=1, ${!x} becomes $1.
  97. case "${!i}" in
  98. -o)
  99. ((i++))
  100. _compopts[${!i}]=1
  101. ;;
  102. +o)
  103. ((i++))
  104. unset _compopts[${!i}]
  105. ;;
  106. esac
  107. done
  108. }
  109. _compfunc="_minimal"
  110. _compact=""
  111. # this is a lot more complicated than it should be, but it's how you
  112. # get the result of 'complete -p "$1"' into an array, splitting it as
  113. # the shell would.
  114. readarray -t _comp < <(xargs -n1 < <(complete -p "$1") )
  115. # _comp is now an array of the appropriate 'complete' invocation, word-split as
  116. # the shell would, so we can now inspect it with getopts to determine the
  117. # appropriate completion action.
  118. # Unfortunately shellcheck doesn't know about readarray:
  119. # shellcheck disable=SC2154
  120. if [[ "${_comp[*]}" ]]; then
  121. while getopts :abcdefgjksuvA:C:W:o:F: opt "${_comp[@]:1}"; do
  122. case "$opt" in
  123. a)
  124. _compact="alias"
  125. ;;
  126. b)
  127. _compact="builtin"
  128. ;;
  129. c)
  130. _compact="command"
  131. ;;
  132. d)
  133. _compact="directory"
  134. ;;
  135. e)
  136. _compact="export"
  137. ;;
  138. f)
  139. _compact="file"
  140. ;;
  141. g)
  142. _compact="group"
  143. ;;
  144. j)
  145. _compact="job"
  146. ;;
  147. k)
  148. _compact="keyword"
  149. ;;
  150. s)
  151. _compact="service"
  152. ;;
  153. u)
  154. _compact="user"
  155. ;;
  156. v)
  157. _compact="variable"
  158. ;;
  159. A)
  160. _compact="$OPTARG"
  161. ;;
  162. o)
  163. _compopts["$OPTARG"]=1
  164. ;;
  165. C|F)
  166. _compfunc="$OPTARG"
  167. ;;
  168. W)
  169. readarray -t COMPREPLY < <( builtin compgen -W "$OPTARG" -- "${COMP_WORDS[COMP_CWORD]}" )
  170. _compfunc=""
  171. ;;
  172. *)
  173. # P, G, S, and X are not supported yet
  174. _die "ERROR: unknown option -$OPTARG"
  175. ;;
  176. esac
  177. done
  178. fi
  179. _bounce=""
  180. case "$_compact" in
  181. # these are for completing things that'll be interpreted by the
  182. # "outside" bash, so send them back to be completed there.
  183. "alias"|"export"|"job"|"variable")
  184. _bounce="$_compact"
  185. ;;
  186. esac
  187. if [ ! "$_bounce" ]; then
  188. if [ "$_compact" ]; then
  189. readarray -t COMPREPLY < <( builtin compgen -A "$_compact" -- "${COMP_WORDS[COMP_CWORD]}" )
  190. elif [ "$_compfunc" ]; then
  191. # execute completion function (or the command if -C)
  192. # from https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html:
  193. # When the function or command is invoked, the first argument ($1) is
  194. # the name of the command whose arguments are being completed, the
  195. # second argument ($2) is the word being completed, and the third
  196. # argument ($3) is the word preceding the word being completed on the
  197. # current command line.
  198. # that's "$1" "${COMP_WORDS[COMP_CWORD]}" and "${COMP_WORDS[COMP_CWORD-1]}"
  199. # (probably)
  200. $_compfunc "$1" "${COMP_WORDS[COMP_CWORD]}" "${COMP_WORDS[COMP_CWORD-1]}"
  201. fi
  202. fi
  203. # print completions to stdout
  204. echo "${!_compopts[@]}"
  205. echo "$_bounce"
  206. echo ""
  207. printf "%s\\n" "${COMPREPLY[@]}"