diff options
author | Kerin Millar <kfm@plushkava.net> | 2024-07-01 02:34:25 +0100 |
---|---|---|
committer | Kerin Millar <kfm@plushkava.net> | 2024-07-01 03:32:27 +0100 |
commit | 2a0c3ce54dda9e72310745d04960bcea7071fc4e (patch) | |
tree | 7e8056c39292df2842d8d42f8096090fd568b756 | |
parent | Move substr() to experimental (diff) | |
download | gentoo-functions-2a0c3ce54dda9e72310745d04960bcea7071fc4e.tar.gz gentoo-functions-2a0c3ce54dda9e72310745d04960bcea7071fc4e.tar.bz2 gentoo-functions-2a0c3ce54dda9e72310745d04960bcea7071fc4e.zip |
Add the contains_all() and contains_any() functions
Here are some examples which presume the default value of IFS.
contains_all " cat mat " cat dog # returns 1
contains_all " cat mat " mat cat # returns 0
contains_any " cat mat " cat dog # returns 0
contains_any " cat mat " dog # returns 1
Here are some examples showing that IFS is taken into account.
IFS=, contains_all "cat,mat" cat dog # returns 1
IFS=, contains_all "cat,mat" mat cat # returns 0
IFS=, contains_any "cat,mat" cat dog # returns 0
IFS=, contains_any "cat,mat" dog # returns 1
Signed-off-by: Kerin Millar <kfm@plushkava.net>
-rw-r--r-- | functions.sh | 117 | ||||
-rwxr-xr-x | test-functions | 80 |
2 files changed, 172 insertions, 25 deletions
diff --git a/functions.sh b/functions.sh index 1926c40..a4fa946 100644 --- a/functions.sh +++ b/functions.sh @@ -17,7 +17,7 @@ # COLUMNS : may be used by _update_columns() to get the column count # EPOCHREALTIME : potentially used by _update_time() to get the time # GENFUN_MODULES : which of the optional function collections must be sourced -# IFS : multiple warn() operands are joined by its first character +# IFS : affects contains_all(), contains_any() and warn() # INVOCATION_ID : used by from_unit() # PORTAGE_BIN_PATH : used by from_portage() # RC_OPENRC_PID : used by from_runscript() @@ -49,6 +49,96 @@ chdir() } # +# Takes the first parameter as a string comprising zero or more words, composes +# a set consisting of the intersection of those words, then determines whether +# the intersection of the remaining parameters forms a subset thereof. The +# words shall be collected by splitting the string into individual fields, in +# accordance with section 2.6.5 of the Shell Command Language specification. +# Therefore, the value of IFS shall be taken into account. If fewer than two +# parameters are provided, or if the first parameter yields no fields, or if the +# second set is disjoint from - or a superset of - the first, the return value +# shall be greater than 0. +# +contains_all() +{ + [ "$#" -ge 2 ] && IFS=${IFS} awk -f - -- "$@" <<-'EOF' + BEGIN { + ifs = ENVIRON["IFS"] + haystack = ARGV[1] + argc = ARGC + ARGC = 1 + if (length(ifs) == 0) { + FS = "^" + } else if (length(ifs) != 3 || ifs ~ /[^ \t\n]/) { + # Split by the first character of IFS. + FS = "[" substr(ifs, 1, 1) "]" + } else { + # Mimic default field splitting behaviour, per section 2.6.5. + FS = "[ \t\n]+" + sub("^" FS, "", haystack) + } + # In sh, fields are terminated, not separated. + sub(FS "$", "", haystack) + len = split(haystack, words) + for (i = 1; i <= len; i++) { + set2[words[i]] + } + for (i = 2; i < argc; i++) { + set1[ARGV[i]] + } + for (word in set2) { + delete set1[word] + } + for (word in set1) { + exit 1 + } + } + EOF +} + +# +# Takes the first parameter as a string comprising zero or more words then +# determines whether at least one of the remaining parameters can be matched +# against any of those words. The words shall be collected by splitting the +# string into individual fields, in accordance with section 2.6.5 of the Shell +# Command Language specification. Therefore, the value of IFS shall be taken +# into account. If fewer than two parameters are provided, or if the first +# parameter yields no fields, or if none of the following parameters can be +# matched, the return value shall be greater than 0. +# +contains_any() +{ + local had_noglob haystack i item needle retval + + [ "$#" -ge 2 ] || return + haystack=$1 + shift + i=0 + case $- in + *f*) + had_noglob=1 + ;; + *) + had_noglob=0 + esac + set -f + for needle; do + if [ "$(( i += 1 ))" -eq 1 ]; then + # shellcheck disable=2086 + set -- ${haystack} + fi + for item; do + [ "${item}" = "${needle}" ] && break 2 + done + done + retval=$? + if [ "${had_noglob}" -eq 0 ]; then + set +f + fi + return "${retval}" +} + +# # Considers the first parameter as an URL then attempts to fetch it with either # curl(1) or wget(1). If the URL does not contain a scheme then the https:// # scheme shall be presumed. Both utilities shall be invoked in a manner that @@ -586,29 +676,6 @@ whenceforth() #------------------------------------------------------------------------------# # -# Considers the first parameter as containing zero or more blank-separated words -# then determines whether any of the remaining parameters can be matched in -# their capacity as discrete words. -# -_contains_word() -{ - local word wordlist - - wordlist=$1 word=$2 - case ${word} in - ''|*[[:blank:]]*) - ;; - *) - case " ${wordlist} " in - *[[:blank:]]"${word}"[[:blank:]]*) - return - ;; - esac - esac - false -} - -# # Determines whether the terminal is a dumb one. # _has_dumb_terminal() @@ -761,7 +828,7 @@ _want_module() local basename basename=${1##*/} - _contains_word "${GENFUN_MODULES}" "${basename%.sh}" + contains_any "${GENFUN_MODULES}" "${basename%.sh}" } # diff --git a/test-functions b/test-functions index 34ff54a..59c0b29 100755 --- a/test-functions +++ b/test-functions @@ -719,6 +719,84 @@ test_substr() { iterate_tests 6 "$@" } +test_contains_all() { + set -- \ + ge 1 N/A N/A N/A N/A \ + ge 1 'foo bar' '' N/A N/A \ + ge 1 'foo bar' '' ' ' N/A \ + ge 1 'foo bar' '' ' bar' N/A \ + ge 1 'foo bar' '' ' bar' N/A \ + ge 1 'foo bar' '' 'foo ' N/A \ + ge 1 'foo bar' '' 'foo bar' N/A \ + ge 1 'foo bar' ' ' '' N/A \ + ge 1 'foo bar' ' ' ' ' N/A \ + ge 1 'foo bar' ' ' N/A N/A \ + ge 1 'foo bar' ' bar' '' N/A \ + ge 1 'foo bar' ' bar' N/A N/A \ + ge 1 'foo bar' 'foo ' '' N/A \ + ge 1 'foo bar' 'foo ' ' bar' N/A \ + ge 1 'foo bar' 'foo ' N/A N/A \ + ge 1 'foo bar' 'foo bar' '' N/A \ + ge 1 'foo bar' 'foo bar' N/A N/A \ + ge 1 'foo bar' N/A N/A N/A \ + ge 1 'foo bar' bar foo '' \ + ge 1 'foo bar' bar foo ' ' \ + ge 1 'foo bar' baz bar foo \ + ge 1 'foo bar' fo ba N/A \ + ge 1 'foo bar' foo bar '' \ + ge 1 'foo bar' foo bar ' ' \ + ge 1 'foo bar' foo bar baz \ + ge 1 'foo bar' o a N/A \ + ge 1 'foo bar' oo ar N/A \ + eq 0 'foo bar' foo bar N/A \ + eq 0 'foo bar' bar foo N/A + + callback() { + shift + test_description="contains_all $(quote_args "$@")" + contains_all "$@" + } + + iterate_tests 6 "$@" +} + +test_contains_any() { + set -- \ + ge 1 N/A N/A N/A \ + ge 1 'foo bar' N/A N/A \ + ge 1 'foo bar' fo ba \ + ge 1 'foo bar' oo ar \ + ge 1 'foo bar' o a \ + ge 1 'foo bar' 'foo bar' 'foo bar' \ + ge 1 'foo bar' 'foo bar' _ \ + ge 1 'foo bar' _ 'foo bar' \ + ge 1 'foo bar' 'foo ' ' bar' \ + ge 1 'foo bar' 'foo ' _ \ + ge 1 'foo bar' _ ' bar' \ + ge 1 'foo bar' ' bar' _ \ + ge 1 'foo bar' _ 'foo ' \ + ge 1 'foo bar' '' '' \ + ge 1 'foo bar' '' _ \ + ge 1 'foo bar' _ '' \ + ge 1 'foo bar' ' ' ' ' \ + ge 1 'foo bar' ' ' _ \ + ge 1 'foo bar' _ ' ' \ + eq 0 'foo bar' foo bar \ + eq 0 'foo bar' bar foo \ + eq 0 'foo bar' foo _ \ + eq 0 'foo bar' _ bar \ + eq 0 'foo bar' bar _ \ + eq 0 'foo bar' _ foo + + callback() { + shift + test_description="contains_any $(quote_args "$@")" + contains_any "$@" + } + + iterate_tests 5 "$@" +} + iterate_tests() { slice_width=$1 shift @@ -794,6 +872,8 @@ test_is_subset || rc=1 test_trueof_all || rc=1 test_trueof_any || rc=1 #test_substr || rc=1 +test_contains_all || rc=1 +test_contains_any || rc=1 cleanup_tmpdir |