diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f8780ab3ee5..0ad234a3ad6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: hooks: - id: shfmt-docker types: [text] - files: ^(bash_completion|completions/.+|test/(config/bashrc|update-test-cmd-list)|.+\.sh(\.in)?)$ + files: ^(bash_completion(\.d/[0-9]{3}_.+)?|completions/.+|test/(config/bashrc|update-test-cmd-list)|.+\.sh(\.in)?)$ exclude: ^completions/(\.gitignore|Makefile.*)$ - repo: https://github.com/shellcheck-py/shellcheck-py @@ -21,7 +21,7 @@ repos: - id: shellcheck args: [-f, gcc] types: [text] - files: ^(bash_completion|completions/.+|test/(config/bashrc|update-test-cmd-list)|.+\.sh(\.in)?)$ + files: ^(bash_completion(\.d/[0-9]{3}_.+)?|completions/.+|test/(config/bashrc|update-test-cmd-list)|.+\.sh(\.in)?)$ exclude: ^completions/(\.gitignore|Makefile.*)$ require_serial: false # We disable SC1090 anyway, so parallel is ok diff --git a/bash_completion b/bash_completion index 11588d6cf3d..68f8fac40d4 100644 --- a/bash_completion +++ b/bash_completion @@ -223,13 +223,13 @@ _comp_unlocal() # Assign variables one scope above the caller # Usage: local varname [varname ...] && -# _upvars [-v varname value] | [-aN varname [value ...]] ... +# _comp_upvars [-v varname value] | [-aN varname [value ...]] ... # Available OPTIONS: # -aN Assign next N values to varname as array # -v Assign single value to varname # @return 1 if error occurs # @see https://fvue.nl/wiki/Bash:_Passing_variables_by_reference -_upvars() +_comp_upvars() { if ! (($#)); then echo "bash_completion: $FUNCNAME: usage: $FUNCNAME" \ @@ -394,7 +394,7 @@ _comp_looks_like_path() # @param $2 words Name of variable to return words to # @param $3 cword Name of variable to return cword to # -__reassemble_comp_words_by_ref() +_comp__reassemble_words() { local exclude i j line ref # Exclude word separator characters? @@ -454,7 +454,7 @@ __reassemble_comp_words_by_ref() printf -v "$2[i]" %s "${COMP_WORDS[i]}" done fi -} # __reassemble_comp_words_by_ref() +} # _comp__reassemble_words() # @param $1 exclude Characters out of $COMP_WORDBREAKS which should NOT be # considered word breaks. This is useful for things like scp where @@ -463,11 +463,11 @@ __reassemble_comp_words_by_ref() # @param $2 words Name of variable to return words to # @param $3 cword Name of variable to return cword to # @param $4 cur Name of variable to return current word to complete to -# @see __reassemble_comp_words_by_ref() -__get_cword_at_cursor_by_ref() +# @see _comp__reassemble_words() +_comp__get_cword_at_cursor() { local cword words=() - __reassemble_comp_words_by_ref "$1" words cword + _comp__reassemble_words "$1" words cword local i cur="" index=$COMP_POINT lead=${COMP_LINE:0:COMP_POINT} # Cursor not at position 0 and not led by just space(s)? @@ -498,7 +498,7 @@ __get_cword_at_cursor_by_ref() ((index < 0)) && index=0 fi - local "$2" "$3" "$4" && _upvars -a${#words[@]} "$2" ${words+"${words[@]}"} \ + local "$2" "$3" "$4" && _comp_upvars -a${#words[@]} "$2" ${words+"${words[@]}"} \ -v "$3" "$cword" -v "$4" "${cur:0:index}" } @@ -508,7 +508,7 @@ __get_cword_at_cursor_by_ref() # (For example, if the line is "ls foobar", # and the cursor is here --------> ^ # Also one is able to cross over possible wordbreak characters. -# Usage: _get_comp_words_by_ref [OPTIONS] [VARNAMES] +# Usage: _comp_get_words [OPTIONS] [VARNAMES] # Available VARNAMES: # cur Return cur via $cur # prev Return prev via $prev @@ -527,9 +527,9 @@ __get_cword_at_cursor_by_ref() # # Example usage: # -# $ _get_comp_words_by_ref -n : cur prev +# $ _comp_get_words -n : cur prev # -_get_comp_words_by_ref() +_comp_get_words() { local exclude flag i OPTIND=1 local cur cword words=() @@ -564,7 +564,7 @@ _get_comp_words_by_ref() ((OPTIND += 1)) done - __get_cword_at_cursor_by_ref "${exclude-}" words cword cur + _comp__get_cword_at_cursor "${exclude-}" words cword cur [[ -v vcur ]] && { upvars+=("$vcur") @@ -583,21 +583,7 @@ _get_comp_words_by_ref() upargs+=(-a${#words[@]} $vwords ${words+"${words[@]}"}) } - ((${#upvars[@]})) && local "${upvars[@]}" && _upvars "${upargs[@]}" -} - -# Get word previous to the current word. -# This is a good alternative to `prev=${COMP_WORDS[COMP_CWORD-1]}' because bash4 -# will properly return the previous word with respect to any given exclusions to -# COMP_WORDBREAKS. -# @deprecated Use `_get_comp_words_by_ref cur prev' instead -# @see _get_comp_words_by_ref() -# -_get_pword() -{ - if ((COMP_CWORD >= 1)); then - _get_cword "${@-}" 1 - fi + ((${#upvars[@]})) && local "${upvars[@]}" && _comp_upvars "${upargs[@]}" } # If the word-to-complete contains a colon (:), left-trim COMPREPLY items with @@ -662,7 +648,7 @@ _quote_readline_by_ref() value=${value//'%'/%%} # Escape % for printf format. # shellcheck disable=SC2059 printf -v value "$value" # Decode escape sequences of \.... - local "$2" && _upvars -v "$2" "$value" + local "$2" && _comp_upvars -v "$2" "$value" fi fi } # _quote_readline_by_ref() @@ -909,7 +895,7 @@ _comp_variable_assignments() # cur, prev, words, and cword are local, ditto split if you use -s. # # Options: -# -n EXCLUDE Passed to _get_comp_words_by_ref -n with redirection chars +# -n EXCLUDE Passed to _comp_get_words -n with redirection chars # -e XSPEC Passed to _filedir as first arg for stderr redirections # -o XSPEC Passed to _filedir as first arg for other output redirections # -i XSPEC Passed to _filedir as first arg for stdin redirections @@ -957,7 +943,7 @@ _comp_initialize() COMPREPLY=() local redir='@(?(+([0-9])|{[a-zA-Z_]*([a-zA-Z_0-9])})@(>?([>|&])|&])|<?(>))' - _get_comp_words_by_ref -n "$exclude<>&" cur prev words cword + _comp_get_words -n "$exclude<>&" cur prev words cword # Complete variable names. _variables && return 1 @@ -1696,7 +1682,7 @@ _realcommand() # This function returns the first argument, excluding options # @param $1 chars Characters out of $COMP_WORDBREAKS which should -# NOT be considered word breaks. See __reassemble_comp_words_by_ref. +# NOT be considered word breaks. See _comp__reassemble_words. _get_first_arg() { local i @@ -1712,13 +1698,13 @@ _get_first_arg() # This function counts the number of args, excluding options # @param $1 chars Characters out of $COMP_WORDBREAKS which should -# NOT be considered word breaks. See __reassemble_comp_words_by_ref. +# NOT be considered word breaks. See _comp__reassemble_words. # @param $2 glob Options whose following argument should not be counted # @param $3 glob Options that should be counted as args _count_args() { local i cword words - __reassemble_comp_words_by_ref "${1-}" words cword + _comp__reassemble_words "${1-}" words cword args=1 for ((i = 1; i < cword; i++)); do @@ -2177,7 +2163,7 @@ _comp_command_offset() COMPREPLY=() local cur - _get_comp_words_by_ref cur + _comp_get_words cur if ((COMP_CWORD == 0)); then local IFS=$'\n' diff --git a/bash_completion.d/000_bash_completion_compat b/bash_completion.d/000_bash_completion_compat index 4ee534ff509..4d76f680e1a 100644 --- a/bash_completion.d/000_bash_completion_compat +++ b/bash_completion.d/000_bash_completion_compat @@ -9,6 +9,10 @@ _comp_deprecate_func _command_offset _comp_command_offset _comp_deprecate_func _command _comp_command _comp_deprecate_func _root_command _comp_root_command _comp_deprecate_func _xfunc _comp_xfunc +_comp_deprecate_func _upvars _comp_upvars +_comp_deprecate_func __reassemble_comp_words_by_ref _comp__reassemble_words +_comp_deprecate_func __get_cword_at_cursor_by_ref _comp__get_cword_at_cursor +_comp_deprecate_func _get_comp_words_by_ref _comp_get_words # Backwards compatibility for compat completions that use have(). # @deprecated should no longer be used; generally not needed with dynamically @@ -47,14 +51,15 @@ dequote() # @param $1 Variable name to assign value to # @param $* Value(s) to assign. If multiple values, an array is # assigned, otherwise a single value is assigned. -# NOTE: For assigning multiple variables, use '_upvars'. Do NOT +# NOTE: For assigning multiple variables, use '_comp_upvars'. Do NOT # use multiple '_upvar' calls, since one '_upvar' call might # reassign a variable to be used by another '_upvar' call. # @see https://fvue.nl/wiki/Bash:_Passing_variables_by_reference +# @deprecated Use `_comp_upvars' instead _upvar() { echo "bash_completion: $FUNCNAME: deprecated function," \ - "use _upvars instead" >&2 + "use _comp_upvars instead" >&2 if unset -v "$1"; then # Unset & validate varname # shellcheck disable=SC2140 # TODO if (($# == 2)); then @@ -78,13 +83,13 @@ _upvar() # current word (default is 0, previous is 1), respecting the exclusions # given at $1. For example, `_get_cword "=:" 1' returns the word left of # the current word, respecting the exclusions "=:". -# @deprecated Use `_get_comp_words_by_ref cur' instead -# @see _get_comp_words_by_ref() +# @deprecated Use `_comp_get_words cur' instead +# @see _comp_get_words() _get_cword() { local LC_CTYPE=C local cword words - __reassemble_comp_words_by_ref "${1-}" words cword + _comp__reassemble_words "${1-}" words cword # return previous word offset by $2 if [[ ${2-} && ${2//[^0-9]/} ]]; then @@ -125,8 +130,23 @@ _get_cword() fi } # _get_cword() +# Get word previous to the current word. +# This is a good alternative to `prev=${COMP_WORDS[COMP_CWORD-1]}' because bash4 +# will properly return the previous word with respect to any given exclusions to +# COMP_WORDBREAKS. +# @deprecated Use `_comp_get_words cur prev' instead +# @see _comp_get_words() +# +_get_pword() +{ + if ((COMP_CWORD >= 1)); then + _get_cword "${@-}" 1 + fi +} + # @deprecated Use the variable `_comp_backup_glob` instead. This is the # backward-compatibility name. +# shellcheck disable=SC2154 # defined in the main "bash_completion" _backup_glob=$_comp_backup_glob # ex: filetype=sh diff --git a/completions/ssh b/completions/ssh index 4cbe87d27f4..cd1b1366c24 100644 --- a/completions/ssh +++ b/completions/ssh @@ -217,9 +217,10 @@ _ssh_suboption() _comp_xfunc_ssh_suboption_check() { # Get prev and cur words without splitting on = - local cureq=$(_get_cword :=) preveq=$(_get_pword :=) - if [[ $cureq == *=* && $preveq == -*o ]]; then - _ssh_suboption "$cureq" "${1-}" + local cur prev + _comp_get_words -n := cur prev + if [[ $cur == *=* && $prev == -*o ]]; then + _ssh_suboption "$cur" "${1-}" return $? fi return 1 diff --git a/completions/xsltproc b/completions/xsltproc index d77f86f6a88..5d81272a5cb 100644 --- a/completions/xsltproc +++ b/completions/xsltproc @@ -34,7 +34,7 @@ _xsltproc() ;; esac - [[ $cword -gt 2 && $(_get_cword '' 2) == --?(string)param ]] && return + [[ $cword -gt 2 && ${words[cword - 2]} == --?(string)param ]] && return if [[ $cur == -* ]]; then COMPREPLY=($(compgen -W '$(_parse_help "$1")' -- "$cur")) diff --git a/test/t/test_umount.py b/test/t/test_umount.py index c77e613da2a..de188358f0b 100644 --- a/test/t/test_umount.py +++ b/test/t/test_umount.py @@ -19,8 +19,8 @@ def dummy_mnt(self, request, bash): assert_bash_exec( bash, "_mnt_completion() { " - "local cur=$(_get_cword); " - "_comp_cmd_umount__linux_fstab $(_get_pword) < mount/test-fstab; " + "local cur prev;_comp_get_words cur prev; " + '_comp_cmd_umount__linux_fstab "$prev" < mount/test-fstab; ' "} && complete -F _mnt_completion _mnt", ) request.addfinalizer( diff --git a/test/t/unit/Makefile.am b/test/t/unit/Makefile.am index 7530a0ed6ea..6de2308ed7e 100644 --- a/test/t/unit/Makefile.am +++ b/test/t/unit/Makefile.am @@ -8,7 +8,7 @@ EXTRA_DIST = \ test_unit_expand_tilde_by_ref.py \ test_unit_filedir.py \ test_unit_find_unique_completion_pair.py \ - test_unit_get_comp_words_by_ref.py \ + test_unit_get_words.py \ test_unit_get_cword.py \ test_unit_initialize.py \ test_unit_ip_addresses.py \ diff --git a/test/t/unit/test_unit_filedir.py b/test/t/unit/test_unit_filedir.py index e0371b8dc62..eeb64f844a5 100644 --- a/test/t/unit/test_unit_filedir.py +++ b/test/t/unit/test_unit_filedir.py @@ -15,18 +15,18 @@ class TestUnitFiledir: def functions(self, request, bash): assert_bash_exec( bash, - "_f() { local cur=$(_get_cword); unset -v COMPREPLY; _filedir; }; " + "_f() { local cur;_comp_get_words cur; unset -v COMPREPLY; _filedir; }; " "complete -F _f f; " "complete -F _f -o filenames f2", ) assert_bash_exec( bash, - "_g() { local cur=$(_get_cword); unset -v COMPREPLY; _filedir e1; }; " + "_g() { local cur;_comp_get_words cur; unset -v COMPREPLY; _filedir e1; }; " "complete -F _g g", ) assert_bash_exec( bash, - "_fd() { local cur=$(_get_cword); unset -v COMPREPLY; _filedir -d; };" + "_fd() { local cur;_comp_get_words cur; unset -v COMPREPLY; _filedir -d; };" "complete -F _fd fd", ) diff --git a/test/t/unit/test_unit_get_comp_words_by_ref.py b/test/t/unit/test_unit_get_words.py similarity index 90% rename from test/t/unit/test_unit_get_comp_words_by_ref.py rename to test/t/unit/test_unit_get_words.py index 90c931cbe96..63c40344f9b 100644 --- a/test/t/unit/test_unit_get_comp_words_by_ref.py +++ b/test/t/unit/test_unit_get_words.py @@ -11,7 +11,7 @@ class TestUnitGetCompWordsByRef(TestUnitBase): def _test(self, bash, *args, **kwargs): assert_bash_exec(bash, "unset cur prev") output = self._test_unit( - "_get_comp_words_by_ref %s cur prev; echo $cur,${prev-}", + "_comp_get_words %s cur prev; echo $cur,${prev-}", bash, *args, **kwargs, @@ -22,7 +22,7 @@ def test_1(self, bash): assert_bash_exec( bash, "COMP_WORDS=() COMP_CWORD= COMP_POINT= COMP_LINE= " - "_get_comp_words_by_ref cur >/dev/null", + "_comp_get_words cur >/dev/null", ) def test_2(self, bash): @@ -134,9 +134,9 @@ def test_22(self, bash): def test_23(self, bash): """a -n| - This test makes sure `_get_cword' doesn't use `echo' to return its - value, because -n might be interpreted by `echo' and thus would not - be returned. + This test makes sure `_comp_get_words' doesn't use `echo' to + return its value, because -n might be interpreted by `echo' + and thus would not be returned. """ output = self._test(bash, "(a -n)", 1, "a -n", 4) assert output == "-n,a" @@ -175,7 +175,7 @@ def test_30(self, bash): """a b| to all vars""" assert_bash_exec(bash, "unset words cword cur prev") output = self._test_unit( - "_get_comp_words_by_ref words cword cur prev%s; " + "_comp_get_words words cword cur prev%s; " 'echo "${words[@]}",$cword,$cur,$prev', bash, "(a b)", @@ -189,7 +189,7 @@ def test_31(self, bash): """a b| to alternate vars""" assert_bash_exec(bash, "unset words2 cword2 cur2 prev2") output = self._test_unit( - "_get_comp_words_by_ref -w words2 -i cword2 -c cur2 -p prev2%s; " + "_comp_get_words -w words2 -i cword2 -c cur2 -p prev2%s; " 'echo $cur2,$prev2,"${words2[@]}",$cword2', bash, "(a b)", @@ -204,7 +204,7 @@ def test_32(self, bash): """a b : c| with wordbreaks -= :""" assert_bash_exec(bash, "unset words") output = self._test_unit( - '_get_comp_words_by_ref -n : words%s; echo "${words[@]}"', + '_comp_get_words -n : words%s; echo "${words[@]}"', bash, "(a b : c)", 3, @@ -217,7 +217,7 @@ def test_33(self, bash): """a b: c| with wordbreaks -= :""" assert_bash_exec(bash, "unset words") output = self._test_unit( - '_get_comp_words_by_ref -n : words%s; echo "${words[@]}"', + '_comp_get_words -n : words%s; echo "${words[@]}"', bash, "(a b : c)", 3, @@ -230,7 +230,7 @@ def test_34(self, bash): """a b :c| with wordbreaks -= :""" assert_bash_exec(bash, "unset words") output = self._test_unit( - '_get_comp_words_by_ref -n : words%s; echo "${words[@]}"', + '_comp_get_words -n : words%s; echo "${words[@]}"', bash, "(a b : c)", 3, @@ -243,7 +243,7 @@ def test_35(self, bash): r"""a b\ :c| with wordbreaks -= :""" assert_bash_exec(bash, "unset words") output = self._test_unit( - '_get_comp_words_by_ref -n : words%s; echo "${words[@]}"', + '_comp_get_words -n : words%s; echo "${words[@]}"', bash, "(a 'b ' : c)", 3, @@ -255,6 +255,6 @@ def test_35(self, bash): def test_unknown_arg_error(self, bash): with pytest.raises(AssertionError) as ex: _ = assert_bash_exec( - bash, "_get_comp_words_by_ref dummy", want_output=True + bash, "_comp_get_words dummy", want_output=True ) ex.match("dummy.* unknown argument") diff --git a/test/t/unit/test_unit_ip_addresses.py b/test/t/unit/test_unit_ip_addresses.py index 9d5bd8ce7b8..fc54acb3176 100644 --- a/test/t/unit/test_unit_ip_addresses.py +++ b/test/t/unit/test_unit_ip_addresses.py @@ -9,20 +9,20 @@ class TestUnitIpAddresses: def functions(self, request, bash): assert_bash_exec( bash, - "_ia() { local cur=$(_get_cword);unset -v COMPREPLY;" - "_ip_addresses; }", + "_ia() { local cur;_comp_get_words cur;" + "unset -v COMPREPLY;_ip_addresses; }", ) assert_bash_exec(bash, "complete -F _ia ia") assert_bash_exec( bash, - "_iaa() { local cur=$(_get_cword);unset -v COMPREPLY;" - "_ip_addresses -a; }", + "_iaa() { local cur;_comp_get_words cur;" + "unset -v COMPREPLY;_ip_addresses -a; }", ) assert_bash_exec(bash, "complete -F _iaa iaa") assert_bash_exec( bash, - " _ia6() { local cur=$(_get_cword);unset -v COMPREPLY;" - "_ip_addresses -6; }", + " _ia6() { local cur;_comp_get_words cur;" + "unset -v COMPREPLY;_ip_addresses -6; }", ) assert_bash_exec(bash, "complete -F _ia6 ia6")