diff --git a/completions/ARRAY b/completions/ARRAY new file mode 100644 index 00000000000..1565634f851 --- /dev/null +++ b/completions/ARRAY @@ -0,0 +1,320 @@ +# 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) +# @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 multiple +# 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 _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 + 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 == @(_*|OPTIND|OPTARG|OPTERR) ]]; then + printf 'bash_completion: %s: %s\n' "$FUNCNAME" "array name '$1' is reserved for internal uses" >&2 + return 2 + fi + # When the array is empty: + eval "((\${#$1[@]}))" || return 0 + + local _predicate + _comp_xfunc_ARRAY__init_predicate "$2" "$_pattype" "$_anchoring" "$_flags" + + local _unset="" + + local _indices _index _ref + eval "_indices=(\"\${!$1[@]}\")" + for _index in "${_indices[@]}"; do + _ref="$1[\$_index]" + _comp_xfunc_ARRAY__predicate "${!_ref}" + case $? in + 0) continue ;; + 1) + unset -v "$_ref" + _unset=set + ;; + 27) break ;; + *) return 2 ;; + esac + done + + # Compaction of the sparse array + [[ $_flags == *C* ]] || + eval "((\${#$1[@]})) && $1=(\"\${$1[@]}\")" + + [[ ! $_unset ]] +} + +# @version bash-4.3 +_comp_uniq() +{ + local -n _comp_uniq__array=$1 + 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]}]-} ]] && + unset -v '_comp_uniq__array[_comp_uniq__i]' + _comp_uniq__tmp[${_comp_uniq__array[_comp_uniq__i]}]=set + done +} + +# Obtain the largest index +# @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[@]}") + REPLY=${_comp_last_index__indices[*]: -1} +} + +# @version bash-4.3 +_comp_compact() +{ + local -n _comp_compact__array=$1 + _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] REPLY +# @version bash-4.3 +_comp_index_of() +{ + 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 + + REPLY=-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) + REPLY=$_i + return 0 + ;; + 1) continue ;; + 27) return 27 ;; + *) return 2 ;; + esac + done + fi + + return 1 +}