-
Notifications
You must be signed in to change notification settings - Fork 413
[WIP] Array utilities #953
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
akinomyoga
wants to merge
13
commits into
scop:main
Choose a base branch
from
akinomyoga:ARRAY
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+320
−0
Draft
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
b068fad
feat(ARRAY): add `_comp_xfunc_ARRAY_filter`
akinomyoga a8799b9
feat(ARRAY): add array utilities
scop bffe955
refactor(ARRAY): apply naming convention
akinomyoga 8a600c8
refactor(ARRAY): use safer "${!arr[@]}" than ${!arr[*]}
akinomyoga 9b7539d
refactor(ARRAY): avoid using local varnames tmp, i, j, and pattern
akinomyoga 4194006
refactor(ARRAY): explicitly specify type to `unset`
akinomyoga c44d8bb
fix(_comp_uniq): avoid expanding array subscripts in arith context
akinomyoga b96e485
fix(_comp_last_index): use ${arr[*]: -1} to get the last element
akinomyoga e173600
refactor(_comp_compact): simplify
akinomyoga 74282b7
fix(_comp_{last_index,index_of})!: return result through `ret`
akinomyoga e56c6ea
refactor(xfunc ARRAY filter): separate predicate
akinomyoga ef3c28c
feat(_comp_index_of): support options
akinomyoga d669dea
feat(ARRAY): use REPLY to return values
akinomyoga File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,320 @@ | ||
| # Utility xfunc functions for array manipulations | ||
|
|
||
| # 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 | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if the intent is to leave this as a separate file, but I suppose we could think of shipping it in
startup-corenow.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I'm not sure if we would finally include those utilities. Currently, those functions are placed in
completions-core/ARRAY.bashexperimentally so that they can be loaded through_comp_xfuncfor delayed loading. However, they might be put inhelpers-corewith a new mechanism of helper autoloader.