You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

388 lines
14 KiB

  1. #!/bin/bash
  2. #
  3. # Bash completion generated for '{{name}}' at {{date}}.
  4. #
  5. # The original template lives here:
  6. # https://github.com/trentm/node-dashdash/blob/master/etc/dashdash.bash_completion.in
  7. #
  8. #
  9. # Copyright 2016 Trent Mick
  10. # Copyright 2016 Joyent, Inc.
  11. #
  12. #
  13. # A generic Bash completion driver script.
  14. #
  15. # This is meant to provide a re-usable chunk of Bash to use for
  16. # "etc/bash_completion.d/" files for individual tools. Only the "Configuration"
  17. # section with tool-specific info need differ. Features:
  18. #
  19. # - support for short and long opts
  20. # - support for knowing which options take arguments
  21. # - support for subcommands (e.g. 'git log <TAB>' to show just options for the
  22. # log subcommand)
  23. # - does the right thing with "--" to stop options
  24. # - custom optarg and arg types for custom completions
  25. # - (TODO) support for shells other than Bash (tcsh, zsh, fish?, etc.)
  26. #
  27. #
  28. # Examples/design:
  29. #
  30. # 1. Bash "default" completion. By default Bash's 'complete -o default' is
  31. # enabled. That means when there are no completions (e.g. if no opts match
  32. # the current word), then you'll get Bash's default completion. Most notably
  33. # that means you get filename completion. E.g.:
  34. # $ tool ./<TAB>
  35. # $ tool READ<TAB>
  36. #
  37. # 2. all opts and subcmds:
  38. # $ tool <TAB>
  39. # $ tool -v <TAB> # assuming '-v' doesn't take an arg
  40. # $ tool -<TAB> # matching opts
  41. # $ git lo<TAB> # matching subcmds
  42. #
  43. # Long opt completions are given *without* the '=', i.e. we prefer space
  44. # separated because that's easier for good completions.
  45. #
  46. # 3. long opt arg with '='
  47. # $ tool --file=<TAB>
  48. # $ tool --file=./d<TAB>
  49. # We maintain the "--file=" prefix. Limitation: With the attached prefix
  50. # the 'complete -o filenames' doesn't know to do dirname '/' suffixing. Meh.
  51. #
  52. # 4. envvars:
  53. # $ tool $<TAB>
  54. # $ tool $P<TAB>
  55. # Limitation: Currently only getting exported vars, so we miss "PS1" and
  56. # others.
  57. #
  58. # 5. Defer to other completion in a subshell:
  59. # $ tool --file $(cat ./<TAB>
  60. # We get this from 'complete -o default ...'.
  61. #
  62. # 6. Custom completion types from a provided bash function.
  63. # $ tool --profile <TAB> # complete available "profiles"
  64. #
  65. #
  66. # Dev Notes:
  67. # - compgen notes, from http://unix.stackexchange.com/questions/151118/understand-compgen-builtin-command
  68. # - https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion-Builtins.html
  69. #
  70. # Debugging this completion:
  71. # 1. Uncomment the "_{{name}}_log_file=..." line.
  72. # 2. 'tail -f /var/tmp/dashdash-completion.log' in one terminal.
  73. # 3. Re-source this bash completion file.
  74. #_{{name}}_log=/var/tmp/dashdash-completion.log
  75. function _{{name}}_completer {
  76. # ---- cmd definition
  77. {{spec}}
  78. # ---- locals
  79. declare -a argv
  80. # ---- support functions
  81. function trace {
  82. [[ -n "$_{{name}}_log" ]] && echo "$*" >&2
  83. }
  84. function _dashdash_complete {
  85. local idx context
  86. idx=$1
  87. context=$2
  88. local shortopts longopts optargs subcmds allsubcmds argtypes
  89. shortopts="$(eval "echo \${cmd${context}_shortopts}")"
  90. longopts="$(eval "echo \${cmd${context}_longopts}")"
  91. optargs="$(eval "echo \${cmd${context}_optargs}")"
  92. subcmds="$(eval "echo \${cmd${context}_subcmds}")"
  93. allsubcmds="$(eval "echo \${cmd${context}_allsubcmds}")"
  94. IFS=', ' read -r -a argtypes <<< "$(eval "echo \${cmd${context}_argtypes}")"
  95. trace ""
  96. trace "_dashdash_complete(idx=$idx, context=$context)"
  97. trace " shortopts: $shortopts"
  98. trace " longopts: $longopts"
  99. trace " optargs: $optargs"
  100. trace " subcmds: $subcmds"
  101. trace " allsubcmds: $allsubcmds"
  102. # Get 'state' of option parsing at this COMP_POINT.
  103. # Copying "dashdash.js#parse()" behaviour here.
  104. local state=
  105. local nargs=0
  106. local i=$idx
  107. local argtype
  108. local optname
  109. local prefix
  110. local word
  111. local dashdashseen=
  112. while [[ $i -lt $len && $i -le $COMP_CWORD ]]; do
  113. argtype=
  114. optname=
  115. prefix=
  116. word=
  117. arg=${argv[$i]}
  118. trace " consider argv[$i]: '$arg'"
  119. if [[ "$arg" == "--" && $i -lt $COMP_CWORD ]]; then
  120. trace " dashdash seen"
  121. dashdashseen=yes
  122. state=arg
  123. word=$arg
  124. elif [[ -z "$dashdashseen" && "${arg:0:2}" == "--" ]]; then
  125. arg=${arg:2}
  126. if [[ "$arg" == *"="* ]]; then
  127. optname=${arg%%=*}
  128. val=${arg##*=}
  129. trace " long opt: optname='$optname' val='$val'"
  130. state=arg
  131. argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1)
  132. word=$val
  133. prefix="--$optname="
  134. else
  135. optname=$arg
  136. val=
  137. trace " long opt: optname='$optname'"
  138. state=longopt
  139. word=--$optname
  140. if [[ "$optargs" == *"-$optname="* && $i -lt $COMP_CWORD ]]; then
  141. i=$(( $i + 1 ))
  142. state=arg
  143. argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1)
  144. word=${argv[$i]}
  145. trace " takes arg (consume argv[$i], word='$word')"
  146. fi
  147. fi
  148. elif [[ -z "$dashdashseen" && "${arg:0:1}" == "-" ]]; then
  149. trace " short opt group"
  150. state=shortopt
  151. word=$arg
  152. local j=1
  153. while [[ $j -lt ${#arg} ]]; do
  154. optname=${arg:$j:1}
  155. trace " consider index $j: optname '$optname'"
  156. if [[ "$optargs" == *"-$optname="* ]]; then
  157. argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1)
  158. if [[ $(( $j + 1 )) -lt ${#arg} ]]; then
  159. state=arg
  160. word=${arg:$(( $j + 1 ))}
  161. trace " takes arg (rest of this arg, word='$word', argtype='$argtype')"
  162. elif [[ $i -lt $COMP_CWORD ]]; then
  163. state=arg
  164. i=$(( $i + 1 ))
  165. word=${argv[$i]}
  166. trace " takes arg (word='$word', argtype='$argtype')"
  167. fi
  168. break
  169. fi
  170. j=$(( $j + 1 ))
  171. done
  172. elif [[ $i -lt $COMP_CWORD && -n "$arg" ]] && $(echo "$allsubcmds" | grep -w "$arg" >/dev/null); then
  173. trace " complete subcmd: recurse _dashdash_complete"
  174. _dashdash_complete $(( $i + 1 )) "${context}__${arg/-/_}"
  175. return
  176. else
  177. trace " not an opt or a complete subcmd"
  178. state=arg
  179. word=$arg
  180. nargs=$(( $nargs + 1 ))
  181. if [[ ${#argtypes[@]} -gt 0 ]]; then
  182. argtype="${argtypes[$(( $nargs - 1 ))]}"
  183. if [[ -z "$argtype" ]]; then
  184. # If we have more args than argtypes, we use the
  185. # last type.
  186. argtype="${argtypes[@]: -1:1}"
  187. fi
  188. fi
  189. fi
  190. trace " state=$state prefix='$prefix' word='$word'"
  191. i=$(( $i + 1 ))
  192. done
  193. trace " parsed: state=$state optname='$optname' argtype='$argtype' prefix='$prefix' word='$word' dashdashseen=$dashdashseen"
  194. local compgen_opts=
  195. if [[ -n "$prefix" ]]; then
  196. compgen_opts="$compgen_opts -P $prefix"
  197. fi
  198. case $state in
  199. shortopt)
  200. compgen $compgen_opts -W "$shortopts $longopts" -- "$word"
  201. ;;
  202. longopt)
  203. compgen $compgen_opts -W "$longopts" -- "$word"
  204. ;;
  205. arg)
  206. # If we don't know what completion to do, then emit nothing. We
  207. # expect that we are running with:
  208. # complete -o default ...
  209. # where "default" means: "Use Readline's default completion if
  210. # the compspec generates no matches." This gives us the good filename
  211. # completion, completion in subshells/backticks.
  212. #
  213. # We cannot support an argtype="directory" because
  214. # compgen -S '/' -A directory -- "$word"
  215. # doesn't give a satisfying result. It doesn't stop at the trailing '/'
  216. # so you cannot descend into dirs.
  217. if [[ "${word:0:1}" == '$' ]]; then
  218. # By default, Bash will complete '$<TAB>' to all envvars. Apparently
  219. # 'complete -o default' does *not* give us that. The following
  220. # gets *close* to the same completions: '-A export' misses envvars
  221. # like "PS1".
  222. trace " completing envvars"
  223. compgen $compgen_opts -P '$' -A export -- "${word:1}"
  224. elif [[ -z "$argtype" ]]; then
  225. # Only include opts in completions if $word is not empty.
  226. # This is to avoid completing the leading '-', which foils
  227. # using 'default' completion.
  228. if [[ -n "$dashdashseen" ]]; then
  229. trace " completing subcmds, if any (no argtype, dashdash seen)"
  230. compgen $compgen_opts -W "$subcmds" -- "$word"
  231. elif [[ -z "$word" ]]; then
  232. trace " completing subcmds, if any (no argtype, empty word)"
  233. compgen $compgen_opts -W "$subcmds" -- "$word"
  234. else
  235. trace " completing opts & subcmds (no argtype)"
  236. compgen $compgen_opts -W "$shortopts $longopts $subcmds" -- "$word"
  237. fi
  238. elif [[ $argtype == "none" ]]; then
  239. # We want *no* completions, i.e. some way to get the active
  240. # 'complete -o default' to not do filename completion.
  241. trace " completing 'none' (hack to imply no completions)"
  242. echo "##-no-completion- -results-##"
  243. elif [[ $argtype == "file" ]]; then
  244. # 'complete -o default' gives the best filename completion, at least
  245. # on Mac.
  246. trace " completing 'file' (let 'complete -o default' handle it)"
  247. echo ""
  248. elif ! type complete_$argtype 2>/dev/null >/dev/null; then
  249. trace " completing '$argtype' (fallback to default b/c complete_$argtype is unknown)"
  250. echo ""
  251. else
  252. trace " completing custom '$argtype'"
  253. completions=$(complete_$argtype "$word")
  254. if [[ -z "$completions" ]]; then
  255. trace " no custom '$argtype' completions"
  256. # These are in ascii and "dictionary" order so they sort
  257. # correctly.
  258. echo "##-no-completion- -results-##"
  259. else
  260. echo $completions
  261. fi
  262. fi
  263. ;;
  264. *)
  265. trace " unknown state: $state"
  266. ;;
  267. esac
  268. }
  269. trace ""
  270. trace "-- $(date)"
  271. #trace "\$IFS: '$IFS'"
  272. #trace "\$@: '$@'"
  273. #trace "COMP_WORDBREAKS: '$COMP_WORDBREAKS'"
  274. trace "COMP_CWORD: '$COMP_CWORD'"
  275. trace "COMP_LINE: '$COMP_LINE'"
  276. trace "COMP_POINT: $COMP_POINT"
  277. # Guard against negative COMP_CWORD. This is a Bash bug at least on
  278. # Mac 10.10.4's bash. See
  279. # <https://lists.gnu.org/archive/html/bug-bash/2009-07/msg00125.html>.
  280. if [[ $COMP_CWORD -lt 0 ]]; then
  281. trace "abort on negative COMP_CWORD"
  282. exit 1;
  283. fi
  284. # I don't know how to do array manip on argv vars,
  285. # so copy over to argv array to work on them.
  286. shift # the leading '--'
  287. i=0
  288. len=$#
  289. while [[ $# -gt 0 ]]; do
  290. argv[$i]=$1
  291. shift;
  292. i=$(( $i + 1 ))
  293. done
  294. trace "argv: '${argv[@]}'"
  295. trace "argv[COMP_CWORD-1]: '${argv[$(( $COMP_CWORD - 1 ))]}'"
  296. trace "argv[COMP_CWORD]: '${argv[$COMP_CWORD]}'"
  297. trace "argv len: '$len'"
  298. _dashdash_complete 1 ""
  299. }
  300. # ---- mainline
  301. # Note: This if-block to help work with 'compdef' and 'compctl' is
  302. # adapted from 'npm completion'.
  303. if type complete &>/dev/null; then
  304. function _{{name}}_completion {
  305. local _log_file=/dev/null
  306. [[ -z "$_{{name}}_log" ]] || _log_file="$_{{name}}_log"
  307. COMPREPLY=($(COMP_CWORD="$COMP_CWORD" \
  308. COMP_LINE="$COMP_LINE" \
  309. COMP_POINT="$COMP_POINT" \
  310. _{{name}}_completer -- "${COMP_WORDS[@]}" \
  311. 2>$_log_file)) || return $?
  312. }
  313. complete -o default -F _{{name}}_completion {{name}}
  314. elif type compdef &>/dev/null; then
  315. function _{{name}}_completion {
  316. local _log_file=/dev/null
  317. [[ -z "$_{{name}}_log" ]] || _log_file="$_{{name}}_log"
  318. compadd -- $(COMP_CWORD=$((CURRENT-1)) \
  319. COMP_LINE=$BUFFER \
  320. COMP_POINT=0 \
  321. _{{name}}_completer -- "${words[@]}" \
  322. 2>$_log_file)
  323. }
  324. compdef _{{name}}_completion {{name}}
  325. elif type compctl &>/dev/null; then
  326. function _{{name}}_completion {
  327. local cword line point words si
  328. read -Ac words
  329. read -cn cword
  330. let cword-=1
  331. read -l line
  332. read -ln point
  333. local _log_file=/dev/null
  334. [[ -z "$_{{name}}_log" ]] || _log_file="$_{{name}}_log"
  335. reply=($(COMP_CWORD="$cword" \
  336. COMP_LINE="$line" \
  337. COMP_POINT="$point" \
  338. _{{name}}_completer -- "${words[@]}" \
  339. 2>$_log_file)) || return $?
  340. }
  341. compctl -K _{{name}}_completion {{name}}
  342. fi
  343. ##
  344. ## This is a Bash completion file for the '{{name}}' command. You can install
  345. ## with either:
  346. ##
  347. ## cp FILE /usr/local/etc/bash_completion.d/{{name}} # Mac
  348. ## cp FILE /etc/bash_completion.d/{{name}} # Linux
  349. ##
  350. ## or:
  351. ##
  352. ## cp FILE > ~/.{{name}}.completion
  353. ## echo "source ~/.{{name}}.completion" >> ~/.bashrc
  354. ##