summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKerin Millar <kfm@plushkava.net>2024-07-01 02:34:25 +0100
committerKerin Millar <kfm@plushkava.net>2024-07-01 03:32:27 +0100
commit2a0c3ce54dda9e72310745d04960bcea7071fc4e (patch)
tree7e8056c39292df2842d8d42f8096090fd568b756
parentMove substr() to experimental (diff)
downloadgentoo-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.sh117
-rwxr-xr-xtest-functions80
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