Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,8 @@ The project adheres to strict conventions to maintain consistency and readabilit
* `124`: Timeout.
* **Shell Options:** Scripts and the library itself operate with `set -euo pipefail` to ensure robust error handling and predictable behavior.
* **Testing Practices:** Unit tests are organized into functions prefixed with `_L_test_` within `tests/test.sh` and are executed by `L_unittest_main`.

## Agent Interaction Rules

* **Task Management:** Always first create a todo list with a todo write tools and then execute it.
* **Communication Style:** Be as concise and short as possible in communications, sacrificing grammatical structure and correctness.
1 change: 1 addition & 0 deletions GEMINI.md
103 changes: 70 additions & 33 deletions bin/L_lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -967,38 +967,38 @@ L_getopts_in() {
${_L_es[@]:+printf} ${_L_es[@]:+-v_L_tmp} ${_L_es[@]:+"%d"} ${_L_es[@]:+"'$_L_opt"}
${_L_es[@]:+${_L_es[_L_tmp]:+eval}} ${_L_es[@]:+${_L_es[_L_tmp]:+"${_L_es[_L_tmp]}"}}
# Parse the command.
if [[ "$_L_spec" == *"$_L_opt::"* ]]; then
L_array_append "$_L_prefix$_L_opt" "$OPTARG"
elif [[ "$_L_spec" == *"$_L_opt:"* ]]; then
"${_L_local[@]}" "$_L_prefix$_L_opt=$OPTARG"
elif [[ "$_L_spec" == *"$_L_opt"* ]]; then
printf -v "$_L_prefix$_L_opt" "%s" "$(( ${_L_prefix}${_L_opt} + 1 ))"
elif [[ "$_L_spec" == h ]]; then
L_func_usage "$_L_up"
return 0
else
L_func_usage_error "$_L_up"
return 2
fi
case "$_L_spec" in
*"$_L_opt::"*) L_array_append "$_L_prefix$_L_opt" "$OPTARG" ;;
*"$_L_opt:"*) "${_L_local[@]}" "$_L_prefix$_L_opt=$OPTARG" ;;
*"$_L_opt"*) printf -v "$_L_prefix$_L_opt" "%s" "$(( ${_L_prefix}${_L_opt} + 1 ))" ;;
h) L_func_usage "$_L_up"; return 0 ;;
*) L_func_usage_error "$_L_up"; return 2 ;;
esac
done
shift "$((OPTIND-1))"
#
case "$_L_nargs" in
'*') ;;
'?')
if (($# > 1)); then
if (( $# > 1 )); then
L_func_usage_error "Wrong number of arguments. At most 1 argument expected but received $#" "$_L_up"
return 2
fi
;;
'+')
if (($# == 0)); then
if (( $# == 0 )); then
L_func_usage_error "Missing positional argument" "$_L_up"
return 2
fi
;;
[0-9]*'+')
if (( $# < ${_L_nargs%%+} )); then
L_func_usage_error "Wrong number of arguments. Expected at least ${_L_nargs%%+} but received $#" "$_L_up"
return 2
fi
;;
[0-9]*)
if (($# != _L_nargs)); then
if (( $# != _L_nargs )); then
L_func_usage_error "Wrong number of arguments. Expected $_L_nargs but received $#" "$_L_up"
return 2
fi
Expand All @@ -1007,7 +1007,7 @@ L_getopts_in() {
esac
#
# L_array_assign "${_L_prefix}args" "$@"
if ((_L_eval)); then
if (( _L_eval )); then
eval "$_L_cmd \"\$@\""
else
"$_L_cmd" "$@"
Expand Down Expand Up @@ -1386,6 +1386,13 @@ else # L_HAS_NAMEREF

L_handle_v_scalar() {
case "${1:-}" in
-vL_v)
if [[ "${2:-}" == -- ]]; then
"${FUNCNAME[1]}"_v "${@:3}"
else
"${FUNCNAME[1]}"_v "${@:2}"
fi
;;
-v?*)
local -n L_v="${1##-v}" || return 2
if [[ "${2:-}" == -- ]]; then
Expand All @@ -1395,7 +1402,9 @@ else # L_HAS_NAMEREF
fi
;;
-v)
local -n L_v="$2" || return 2
if [[ "$2" != L_v ]]; then
local -n L_v="$2" || return 2
fi
if [[ "${3:-}" == -- ]]; then
"${FUNCNAME[1]}"_v "${@:4}"
else
Expand Down Expand Up @@ -1427,6 +1436,13 @@ else # L_HAS_NAMEREF

L_handle_v_array() {
case "${1:-}" in
-vL_v)
if [[ "${2:-}" == -- ]]; then
"${FUNCNAME[1]}"_v "${@:3}"
else
"${FUNCNAME[1]}"_v "${@:2}"
fi
;;
-v?*)
local -n L_v="${1##-v}" || return 2
if [[ "${2:-}" == -- ]]; then
Expand All @@ -1436,7 +1452,9 @@ else # L_HAS_NAMEREF
fi
;;
-v)
local -n L_v="$2" || return 2
if [[ "$2" != L_v ]]; then
local -n L_v="$2" || return 2
fi
if [[ "${3:-}" == -- ]]; then
"${FUNCNAME[1]}"_v "${@:4}"
else
Expand Down Expand Up @@ -3363,10 +3381,18 @@ L_json_create_v() {
# @example L_array_len arr
L_array_len() { L_handle_v_scalar "$@"; }

# @description Get array keys
# @option -v <var> Store the output in variable instead of printing it.
# @option -h Print this help and return 0.
# @arg $1 <var array nameref
L_array_keys() { L_handle_v_array "$@"; }

if ((L_HAS_NAMEREF)); then

L_array_len_v() { local -n _L_arr="$1" || return 2; L_v=${#_L_arr[@]}; }

L_array_keys_v() { local -n _L_arr="$1" || return 2; L_v=("${!_L_arr[@]}"); }

# @description Set elements of array.
# @arg $1 <var> array nameref
# @arg $@ elements to set
Expand Down Expand Up @@ -3421,6 +3447,7 @@ L_array_copy() {

else # L_HAS_NAMEREF
L_array_len_v() { L_is_valid_variable_name "$1" && eval "L_v=\${#$1[@]}"; }
L_array_keys_v() { L_is_valid_variable_name "$1" && eval "L_v=(\"\${!$1[@]}\")"; }
L_array_assign() { L_is_valid_variable_name "$1" && eval "$1=(\"\${@:2}\")"; }
L_array_set() { L_is_valid_variable_name "$1" && eval "$1[\"\$2\"]=\"\$3\""; }
L_array_append() { L_is_valid_variable_name "$1" && eval "$1+=(\"\${@:2}\")"; }
Expand Down Expand Up @@ -3579,9 +3606,20 @@ L_array_pipe() {
# arr=("Hello" "World")
# L_array_contains arr "Hello"
# echo $? # prints 0
# @see L_args_contain
L_array_contains() {
local _L_arr="$1[@]"
L_args_contain "$2" ${!_L_arr:+"${!_L_arr}"}
local _L_arr="$1[*]" IFS=$'\x1D'
if [[ "${!_L_arr:+${!_L_arr//"$IFS"}}" == "${!_L_arr:+${!_L_arr}}" ]]; then
[[ "$IFS${!_L_arr:+${!_L_arr}}$IFS" == *"$IFS$2$IFS"* ]]
else
local _L_arr="$1[@]" i
for i in "${!_L_arr}"; do
if [[ "$i" == "$2" ]]; then
return 0
fi
done
return 1
fi
}

# @description Remove elements from array for which expression evaluates to failure.
Expand Down Expand Up @@ -3611,17 +3649,16 @@ L_array_filter_eval() {
# @arg $2 element to find
L_array_index() { L_handle_v_scalar "$@"; }
L_array_index_v() {
local _L_i="$1[@]"
(( ${!_L_i:+1}+0 )) && {
eval "local _L_i=(\"\${!$1[@]}\")"
for L_v in "${_L_i[@]}"; do
_L_i="$1[$L_v]"
if [[ "$2" == "${!_L_i}" ]]; then
return 0
fi
done
return 1
}
local _L_i _L_k
L_array_keys_v "$1" || return 2
for _L_k in ${L_v[@]+"${L_v[@]}"}; do
_L_i="$1[$_L_k]"
if [[ "$2" == "${!_L_i}" ]]; then
L_v=$_L_k
return 0
fi
done
return 1
}

# @description Join array elements separated with the second argument.
Expand Down Expand Up @@ -4633,7 +4670,7 @@ L_ok() {
else
set -- "$L_GREEN$1$L_RESET" "${@:2}"
fi
L_log -s 1 -l "$L_LOGLEVEL_INFO" "${a[@]}" "$@"
L_log -s 1 -l "$L_LOGLEVEL_INFO" ${a[@]+"${a[@]}"} "$@"
}

# @description set to 1 if L_run should not execute the function.
Expand Down
109 changes: 109 additions & 0 deletions tests/array_index_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
_L_test_array_index() {
# Test indexed array
local -a arr=(1 2 3)
local idx
L_array_index -v idx arr 1
L_unittest_eq "$idx" "0"

L_array_index -v idx arr 2
L_unittest_eq "$idx" "1"

L_array_index -v idx arr 3
L_unittest_eq "$idx" "2"

# Test failure

arr=(1 2 3)
if L_array_index arr 4; then
echo "L_array_index should have failed"
return 1
fi

# Test associative array
if (( L_HAS_ASSOCIATIVE_ARRAY )); then
local -A assoc=([one]=1 [two]=2 [three]=3)
local key
L_array_index -v key assoc 2
L_unittest_eq "$key" "two"
fi
}

_L_test_array_keys() {
local keys

# Empty array
local -a empty_arr=() keys=()
L_array_keys -v keys empty_arr
L_unittest_eq "${#keys[@]}" "0"

# Normal array
local -a arr=(a b c)
L_array_keys -v keys arr
L_unittest_eq "${#keys[@]}" "3"
# keys should be 0 1 2
L_unittest_eq "${keys[*]}" "0 1 2"

if (( L_HAS_ASSOCIATIVE_ARRAY )); then
# Empty associative array
local -A empty_assoc=()
L_array_keys -v keys empty_assoc
L_unittest_eq "${#keys[@]}" "0"

# Associative array
local -A assoc=([a]=1 [b]=2)
L_array_keys -v keys assoc
L_unittest_eq "${#keys[@]}" "2"

# Sort keys for consistent comparison
local sorted_keys
# shellcheck disable=SC2207
sorted_keys=($(printf "%s\n" "${keys[@]}" | sort))
L_unittest_eq "${sorted_keys[*]}" "a b"
fi
}

_L_test_args_index() {
local idx
L_args_index -v idx "World" "Hello" "World"
L_unittest_eq "$idx" "1"

L_args_index -v idx "Hello" "Hello" "World"
L_unittest_eq "$idx" "0"

if L_args_index -v idx "Missing" "Hello" "World"; then
echo "L_args_index should have failed"
return 1
fi
}

_L_test_args() {
# L_args_contain tests
L_unittest_checkexit 0 L_args_contain 1 0 1 2
L_unittest_checkexit 0 L_args_contain 1 2 1
L_unittest_checkexit 0 L_args_contain 1 1 0
L_unittest_checkexit 0 L_args_contain 1 1
L_unittest_checkexit 1 L_args_contain 0 1
L_unittest_checkexit 1 L_args_contain 0
}

_L_test_array_contains() {
local arr

arr=(0 1 2)
L_unittest_checkexit 0 L_array_contains arr 1

arr=(2 1)
L_unittest_checkexit 0 L_array_contains arr 1

arr=(1 0)
L_unittest_checkexit 0 L_array_contains arr 1

arr=(1)
L_unittest_checkexit 0 L_array_contains arr 1

arr=(1)
L_unittest_checkexit 1 L_array_contains arr 0

arr=()
L_unittest_checkexit 1 L_array_contains arr 0
}
2 changes: 2 additions & 0 deletions tests/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ USR2_CNT=0

###############################################################################

. "$L_DIR"/array_index_tests.sh

_L_test_color() {
{
L_color_enable
Expand Down
Loading