From 39cc200f9e6d9b03d1e76a924371f85304e786d5 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Mon, 29 May 2023 03:26:32 +0900 Subject: [PATCH] feat(_comp_compgen): support `-i cmd` and `-x cmd` --- bash_completion | 39 ++++++++++++++++--- .../_comp_compgen/completions/compgen-cmd1 | 19 +++++++++ .../_comp_compgen/completions/compgen-cmd2 | 11 ++++++ test/t/unit/test_unit_compgen.py | 18 +++++++++ 4 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 test/fixtures/_comp_compgen/completions/compgen-cmd1 create mode 100644 test/fixtures/_comp_compgen/completions/compgen-cmd2 diff --git a/bash_completion b/bash_completion index c127b043aa5..fc7b8c3271a 100644 --- a/bash_completion +++ b/bash_completion @@ -457,11 +457,14 @@ _comp_compgen__error_fallback() # @return True (0) if at least one completion is generated, False (1) if no # completion is generated, or 2 with an incorrect usage. # -# Usage #2: _comp_compgen [-aR|-v arr|-c cur|-C dir] name args... +# Usage #2: _comp_compgen [-aR|-v arr|-c cur|-C dir|-i cmd|-x cmd] name args... # Call the generator `_comp_compgen_NAME ARGS...` with the specified options. # This provides a common interface to call the functions `_comp_compgen_NAME`, # which produce completion candidates, with custom options [-alR|-v arr|-c # cur]. The option `-F sep` is not used with this usage. +# OPTIONS +# -x cmd Call exported generator `_comp_xfunc_CMD_compgen_NAME` +# -i cmd Call internal generator `_comp_cmd_CMD__compgen_NAME` # @param $1... name args Calls the function _comp_compgen_NAME with the # specified ARGS (if $1 does not start with a hyphen `-`). The options # [-alR|-v arr|-c cur] are inherited by the child calls of `_comp_compgen` @@ -516,7 +519,7 @@ _comp_compgen() shopt -u nocasematch fi local OPTIND=1 OPTARG="" OPTERR=0 _opt - while getopts ':av:Rc:C:lF:' _opt "$@"; do + while getopts ':av:Rc:C:lF:i:x:' _opt "$@"; do case $_opt in a) _append=set ;; v) @@ -537,6 +540,20 @@ _comp_compgen() ;; l) _has_ifs=set _ifs=$'\n' ;; F) _has_ifs=set _ifs=$OPTARG ;; + [ix]) + if [[ ! $OPTARG ]]; then + printf 'bash_completion: %s: -%s: invalid command name `%s'\''\n' "$FUNCNAME" "$_opt" "$OPTARG" >&2 + return 2 + elif [[ $_icmd ]]; then + printf 'bash_completion: %s: -%s: `-i %s'\'' is already specified\n' "$FUNCNAME" "$_opt" "$_icmd" >&2 + return 2 + elif [[ $_xcmd ]]; then + printf 'bash_completion: %s: -%s: `-x %s'\'' is already specified\n' "$FUNCNAME" "$_opt" "$_xcmd" >&2 + return 2 + fi + ;;& + i) _icmd=$OPTARG ;; + x) _xcmd=$OPTARG ;; *) printf 'bash_completion: %s: usage error\n' "$FUNCNAME" >&2 return 2 @@ -558,8 +575,16 @@ _comp_compgen() return 2 fi - if ! declare -F "_comp_compgen_$1" &>/dev/null; then - printf 'bash_completion: %s: unrecognized category `%s'\'' (function _comp_compgen_%s not found)\n' "$FUNCNAME" "$1" "$1" >&2 + local -a _generator + if [[ $_icmd ]]; then + _generator=("_comp_cmd_${_icmd//[^a-zA-Z0-9_]/_}__compgen_$1") + elif [[ $_xcmd ]]; then + _generator=(_comp_xfunc "$_xcmd" "compgen_$1") + else + _generator=("_comp_compgen_$1") + fi + if ! declare -F "${_generator[0]}" &>/dev/null; then + printf 'bash_completion: %s: unrecognized generator `%s'\'' (function %s not found)\n' "$FUNCNAME" "$1" "${_generator[0]}" >&2 return 2 fi @@ -581,7 +606,7 @@ _comp_compgen() # Note: we use $1 as a part of a function name, and we use $2... as # arguments to the function if any. # shellcheck disable=SC2145 - _comp_compgen_"$@" + "${_generator[@]}" "${@:2}" local _status=$? # Go back to the original directory. @@ -595,6 +620,10 @@ _comp_compgen() fi # usage: _comp_compgen [options] -- [compgen_options] + if [[ $_icmd || $_xcmd ]]; then + printf 'bash_completion: %s: generator name is unspecified for `%s'\''\n' "$FUNCNAME" "${_icmd:+-i $_icmd}${_xcmd:+x $_xcmd}" >&2 + return 2 + fi # Note: $* in the below checks would be affected by uncontrolled IFS in # bash >= 5.0, so we need to set IFS to the normal value. The behavior in diff --git a/test/fixtures/_comp_compgen/completions/compgen-cmd1 b/test/fixtures/_comp_compgen/completions/compgen-cmd1 new file mode 100644 index 00000000000..70199ba8f88 --- /dev/null +++ b/test/fixtures/_comp_compgen/completions/compgen-cmd1 @@ -0,0 +1,19 @@ +# Dummy completion file for _comp_compgen tests -*- shell-script -*- + +_comp_xfunc_compgen_cmd1_compgen_generator1() { + _comp_compgen -- -W '5foo 6bar 7baz' +} + +_comp_cmd_compgen_cmd1__compgen_generator2() { + _comp_compgen -- -W '5abc 6def 7ghi' +} + +_comp_cmd_compgen_cmd1() { + local cur prev words cword comp_args + _comp_initialize -- "$@" || return + _comp_compgen -- -W '012 123 234' + _comp_compgen -ai compgen-cmd1 generator2 +} && + complete -F _comp_cmd_compgen_cmd1 compgen-cmd1 + +# ex: filetype=sh diff --git a/test/fixtures/_comp_compgen/completions/compgen-cmd2 b/test/fixtures/_comp_compgen/completions/compgen-cmd2 new file mode 100644 index 00000000000..6b6255f8e42 --- /dev/null +++ b/test/fixtures/_comp_compgen/completions/compgen-cmd2 @@ -0,0 +1,11 @@ +# Dummy completion file for _comp_compgen tests -*- shell-script -*- + +_comp_cmd_compgen_cmd2() { + local cur prev words cword comp_args + _comp_initialize -- "$@" || return + _comp_compgen -- -W '012 123 234' + _comp_compgen -ax compgen-cmd1 generator1 +} && + complete -F _comp_cmd_compgen_cmd2 compgen-cmd2 + +# ex: filetype=sh diff --git a/test/t/unit/test_unit_compgen.py b/test/t/unit/test_unit_compgen.py index 90ef54540f0..f70de303033 100644 --- a/test/t/unit/test_unit_compgen.py +++ b/test/t/unit/test_unit_compgen.py @@ -128,3 +128,21 @@ def test_6_option_C_4(self, functions, completion): # Note: we are not in the original directory that "b" exists, so Bash # will not suffix a slash to the directory name. assert completion == "b" + + def test_7_icmd(self, bash, functions): + with bash_env_saved(bash) as bash_env: + bash_env.write_variable( + "BASH_COMPLETION_USER_DIR", "$PWD/_comp_compgen", quote=False + ) + + completions = assert_complete(bash, "compgen-cmd1 '") + assert completions == ["012", "123", "234", "5abc", "6def", "7ghi"] + + def test_7_xcmd(self, bash, functions): + with bash_env_saved(bash) as bash_env: + bash_env.write_variable( + "BASH_COMPLETION_USER_DIR", "$PWD/_comp_compgen", quote=False + ) + + completions = assert_complete(bash, "compgen-cmd2 '") + assert completions == ["012", "123", "234", "5foo", "6bar", "7baz"]