|
| 1 | +# Utility xfunc functions for array manipulations -*- shell-script -*- |
| 2 | + |
| 3 | +# Filter the array elements with the specified condition. |
| 4 | +# @param $1 Array name (that is not "value" or other internal variable names) |
| 5 | +# @param $2 When any of the options `-EFG' is specified, the second argument is |
| 6 | +# used as a pattern string whose meaning is determined by the option `-EFG'. |
| 7 | +# Otherwise, the second argument specifies the command that tests the array |
| 8 | +# element. The command is supposed to exit with: |
| 9 | +# |
| 10 | +# status 0 .... when the element should be preserved |
| 11 | +# status 1 .... when the element should be removed |
| 12 | +# status 2 .... when the usage of the predicate is wrong |
| 13 | +# status 27 ... when the loop should be canceled. All the remaining |
| 14 | +# elements will be preserved regardless of the presence of |
| 15 | +# option `-r'. |
| 16 | +# |
| 17 | +# The other exit statuses are reserved and cancel the array filtering with an |
| 18 | +# error message, and the function returns with the exit status 2. If this is |
| 19 | +# an existing command name, the command is called with the value of the array |
| 20 | +# element being specified as the first command argument. Otherwise, this |
| 21 | +# shall be a shell command that tests the array-element value stored in the |
| 22 | +# environment variable "value". |
| 23 | +# |
| 24 | +# Options: |
| 25 | +# |
| 26 | +# The following options specify the type of the pattern. When mutiple |
| 27 | +# options are supplied, the last-specified one overwrite the previous |
| 28 | +# option. |
| 29 | +# |
| 30 | +# -E $2 is interpreted as a POSIX extended regular expression. |
| 31 | +# The default anchoring is `-m` (see below). |
| 32 | +# -F $2 is interpreted as a fixed string. The default anchoring |
| 33 | +# is `-m` (see below). |
| 34 | +# -G $2 is interpreted as a glob pattern. The default anchoring |
| 35 | +# is `-x` (see below). |
| 36 | +# |
| 37 | +# Combined with any of -EFG, the following options specify the anchoring |
| 38 | +# type of the pattern matching. When multiple options are supplied, the |
| 39 | +# last-specified one overwrites the previous option. |
| 40 | +# |
| 41 | +# -p performs the prefix matching. |
| 42 | +# -s performs the suffix matching. |
| 43 | +# -m performs the middle matching. |
| 44 | +# -x performs the exact matching. |
| 45 | +# |
| 46 | +# -r Revert the condition, i.e., remove elements that satisfy |
| 47 | +# the original condition. |
| 48 | +# -C Array compaction is not performed. |
| 49 | +# |
| 50 | +# @return 2 with a wrong usage, 1 when any elements are removed, 0 when the set |
| 51 | +# of array elements are unchanged. [ Note: the compaction will be performed |
| 52 | +# (without the option -C) even when the set of array elements are |
| 53 | +# unchanged. ] |
| 54 | +_comp_xfunc_ARRAY_filter() |
| 55 | +{ |
| 56 | + local _comp_local_flags='' _comp_local_pattype='' _comp_local_anchoring='' |
| 57 | + local OPTIND=1 OPTARG='' OPTERR=0 _comp_local_opt='' |
| 58 | + while getopts 'EFGpsmxrC' _comp_local_opt "$@"; do |
| 59 | + case $_comp_local_opt in |
| 60 | + [EFG]) _comp_local_pattype=$_comp_local_opt ;; |
| 61 | + [psmx]) _comp_local_anchoring=$_comp_local_opt ;; |
| 62 | + [rC]) _comp_local_flags=$_comp_local_opt$_comp_local_flags ;; |
| 63 | + *) |
| 64 | + printf 'bash_completion: %s: %s\n' "$FUNCNAME" 'usage error' >&2 |
| 65 | + printf 'usage: %s %s\n' "$FUNCNAME" "[-EFGpsmxrC] ARRAY_NAME CONDITION" >&2 |
| 66 | + return 2 |
| 67 | + ;; |
| 68 | + esac |
| 69 | + done |
| 70 | + |
| 71 | + shift "$((OPTIND - 1))" |
| 72 | + if (($# != 2)); then |
| 73 | + printf 'bash_completion: %s: %s\n' "$FUNCNAME" "unexpected number of arguments: $#" >&2 |
| 74 | + printf 'usage: %s %s\n' "$FUNCNAME" "[-EFGpsmxrC] ARRAY_NAME CONDITION" >&2 |
| 75 | + return 2 |
| 76 | + elif [[ $1 != [a-zA-Z_]*([a-zA-Z_0-9]) ]]; then |
| 77 | + printf 'bash_completion: %s: %s\n' "$FUNCNAME" "invalid array name '$1'." >&2 |
| 78 | + return 2 |
| 79 | + elif [[ $1 == @(_comp_local_*|OPTIND|OPTARG|OPTERR) ]]; then |
| 80 | + printf 'bash_completion: %s: %s\n' "$FUNCNAME" "array name '$1' is reserved for internal uses" >&2 |
| 81 | + return 2 |
| 82 | + elif [[ ! $_comp_local_pattype && $1 == value ]]; then |
| 83 | + printf 'bash_completion: %s: %s\n' "$FUNCNAME" "array name '$1' cannot be used for the predicate" >&2 |
| 84 | + return 2 |
| 85 | + fi |
| 86 | + # When the array is empty: |
| 87 | + eval "((\${#$1[@]}))" || return 0 |
| 88 | + |
| 89 | + local _comp_local_predicate='' _comp_local_pattern=$2 |
| 90 | + case $_comp_local_pattype in |
| 91 | + E) |
| 92 | + case $_comp_local_anchoring in |
| 93 | + p) _comp_local_predicate='[[ $_comp_local_value =~ ^($_comp_local_pattern) ]]' ;; |
| 94 | + s) _comp_local_predicate='[[ $_comp_local_value =~ ($_comp_local_pattern)$ ]]' ;; |
| 95 | + x) _comp_local_predicate='[[ $_comp_local_value =~ ^($_comp_local_pattern)$ ]]' ;; |
| 96 | + *) _comp_local_predicate='[[ $_comp_local_value =~ $_comp_local_pattern ]]' ;; |
| 97 | + esac |
| 98 | + ;; |
| 99 | + F) |
| 100 | + case $_comp_local_anchoring in |
| 101 | + p) _comp_local_predicate='[[ $_comp_local_value == "$_comp_local_pattern"* ]]' ;; |
| 102 | + s) _comp_local_predicate='[[ $_comp_local_value == *"$_comp_local_pattern" ]]' ;; |
| 103 | + x) _comp_local_predicate='[[ $_comp_local_value == "$_comp_local_pattern" ]]' ;; |
| 104 | + *) _comp_local_predicate='[[ $_comp_local_value == *"$_comp_local_pattern"* ]]' ;; |
| 105 | + esac |
| 106 | + ;; |
| 107 | + G) |
| 108 | + case $_comp_local_anchoring in |
| 109 | + p) _comp_local_predicate='[[ $_comp_local_value == $_comp_local_pattern* ]]' ;; |
| 110 | + s) _comp_local_predicate='[[ $_comp_local_value == *$_comp_local_pattern ]]' ;; |
| 111 | + m) _comp_local_predicate='[[ $_comp_local_value == *$_comp_local_pattern* ]]' ;; |
| 112 | + *) _comp_local_predicate='[[ $_comp_local_value == $_comp_local_pattern ]]' ;; |
| 113 | + esac |
| 114 | + ;; |
| 115 | + *) |
| 116 | + if type -t "$2" &>/dev/null; then |
| 117 | + _comp_local_predicate="$2 \"\$_comp_local_value\"" |
| 118 | + else |
| 119 | + _comp_local_predicate="local -x value=\$_comp_local_value; $2" |
| 120 | + fi |
| 121 | + ;; |
| 122 | + esac |
| 123 | + |
| 124 | + local _comp_local_unset='' _comp_local_expected_status=0 |
| 125 | + [[ $_comp_local_flags == *r* ]] && _comp_local_expected_status=1 |
| 126 | + |
| 127 | + local _comp_local_indices _comp_local_index _comp_local_value |
| 128 | + eval "_comp_local_indices=(\"\${!$1[@]}\")" |
| 129 | + for _comp_local_index in "${_comp_local_indices[@]}"; do |
| 130 | + eval "_comp_local_value=\${$1[\$_comp_local_index]}; $_comp_local_predicate" |
| 131 | + case $? in |
| 132 | + "$_comp_local_expected_status") continue ;; |
| 133 | + [01]) |
| 134 | + unset -v "$1[\$_comp_local_index]" |
| 135 | + _comp_local_unset=1 |
| 136 | + ;; |
| 137 | + 27) break ;; |
| 138 | + *) |
| 139 | + printf 'bash_completion: %s: %s\n' "$FUNCNAME" \ |
| 140 | + "filter condition broken '${_comp_local_pattype:+-$_comp_local_pattype }$2'" >&2 |
| 141 | + return 2 |
| 142 | + ;; |
| 143 | + esac |
| 144 | + done |
| 145 | + |
| 146 | + # Compaction of the sparse array |
| 147 | + [[ $_comp_local_flags == *C* ]] || |
| 148 | + eval "((\${#$1[@]})) && $1=(\"\${$1[@]}\")" |
| 149 | + |
| 150 | + [[ ! $_comp_local_unset ]] |
| 151 | +} |
0 commit comments