Mercurial > hgrepos > FreeBSD > ports > sysutils > local-bsdtools
view share/local-bsdtools/farray.sh @ 717:f4725ad5cd21
farray.sh: type tests for array and alist
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Fri, 04 Oct 2024 17:18:14 +0200 |
| parents | 36d953791e0c |
| children | 2502e077d5e9 |
line wrap: on
line source
#!/bin/sh # -*- indent-tabs-mode: nil; -*- #: #: A simple library to emulate simple arrays (one-dimensional) and alists #: in a POSIX shell. #: #: :Author: Franz Glasner #: :Copyright: (c) 2024 Franz Glasner. #: All rights reserved. #: :License: BSD 3-Clause "New" or "Revised" License. #: See LICENSE for details. #: If you cannot find LICENSE see #: <https://opensource.org/licenses/BSD-3-Clause> #: :ID: @(#)@@SIMPLEVERSIONTAG@@ #: #: This implementation is somewhat inspired by #: https://unix.stackexchange.com/questions/137566/arrays-in-unix-bourne-shell #: #: Is implements one-dimensional array with one-based indexing. #: #: Hints and rules for arrays: #: #: - Every array has a NAME. #: - One-based indexing is used. #: - The array with name NAME is associated with a primary global or local #: shell variable with the same name. It contains a token TOKEN that is #: used in variable names that make up the backing store of the array. #: The value of this variable has the form ``_farr_A*_<TOKEN>``. #: - Array elements for array NAME that has associated TOKEN are stored in #: global variables named ``_farr_A_<TOKEN>_<index-number>`` (values) #: and ``_farr_A_<TOKEN>__`` (length). #: - An array name must conform to shell variable naming conventions. #: - Currently the number of of elements of an array must be >= 0. #: - Variable NAME can also be a local variable. In this case it MUST #: be initialized with an empty value first. #: - Any unset global variables ``_farr_A_<TOKEN>__`` variable is a severe #: error normally and forces an immediate error ``exit``. #: Exceptions to this rule are documented. #: #: #: This module also contains a very rough implementation of an alist #: (aka dict, aka map). #: This is implemented somewhat similar to arrays. #: #: Hints and rules for alists: #: #: - Every alist has a NAME. #: - One-based indexing is used internally. #: - The alist with name NAME is associated with a primary global or local #: shell variable with the same name. It contains a token TOKEN that is #: used in variable names that make up the backing stores of the alist. #: The value of this variable is of the form ``_farr_KV*_<TOKEN>``. #: - The length of the alist is stored in a global ``_farr_KV_<token>__``. #: - Every key-value pair is stored at an INDEX. #: - Keys are stored in an associated global ``_farr_K_<TOKEN>_<INDEX>``. #: - Values are stored in an associated global ``_farr_V_<TOKEN>_<INDEX>``. #: - An alist name must conform to shell variable naming conventions. #: - Currently the number of of elements of an alist must be >= 0. #: - Variable NAME can also be a local variable. In this case it MUST #: be initialized with an empty value first. #: - Any unset global variables ``_farr_KV_<TOKEN>__`` variable is a severe #: error normally and forces an immediate fatal error exit by calling #: ``exit``. #: - The same is true if ``_farr_K_<TOKEN>_<INDEX>`` or #: ``_farr_V_<TOKEN>_<INDEX>`` is unexpectedly unset. #: Exceptions to this rule are documented. #: - An alist preserves insertion order. Note that updating a key does not #: affect the order. Keys added after deletion are inserted at the end. #: - Alists compare equal if and only if they have the same (key, value) pairs #: (regardless of ordering). #: #: Hints and rules for indexes: #: #: For all functions these facts hold: #: #: - Every object (array, alist) has a current length LEN. #: - Indexes range from 1 <= INDEX <= LEN. #: - Indexes 0, -1, -2 are evaluated to LEN + given index. #: So `0` indexes the last item, `-1` the second last and `-LEN+1` the #: first item. #: - All index (and length) values must be given as decimal numbers. #: #: `null` (aka empty) and/or missing indexes are handled differently in #: the function context. #: #: Important: #: All names that start with ``_farr_`` or ``__farr_`` are reserved #: for private use in this module. #: Do not use such names in your scripts. #: #: Public functions start with ``farray_`` or ``falist_``. #: _farr_array_token_prefix='_farr_A*_' _farr_array_prefix=_farr_A_ _farr_alist_token_prefix='_farr_KV*_' _farr_alist_prefix=_farr_KV_ _farr_alist_key_prefix=_farr_K_ _farr_alist_value_prefix=_farr_V_ #: #: Internal error for fatal errors. #: #: Args: #: $@ (str): The error messages. #: _farr_fatal() { echo "ERROR:" "$@" 1>&2 exit 70 # EX_SOFTWARE } : #: Internal error for other error message. #: #: Args: #: $@ (str): The error messages. #: _farr_err() { echo "ERROR:" "$@" 1>&2 } #: #: Check whether given argument string is a decimal number string #: #: Args: #: $1: The string to be checked #: #: Returns: #: int: 0 (truish) if `$1` is a valid decimal number, #: 1 (falsy) otherwise #: #: See Also: #: https://mywiki.wooledge.org/BashFAQ/054 #: _farr_is_decimal_number() { case "$1" in '') # empty is not allowed return 1 ;; +|-) # just the sign is not allowed return 1 ;; *) # FALL THROUGH ;; esac # here we can trim the optional sign safely case "${1#[-+]}" in *[!0123456789]*) return 1 ;; *) return 0 ;; esac } #: #: From an input index value compute an effective index value and store #: it into a variable with a given name. #: #: Args: #: $1 (str): The name of a variable where to store the result #: $2 (int, null): The index value to check. #: It must be a valid decimal number or the `null` value. #: If the argument is `null` then ``$3 + 1`` is used as the #: resulting effective index value. For this to work a #: valid length must be given in `$3`. #: If the argument is `<= 0` then -- if `$3` is given #: the effective index is computed as ``$3 + $2``. #: $3 (int, optional): If given a length value that is used to compute #: effective index values in some cases (`$2` is null or #: `$2` <= 0). #: _farr_make_index() { local __farr_mi_varname __farr_mi_index __farr_mi_length __farr_mi_varname="$1" __farr_mi_index="$2" __farr_mi_length="${3-}" # If it is given and non-null it must be a valid decimal length number if [ -n "${__farr_mi_length}" ]; then _farr_is_decimal_number "${__farr_mi_length}" || _farr_fatal "given length is not a valid decimal number" fi if [ -z "${__farr_mi_index}" ]; then if [ -n "${__farr_mi_length}" ]; then eval "${__farr_mi_varname}"="\$((__farr_mi_length + 1))" else _farr_fatal "length not given: cannot autocompute index" fi else _farr_is_decimal_number "${__farr_mi_index}" || _farr_fatal "given index is not a valid decimal number" if [ "${__farr_mi_index}" -le 0 ]; then if [ -n "${__farr_mi_length}" ]; then eval "${__farr_mi_varname}"="\$((__farr_mi_length + __farr_mi_index))" else _farr_fatal "cannot compute effective index because no length is given" fi else eval "${__farr_mi_varname}"=\$\{__farr_mi_index\} fi fi } #: #: Quote the given input using "Dollar-Single-Quotes" to be safely used in #: evals. #: #: Args: #: $1: The value to be quoted. #: #: Output (stdout): #: The properly quoted string including surrounding "Dollar-Single-Quotes" #: (e.g. $'...'). #: #: Using "Dollar-Single-Quotes" is not strictly posixly correct. But #: FreeBSD's :command:`/bin/sh` understands them. #: #: From FreeBSD's :manpage:`sh(1)`: #: #: Dollar-Single Quotes #: #: Enclosing characters between ``$'`` and ``'`` preserves the literal #: meaning of all characters except backslashes and single quotes. #: #: A backslash introduces a C-style escape sequence. #: Most important here: #: #: ``\\`` #: Literal backslash #: #: ``\'`` #: Literal single-quote #: _farr_quote_for_eval_dsq() { printf "\$'%s'" "$(_farr_inner_quote_for_dsq "${1}")" } #: #: Helper to quote for "Dollar-Single-Quotes". #: #: This function handles just the quoting mechanics. It does not surround #: the result with any other string decoration. #: See also `_farr_quote_for_eval_dsq`. #: _farr_inner_quote_for_dsq() { printf "%s" "${1}" \ | LC_ALL=C /usr/bin/sed -e $'s/\\\\/\\\\\\\\/g' -e $'s/\'/\\\\\'/g' # escape a backslash escape a single quote # ' # make Emacs happy for correct syntax highlighting } #: #: Quote the given input string for eval. #: #: If the argument contains a ``'`` character then "Dollar-Single-Quotes" #: are used; "Single-Quotes" otherwise. #: #: Args: #: $1: The value to be quoted. #: #: Output (stdout): #: The properly quoted string including surrounding quotes. #: #: _farr_quote_for_eval() { case "${1}" in *\'*) _farr_quote_for_eval_dsq "${1}" ;; *) printf "'%s'" "${1}" ;; esac } #: #: Quote the given input using just POSIX correct "Single-Quotes" to be safely #: used in evals. #: #: Args: #: $1: The value to be quoted. #: #: Output (stdout): #: The properly quoted string including surrounding "Single-Quotes" #: (e.g. '...'). #: #: #: From FreeBSD's :manpage:`sh(1)`: #: #: Single Quotes #: #: Enclosing characters in single quotes preserves the literal #: meaning of all the characters (except single quotes, making it #: impossible to put single-quotes in a single-quoted string). #: _farr_quote_for_eval_sq() { printf "'%s'" "$(_farr_inner_quote_for_sq "${1}")" } #: #: Helper to quote for "Single-Quotes". #: #: This function handles just the quoting mechanics. It does not surround #: the result with any other string decoration. #: See also `_farr_quote_for_eval_dsq`. #: _farr_inner_quote_for_sq() { printf "%s" "${1}" \ | LC_ALL=C /usr/bin/sed -e $'s/\'/\'\\\\\\\'\'/g' # escape a single quote # (here double-escaped: for $'...' AND sed # ' # make Emacs happy for correct syntax highlighting } #: #: Quote the given input string for eval and produce a strictly posixly correct #: encoding. #: #: It does not use FreeBSD's ``$'...'`` shell feature. #: #: If the argument contains a ``'`` character then a proper "Single-Quotes" #: combination is used. #: #: Args: #: $1: The value to be quoted. #: #: Output (stdout): #: The properly quoted string including surrounding quotes. #: #: _farr_quote_for_eval_strict() { case "${1}" in *\'*) _farr_quote_for_eval_sq "${1}" ;; *) printf "'%s'" "${1}" ;; esac } #: #: Internal implementation of typing #: #: Args: #: $1 (str): The name of an object #: #: Output (stdout): #: - array: an array #: - alist: an alist #: - null: an empty thing #: - value: some other shell type (string, number, ...) #: - unknown: if the name in `$1` was not given #: #: Returns: #: int: 0 always #: _farr_type() { local __farr_type_name local __farr_type_token __farr_type_name="${1-}" if [ -n "${__farr_type_name}" ] ; then eval __farr_type_token=\"\$\{"${__farr_type_name}"+SET\}\" if [ "${__farr_type_token}" = 'SET' ] ; then if eval __farr_type_token=\"\$\{"${__farr_type_name}"\}\" ; then case "${__farr_type_token}" in '') printf '%s' 'null';; "${_farr_array_token_prefix}"*) printf '%s' 'array';; "${_farr_alist_token_prefix}"*) printf '%s' 'alist';; *) printf '%s' 'value';; esac else # error in evaluation printf '%s' 'error' fi else # unset printf '%s' 'unknown' fi else # no name given printf '%s' 'unknown' fi return 0 } #: #: Just an official alias for `_farr_type` #: farray_type() { _farr_type "$@" } #: #: Test whether `$1` is an array. #: #: Args: #: $1 (str): The name of an object #: #: Returns: #: int: 0 if `$1` is an array, 1 otherwise #: farray_isarray() { [ "$(_farr_type "$@")" = 'array' ] } #: #: Create a new array. #: #: It is assumed that the array does not exist already. #: #: Args: #: $1 (str): The name of the array. #: Must conform to shell variable naming conventions. #: $2... (optional): Initialization values that will be appended to the #: freshly created array. #: #: Exit: #: Iff the array already exists in some fashion (token and/or storage). #: farray_create() { local __farr_name local __farr_token __farr_gvrname __farr_len __farr_name="${1-}" [ -z "${__farr_name}" ] && _farr_fatal "missing farray name" eval __farr_token=\"\$\{"${__farr_name}"-\}\" [ -n "${__farr_token}" ] && _farr_fatal "object \`${__farr_name}' already created (value \`${__farr_token}')" __farr_token="$(/usr/bin/hexdump -v -e '/1 "%02x"' -n 16 '/dev/urandom')" __farr_gvrname=${_farr_array_prefix}${__farr_token} shift # Check whether the variable already exists eval __farr_len=\$\{${__farr_gvrname}__+SET\} [ -n "${__farr_len}" ] && _farr_fatal "farray \`${__farr_name}' already exists: existing token \`${__farr_token}'" # Really create the storage by initializing its length eval ${__farr_gvrname}__=0 # And associate the token with the array name eval "${__farr_name}=\"\${_farr_array_token_prefix}\${__farr_token}\"" if [ $# -gt 0 ]; then farray_append "${__farr_name}" "$@" fi } #: #: Internal helper to get all the metadata for an array. #: #: Args: #: $1 (str): The name of the array #: #: Output (Globals): #: __farr_name (str): The name of the array. #: __farr_token (str): The token that is the value of the name. #: __farr_gvrname (str): The variable prefix for all items. #: __farr_len (int): The length of the array. #: #: Exit: #: Iff the array does not exist in some fashion (token and/or storage). #: _farr_array_get_meta() { __farr_name="${1-}" [ -z "${__farr_name}" ] && _farr_fatal "missing farray name" eval __farr_token=\"\$\{"${__farr_name}"-\}\" case "${__farr_token}" in '') _farr_fatal "object \`${__farr_name}' not created properly: token empty" ;; "${_farr_array_token_prefix}"*) __farr_token="${__farr_token#"${_farr_array_token_prefix}"}" ;; *) _farr_fatal "object \`${__farr_name}' is not an array" ;; esac __farr_gvrname="${_farr_array_prefix}${__farr_token}" eval __farr_len=\$\{${__farr_gvrname}__:+SET\} [ -z "${__farr_len}" ] && _farr_fatal "farray \`${__farr_name}' not created properly: no storage for token \`${__farr_token}'" # eval __farr_len="\$((\${${__farr_gvrname}__} + 0))" eval __farr_len=\"\$\{${__farr_gvrname}__\}\" return 0 } #: #: Internal helper to try to get all the metadata for an array. #: #: Args: #: $1 (str): The name of the array #: #: Output (Globals): #: __farr_name (str): The name of the array. #: __farr_token (str): The token that is the value of the name. #: __farr_gvrname (str): The variable prefix for all items. #: __farr_len (int): The length of the array #: #: Returns: #: 0 if the array exists, 1 if something is missing. #: #: Exit: #: Iff the array name is not given #: _farr_array_tryget_meta() { __farr_name="${1-}" [ -z "${__farr_name}" ] && _farr_fatal "missing farray name" __farr_token='' __farr_gvrname='' __farr_len='' eval __farr_token=\"\$\{"${__farr_name}"-\}\" case "${__farr_token}" in '') _farr_err "object \`${__farr_name}' not created properly: token empty" return 1 ;; "${_farr_array_token_prefix}"*) __farr_token="${__farr_token#"${_farr_array_token_prefix}"}" ;; *) _farr_err "object \`${__farr_name}' is not an array" return 1 ;; esac __farr_gvrname="${_farr_array_prefix}${__farr_token}" eval __farr_len=\$\{${__farr_gvrname}__:+SET\} if [ -z "${__farr_len}" ]; then _farr_err "farray \`${__farr_name}' not created properly: no storage for token \`${__farr_token}'" return 1 fi # eval __farr_len="\$((\${${__farr_gvrname}__} + 0))" eval __farr_len=\"\$\{${__farr_gvrname}__\}\" return 0 } #: #: Internal helper to try to get all the metadata for an array. #: #: This function is similar to `_farr_array_tryget_meta` but normally fatal #: errors are reported with a "normal" error return. #: #: All output variables are properly initialized to the `null` value. #: #: Args: #: $1 (str): The name of the array #: #: Output (Globals): #: __farr_name (str): The name of the array. #: __farr_token (str): The token that is the value of the name. #: __farr_gvrname (str): The variable prefix for all items. #: __farr_len (int): The length of the array #: #: Returns: #: 0 if the array exists, 1 if something is missing or `$1` is not an array #: #: Important: #: No fatal error exits should happen. #: _farr_array_tryget_meta_nonfatal() { __farr_token='' __farr_gvrname='' __farr_len='' __farr_name="${1-}" [ -z "${__farr_name}" ] && return 1 eval __farr_token=\"\$\{"${__farr_name}"-\}\" case "${__farr_token}" in '') return 1 ;; "${_farr_array_token_prefix}"*) __farr_token="${__farr_token#"${_farr_array_token_prefix}"}" ;; *) return 1 ;; esac __farr_gvrname="${_farr_array_prefix}${__farr_token}" eval __farr_len=\$\{${__farr_gvrname}__:+SET\} [ -z "${__farr_len}" ] && return 1 # eval __farr_len="\$((\${${__farr_gvrname}__} + 0))" eval __farr_len=\"\$\{${__farr_gvrname}__\}\" return 0 } #: #: Get the length of an array and put it into a variable #: #: Args: #: $1 (str): The name of the variable to put the length into. #: $2 (str): The name of the array. #: farray_length() { local __farr_varname __farr_name local __farr_token __farr_gvrname __farr_len __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_array_get_meta "$@" eval "${__farr_varname}"=${__farr_len} } #: #: Get the length of an array and print it to stdout. #: #: Args: #: $1 (str): The name of the array. #: #: Output (stdout): #: The number of elements of the array. #: If the array does not exist the output is -1. #: #: Returns: #: 0 (truthy) #: farray_print_length() { local __farr_vn ( farray_length __farr_vn "$@" && printf "%s" "${__farr_vn}"; ) \ || printf "%s" "-1" } #: #: Append one or more values to an existing array. #: #: Args: #: $1 (str): The name of the existing array. #: $2...: The values to append. #: farray_append() { local __farr_name local __farr_token __farr_gvrname __farr_len __farr_len_1 local __farr_newval _farr_array_get_meta "$@" shift for __farr_newval in "$@"; do __farr_len_1=$((__farr_len + 1)) # # Set value Escape properly: use $' ' and escape any # backslashes and single quotes. # eval ${__farr_gvrname}_${__farr_len_1}="$(_farr_quote_for_eval "${__farr_newval}")" # the implementation below line does not escape properly # eval ${__farr_gvrname}_${__farr_len_1}="\"${__farr_value}\"" # Set new array length # ... persistently in the array data storage eval ${__farr_gvrname}__=${__farr_len_1} # ... and locally __farr_len=${__farr_len_1} done } #: #: Set an array at an index to a value. #: #: Args: #: $1 (str): The name of the existing array. #: $2 (int): The index. #: $3 (optional): The value to set. If the value is not given the null #: will be set. #: #: No holes are allowed in an array: only values at existing indices are #: allowed. As an exception a value can be appended if the given index #: is exactly the current length + 1. #: farray_set() { local __farr_name __farr_index __farr_value local __farr_token __farr_gvrname __farr_len __farr_len_1 _farr_array_get_meta "$@" _farr_make_index __farr_index "${2-}" "${__farr_len}" # first check [ ${__farr_index} -lt 1 ] && _farr_fatal "index must be >= 1" __farr_value="${3-}" # For proper quoting: see farray_append if [ \( ${__farr_index} -ge 1 \) -a \( ${__farr_index} -le ${__farr_len} \) ]; then # replace a value at an existing index eval ${__farr_gvrname}_${__farr_index}="$(_farr_quote_for_eval "${__farr_value}")" else __farr_len_1=$((__farr_len + 1)) if [ ${__farr_index} -eq ${__farr_len_1} ]; then # append value eval ${__farr_gvrname}_${__farr_len_1}="$(_farr_quote_for_eval "${__farr_value}")" # and set new length eval ${__farr_gvrname}__=${__farr_len_1} else _farr_fatal "array index out of bounds (cannot create holes)" fi fi } #: #: Get an array value from a given index and put it into given variable. #: #: Args: #: $1 (str): The name of the variable to put the value into. #: $2 (str): The name of the existing array. #: $3 (int): The index. #: farray_get() { local __farr_varname __farr_name __farr_index local __farr_token __farr_gvrname __farr_len __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_array_get_meta "$@" _farr_make_index __farr_index "${2-}" "${__farr_len}" # check index range if [ \( "${__farr_index}" -lt 1 \) -o \( "${__farr_index}" -gt ${__farr_len} \) ]; then _farr_fatal "array index out of bounds" fi eval "${__farr_varname}"=\"\$\{${__farr_gvrname}_${__farr_index}\}\" } #: #: Try to get an array value from a given index into a variable #: #: Args: #: $1 (str): The name of the variable to put the value into. #: $2 (str): The name of the existing array. #: $3 (int): The index. #: #: Returns: #: 0 (truthy) on success, #: 1 (falsy) if the given index is out of bounds (i.e. EOD). #: #: Exit: #: Other errors (missing array name, missing index value) are considered #: fatal and call `_farr_fatal` (i.e. `exit`). #: farray_tryget() { local __farr_varname __farr_name __farr_index local __farr_token __farr_gvrname __farr_len __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_array_get_meta "$@" _farr_make_index __farr_index "${2-}" "${__farr_len}" # check index range if [ \( "${__farr_index}" -lt 1 \) -o \( "${__farr_index}" -gt ${__farr_len} \) ]; then return 1 fi eval "${__farr_varname}"=\"\$\{${__farr_gvrname}_${__farr_index}\}\" return 0 } #: #: Delete an array value at a given index. #: #: Args: #: $1 (str): The name of the existing array. #: $2 (int): The index to delete to. #: farray_del() { local __farr_name __farr_index local __farr_token __farr_gvrname __farr_len __farr_idx _farr_array_get_meta "$@" _farr_make_index __farr_index "${2-}" "${__farr_len}" # check index range if [ \( "${__farr_index}" -lt 1 \) -o \( "${__farr_index}" -gt ${__farr_len} \) ]; then _farr_fatal "array index out of bounds" fi __farr_idx=${__farr_index} while [ ${__farr_idx} -lt ${__farr_len} ]; do # copy the following value to the current index eval ${__farr_gvrname}_${__farr_idx}=\"\$\{${__farr_gvrname}_$((__farr_idx + 1))\}\" __farr_idx=$((__farr_idx + 1)) done # Drop the last item eval unset ${__farr_gvrname}_${__farr_idx} # Set the new length eval ${__farr_gvrname}__=$((__farr_len - 1)) } #: #: Remove the elements designated by an index and a length from an array, #: and replace them with the elements of another array list, if any. #: #: Args: #: $1 (str, null): The name of an array where the deleted items will be #: appended to. May be the `null` value if this is not #: desired: nothing will be done with the deleted elements. #: $2 (str): The name of the array that is to be spliced #: $3 (int, null): Index, where to remove and/or insert elements. #: A `null` index value is evaluated to `len + 1`. #: $4 (int, null): The length -- the number of elements to delete at given #: index. #: If `null` then the length from index to the end of the #: array is applied ("automatic"). #: $5 (str, null, optional): An array whose elements will be inserted at #: given index. #: #: Using this universal function you can insert, replace or delete items. #: #: Examples: #: #: Using ARRAY for the array to be changed and INSERTED for the new #: items and DELETED/POPPED for the returned items: #: #: Prepend (insert at the start position):: #: #: farray_splice "" ARRAY 1 0 INSERTED #: #: Extend (insert after the end position):: #: #: farray_splice "" ARRAY "" 0 INSERTED #: #: Copy INSERTED into ARRAY, replacing the complete existing content:: #: #: farray_splice "" ARRAY 1 "" INSERTED #: #: Copy INSERTED into ARRAY, replacing the complete existing content and #: returning it in DELETED:: #: #: farray_splice DELETED ARRAY 1 "" INSERTED #: #: Pop the first item:: #: #: farray_splice POPPED ARRAY 1 1 #: #: Pop the last item:: #: #: farray_splice POPPED ARRAY 0 1 #: #: Shift by one item (similar to pop the first item, but no return value):: #: #: farray_splice "" ARRAY 1 1 #: farray_splice() { local __farr_del_name __farr_del_token __farr_del_gvrname __farr_del_len local __farr_l_name __farr_l_token __farr_l_gvrname __farr_l_len local __farr_index __farr_length local __farr_r_name __farr_r_token __farr_r_gvrname __farr_r_len # # Dynamically scoped variables for _farr_array_tryget_meta_nonfatal() # and _farr_array_tryget_meta(). # local __farr_name __farr_token __farr_gvrname __farr_len local __farr_off __farr_v __farr_delta __farr_src_idx __farr_dst_idx [ $# -lt 4 ] && _farr_fatal "missing required arguments" __farr_del_name='' if [ -n "${1}" ] && _farr_array_tryget_meta "${1}"; then __farr_del_name="${__farr_name}" __farr_del_token="${__farr_token}" __farr_del_gvrname="${__farr_gvrname}" __farr_del_len="${__farr_len}" fi _farr_array_get_meta "${2}" __farr_l_name="${__farr_name}" __farr_l_token="${__farr_token}" __farr_l_gvrname="${__farr_gvrname}" __farr_l_len="${__farr_len}" __farr_index="${3}" __farr_length="${4}" if _farr_array_tryget_meta_nonfatal "${5-}"; then __farr_r_name="${__farr_name}" __farr_r_token="${__farr_token}" __farr_r_gvrname="${__farr_gvrname}" __farr_r_len="${__farr_len}" else __farr_r_name='' __farr_r_len=0 fi _farr_make_index __farr_index "${__farr_index}" "${__farr_l_len}" # NOTE: index value array_length + 1 is allowed: splice at the end [ \( "${__farr_index}" -lt 1 \) -o \( "${__farr_index}" -gt "$((__farr_l_len + 1))" \) ] && _farr_fatal "index out of range" # also check the given length if [ -z "${__farr_length}" ]; then __farr_length="$((__farr_l_len - __farr_index + 1))" else _farr_is_decimal_number "${__farr_length}" || _farr_fatal "given length is not a valid number" [ \( "${__farr_length}" -lt 0 \) -o \( "${__farr_length}" -gt "$((__farr_l_len - __farr_index + 1))" \) ] && _farr_fatal "length out of valid range" fi if [ ${__farr_length} -eq ${__farr_r_len} ]; then # Just replace __farr_off=0 while [ ${__farr_off} -lt ${__farr_length} ]; do if [ -n "${__farr_del_name}" ]; then eval __farr_v=\"\$\{${__farr_l_gvrname}_$((__farr_index + __farr_off))\}\" farray_append "${__farr_del_name}" "${__farr_v}" fi eval __farr_v=\"\$\{${__farr_r_gvrname}_$((__farr_off + 1))\}\" eval ${__farr_l_gvrname}_$((__farr_index + __farr_off))=\"\$\{__farr_v\}\" __farr_off=$((__farr_off + 1)) done elif [ ${__farr_length} -gt ${__farr_r_len} ]; then # More to delete than to copy: the resulting array shrinks __farr_delta=$((__farr_length - __farr_r_len)) __farr_off=0 while [ ${__farr_off} -lt ${__farr_r_len} ]; do if [ -n "${__farr_del_name}" ]; then eval __farr_v=\"\$\{${__farr_l_gvrname}_$((__farr_index + __farr_off))\}\" farray_append "${__farr_del_name}" "${__farr_v}" fi eval __farr_v=\"\$\{${__farr_r_gvrname}_$((__farr_off + 1))\}\" eval ${__farr_l_gvrname}_$((__farr_index + __farr_off))=\"\$\{__farr_v\}\" __farr_off=$((__farr_off + 1)) done # Copy / unset the rest that is to delete while [ ${__farr_off} -lt ${__farr_length} ]; do if [ -n "${__farr_del_name}" ]; then eval __farr_v=\"\$\{${__farr_l_gvrname}_$((__farr_index + __farr_off))\}\" farray_append "${__farr_del_name}" "${__farr_v}" fi eval unset ${__farr_l_gvrname}_$((__farr_index + __farr_off)) __farr_off=$((__farr_off + 1)) done # Move the rest __farr_src_idx=$((__farr_index + __farr_length)) __farr_dst_idx=$((__farr_index + __farr_r_len)) while [ ${__farr_src_idx} -le ${__farr_l_len} ]; do eval __farr_v=\"\$\{${__farr_l_gvrname}_${__farr_src_idx}\}\" eval ${__farr_l_gvrname}_${__farr_dst_idx}=\"\$\{__farr_v\}\" eval unset ${__farr_l_gvrname}_${__farr_src_idx} __farr_src_idx=$((__farr_src_idx + 1)) __farr_dst_idx=$((__farr_dst_idx + 1)) done # Adjust the length eval ${__farr_l_gvrname}__=$((__farr_l_len - __farr_delta)) else # More to copy than to delete: the resulting array grows __farr_delta=$((__farr_r_len - __farr_length)) __farr_off=0 while [ ${__farr_off} -lt ${__farr_length} ]; do if [ -n "${__farr_del_name}" ]; then eval __farr_v=\"\$\{${__farr_l_gvrname}_$((__farr_index + __farr_off))\}\" farray_append "${__farr_del_name}" "${__farr_v}" fi eval __farr_v=\"\$\{${__farr_r_gvrname}_$((__farr_off + 1))\}\" eval ${__farr_l_gvrname}_$((__farr_index + __farr_off))=\"\$\{__farr_v\}\" __farr_off=$((__farr_off + 1)) done # Make room from last item to the first non-deleted __farr_src_idx=${__farr_l_len} __farr_dst_idx=$((__farr_src_idx + __farr_delta)) while [ ${__farr_src_idx} -ge $((__farr_index + __farr_length)) ]; do eval __farr_v=\"\$\{${__farr_l_gvrname}_${__farr_src_idx}\}\" eval ${__farr_l_gvrname}_${__farr_dst_idx}=\"\$\{__farr_v\}\" __farr_src_idx=$((__farr_src_idx - 1)) __farr_dst_idx=$((__farr_dst_idx - 1)) done # # Copy the rest of the given data to be inserted # # NOTE: The offset variable __farr_off from above is NOT changed in # between and valid here! # while [ ${__farr_off} -lt ${__farr_r_len} ]; do eval __farr_v=\"\$\{${__farr_r_gvrname}_$((__farr_off + 1))\}\" eval ${__farr_l_gvrname}_$((__farr_index + __farr_off))=\"\$\{__farr_v\}\" __farr_off=$((__farr_off + 1)) done # Adjust the length eval ${__farr_l_gvrname}__=$((__farr_l_len + __farr_delta)) fi return 0 } #: #: Empty an existing array. #: #: Args: #: $1 (str): The name of the existing array. #: farray_clear() { local __farr_name local __farr_token __farr_gvrname __farr_len __farr_idx _farr_array_get_meta "$@" __farr_idx=1 while [ ${__farr_idx} -le ${__farr_len} ]; do eval unset ${__farr_gvrname}_${__farr_idx} __farr_idx=$((__farr_idx + 1)) done # Length is now zero eval ${__farr_gvrname}__=0 } #: #: Destroy and unset an array and all its elements. #: #: Args: #: $1 (str): The name of an array. The array may exist or not. #: #: Returns: #: - A truthy value if the array existed and has been deleted. #: - A falsy value if the array does not exist. #: farray_destroy() { local __farr_name local __farr_token __farr_gvrname __farr_len local __farr_idx if ! _farr_array_tryget_meta "$@" ; then return 1 fi # Remove "storage" __farr_idx=1 while [ ${__farr_idx} -le ${__farr_len} ]; do eval unset ${__farr_gvrname}_${__farr_idx} __farr_idx=$((__farr_idx + 1)) done # Remove length itselt eval unset ${__farr_gvrname}__ # Clean out the array name from the token eval ${__farr_name}=\"\" } #: #: Test the boolean truth of an array. #: #: Args: #: $1 (str): The name of the array. #: #: Returns: #: int: 0 (truthy) if the array contains elements, #: 1 (falsy) otherwise (empty or not created/destroyed properly). #: farray_istrue() { local __farr_name local __farr_token __farr_gvrname __farr_len _farr_array_tryget_meta "$@" || return 1 if [ "${__farr_len}" -gt 0 ]; then return 0 else return 1 fi } #: #: Determine whether any of given values is found within the array. #: #: Args: #: $1 (str): The name of an existing array. #: $2...: The value to search for. #: #: Returns: #: int: 0 (truish) if any of the argument value is found within the given #: array, #: 1 (falsy) if the value is not found. #: farray_contains() { local __farr_name local __farr_token __farr_gvrname __farr_len local __farr_idx __farr_existing_value __farr_searched_value _farr_array_get_meta "$@" shift [ $# -lt 1 ] && _farr_fatal "no search value given" for __farr_searched_value in "$@"; do __farr_idx=1 while [ ${__farr_idx} -le ${__farr_len} ]; do eval __farr_existing_value=\"\$\{${__farr_gvrname}_${__farr_idx}\}\" [ "${__farr_existing_value}" = "${__farr_searched_value}" ] && return 0 __farr_idx=$((__farr_idx + 1)) done done return 1 } #: #: Try to find the index of a given value in an existing array. #: #: Args: #: $1 (str): The name of a variable where to put the found index into #: $2 (str): The name of an existing array #: $3: The value to search for #: $4 (int, nuĺl, optional): The start index to search for (inclusive). #: If not given or `null` then `1` is used. #: $5 (int, null, optional): The index to stop (inclusive). #: If not given or `null` then the length of #: `$2` is used. #: #: Returns: #: - 0 (truish) if the argument value is found within the given array #: and index constraints #: - 1 (falsy) otherwise #: farray_find() { local __farr_varname __farr_name __farr_searched_value local __farr_start __farr_end local __farr_token __farr_gvrname __farr_len local __farr_cur_idx __farr_existing_value __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_array_get_meta "$@" __farr_searched_value="${2+SET}" [ -z "${__farr_searched_value}" ] && _farr_fatal "no search value given" __farr_searched_value="$2" __farr_start="${3-}" [ -z "${__farr_start}" ] && __farr_start=1 _farr_make_index __farr_start "${__farr_start}" "${__farr_len}" [ ${__farr_start} -lt 1 ] && _farr_fatal "start index must be >= 1" __farr_end="${4-}" [ -z "${__farr_end}" ] && __farr_end="${__farr_len}" _farr_make_index __farr_end "${__farr_end}" "${__farr_len}" [ ${__farr_end} -lt 1 ] && _farr_fatal "end index must be >= 1" [ ${__farr_end} -gt "${__farr_len}" ] && _farr_fatal "end index exceeds array length" __farr_cur_idx=${__farr_start} while [ ${__farr_cur_idx} -le ${__farr_end} ]; do eval __farr_existing_value=\"\$\{${__farr_gvrname}_${__farr_cur_idx}\}\" if [ "${__farr_existing_value}" = "${__farr_searched_value}" ]; then #printf "%d" ${__farr_cur_idx} eval "${__farr_varname}"=${__farr_cur_idx} return 0 fi __farr_cur_idx=$((__farr_cur_idx + 1)) done return 1 } #: #: Join all the elements in given array with a separator and put the result #: into a variable. #: #: Args: #: $1 (str): The name of a variable where to put joined string value into. #: $2 (str): The name of an existing array. #: $3 (str, optional): The separator (default: a space `` ``). #: farray_join() { local __farr_varname __farr_name __farr_separator local __farr_token __farr_gvrname __farr_len __farr_join_idx local __farr_command __farr_real_separator __farr_current_value __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_array_get_meta "$@" __farr_separator="${2-" "}" __farr_real_separator="" __farr_command="" __farr_join_idx=1 while [ ${__farr_join_idx} -le ${__farr_len} ]; do eval __farr_current_value=\"\$\{${__farr_gvrname}_${__farr_join_idx}\}\" __farr_command="${__farr_command}${__farr_real_separator}${__farr_current_value}" __farr_real_separator="${__farr_separator}" __farr_join_idx=$((__farr_join_idx + 1)) done eval "${__farr_varname}"=\"\$\{__farr_command\}\" } #: #: Join all the elements in given array with a space `` `` character while #: escaping properly for feeding into shell's ``eval`` and put it into a #: variable. #: #: It is meant that ``eval "$(farray_join_for_eval ARRAY)"`` is safe: #: every item of the array becomes a word when the eval evaluates the #: joined string. #: #: Args: #: $1 (str): The name of a variable where to put joined and properly encoded #: string value into. #: $2 (str): The name of an existing array. #: #: Output (stdout): #: The result string. #: farray_join_for_eval() { local __farr_varname __farr_name local __farr_token __farr_gvrname __farr_len local __farr_join_idx __farr_command __farr_real_separator local __farr_current_value __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_array_get_meta "$@" __farr_real_separator="" __farr_command="" __farr_join_idx=1 while [ ${__farr_join_idx} -le ${__farr_len} ]; do eval __farr_current_value=\"\$\{${__farr_gvrname}_${__farr_join_idx}\}\" __farr_command="${__farr_command}${__farr_real_separator}$(_farr_quote_for_eval "${__farr_current_value}")" __farr_real_separator=' ' __farr_join_idx=$((__farr_join_idx + 1)) done eval "${__farr_varname}"=\"\$\{__farr_command\}\" } #: #: Join all the elements in given array with a space `` `` character while #: escaping properly for feeding into shell's ``eval``. #: #: ``eval "$(farray_join_for_eval ARRAY)"`` is safe: #: every item of the array becomes a word when the eval evaluates the #: joined string. #: #: Args: #: $1 (str): The name of an existing array. #: #: Output (stdout): #: The joined and properly encoded string. #: farray_print_join_for_eval() { local __farr_name local __farr_token __farr_gvrname __farr_len local __farr_join_idx __farr_current_value _farr_array_get_meta "$@" __farr_join_idx=1 while [ ${__farr_join_idx} -le ${__farr_len} ]; do eval __farr_current_value=\"\$\{${__farr_gvrname}_${__farr_join_idx}\}\" if [ ${__farr_join_idx} -gt 1 ]; then printf "%s" " " fi printf "%s" "$(_farr_quote_for_eval "${__farr_current_value}")" __farr_join_idx=$((__farr_join_idx + 1)) done } #: #: Test whether two arrays are equal. #: #: Two arrays are equal if they have both the same length and if #: all elements are equal (using ``test value1 = value2``). #: #: Args: #: $1 (str): The name of the first array #: $2 (str): The name of the second array #: #: Returns: #: int: 0 if both input arrays are equal, 1 otherwise #: farray_are_equal() { local __farr_l_name __farr_r_name local __farr_l_token __farr_l_gvrname __farr_l_len local __farr_r_token __farr_r_gvrname __farr_r_len local __farr_name __farr_token __farr_gvrname __farr_len local __farr_idx __farr_vl __farr_vr [ $# -ne 2 ] && _farr_fatal "missing array" _farr_array_get_meta "$1" __farr_l_name="${__farr_name}" __farr_l_token="${__farr_token}" __farr_l_gvrname="${__farr_gvrname}" __farr_l_len="${__farr_len}" _farr_array_get_meta "$2" __farr_r_name="${__farr_name}" __farr_r_token="${__farr_token}" __farr_r_gvrname="${__farr_gvrname}" __farr_r_len="${__farr_len}" [ ${__farr_l_len} -ne ${__farr_r_len} ] && return 1 __farr_idx=1 while [ ${__farr_idx} -le ${__farr_l_len} ]; do eval __farr_vl=\"\$\{${__farr_l_gvrname}_${__farr_idx}\}\" eval __farr_vr=\"\$\{${__farr_r_gvrname}_${__farr_idx}\}\" [ "${__farr_vl}" != "${__farr_vr}" ] && return 1 __farr_idx=$((__farr_idx + 1)) done return 0 } #: #: Call a function for every element in an array starting at the first index. #: #: The function to be called must accept three arguments: #: - the array name #: - the current index #: - the element value at the current index #: #: The iteration stops if the called function returns a falsy value. #: #: Args: #: $1 (str): The name of an existing array. #: $2 (str): The name of a function to be called with three arguments. #: #: Warning: #: If the number of elements changes while being in `farray_for_each` then #: the behaviour is undefined. #: The current implementation determines the length of the array once #: at the start of execution. #: farray_for_each() { local __farr_name __farr_callback local __farr_token __farr_gvrname __farr_len __farr_idx __farr_rv _farr_array_get_meta "$@" __farr_callback="${2-}" [ -z "${__farr_callback}" ] && _farr_fatal "missing callback function name" __farr_idx=1 while [ ${__farr_idx} -le ${__farr_len} ]; do eval "${__farr_callback} ${__farr_name} ${__farr_idx} \"\${${__farr_gvrname}_${__farr_idx}}\"" __farr_rv=$? [ ${__farr_rv} -ne 0 ] && return ${__farr_rv} __farr_idx=$((__farr_idx + 1)) done return 0 } #: #: Like `farray_for_each`, but the callback function is called in reversed #: order -- beginning with the last index. #: farray_reversed_for_each() { local __farr_name __farr_callback local __farr_token __farr_gvrname __farr_len __farr_idx __farr_rv __farr_callback="${2-}" [ -z "${__farr_callback}" ] && _farr_fatal "missing callback function name" __farr_idx=${__farr_len} while [ ${__farr_idx} -gt 0 ]; do eval "${__farr_callback} ${__farr_name} ${__farr_idx} \"\${${__farr_gvrname}_${__farr_idx}}\"" __farr_rv=$? [ ${__farr_rv} -ne 0 ] && return ${__farr_rv} __farr_idx=$((__farr_idx - 1)) done return 0 } #: #: Print the contents of an array to stderr. #: #: Args: #: $1 (str): The name of an array. The array may exist or not. #: #: Returns: #: 0 #: farray_debug() { local __farr_name local __farr_token __farr_gvrname __farr_len if ! _farr_array_tryget_meta "$@"; then echo "DEBUG: no (meta-)data for farray \`${1-}'" 1>&2 return 0 fi echo "DEBUG: array \`${__farr_name}' has length ${__farr_len}" 1>&2 if [ ${__farr_len} -gt 0 ]; then echo "DEBUG: its contents:" 1>&2 farray_for_each "${__farr_name}" _farr_debug_print_value fi return 0 } #: #: Debug output helper for `farray_debug`. #: _farr_debug_print_value() { printf "DEBUG: %s: \`%s'\\n" "$2" "$3" 1>&2 return 0 } #: #: Just an official alias for `_farr_type` #: falist_type() { _farr_type "$@" } #: #: Test whether `$1` is an alist. #: #: Args: #: $1 (str): The name of an object #: #: Returns: #: int: 0 if `$1` is an alist, 1 otherwise #: falist_isalist() { [ "$(_farr_type "$@")" = 'alist' ] } #: #: Create a new alist. #: #: Args: #: $1 (str): The name of the alist. #: Must conform to shell variable naming conventions. #: $2, $3 ... (optional): A sequence of key-value pairs the alist will be #: populated with. #: #: Exit: #: Iff the alist already exists. #: falist_create() { local __farr_name local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len __farr_name="${1-}" [ -z "${__farr_name}" ] && _farr_fatal "missing falist name" eval __farr_token=\"\$\{"${__farr_name}"-\}\" [ -n "${__farr_token}" ] && _farr_fatal "object \`${__farr_name}' already created (value \`${__farr_token}')" __farr_token="$(/usr/bin/hexdump -v -e '/1 "%02x"' -n 16 '/dev/urandom')" __farr_objname=${_farr_alist_prefix}${__farr_token} __farr_keyname=${_farr_alist_key_prefix}${__farr_token} __farr_valname=${_farr_alist_value_prefix}${__farr_token} # Check whether the variable already exists eval __farr_len=\$\{${__farr_objname}__+SET\} [ -n "${__farr_len}" ] && _farr_fatal "falist \`${__farr_name}' already exists: existing token \`${__farr_token}'" # Really create the storage by initializing its length eval ${__farr_objname}__=0 # And associate the token with the array name eval "${__farr_name}=\"\${_farr_alist_token_prefix}\${__farr_token}\"" # # When there are key-value pairs populate the alist with. # Note that the alist's name is not shifted away yet and must be taken # into account. It is also needed for `falist_set`. # if [ $# -gt 2 ]; then falist_set "$@" fi } #: #: Internal helper to get all the metadata for an alist. #: #: Args: #: $1 (str): The name of the alist. #: #: Output (Globals): #: __farr_name (str): The name of the alist. #: __farr_token (str): The token that is the value of the name. #: __farr_objname (str): The variable prefix for the length ("object"). #: __farr_keyname (str): The variable prefix for all item keys. #: __farr_valname (str): The variable prefix for all item values. #: __farr_len (int): The length of the alist. #: #: Exit: #: Iff the array does not exist in some fashion (token and/or storage). #: _farr_alist_get_meta() { __farr_name="${1-}" [ -z "${__farr_name}" ] && _farr_fatal "missing farray name" eval __farr_token=\"\$\{"${__farr_name}"-\}\" case "${__farr_token}" in '') _farr_fatal "object \`${__farr_name}' not created properly: token empty" ;; "${_farr_alist_token_prefix}"*) __farr_token="${__farr_token#"${_farr_alist_token_prefix}"}" ;; *) _farr_fatal "object \`${__farr_name}' is not an alist" ;; esac __farr_objname="${_farr_alist_prefix}${__farr_token}" __farr_keyname=${_farr_alist_key_prefix}${__farr_token} __farr_valname=${_farr_alist_value_prefix}${__farr_token} eval __farr_len=\$\{${__farr_objname}__:+SET\} [ -z "${__farr_len}" ] && _farr_fatal "falist \`${__farr_name}' not created properly: no object for token \`${__farr_token}'" # eval __farr_len="\$((\${${__farr_objname}__} + 0))" eval __farr_len="\${${__farr_objname}__}" return 0 } #: #: Internal helper to try to get all the metadata for an alist. #: #: Args: #: $1 (str): The name of the alist #: #: Output (Globals): #: __farr_name (str): The name of the alist. #: __farr_token (str): The token that is the value of the name. #: __farr_objname (str): The variable prefix for the length ("object"). #: __farr_keyname (str): The variable prefix for all item keys. #: __farr_valname (str): The variable prefix for all item values. #: __farr_len (int): The length of the alist. #: #: Returns: #: 0 if the array exists, 1 if something is missing. #: #: Exit: #: Iff the array name is not given #: _farr_alist_tryget_meta() { __farr_name="${1-}" [ -z "${__farr_name}" ] && _farr_fatal "missing farray name" __farr_token="" __farr_objname="" __farr_keyname="" __farr_valname="" __farr_len="" eval __farr_token=\"\$\{"${__farr_name}"-\}\" case "${__farr_token}" in '') _farr_err "object \`${__farr_name}' not created properly: token empty" return 1 ;; "${_farr_alist_token_prefix}"*) __farr_token="${__farr_token#"${_farr_alist_token_prefix}"}" ;; *) _farr_err "object \`${__farr_name}' not an alist" return 1 ;; esac __farr_objname="${_farr_alist_prefix}${__farr_token}" __farr_keyname=${_farr_alist_key_prefix}${__farr_token} __farr_valname=${_farr_alist_value_prefix}${__farr_token} eval __farr_len=\$\{${__farr_objname}__:+SET\} if [ -z "${__farr_len}" ]; then _farr_err "falist \`${__farr_name}' not created properly: no object for token \`${__farr_token}'" return 1 fi # eval __farr_len="\$((\${${__farr_objname}__} + 0))" eval __farr_len="\${${__farr_objname}__}" return 0 } #: #: Get the length of an alist and put it into a variable. #: #: Args: #: $1 (str): The name of the variable to put the length info. #: $2 (str): The name of the alist. #: falist_length() { local __farr_varname __farr_name local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_alist_get_meta "$@" eval "${__farr_varname}"=${__farr_len} } #: #: Get the length of an alist and print it to stdout. #: #: Args: #: $1 (str): The name of the alist. #: #: Output (stdout): #: The number of elements in the alist. #: If the array does not exist the output is -1. #: #: Returns: #: 0 (truthy) #: falist_print_length() { local __farr_vn ( falist_length __farr_vn "$@" && printf "%s" "${__farr_vn}"; ) \ || printf "%s" "-1" } #: #: Empty an existing alist. #: #: Args: #: $1 (str): The name of the existing alist. #: falist_clear() { local __farr_name local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len local __farr_idx _farr_alist_get_meta "$@" # Remove "storage" __farr_idx=1 while [ ${__farr_idx} -le ${__farr_len} ]; do eval unset ${__farr_valname}_${__farr_idx} eval unset ${__farr_keyname}_${__farr_idx} __farr_idx=$((__farr_idx + 1)) done # Reset object (length) itself eval ${__farr_objname}__=0 return 0 } #: #: Destroy and unset an alist #: #: Args: #: $1 (str): The name of an alist. The alist may exist or not. #: #: Returns: #: - A truthy value if the alist existed and has been deleted. #: - A falsy value if the alist does not exist. #: falist_destroy() { local __farr_name local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len local __farr_idx if ! _farr_alist_tryget_meta "$@" ; then return 1 fi # Remove "storage" __farr_idx=1 while [ ${__farr_idx} -le ${__farr_len} ]; do eval unset ${__farr_valname}_${__farr_idx} eval unset ${__farr_keyname}_${__farr_idx} __farr_idx=$((__farr_idx + 1)) done # Remove object (length) itselt eval unset ${__farr_objname}__ # Clean out the alist name from the token eval ${__farr_name}=\"\" return 0 } #: #: Map a key to a value. #: #: Args: #: $1 (str): The name of an existing alist. #: $2, $3 ... (optional): A sequence of key-value pairs the alist will be #: populated with. #: #: Exit: #: If one of the underlying arrays that implement the alist does not exist #: or if an internal inconsistency will be detected. #: falist_set() { local __farr_name __farr_key __farr_value local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len local __farr_idx __farr_elkey _farr_alist_get_meta "$@" shift while [ $# -ne 0 ]; do [ $# -lt 1 ] && _farr_fatal "missing key" __farr_key="$1" [ $# -lt 2 ] && _farr_fatal "missing value" __farr_value="$2" __farr_idx=1 while [ ${__farr_idx} -le ${__farr_len} ]; do eval __farr_elkey=\"\$\{${__farr_keyname}_${__farr_idx}+SET\}\" if [ -n "${__farr_elkey}" ]; then eval __farr_elkey=\"\$\{${__farr_keyname}_${__farr_idx}\}\" if [ "${__farr_elkey}" = "${__farr_key}" ]; then eval ${__farr_valname}_${__farr_idx}="$(_farr_quote_for_eval "${__farr_value}")" return 0 fi else _farr_fatal "key unexpectedly unset (index ${__farr_idx})" fi __farr_idx=$((__farr_idx + 1)) done # # Not yet found: "append" .. # # NOTE: __farr_idx here already is the new correct length # __farr_len=${__farr_idx} # ... the key/value pairs to storage eval ${__farr_keyname}_${__farr_len}="$(_farr_quote_for_eval "${__farr_key}")" eval ${__farr_valname}_${__farr_len}="$(_farr_quote_for_eval "${__farr_value}")" # ... the new length eval ${__farr_objname}__=${__farr_len} shift 2 done return 0 } #: #: Update an alist from another alist. #: #: Key-values from the other alist are given precedence. #: #: Args: #: $1 (str): The name of the alist that is to be updated. #: $2 (str): The name of the other alist. #: falist_update() { local __farr_l_name __farr_r_name local __farr_l_token __farr_l_objname __farr_l_keyname __farr_l_valname __farr_l_len local __farr_r_token __farr_r_objname __farr_r_keyname __farr_r_valname __farr_r_len local __farr_name __farr_token __farr_objname __farr_keyname __farr_valname __farr_len local __farr_l_idx __farr_r_idx __farr_r_key __farr_l_key __farr_value [ $# -ne 2 ] && _farr_fatal "exactly two arrays must be given" _farr_alist_get_meta "$1" __farr_l_name="${__farr_name}" __farr_l_token="${__farr_token}" __farr_l_objname="${__farr_objname}" __farr_l_keyname="${__farr_keyname}" __farr_l_valname="${__farr_valname}" __farr_l_len="${__farr_len}" _farr_alist_get_meta "$2" __farr_r_name="${__farr_name}" __farr_r_token="${__farr_token}" __farr_r_objname="${__farr_objname}" __farr_r_keyname="${__farr_keyname}" __farr_r_valname="${__farr_valname}" __farr_r_len="${__farr_len}" __farr_r_idx=1 while [ ${__farr_r_idx} -le ${__farr_r_len} ]; do eval __farr_r_key=\"\$\{${__farr_r_keyname}_${__farr_r_idx}\}\" __farr_l_idx=1 while [ ${__farr_l_idx} -le ${__farr_l_len} ]; do eval __farr_l_key=\"\$\{${__farr_l_keyname}_${__farr_l_idx}\}\" if [ "${__farr_l_key}" = "${__farr_r_key}" ]; then eval __farr_value=\"\$\{${__farr_r_valname}_${__farr_r_idx}\}\" eval ${__farr_l_valname}_${__farr_l_idx}=\"\$\{__farr_value\}\" break fi __farr_l_idx=$((__farr_l_idx + 1)) done # # If __farr_l_idx > __farr_l_len: Not yet found: "append" .. # # NOTE: __farr_l_idx is here also the new correct length # if [ ${__farr_l_idx} -eq $((__farr_l_len + 1)) ]; then # ... the key/value pairs to storage eval ${__farr_l_keyname}_${__farr_l_idx}=\"\$\{__farr_r_key\}\" eval __farr_value=\"\$\{${__farr_r_valname}_${__farr_r_idx}\}\" eval ${__farr_l_valname}_${__farr_l_idx}=\"\$\{__farr_value\}\" # ... the new length in storage eval ${__farr_l_objname}__=${__farr_l_idx} # ... and temporary here __farr_l_len=${__farr_l_idx} fi __farr_r_idx=$((__farr_r_idx + 1)) done return 0 } #: #: Get the value that is associated with a key and put it into a variable. #: #: Args: #: $1 (str): The name of the variable to put the value into. #: $2 (str): The name of an existing alist. #: $3: The key. #: #: Exit: #: - If the key is not found. #: - If one of the underlying arrays that implement the alist does not exist. #: or if an internal inconsistency will be detected. #: falist_get() { local __farr_varname __farr_name __farr_key local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len local __farr_idx __farr_getkey __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_alist_get_meta "$@" [ $# -lt 2 ] && _farr_fatal "missing key" __farr_key="$2" __farr_idx=1 while [ ${__farr_idx} -le ${__farr_len} ]; do eval __farr_getkey=\"\$\{${__farr_keyname}_${__farr_idx}+SET\}\" if [ -n "${__farr_getkey}" ]; then eval __farr_getkey=\"\$\{${__farr_keyname}_${__farr_idx}\}\" if [ "${__farr_getkey}" = "${__farr_key}" ]; then eval "${__farr_varname}"=\"\$\{${__farr_valname}_${__farr_idx}\}\" return 0 fi else _farr_fatal "key unexpectedly unset (index ${__farr_idx})" fi __farr_idx=$((__farr_idx + 1)) done _farr_fatal "alist \`${__farr_name}': key \`${__farr_key}' not found" } #: #: Try to get the value that is associated with a key and store it in a #: variable. #: #: Args: #: $1 (str): The name of the variable where to put the value into. #: $2 (str): The name of an existing alist. #: $3: The key. #: #: Returns: #: 0 (truthy) on success, #: 1 (falsy) if the given key is not found. #: falist_tryget() { local __farr_varname __farr_name __farr_key local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len local __farr_idx __farr_getkey __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_alist_get_meta "$@" [ $# -lt 2 ] && _farr_fatal "missing key" __farr_key="$2" __farr_idx=1 while [ ${__farr_idx} -le ${__farr_len} ]; do eval __farr_getkey=\"\$\{${__farr_keyname}_${__farr_idx}+SET\}\" if [ -n "${__farr_getkey}" ]; then eval __farr_getkey=\"\$\{${__farr_keyname}_${__farr_idx}\}\" if [ "${__farr_getkey}" = "${__farr_key}" ]; then eval "${__farr_varname}"=\"\$\{${__farr_valname}_${__farr_idx}\}\" return 0 fi else _farr_fatal "key unexpectedly unset (index ${__farr_idx})" fi __farr_idx=$((__farr_idx + 1)) done return 1 } #: #: Try to get the key that is associated with a storage index and store it in a variable. #: #: Use this for iteration over values. #: #: Args: #: $1 (str): The name of the variable where to put the key into. #: $2 (str): The name of an existing alist. #: $3 (int): The index. #: #: Returns: #: 0 (truthy) on success, #: 1 (falsy) if the given index is out of bounds. #: #: Exit: #: Other errors (missing array name, missing index value) are considered #: fatal and call `_farr_fatal` (i.e. `exit`). #: falist_tryget_key_at_index() { local __farr_varname __farr_name __farr_index local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len local __farr_idx __farr_getikey __farr_varname=${1-} [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_alist_get_meta "$@" __farr_index="${2-}" [ -z "${__farr_index}" ] && _farr_fatal "missing index" _farr_make_index __farr_index "${__farr_index}" "${__farr_len}" if [ \( ${__farr_index} -ge 1 \) -a \( ${__farr_index} -le ${__farr_len} \) ]; then eval __farr_getikey=\"\$\{${__farr_keyname}_${__farr_index}+SET\}\" if [ -n "${__farr_getikey}" ]; then eval "${__farr_varname}"=\"\$\{${__farr_keyname}_${__farr_index}\}\" return 0 else _farr_fatal "key unexpectedly unset (index ${__farr_index})" fi else return 1 fi } #: #: Try to get the value that is associated with a storage index and store it in a variable. #: #: Use this for iteration over keys. #: #: Args: #: $1 (str): The name of the variable where to put the value into. #: $2 (str): The name of an existing alist. #: $3 (int): The index. #: #: Returns: #: 0 (truthy) on success, #: 1 (falsy) if the given index is out of bounds. #: #: Exit: #: Other errors (missing array name, missing index value) are considered #: fatal and call `_farr_fatal` (i.e. `exit`). #: falist_tryget_value_at_index() { local __farr_varname __farr_name __farr_index local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len local __farr_idx __farr_getival __farr_varname=${1-} [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_alist_get_meta "$@" __farr_index="${2-}" [ -z "${__farr_index}" ] && _farr_fatal "missing index" _farr_make_index __farr_index "${__farr_index}" "${__farr_len}" if [ \( ${__farr_index} -ge 1 \) -a \( ${__farr_index} -le ${__farr_len} \) ]; then eval __farr_getival=\"\$\{${__farr_valname}_${__farr_index}+SET\}\" if [ -n "${__farr_getival}" ]; then eval "${__farr_varname}"=\"\$\{${__farr_valname}_${__farr_index}\}\" return 0 else _farr_fatal "value unexpectedly unset (index ${__farr_index})" fi else return 1 fi } #: #: Try to get the key-value item that is associated with a storage index and #: store it into variables. #: #: Use this for iteration over key-value items. #: #: Args: #: $1 (str): The name of the variable where to put the key into. #: $2 (str): The name of the variable where to put the value into. #: $3 (str): The name of an existing alist. #: $4 (int): The index. #: #: Returns: #: 0 (truthy) on success, #: 1 (falsy) if the given index is out of bounds. #: #: Exit: #: Other errors (missing array name, missing index value) are considered #: fatal and call `_farr_fatal` (i.e. `exit`). #: falist_tryget_item_at_index() { local __farr_key_varname __farr_value_varname __farr_name __farr_index local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len local __farr_idx __farr_getikey __farr_getival __farr_key_varname=${1-} [ -z "${__farr_key_varname}" ] && _farr_fatal "missing variable name for key" __farr_value_varname=${2-} [ -z "${__farr_value_varname}" ] && _farr_fatal "missing variable name for value" shift 2 _farr_alist_get_meta "$@" __farr_index="${2-}" [ -z "${__farr_index}" ] && _farr_fatal "missing index" _farr_make_index __farr_index "${__farr_index}" "${__farr_len}" if [ \( ${__farr_index} -ge 1 \) -a \( ${__farr_index} -le ${__farr_len} \) ]; then eval __farr_getikey=\"\$\{${__farr_keyname}_${__farr_index}+SET\}\" if [ -n "${__farr_getikey}" ]; then eval __farr_getival=\"\$\{${__farr_valname}_${__farr_index}+SET\}\" if [ -n "${__farr_getival}" ]; then eval "${__farr_key_varname}"=\"\$\{${__farr_keyname}_${__farr_index}\}\" eval "${__farr_value_varname}"=\"\$\{${__farr_valname}_${__farr_index}\}\" return 0 else _farr_fatal "value unexpectedly unset (index ${__farr_index})" fi else _farr_fatal "key unexpectedly unset (index ${__farr_index})" fi else return 1 fi } #: #: Determine whether a key is found within the alist. #: #: Args: #: $1 (str): The name of an existing alist. #: $2: The key. #: #: Returns: #: 0 (truthy) on success if the key is found, #: 1 (falsy) if the given key is not found. #: falist_contains() { local __farr_name __farr_key local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len local __farr_idx __farr_cokey _farr_alist_get_meta "$@" [ $# -lt 2 ] && _farr_fatal "missing key" __farr_key="$2" __farr_idx=1 while [ ${__farr_idx} -le ${__farr_len} ]; do eval __farr_cokey=\"\$\{${__farr_keyname}_${__farr_idx}+SET\}\" if [ -n "${__farr_cokey}" ]; then eval __farr_cokey=\"\$\{${__farr_keyname}_${__farr_idx}\}\" if [ "${__farr_cokey}" = "${__farr_key}" ]; then return 0 fi else _farr_fatal "key unexpectedly unset (index ${__farr_idx})" fi __farr_idx=$((__farr_idx + 1)) done return 1 } #: #: Internal helper to delete an item from an alist storage. #: #: Args: #: $1 (str): The name of the storage object #: $2 (int): The current length of the storage object (i.e. the current #: length of the alist) #: $3 (int): The storage index that will be deleted #: #: Returns: #: int: Always 0 #: #: Important: #: This is an internal function. No index and other storage consistency #: checks are done. #: #: Note: #: The implementation is very similar to `farray_del`. #: But the new length must be managed by the caller. #: _farr_alist_del_storage_at_index() { local __farr_storage_name __farr_storage_length __farr_storage_index local __farr_idx __farr_storage_name="$1" __farr_storage_length="$2" __farr_storage_index="$3" __farr_idx=${__farr_storage_index} while [ ${__farr_idx} -lt ${__farr_storage_length} ] ; do # copy the following value to the current index eval ${__farr_storage_name}_${__farr_idx}=\"\$\{${__farr_storage_name}_$((__farr_idx + 1))\}\" __farr_idx=$((__farr_idx + 1)) done # Drop the last item eval unset ${__farr_storage_name}_${__farr_idx} return 0 } #: #: Try to delete an item with a given key from the alist. #: #: Args: #: $1 (str): The name of the alist. #: $2: The key of the key-value pair to delete to #: #: Returns: #: int: 0 if the key has been found and is deleted, #: 1 if the key has not been found. #: falist_trydel() { local __farr_name __farr_delkey local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len local __farr_idx __farr_curkey _farr_alist_get_meta "$@" [ $# -lt 2 ] && _farr_fatal "missing key" __farr_delkey="$2" __farr_idx=1 while [ ${__farr_idx} -le ${__farr_len} ] ; do eval __farr_curkey=\"\$\{${__farr_keyname}_${__farr_idx}+SET\}\" if [ -n "${__farr_curkey}" ]; then eval __farr_curkey=\"\$\{${__farr_keyname}_${__farr_idx}\}\" if [ "${__farr_curkey}" = "${__farr_delkey}" ]; then _farr_alist_del_storage_at_index "${__farr_valname}" ${__farr_len} ${__farr_idx} _farr_alist_del_storage_at_index "${__farr_keyname}" ${__farr_len} ${__farr_idx} # Reduce the length by 1 eval ${__farr_objname}__=$((__farr_len - 1)) return 0 fi else _farr_fatal "key unexpectedly unset (index ${__farr_idx})" fi __farr_idx=$((__farr_idx + 1)) done # Not found return 1 } #: #: Test the boolean truth of an alist. #: #: Args: #: $1 (str): The name of the alist. #: #: Returns: #: int: 0 (truthy) if the alist contains items, #: 1 (falsy) otherwise (empty or not created/destroyed properly). #: falist_istrue() { local __farr_name local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len _farr_alist_tryget_meta "$@" || return 1 if [ "${__farr_len}" -gt 0 ]; then return 0 else return 1 fi } #: #: Test whether two alists are equal. #: #: Two alists compare equal if and only if they have the same (key, value) #: pairs (regardless of ordering). Comparison is done using the shell's #: builtin ``=`` operator for both keys and values. #: #: Args: #: $1 (str): The name of the first alist #: $2 (str): The name of the second alist #: #: Returns: #: int: 0 if both input alists are equal, 1 otherwise #: falist_are_equal() { local __farr_l_name __farr_r_name local __farr_l_token __farr_l_objname __farr_l_keyname __farr_l_valname __farr_l_len local __farr_r_token __farr_r_objname __farr_r_keyname __farr_r_valname __farr_r_len local __farr_name __farr_token __farr_objname __farr_keyname __farr_valname __farr_len local __farr_l_idx __farr_l_key __farr_l_value local __farr_r_idx __farr_r_key __farr_r_value [ $# -ne 2 ] && _farr_fatal "missing alist parameter" _farr_alist_get_meta "$1" __farr_l_name="${__farr_name}" __farr_l_token="${__farr_token}" __farr_l_objname="${__farr_objname}" __farr_l_keyname="${__farr_keyname}" __farr_l_valname="${__farr_valname}" __farr_l_len="${__farr_len}" _farr_alist_get_meta "$2" __farr_r_name="${__farr_name}" __farr_r_token="${__farr_token}" __farr_r_objname="${__farr_objname}" __farr_r_keyname="${__farr_keyname}" __farr_r_valname="${__farr_valname}" __farr_r_len="${__farr_len}" [ ${__farr_l_len} -ne ${__farr_r_len} ] && return 1 __farr_l_idx=1 while [ ${__farr_l_idx} -le ${__farr_l_len} ]; do __farr_r_idx=1 eval __farr_l_key=\"\$\{${__farr_l_keyname}_${__farr_l_idx}\}\" # Find within the right alist __farr_r_idx=1 while [ ${__farr_r_idx} -le ${__farr_r_len} ]; do eval __farr_r_key=\"\$\{${__farr_r_keyname}_${__farr_r_idx}\}\" [ "${__farr_l_key}" = "${__farr_r_key}" ] && break __farr_r_idx=$((__farr_r_idx + 1)) done # The index is above the right length if not found [ ${__farr_r_idx} -gt ${__farr_r_len} ] && return 1 # Also compare the values eval __farr_l_value=\"\$\{${__farr_l_valname}_${__farr_l_idx}\}\" eval __farr_r_value=\"\$\{${__farr_r_valname}_${__farr_r_idx}\}\" [ "${__farr_l_value}" != "${__farr_r_value}" ] && return 1 __farr_l_idx=$((__farr_l_idx + 1)) done return 0 } #: #: Test whether two alists are equal respecting the (insertion) order. #: #: Two alists compare equal if and only if they have the same (key, value) #: pairs **with** regard of ordering. Comparison is done using the shell's #: builtin ``=`` operator for both keys and values. #: #: Args: #: $1 (str): The name of the first alist #: $2 (str): The name of the second alist #: #: Returns: #: int: 0 if both input alists are equal, 1 otherwise #: falist_are_equal_with_order() { local __farr_l_name __farr_r_name local __farr_l_token __farr_l_objname __farr_l_keyname __farr_l_valname __farr_l_len local __farr_r_token __farr_r_objname __farr_r_keyname __farr_r_valname __farr_r_len local __farr_name __farr_token __farr_objname __farr_keyname __farr_valname __farr_len local __farr_idx local __farr_l_key __farr_l_value local __farr_r_key __farr_r_value [ $# -ne 2 ] && _farr_fatal "missing alist parameter" _farr_alist_get_meta "$1" __farr_l_name="${__farr_name}" __farr_l_token="${__farr_token}" __farr_l_objname="${__farr_objname}" __farr_l_keyname="${__farr_keyname}" __farr_l_valname="${__farr_valname}" __farr_l_len="${__farr_len}" _farr_alist_get_meta "$2" __farr_r_name="${__farr_name}" __farr_r_token="${__farr_token}" __farr_r_objname="${__farr_objname}" __farr_r_keyname="${__farr_keyname}" __farr_r_valname="${__farr_valname}" __farr_r_len="${__farr_len}" [ ${__farr_l_len} -ne ${__farr_r_len} ] && return 1 __farr_idx=1 while [ ${__farr_idx} -le ${__farr_l_len} ]; do eval __farr_l_key=\"\$\{${__farr_l_keyname}_${__farr_idx}\}\" eval __farr_r_key=\"\$\{${__farr_r_keyname}_${__farr_idx}\}\" [ "${__farr_l_key}" != "${__farr_r_key}" ] && return 1 # Also compare the values eval __farr_l_value=\"\$\{${__farr_l_valname}_${__farr_idx}\}\" eval __farr_r_value=\"\$\{${__farr_r_valname}_${__farr_idx}\}\" [ "${__farr_l_value}" != "${__farr_r_value}" ] && return 1 __farr_idx=$((__farr_idx + 1)) done return 0 } #: #: Get all keys from the alist and put it into an array. #: #: Args: #: $1 (str): The name of the target array where the keys are appended to. #: $2 (str): The name of the alist from where to get the keys. #: falist_keys() { local _farr_l_name __farr_r_name local __farr_r_token __farr_r_objname __farr_r_keyname __farr_r_valname __farr_r_len local __farr_l_token __farr_l_gvrname __farr_l_len local __farr_name __farr_token __farr_gvrname __farr_objname local __farr_keyname __farr_valname __farr_len local __farr_idx __farr_key # Try to get the array metadata here to provide an early error message [ $# -lt 1 ] && _ferr_fatal "missing target array" _farr_array_get_meta "$1" __farr_l_name="${__farr_name}" __farr_l_token="${__farr_token}" __farr_l_gvrname="${__farr_gvrname}" __farr_l_len="${__farr_len}" [ $# -lt 2 ] && _farr_fatal "missing alist" _farr_alist_get_meta "$2" __farr_r_name="${__farr_name}" __farr_r_token="${__farr_token}" __farr_r_objname="${__farr_objname}" __farr_r_keyname="${__farr_keyname}" __farr_r_valname="${__farr_valname}" __farr_r_len="${__farr_len}" __farr_idx=1 while [ ${__farr_idx} -le ${__farr_r_len} ]; do eval __farr_key=\"\$\{${__farr_r_keyname}_${__farr_idx}+SET\}\" if [ -n "${__farr_key}" ]; then eval __farr_key=\"\$\{${__farr_r_keyname}_${__farr_idx}\}\" farray_append "${__farr_l_name}" "${__farr_key}" else _farr_fatal "alist \`${__farr_r_name}': missing key index" fi __farr_idx=$((__farr_idx + 1)) done return 0 } #: #: Get all values from the alist and put it into an array. #: #: Args: #: $1 (str): The name of the target array where values are appended to. #: $2 (str): The name of the alist from where to get the values. #: falist_values() { local _farr_l_name __farr_r_name local __farr_r_token __farr_r_objname __farr_r_keyname __farr_r_valname __farr_r_len local __farr_l_token __farr_l_gvrname __farr_l_len local __farr_name __farr_token __farr_gvrname __farr_objname local __farr_keyname __farr_valname __farr_len local __farr_idx __farr_value # Try to get the array metadata here to provide an early error message [ $# -lt 1 ] && _ferr_fatal "missing target array" _farr_array_get_meta "$1" __farr_l_name="${__farr_name}" __farr_l_token="${__farr_token}" __farr_l_gvrname="${__farr_gvrname}" __farr_l_len="${__farr_len}" [ $# -lt 2 ] && _farr_fatal "missing alist" _farr_alist_get_meta "$2" __farr_r_name="${__farr_name}" __farr_r_token="${__farr_token}" __farr_r_objname="${__farr_objname}" __farr_r_keyname="${__farr_keyname}" __farr_r_valname="${__farr_valname}" __farr_r_len="${__farr_len}" __farr_idx=1 while [ ${__farr_idx} -le ${__farr_r_len} ]; do eval __farr_value=\"\$\{${__farr_r_valname}_${__farr_idx}+SET\}\" if [ -n "${__farr_value}" ]; then eval __farr_value=\"\$\{${__farr_r_valname}_${__farr_idx}\}\" farray_append "${__farr_l_name}" "${__farr_value}" else _farr_fatal "alist \`${__farr_r_name}': missing value index" fi __farr_idx=$((__farr_idx + 1)) done return 0 } #: #: Get all items (key-value pairs) from the alist and put it into an array. #: #: Args: #: $1 (str): The name of the target array where the sequence of #: key-value pairs are to be appended to. #: $2 (str): The name of the alist from where to get the items. #: falist_items() { local _farr_l_name __farr_r_name local __farr_r_token __farr_r_objname __farr_r_keyname __farr_r_valname __farr_r_len local __farr_l_token __farr_l_gvrname __farr_l_len local __farr_name __farr_token __farr_gvrname __farr_objname local __farr_keyname __farr_valname __farr_len local __farr_idx __farr_key __farr_value # Try to get the array metadata here to provide an early error message [ $# -lt 1 ] && _ferr_fatal "missing target array" _farr_array_get_meta "$1" __farr_l_name="${__farr_name}" __farr_l_token="${__farr_token}" __farr_l_gvrname="${__farr_gvrname}" __farr_l_len="${__farr_len}" [ $# -lt 2 ] && _farr_fatal "missing alist" _farr_alist_get_meta "$2" __farr_r_name="${__farr_name}" __farr_r_token="${__farr_token}" __farr_r_objname="${__farr_objname}" __farr_r_keyname="${__farr_keyname}" __farr_r_valname="${__farr_valname}" __farr_r_len="${__farr_len}" __farr_idx=1 while [ ${__farr_idx} -le ${__farr_r_len} ]; do eval __farr_key=\"\$\{${__farr_r_keyname}_${__farr_idx}+SET\}\" if [ -n "${__farr_key}" ]; then eval __farr_key=\"\$\{${__farr_r_keyname}_${__farr_idx}\}\" eval __farr_value=\"\$\{${__farr_r_valname}_${__farr_idx}+SET\}\" if [ -n "${__farr_value}" ]; then eval __farr_value=\"\$\{${__farr_r_valname}_${__farr_idx}\}\" farray_append "${__farr_l_name}" "${__farr_key}" "${__farr_value}" else _farr_fatal "alist \`${__farr_r_name}': missing value index" fi else _farr_fatal "alist \`${__farr_r_name}': missing key index" fi __farr_idx=$((__farr_idx + 1)) done return 0 } #: #: Call a function for every key-value pair in an alist starting in index order. #: #: The function to be called must accept three or four arguments: #: - the alist name #: - the element key at the current index #: - the element value at the current index #: - the current index #: #: The iteration stops if the called function returns a falsy value. #: #: Args: #: $1 (str): The name of an existing array. #: $2 (str): The name of a function to be called with four arguments. #: #: Warning: #: If the number of elements changes while being in `falist_for_each` then #: the behaviour is undefined. #: The current implementation determines the length of the alist once #: at the start of execution. #: falist_for_each() { local __farr_name __farr_callback local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len local __farr_feidx __farr_fekey __farr_feval __farr_rv _farr_alist_get_meta "$@" __farr_callback="${2-}" [ -z "${__farr_callback}" ] && _farr_fatal "missing callback function name" __farr_feidx=1 while [ ${__farr_feidx} -le ${__farr_len} ]; do eval __farr_fekey=\"\$\{${__farr_keyname}_${__farr_feidx}+SET\}\" if [ -n "${__farr_fekey}" ]; then eval __farr_fekey=\"\$\{${__farr_keyname}_${__farr_feidx}\}\" eval __farr_feval=\"\$\{${__farr_valname}_${__farr_feidx}+SET\}\" if [ -n "${__farr_feval}" ]; then eval __farr_feval=\"\$\{${__farr_valname}_${__farr_feidx}\}\" eval "${__farr_callback} ${__farr_name} \"\${__farr_fekey}\" \"\${__farr_feval}\" ${__farr_feidx}" __farr_rv=$? [ ${__farr_rv} -ne 0 ] && return ${__farr_rv} else _farr_fatal "alist \`${__farr_name}': missing value index" fi else _farr_fatal "alist \`${__farr_name}': missing key index" fi __farr_feidx=$((__farr_feidx + 1)) done return 0 } #: #: Print the contents of an alist to stderr. #: #: Args: #: $1 (str): The name of an alist. The array may exist or not. #: #: Returns: #: 0 #: falist_debug() { local __farr_name local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len local __farr_idx __farr_el_key __farr_el_val if ! _farr_alist_tryget_meta "$@"; then echo "DEBUG: no (meta-)data for falist \`${1-}'" 1>&2 return 0 fi echo "DEBUG: alist \`${__farr_name}' has length ${__farr_len}" 1>&2 __farr_idx=1 while [ ${__farr_idx} -le ${__farr_len} ]; do eval __farr_el_key=\"\$\{${__farr_keyname}_${__farr_idx}+SET\}\" if [ -z "${__farr_el_key}" ]; then echo "DEBUG: key unexpectedly unset (index ${__farr_idx})" 1>&2 fi eval __farr_el_val=\"\$\{${__farr_valname}_${__farr_idx}+SET\}\" if [ -z "${__farr_el_val}" ]; then echo "DEBUG: value unexpectedly unset (index ${__farr_idx})" 1>&2 fi if [ \( -n "${__farr_el_key}" \) -a \( -n "${__farr_el_val}" \) ]; then eval __farr_el_key=\"\$\{${__farr_keyname}_${__farr_idx}\}\" eval __farr_el_val=\"\$\{${__farr_valname}_${__farr_idx}\}\" printf "DEBUG: \`%s' -> \`%s'\\n" "${__farr_el_key}" "${__farr_el_val}" 1>&2 fi __farr_idx=$((__farr_idx + 1)) done return 0 }
