From 68a02fa1dab00b69e75c899a0848179c4ba5a473 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Tue, 12 Apr 2022 19:36:23 +0900 Subject: [PATCH 01/15] feat(completions/ARRAY): add `_comp_xfunc_ARRAY_filter` Co-authored-by: Christoph Anton Mitterer --- completions/ARRAY | 151 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 completions/ARRAY diff --git a/completions/ARRAY b/completions/ARRAY new file mode 100644 index 00000000000..bd2dadbed16 --- /dev/null +++ b/completions/ARRAY @@ -0,0 +1,151 @@ +# Utility xfunc functions for array manipulations -*- shell-script -*- + +# Filter the array elements with the specified condition. +# @param $1 Array name (that is not "value" or other internal variable names) +# @param $2 When any of the options `-EFG' is specified, the second argument is +# used as a pattern string whose meaning is determined by the option `-EFG'. +# Otherwise, the second argument specifies the command that tests the array +# element. The command is supposed to exit with: +# +# status 0 .... when the element should be preserved +# status 1 .... when the element should be removed +# status 2 .... when the usage of the predicate is wrong +# status 27 ... when the loop should be canceled. All the remaining +# elements will be preserved regardless of the presence of +# option `-r'. +# +# The other exit statuses are reserved and cancel the array filtering with an +# error message, and the function returns with the exit status 2. If this is +# an existing command name, the command is called with the value of the array +# element being specified as the first command argument. Otherwise, this +# shall be a shell command that tests the array-element value stored in the +# environment variable "value". +# +# Options: +# +# The following options specify the type of the pattern. When mutiple +# options are supplied, the last-specified one overwrite the previous +# option. +# +# -E $2 is interpreted as a POSIX extended regular expression. +# The default anchoring is `-m` (see below). +# -F $2 is interpreted as a fixed string. The default anchoring +# is `-m` (see below). +# -G $2 is interpreted as a glob pattern. The default anchoring +# is `-x` (see below). +# +# Combined with any of -EFG, the following options specify the anchoring +# type of the pattern matching. When multiple options are supplied, the +# last-specified one overwrites the previous option. +# +# -p performs the prefix matching. +# -s performs the suffix matching. +# -m performs the middle matching. +# -x performs the exact matching. +# +# -r Revert the condition, i.e., remove elements that satisfy +# the original condition. +# -C Array compaction is not performed. +# +# @return 2 with a wrong usage, 1 when any elements are removed, 0 when the set +# of array elements are unchanged. [ Note: the compaction will be performed +# (without the option -C) even when the set of array elements are +# unchanged. ] +_comp_xfunc_ARRAY_filter() +{ + local _comp_local_flags='' _comp_local_pattype='' _comp_local_anchoring='' + local OPTIND=1 OPTARG='' OPTERR=0 _comp_local_opt='' + while getopts 'EFGpsmxrC' _comp_local_opt "$@"; do + case $_comp_local_opt in + [EFG]) _comp_local_pattype=$_comp_local_opt ;; + [psmx]) _comp_local_anchoring=$_comp_local_opt ;; + [rC]) _comp_local_flags=$_comp_local_opt$_comp_local_flags ;; + *) + printf 'bash_completion: %s: %s\n' "$FUNCNAME" 'usage error' >&2 + printf 'usage: %s %s\n' "$FUNCNAME" "[-EFGpsmxrC] ARRAY_NAME CONDITION" >&2 + return 2 + ;; + esac + done + + shift "$((OPTIND - 1))" + if (($# != 2)); then + printf 'bash_completion: %s: %s\n' "$FUNCNAME" "unexpected number of arguments: $#" >&2 + printf 'usage: %s %s\n' "$FUNCNAME" "[-EFGpsmxrC] ARRAY_NAME CONDITION" >&2 + return 2 + elif [[ $1 != [a-zA-Z_]*([a-zA-Z_0-9]) ]]; then + printf 'bash_completion: %s: %s\n' "$FUNCNAME" "invalid array name '$1'." >&2 + return 2 + elif [[ $1 == @(_comp_local_*|OPTIND|OPTARG|OPTERR) ]]; then + printf 'bash_completion: %s: %s\n' "$FUNCNAME" "array name '$1' is reserved for internal uses" >&2 + return 2 + elif [[ ! $_comp_local_pattype && $1 == value ]]; then + printf 'bash_completion: %s: %s\n' "$FUNCNAME" "array name '$1' cannot be used for the predicate" >&2 + return 2 + fi + # When the array is empty: + eval "((\${#$1[@]}))" || return 0 + + local _comp_local_predicate='' _comp_local_pattern=$2 + case $_comp_local_pattype in + E) + case $_comp_local_anchoring in + p) _comp_local_predicate='[[ $_comp_local_value =~ ^($_comp_local_pattern) ]]' ;; + s) _comp_local_predicate='[[ $_comp_local_value =~ ($_comp_local_pattern)$ ]]' ;; + x) _comp_local_predicate='[[ $_comp_local_value =~ ^($_comp_local_pattern)$ ]]' ;; + *) _comp_local_predicate='[[ $_comp_local_value =~ $_comp_local_pattern ]]' ;; + esac + ;; + F) + case $_comp_local_anchoring in + p) _comp_local_predicate='[[ $_comp_local_value == "$_comp_local_pattern"* ]]' ;; + s) _comp_local_predicate='[[ $_comp_local_value == *"$_comp_local_pattern" ]]' ;; + x) _comp_local_predicate='[[ $_comp_local_value == "$_comp_local_pattern" ]]' ;; + *) _comp_local_predicate='[[ $_comp_local_value == *"$_comp_local_pattern"* ]]' ;; + esac + ;; + G) + case $_comp_local_anchoring in + p) _comp_local_predicate='[[ $_comp_local_value == $_comp_local_pattern* ]]' ;; + s) _comp_local_predicate='[[ $_comp_local_value == *$_comp_local_pattern ]]' ;; + m) _comp_local_predicate='[[ $_comp_local_value == *$_comp_local_pattern* ]]' ;; + *) _comp_local_predicate='[[ $_comp_local_value == $_comp_local_pattern ]]' ;; + esac + ;; + *) + if type -t "$2" &>/dev/null; then + _comp_local_predicate="$2 \"\$_comp_local_value\"" + else + _comp_local_predicate="local -x value=\$_comp_local_value; $2" + fi + ;; + esac + + local _comp_local_unset='' _comp_local_expected_status=0 + [[ $_comp_local_flags == *r* ]] && _comp_local_expected_status=1 + + local _comp_local_indices _comp_local_index _comp_local_value + eval "_comp_local_indices=(\"\${!$1[@]}\")" + for _comp_local_index in "${_comp_local_indices[@]}"; do + eval "_comp_local_value=\${$1[\$_comp_local_index]}; $_comp_local_predicate" + case $? in + "$_comp_local_expected_status") continue ;; + [01]) + unset -v "$1[\$_comp_local_index]" + _comp_local_unset=1 + ;; + 27) break ;; + *) + printf 'bash_completion: %s: %s\n' "$FUNCNAME" \ + "filter condition broken '${_comp_local_pattype:+-$_comp_local_pattype }$2'" >&2 + return 2 + ;; + esac + done + + # Compaction of the sparse array + [[ $_comp_local_flags == *C* ]] || + eval "((\${#$1[@]})) && $1=(\"\${$1[@]}\")" + + [[ ! $_comp_local_unset ]] +} From e203fec10a3972758cb677285ada75b77d8a4a55 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Sun, 8 May 2022 20:08:56 +0900 Subject: [PATCH 02/15] refactor(xfunc ARRAY filter): use a simple prefix for local variables --- completions/ARRAY | 85 ++++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/completions/ARRAY b/completions/ARRAY index bd2dadbed16..e2875c29c2d 100644 --- a/completions/ARRAY +++ b/completions/ARRAY @@ -1,7 +1,8 @@ # Utility xfunc functions for array manipulations -*- shell-script -*- # Filter the array elements with the specified condition. -# @param $1 Array name (that is not "value" or other internal variable names) +# @param $1 Array name (that is not "value", "_*" or other internal variable +# names) # @param $2 When any of the options `-EFG' is specified, the second argument is # used as a pattern string whose meaning is determined by the option `-EFG'. # Otherwise, the second argument specifies the command that tests the array @@ -23,7 +24,7 @@ # # Options: # -# The following options specify the type of the pattern. When mutiple +# The following options specify the type of the pattern. When multiple # options are supplied, the last-specified one overwrite the previous # option. # @@ -53,13 +54,13 @@ # unchanged. ] _comp_xfunc_ARRAY_filter() { - local _comp_local_flags='' _comp_local_pattype='' _comp_local_anchoring='' - local OPTIND=1 OPTARG='' OPTERR=0 _comp_local_opt='' - while getopts 'EFGpsmxrC' _comp_local_opt "$@"; do - case $_comp_local_opt in - [EFG]) _comp_local_pattype=$_comp_local_opt ;; - [psmx]) _comp_local_anchoring=$_comp_local_opt ;; - [rC]) _comp_local_flags=$_comp_local_opt$_comp_local_flags ;; + local _flags='' _pattype='' _anchoring='' + local OPTIND=1 OPTARG='' OPTERR=0 _opt='' + while getopts 'EFGpsmxrC' _opt "$@"; do + case $_opt in + [EFG]) _pattype=$_opt ;; + [psmx]) _anchoring=$_opt ;; + [rC]) _flags=$_opt$_flags ;; *) printf 'bash_completion: %s: %s\n' "$FUNCNAME" 'usage error' >&2 printf 'usage: %s %s\n' "$FUNCNAME" "[-EFGpsmxrC] ARRAY_NAME CONDITION" >&2 @@ -76,76 +77,76 @@ _comp_xfunc_ARRAY_filter() elif [[ $1 != [a-zA-Z_]*([a-zA-Z_0-9]) ]]; then printf 'bash_completion: %s: %s\n' "$FUNCNAME" "invalid array name '$1'." >&2 return 2 - elif [[ $1 == @(_comp_local_*|OPTIND|OPTARG|OPTERR) ]]; then + elif [[ $1 == @(_*|OPTIND|OPTARG|OPTERR) ]]; then printf 'bash_completion: %s: %s\n' "$FUNCNAME" "array name '$1' is reserved for internal uses" >&2 return 2 - elif [[ ! $_comp_local_pattype && $1 == value ]]; then + elif [[ ! $_pattype && $1 == value ]]; then printf 'bash_completion: %s: %s\n' "$FUNCNAME" "array name '$1' cannot be used for the predicate" >&2 return 2 fi # When the array is empty: eval "((\${#$1[@]}))" || return 0 - local _comp_local_predicate='' _comp_local_pattern=$2 - case $_comp_local_pattype in + local _predicate='' _pattern=$2 + case $_pattype in E) - case $_comp_local_anchoring in - p) _comp_local_predicate='[[ $_comp_local_value =~ ^($_comp_local_pattern) ]]' ;; - s) _comp_local_predicate='[[ $_comp_local_value =~ ($_comp_local_pattern)$ ]]' ;; - x) _comp_local_predicate='[[ $_comp_local_value =~ ^($_comp_local_pattern)$ ]]' ;; - *) _comp_local_predicate='[[ $_comp_local_value =~ $_comp_local_pattern ]]' ;; + case $_anchoring in + p) _predicate='[[ $_value =~ ^($_pattern) ]]' ;; + s) _predicate='[[ $_value =~ ($_pattern)$ ]]' ;; + x) _predicate='[[ $_value =~ ^($_pattern)$ ]]' ;; + *) _predicate='[[ $_value =~ $_pattern ]]' ;; esac ;; F) - case $_comp_local_anchoring in - p) _comp_local_predicate='[[ $_comp_local_value == "$_comp_local_pattern"* ]]' ;; - s) _comp_local_predicate='[[ $_comp_local_value == *"$_comp_local_pattern" ]]' ;; - x) _comp_local_predicate='[[ $_comp_local_value == "$_comp_local_pattern" ]]' ;; - *) _comp_local_predicate='[[ $_comp_local_value == *"$_comp_local_pattern"* ]]' ;; + case $_anchoring in + p) _predicate='[[ $_value == "$_pattern"* ]]' ;; + s) _predicate='[[ $_value == *"$_pattern" ]]' ;; + x) _predicate='[[ $_value == "$_pattern" ]]' ;; + *) _predicate='[[ $_value == *"$_pattern"* ]]' ;; esac ;; G) - case $_comp_local_anchoring in - p) _comp_local_predicate='[[ $_comp_local_value == $_comp_local_pattern* ]]' ;; - s) _comp_local_predicate='[[ $_comp_local_value == *$_comp_local_pattern ]]' ;; - m) _comp_local_predicate='[[ $_comp_local_value == *$_comp_local_pattern* ]]' ;; - *) _comp_local_predicate='[[ $_comp_local_value == $_comp_local_pattern ]]' ;; + case $_anchoring in + p) _predicate='[[ $_value == $_pattern* ]]' ;; + s) _predicate='[[ $_value == *$_pattern ]]' ;; + m) _predicate='[[ $_value == *$_pattern* ]]' ;; + *) _predicate='[[ $_value == $_pattern ]]' ;; esac ;; *) if type -t "$2" &>/dev/null; then - _comp_local_predicate="$2 \"\$_comp_local_value\"" + _predicate="$2 \"\$_value\"" else - _comp_local_predicate="local -x value=\$_comp_local_value; $2" + _predicate="local -x value=\$_value; $2" fi ;; esac - local _comp_local_unset='' _comp_local_expected_status=0 - [[ $_comp_local_flags == *r* ]] && _comp_local_expected_status=1 + local _unset='' _expected_status=0 + [[ $_flags == *r* ]] && _expected_status=1 - local _comp_local_indices _comp_local_index _comp_local_value - eval "_comp_local_indices=(\"\${!$1[@]}\")" - for _comp_local_index in "${_comp_local_indices[@]}"; do - eval "_comp_local_value=\${$1[\$_comp_local_index]}; $_comp_local_predicate" + local _indices _index _value + eval "_indices=(\"\${!$1[@]}\")" + for _index in "${_indices[@]}"; do + eval "_value=\${$1[\$_index]}; $_predicate" case $? in - "$_comp_local_expected_status") continue ;; + "$_expected_status") continue ;; [01]) - unset -v "$1[\$_comp_local_index]" - _comp_local_unset=1 + unset -v "$1[\$_index]" + _unset=1 ;; 27) break ;; *) printf 'bash_completion: %s: %s\n' "$FUNCNAME" \ - "filter condition broken '${_comp_local_pattype:+-$_comp_local_pattype }$2'" >&2 + "filter condition broken '${_pattype:+-$_pattype }$2'" >&2 return 2 ;; esac done # Compaction of the sparse array - [[ $_comp_local_flags == *C* ]] || + [[ $_flags == *C* ]] || eval "((\${#$1[@]})) && $1=(\"\${$1[@]}\")" - [[ ! $_comp_local_unset ]] + [[ ! $_unset ]] } From 6eee0e1ae001c3f57c94d50319c785a13ca4198f Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Wed, 3 May 2023 14:56:16 +0900 Subject: [PATCH 03/15] refactor(xfunc ARRAY filter): use '' for empty init instead of "" --- completions/ARRAY | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/completions/ARRAY b/completions/ARRAY index e2875c29c2d..70cb82a5c5a 100644 --- a/completions/ARRAY +++ b/completions/ARRAY @@ -54,8 +54,8 @@ # unchanged. ] _comp_xfunc_ARRAY_filter() { - local _flags='' _pattype='' _anchoring='' - local OPTIND=1 OPTARG='' OPTERR=0 _opt='' + local _flags="" _pattype="" _anchoring="" + local OPTIND=1 OPTARG="" OPTERR=0 _opt="" while getopts 'EFGpsmxrC' _opt "$@"; do case $_opt in [EFG]) _pattype=$_opt ;; @@ -122,7 +122,7 @@ _comp_xfunc_ARRAY_filter() ;; esac - local _unset='' _expected_status=0 + local _unset="" _expected_status=0 [[ $_flags == *r* ]] && _expected_status=1 local _indices _index _value @@ -133,7 +133,7 @@ _comp_xfunc_ARRAY_filter() "$_expected_status") continue ;; [01]) unset -v "$1[\$_index]" - _unset=1 + _unset=set ;; 27) break ;; *) From 132176da53eecd539f9f4bd6c79c9f46efe79c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 3 May 2023 05:06:00 +0900 Subject: [PATCH 04/15] feat: add array utilities --- completions/ARRAY | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/completions/ARRAY b/completions/ARRAY index 70cb82a5c5a..034a146c17b 100644 --- a/completions/ARRAY +++ b/completions/ARRAY @@ -150,3 +150,57 @@ _comp_xfunc_ARRAY_filter() [[ ! $_unset ]] } + +_bashcomp_uniq() +{ + local -n _bashcomp_uniq_array_=$1 + local -A tmp + local -i i + for i in ${!_bashcomp_uniq_array_[*]}; do + ((tmp["${_bashcomp_uniq_array_[i]}"]++ > 0)) && + unset '_bashcomp_uniq_array_[i]' + done +} + +_bashcomp_last_index() +{ + local -n _bashcomp_last_index_array_=$1 _bashcomp_last_index_ret_=$2 + local -i i + for i in ${!_bashcomp_last_index_array_[*]}; do :; done + _bashcomp_last_index_ret_=$i +} + +_bashcomp_compact() +{ + local -n _bashcomp_compact_array_=$1 + local i j=0 + + for i in ${!_bashcomp_compact_array_[*]}; do + if ((i > j)); then + _bashcomp_compact_array_[j]="${_bashcomp_compact_array_[i]}" + unset "_bashcomp_compact_array_[i]" + fi + ((j++)) + done +} + +_bashcomp_index_of() +{ + # TODO getopts -> -r gets rightmost (last) index + # TODO getopts: -R uses regex instead of glob + local -n _bashcomp_index_of_array_=$1 + local pattern=$2 + local -n _bashcomp_index_of_ret_=$3 + + local -i i + for i in ${!_bashcomp_index_of_array_[*]}; do + # shellcheck disable=SC2053 + if [[ ${_bashcomp_index_of_array_[i]} == $pattern ]]; then + _bashcomp_index_of_ret_=$i + return 0 + fi + done + + _bashcomp_index_of_ret_=-1 + return 1 +} From 2326b19d034038f6d41fb2358ee04d1a41f73f40 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Wed, 3 May 2023 12:17:54 +0900 Subject: [PATCH 05/15] refactor(ARRAY): apply naming convention --- completions/ARRAY | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/completions/ARRAY b/completions/ARRAY index 034a146c17b..fd329af1431 100644 --- a/completions/ARRAY +++ b/completions/ARRAY @@ -151,56 +151,56 @@ _comp_xfunc_ARRAY_filter() [[ ! $_unset ]] } -_bashcomp_uniq() +_comp_uniq() { - local -n _bashcomp_uniq_array_=$1 + local -n _comp_uniq__array=$1 local -A tmp local -i i - for i in ${!_bashcomp_uniq_array_[*]}; do - ((tmp["${_bashcomp_uniq_array_[i]}"]++ > 0)) && - unset '_bashcomp_uniq_array_[i]' + for i in ${!_comp_uniq__array[*]}; do + ((tmp["${_comp_uniq__array[i]}"]++ > 0)) && + unset '_comp_uniq__array[i]' done } -_bashcomp_last_index() +_comp_last_index() { - local -n _bashcomp_last_index_array_=$1 _bashcomp_last_index_ret_=$2 + local -n _comp_last_index__array=$1 _comp_last_index__ret=$2 local -i i - for i in ${!_bashcomp_last_index_array_[*]}; do :; done - _bashcomp_last_index_ret_=$i + for i in ${!_comp_last_index__array[*]}; do :; done + _comp_last_index__ret=$i } -_bashcomp_compact() +_comp_compact() { - local -n _bashcomp_compact_array_=$1 + local -n _comp_compact__array=$1 local i j=0 - for i in ${!_bashcomp_compact_array_[*]}; do + for i in ${!_comp_compact__array[*]}; do if ((i > j)); then - _bashcomp_compact_array_[j]="${_bashcomp_compact_array_[i]}" - unset "_bashcomp_compact_array_[i]" + _comp_compact__array[j]="${_comp_compact__array[i]}" + unset "_comp_compact__array[i]" fi ((j++)) done } -_bashcomp_index_of() +_comp_index_of() { # TODO getopts -> -r gets rightmost (last) index # TODO getopts: -R uses regex instead of glob - local -n _bashcomp_index_of_array_=$1 + local -n _comp_index_of__array=$1 local pattern=$2 - local -n _bashcomp_index_of_ret_=$3 + local -n _comp_index_of__ret=$3 local -i i - for i in ${!_bashcomp_index_of_array_[*]}; do + for i in ${!_comp_index_of__array[*]}; do # shellcheck disable=SC2053 - if [[ ${_bashcomp_index_of_array_[i]} == $pattern ]]; then - _bashcomp_index_of_ret_=$i + if [[ ${_comp_index_of__array[i]} == $pattern ]]; then + _comp_index_of__ret=$i return 0 fi done - _bashcomp_index_of_ret_=-1 + _comp_index_of__ret=-1 return 1 } From 9bcc0f82a8ce2f4e72b85ba1bbc778853a37bfea Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Wed, 3 May 2023 12:36:24 +0900 Subject: [PATCH 06/15] refactor(ARRAY): use safer "${!arr[@]}" than ${!arr[*]} --- completions/ARRAY | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/completions/ARRAY b/completions/ARRAY index fd329af1431..b5c5d6f4489 100644 --- a/completions/ARRAY +++ b/completions/ARRAY @@ -156,7 +156,7 @@ _comp_uniq() local -n _comp_uniq__array=$1 local -A tmp local -i i - for i in ${!_comp_uniq__array[*]}; do + for i in "${!_comp_uniq__array[@]}"; do ((tmp["${_comp_uniq__array[i]}"]++ > 0)) && unset '_comp_uniq__array[i]' done @@ -166,7 +166,7 @@ _comp_last_index() { local -n _comp_last_index__array=$1 _comp_last_index__ret=$2 local -i i - for i in ${!_comp_last_index__array[*]}; do :; done + for i in "${!_comp_last_index__array[@]}"; do :; done _comp_last_index__ret=$i } @@ -175,7 +175,7 @@ _comp_compact() local -n _comp_compact__array=$1 local i j=0 - for i in ${!_comp_compact__array[*]}; do + for i in "${!_comp_compact__array[@]}"; do if ((i > j)); then _comp_compact__array[j]="${_comp_compact__array[i]}" unset "_comp_compact__array[i]" @@ -193,7 +193,7 @@ _comp_index_of() local -n _comp_index_of__ret=$3 local -i i - for i in ${!_comp_index_of__array[*]}; do + for i in "${!_comp_index_of__array[@]}"; do # shellcheck disable=SC2053 if [[ ${_comp_index_of__array[i]} == $pattern ]]; then _comp_index_of__ret=$i From 1f09e8e83f6958440662061a1990eb3484ecb406 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Wed, 3 May 2023 12:38:59 +0900 Subject: [PATCH 07/15] refactor(ARRAY): avoid using local varnames tmp, i, j, and pattern They can hide the array names to store the results. --- completions/ARRAY | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/completions/ARRAY b/completions/ARRAY index b5c5d6f4489..b1077475d04 100644 --- a/completions/ARRAY +++ b/completions/ARRAY @@ -154,33 +154,33 @@ _comp_xfunc_ARRAY_filter() _comp_uniq() { local -n _comp_uniq__array=$1 - local -A tmp - local -i i - for i in "${!_comp_uniq__array[@]}"; do - ((tmp["${_comp_uniq__array[i]}"]++ > 0)) && - unset '_comp_uniq__array[i]' + local -A _comp_uniq__tmp + local -i _comp_uniq__i + for _comp_uniq__i in "${!_comp_uniq__array[@]}"; do + ((_comp_uniq__tmp["${_comp_uniq__array[_comp_uniq__i]}"]++ > 0)) && + unset '_comp_uniq__array[_comp_uniq__i]' done } _comp_last_index() { local -n _comp_last_index__array=$1 _comp_last_index__ret=$2 - local -i i - for i in "${!_comp_last_index__array[@]}"; do :; done - _comp_last_index__ret=$i + local -i _comp_last_index__i + for _comp_last_index__i in "${!_comp_last_index__array[@]}"; do :; done + _comp_last_index__ret=$_comp_last_index__i } _comp_compact() { local -n _comp_compact__array=$1 - local i j=0 + local _comp_compact__i _comp_compact__j=0 - for i in "${!_comp_compact__array[@]}"; do - if ((i > j)); then - _comp_compact__array[j]="${_comp_compact__array[i]}" - unset "_comp_compact__array[i]" + for _comp_compact__i in "${!_comp_compact__array[@]}"; do + if ((_comp_compact__i > _comp_compact__j)); then + _comp_compact__array[_comp_compact__j]="${_comp_compact__array[_comp_compact__i]}" + unset "_comp_compact__array[_comp_compact__i]" fi - ((j++)) + ((_comp_compact__j++)) done } @@ -189,14 +189,14 @@ _comp_index_of() # TODO getopts -> -r gets rightmost (last) index # TODO getopts: -R uses regex instead of glob local -n _comp_index_of__array=$1 - local pattern=$2 + local _comp_compact__pattern=$2 local -n _comp_index_of__ret=$3 - local -i i - for i in "${!_comp_index_of__array[@]}"; do + local -i _comp_index_of__i + for _comp_index_of__i in "${!_comp_index_of__array[@]}"; do # shellcheck disable=SC2053 - if [[ ${_comp_index_of__array[i]} == $pattern ]]; then - _comp_index_of__ret=$i + if [[ ${_comp_index_of__array[_comp_index_of__i]} == $_comp_compact__pattern ]]; then + _comp_index_of__ret=$_comp_index_of__i return 0 fi done From 48a35b573b0acafc662b35106804b7073e186b44 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Wed, 3 May 2023 12:43:18 +0900 Subject: [PATCH 08/15] refactor(ARRAY): explicitly specify type to `unset` --- completions/ARRAY | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/completions/ARRAY b/completions/ARRAY index b1077475d04..c0bf68044a1 100644 --- a/completions/ARRAY +++ b/completions/ARRAY @@ -158,7 +158,7 @@ _comp_uniq() local -i _comp_uniq__i for _comp_uniq__i in "${!_comp_uniq__array[@]}"; do ((_comp_uniq__tmp["${_comp_uniq__array[_comp_uniq__i]}"]++ > 0)) && - unset '_comp_uniq__array[_comp_uniq__i]' + unset -v '_comp_uniq__array[_comp_uniq__i]' done } @@ -178,7 +178,7 @@ _comp_compact() for _comp_compact__i in "${!_comp_compact__array[@]}"; do if ((_comp_compact__i > _comp_compact__j)); then _comp_compact__array[_comp_compact__j]="${_comp_compact__array[_comp_compact__i]}" - unset "_comp_compact__array[_comp_compact__i]" + unset -v '_comp_compact__array[_comp_compact__i]' fi ((_comp_compact__j++)) done From b90708045ef9af541f7dbcb620e4b6d597be94e5 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Wed, 3 May 2023 12:24:33 +0900 Subject: [PATCH 09/15] fix(_comp_uniq): avoid expanding array subscripts in arith context When the array elements contains a confusing value such as `arr[10]='1],x*=10,a[2'`, (( tmp["${arr[10]}"] )) would be unexpectedly interpreted as ((tmp[1], x *= 10, a[2])) in bash < 5.2. We here instead use an independent parameter expansions and assignments. --- completions/ARRAY | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/completions/ARRAY b/completions/ARRAY index c0bf68044a1..ad29a2adf57 100644 --- a/completions/ARRAY +++ b/completions/ARRAY @@ -154,11 +154,12 @@ _comp_xfunc_ARRAY_filter() _comp_uniq() { local -n _comp_uniq__array=$1 - local -A _comp_uniq__tmp + local -A _comp_uniq__tmp=() local -i _comp_uniq__i for _comp_uniq__i in "${!_comp_uniq__array[@]}"; do - ((_comp_uniq__tmp["${_comp_uniq__array[_comp_uniq__i]}"]++ > 0)) && + [[ ${_comp_uniq__tmp[${_comp_uniq__array[_comp_uniq__i]}]-} ]] && unset -v '_comp_uniq__array[_comp_uniq__i]' + _comp_uniq__tmp[${_comp_uniq__array[_comp_uniq__i]}]=set done } From 87d50e3fa8591a767d1d9d1bb1d821a5a4a6f53f Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Wed, 3 May 2023 12:33:17 +0900 Subject: [PATCH 10/15] fix(_comp_last_index): use ${arr[*]: -1} to get the last element --- completions/ARRAY | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/completions/ARRAY b/completions/ARRAY index ad29a2adf57..3a704b81490 100644 --- a/completions/ARRAY +++ b/completions/ARRAY @@ -166,9 +166,8 @@ _comp_uniq() _comp_last_index() { local -n _comp_last_index__array=$1 _comp_last_index__ret=$2 - local -i _comp_last_index__i - for _comp_last_index__i in "${!_comp_last_index__array[@]}"; do :; done - _comp_last_index__ret=$_comp_last_index__i + local -a _comp_last_index__indices=("${!_comp_last_index__array[@]}") + _comp_last_index__ret=${_comp_last_index__indices[*]: -1} } _comp_compact() From 5fb04e2d9f439dcda06000eb0f54b1b240bbe61e Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Wed, 3 May 2023 12:46:04 +0900 Subject: [PATCH 11/15] refactor(_comp_compact): simplify --- completions/ARRAY | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/completions/ARRAY b/completions/ARRAY index 3a704b81490..fb507d75768 100644 --- a/completions/ARRAY +++ b/completions/ARRAY @@ -173,15 +173,7 @@ _comp_last_index() _comp_compact() { local -n _comp_compact__array=$1 - local _comp_compact__i _comp_compact__j=0 - - for _comp_compact__i in "${!_comp_compact__array[@]}"; do - if ((_comp_compact__i > _comp_compact__j)); then - _comp_compact__array[_comp_compact__j]="${_comp_compact__array[_comp_compact__i]}" - unset -v '_comp_compact__array[_comp_compact__i]' - fi - ((_comp_compact__j++)) - done + _comp_compact__array=("${_comp_compact__array[@]}") } _comp_index_of() From 5ce056bdd70a3719e2146633bc3952d21f0931ed Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Wed, 3 May 2023 15:00:01 +0900 Subject: [PATCH 12/15] fix(_comp_{last_index,index_of})!: return result through `ret` --- completions/ARRAY | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/completions/ARRAY b/completions/ARRAY index fb507d75768..9152da761f5 100644 --- a/completions/ARRAY +++ b/completions/ARRAY @@ -151,6 +151,7 @@ _comp_xfunc_ARRAY_filter() [[ ! $_unset ]] } +# @version bash-4.3 _comp_uniq() { local -n _comp_uniq__array=$1 @@ -163,36 +164,42 @@ _comp_uniq() done } +# Obtain the largest index +# @var[out] ret +# @version bash-4.3 _comp_last_index() { - local -n _comp_last_index__array=$1 _comp_last_index__ret=$2 + local -n _comp_last_index__array=$1 local -a _comp_last_index__indices=("${!_comp_last_index__array[@]}") - _comp_last_index__ret=${_comp_last_index__indices[*]: -1} + ret=${_comp_last_index__indices[*]: -1} } +# @version bash-4.3 _comp_compact() { local -n _comp_compact__array=$1 _comp_compact__array=("${_comp_compact__array[@]}") } +# Find the index of a matching element +# @var[out] ret +# @version bash-4.3 _comp_index_of() { # TODO getopts -> -r gets rightmost (last) index # TODO getopts: -R uses regex instead of glob local -n _comp_index_of__array=$1 local _comp_compact__pattern=$2 - local -n _comp_index_of__ret=$3 local -i _comp_index_of__i for _comp_index_of__i in "${!_comp_index_of__array[@]}"; do # shellcheck disable=SC2053 if [[ ${_comp_index_of__array[_comp_index_of__i]} == $_comp_compact__pattern ]]; then - _comp_index_of__ret=$_comp_index_of__i + ret=$_comp_index_of__i return 0 fi done - _comp_index_of__ret=-1 + ret=-1 return 1 } From eebec3c39f22e6a4896ce45099b51a62b4fe8627 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Wed, 3 May 2023 14:53:05 +0900 Subject: [PATCH 13/15] refactor(xfunc ARRAY filter): separate predicate --- completions/ARRAY | 141 ++++++++++++++++++++++++++++++---------------- 1 file changed, 92 insertions(+), 49 deletions(-) diff --git a/completions/ARRAY b/completions/ARRAY index 9152da761f5..203858058aa 100644 --- a/completions/ARRAY +++ b/completions/ARRAY @@ -1,5 +1,87 @@ # Utility xfunc functions for array manipulations -*- shell-script -*- +# usage: _comp_xfunc_ARRAY__init_predicate pattern pattype [anchoring flags] +# @param $1 pattern Pattern +# @param $2 pattype /[EFG]/ or empty +# @param[opt] $3 anchoring /[psmx]/ or empty +# @param[opt] $4 flags /r/ or empty +# See _comp_xfunc_ARRAY_filter for details of pattern, pattype, +# anchoring, and flags. +# +# @var[out] _predicate[0] command +# @var[out] _predicate[1] pattern +# @var[out] _predicate[2] type +# @var[out] _predicate[3] revert +_comp_xfunc_ARRAY__init_predicate() +{ + _predicate[0]=false + _predicate[1]=$1 + _predicate[2]=$2 + _predicate[3]="" + + local old_nocasematch="" + if shopt -q nocasematch; then + old_nocasematch=set + shopt -u nocasematch + fi + + local _pattype=$2 _anchoring=${3-} flags=${4-} + case $_pattype in + E) + case $_anchoring in + p) _predicate[0]='[[ $_value =~ ^(${_predicate[1]}) ]]' ;; + s) _predicate[0]='[[ $_value =~ (${_predicate[1]})$ ]]' ;; + x) _predicate[0]='[[ $_value =~ ^(${_predicate[1]})$ ]]' ;; + *) _predicate[0]='[[ $_value =~ ${_predicate[1]} ]]' ;; + esac + ;; + F) + case $_anchoring in + p) _predicate[0]='[[ $_value == "${_predicate[1]}"* ]]' ;; + s) _predicate[0]='[[ $_value == *"${_predicate[1]}" ]]' ;; + x) _predicate[0]='[[ $_value == "${_predicate[1]}" ]]' ;; + *) _predicate[0]='[[ $_value == *"${_predicate[1]}"* ]]' ;; + esac + ;; + G) + case $_anchoring in + p) _predicate[0]='[[ $_value == ${_predicate[1]}* ]]' ;; + s) _predicate[0]='[[ $_value == *${_predicate[1]} ]]' ;; + m) _predicate[0]='[[ $_value == *${_predicate[1]}* ]]' ;; + *) _predicate[0]='[[ $_value == ${_predicate[1]} ]]' ;; + esac + ;; + *) + if type -t "$2" &>/dev/null; then + _predicate[0]="$2 \"\$_value\"" + else + _predicate[0]="local -x value=\$_value; $2" + fi + ;; + esac + + [[ $_flags == *r* ]] && _predicate[3]=set + [[ $old_nocasematch ]] && shopt -s nocasematch +} + +_comp_xfunc_ARRAY__predicate() +{ + local _value=$1 + eval "${_predicate[0]}" + + local _ext=$? + case $_ext in + [01]) [[ ${_predicate[3]} ]] && _ext=$((1 - _ext)) ;; + 27) ;; + *) + printf 'bash_completion: %s: %s\n' "$FUNCNAME" \ + "filter condition broken '${_predicate[2]:+-${_predicate[2]} }$2'" >&2 + return 2 + ;; + esac + return "$_ext" +} + # Filter the array elements with the specified condition. # @param $1 Array name (that is not "value", "_*" or other internal variable # names) @@ -80,67 +162,28 @@ _comp_xfunc_ARRAY_filter() elif [[ $1 == @(_*|OPTIND|OPTARG|OPTERR) ]]; then printf 'bash_completion: %s: %s\n' "$FUNCNAME" "array name '$1' is reserved for internal uses" >&2 return 2 - elif [[ ! $_pattype && $1 == value ]]; then - printf 'bash_completion: %s: %s\n' "$FUNCNAME" "array name '$1' cannot be used for the predicate" >&2 - return 2 fi # When the array is empty: eval "((\${#$1[@]}))" || return 0 - local _predicate='' _pattern=$2 - case $_pattype in - E) - case $_anchoring in - p) _predicate='[[ $_value =~ ^($_pattern) ]]' ;; - s) _predicate='[[ $_value =~ ($_pattern)$ ]]' ;; - x) _predicate='[[ $_value =~ ^($_pattern)$ ]]' ;; - *) _predicate='[[ $_value =~ $_pattern ]]' ;; - esac - ;; - F) - case $_anchoring in - p) _predicate='[[ $_value == "$_pattern"* ]]' ;; - s) _predicate='[[ $_value == *"$_pattern" ]]' ;; - x) _predicate='[[ $_value == "$_pattern" ]]' ;; - *) _predicate='[[ $_value == *"$_pattern"* ]]' ;; - esac - ;; - G) - case $_anchoring in - p) _predicate='[[ $_value == $_pattern* ]]' ;; - s) _predicate='[[ $_value == *$_pattern ]]' ;; - m) _predicate='[[ $_value == *$_pattern* ]]' ;; - *) _predicate='[[ $_value == $_pattern ]]' ;; - esac - ;; - *) - if type -t "$2" &>/dev/null; then - _predicate="$2 \"\$_value\"" - else - _predicate="local -x value=\$_value; $2" - fi - ;; - esac + local _predicate + _comp_xfunc_ARRAY__init_predicate "$2" "$_pattype" "$_anchoring" "$_flags" - local _unset="" _expected_status=0 - [[ $_flags == *r* ]] && _expected_status=1 + local _unset="" - local _indices _index _value + local _indices _index _ref eval "_indices=(\"\${!$1[@]}\")" for _index in "${_indices[@]}"; do - eval "_value=\${$1[\$_index]}; $_predicate" + _ref="$1[\$_index]" + _comp_xfunc_ARRAY__predicate "${!_ref}" case $? in - "$_expected_status") continue ;; - [01]) - unset -v "$1[\$_index]" + 0) continue ;; + 1) + unset -v "$_ref" _unset=set ;; 27) break ;; - *) - printf 'bash_completion: %s: %s\n' "$FUNCNAME" \ - "filter condition broken '${_pattype:+-$_pattype }$2'" >&2 - return 2 - ;; + *) return 2 ;; esac done From c9669218c81006dce01f4cc240d9f2cf14a74143 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Wed, 3 May 2023 15:57:44 +0900 Subject: [PATCH 14/15] feat(_comp_index_of): support options --- completions/ARRAY | 96 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 84 insertions(+), 12 deletions(-) diff --git a/completions/ARRAY b/completions/ARRAY index 203858058aa..c78f9723ca0 100644 --- a/completions/ARRAY +++ b/completions/ARRAY @@ -224,25 +224,97 @@ _comp_compact() _comp_compact__array=("${_comp_compact__array[@]}") } +# @version bash-4.3 +_comp_xfunc_ARRAY_reverse() +{ + _comp_compact "$1" + local -n _comp_reverse__arr=$1 + local _comp_reverse__i=0 + local _comp_reverse__j=$((${#_comp_reverse__arr[@]} - 1)) + local _comp_reverse__tmp + while ((_comp_reverse__i < _comp_reverse__j)); do + _comp_reverse__tmp=${_comp_reverse__arr[_comp_reverse__i]} + _comp_reverse__arr[_comp_reverse__i]=${_comp_reverse__arr[_comp_reverse__j]} + _comp_reverse__arr[_comp_reverse__j]=$_comp_reverse__tmp + ((_comp_reverse__i++, _comp_reverse__j--)) + done +} + +# usage: _comp_index_of [-EFGpxmxrl] array pattern # Find the index of a matching element +# Options: +# +# -EFGe Select the type of the pattern. The default is -F. +# -psmx Select the anchoring option. +# -r Revert the condition. +# See _comp_xfunc_ARRAY_filter for the details of these options. +# +# -l Get the last index of matching elements. +# # @var[out] ret # @version bash-4.3 _comp_index_of() { - # TODO getopts -> -r gets rightmost (last) index - # TODO getopts: -R uses regex instead of glob - local -n _comp_index_of__array=$1 - local _comp_compact__pattern=$2 - - local -i _comp_index_of__i - for _comp_index_of__i in "${!_comp_index_of__array[@]}"; do - # shellcheck disable=SC2053 - if [[ ${_comp_index_of__array[_comp_index_of__i]} == $_comp_compact__pattern ]]; then - ret=$_comp_index_of__i - return 0 - fi + local _old_nocasematch="" + if shopt -q nocasematch; then + _old_nocasematch=set + shopt -u nocasematch + fi + local _flags="" _pattype=F _anchoring="" + local OPTIND=1 OPTARG="" OPTERR=0 _opt="" + while getopts 'EFGepsmxrl' _opt "$@"; do + case $_opt in + [EFGe]) _pattype=$_opt ;; + [psmx]) _anchoring=$_opt ;; + [rl]) _flags+=$_opt ;; + *) + printf 'bash_completion: %s: %s\n' "$FUNCNAME" 'usage error' >&2 + printf 'usage: %s %s\n' "$FUNCNAME" "[-EFGepsmxrl] ARRAY_NAME CONDITION" >&2 + return 2 + ;; + esac done + shift "$((OPTIND - 1))" + if (($# != 2)); then + printf 'bash_completion: %s: %s\n' "$FUNCNAME" "unexpected number of arguments: $#" >&2 + printf 'usage: %s %s\n' "$FUNCNAME" "[-EFGepsmxrl] ARRAY_NAME CONDITION" >&2 + [[ $_old_nocasematch ]] && shopt -s nocasematch + return 2 + elif [[ $1 != [a-zA-Z_]*([a-zA-Z_0-9]) ]]; then + printf 'bash_completion: %s: %s\n' "$FUNCNAME" "invalid array name '$1'." >&2 + [[ $_old_nocasematch ]] && shopt -s nocasematch + return 2 + elif [[ $1 == @(_*|OPTIND|OPTARG|OPTERR) ]]; then + printf 'bash_completion: %s: %s\n' "$FUNCNAME" "array name '$1' is reserved for internal uses" >&2 + [[ $_old_nocasematch ]] && shopt -s nocasematch + return 2 + fi + [[ $_old_nocasematch ]] && shopt -s nocasematch ret=-1 + + local -n _array=$1 + if ((${#_array[@]})); then + local _predicate + _comp_xfunc_ARRAY__init_predicate "$2" "$_pattype" "$_anchoring" "$_flags" + + local -a _indices=("${!_array[@]}") + [[ $_flags == *l* ]] && _comp_xfunc_ARRAY_reverse _indices + + local -i _i + for _i in "${_indices[@]}"; do + _comp_xfunc_ARRAY__predicate "${_array[_i]}" + case $? in + 0) + ret=$_i + return 0 + ;; + 1) continue ;; + 27) return 27 ;; + *) return 2 ;; + esac + done + fi + return 1 } From b3455529c91b75afc5df34adb89d0c1f5dc9b8a1 Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Mon, 13 May 2024 14:42:03 +0900 Subject: [PATCH 15/15] feat(ARRAY): use REPLY to return values --- completions/ARRAY | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/completions/ARRAY b/completions/ARRAY index c78f9723ca0..1565634f851 100644 --- a/completions/ARRAY +++ b/completions/ARRAY @@ -208,13 +208,13 @@ _comp_uniq() } # Obtain the largest index -# @var[out] ret +# @var[out] REPLY # @version bash-4.3 _comp_last_index() { local -n _comp_last_index__array=$1 local -a _comp_last_index__indices=("${!_comp_last_index__array[@]}") - ret=${_comp_last_index__indices[*]: -1} + REPLY=${_comp_last_index__indices[*]: -1} } # @version bash-4.3 @@ -251,7 +251,7 @@ _comp_xfunc_ARRAY_reverse() # # -l Get the last index of matching elements. # -# @var[out] ret +# @var[out] REPLY # @version bash-4.3 _comp_index_of() { @@ -291,7 +291,7 @@ _comp_index_of() fi [[ $_old_nocasematch ]] && shopt -s nocasematch - ret=-1 + REPLY=-1 local -n _array=$1 if ((${#_array[@]})); then @@ -306,7 +306,7 @@ _comp_index_of() _comp_xfunc_ARRAY__predicate "${_array[_i]}" case $? in 0) - ret=$_i + REPLY=$_i return 0 ;; 1) continue ;;