From 4dc736c1582be9c4accfc9af7f6b1287bbb54917 Mon Sep 17 00:00:00 2001 From: Matt Blum Date: Wed, 4 Mar 2026 10:46:32 -0600 Subject: [PATCH 1/6] add mac compatibility, fix a couple bugs --- .github/workflows/test.yml | 5 +- flex_ini.sh | 111 ++---------------- .../test_flex_ini_save_macos_compatibility.sh | 51 ++++++++ 3 files changed, 67 insertions(+), 100 deletions(-) create mode 100644 tests/src/test_flex_ini_save_macos_compatibility.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 21d4f54..ca56548 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,7 +9,10 @@ on: - main jobs: Test: - runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} steps: - name: Check out repository code uses: actions/checkout@v4 diff --git a/flex_ini.sh b/flex_ini.sh index 80f8294..90b88bd 100644 --- a/flex_ini.sh +++ b/flex_ini.sh @@ -3,32 +3,26 @@ # You may change these here or overwrite them # within your scripts, whatever works best for # your use case. - auto_create_ini_on_load=true auto_save_on_changes=false back_up_changes_on_save=true back_up_changes_on_save_as=false reassign_file_permissions_when_possible=false tmp_directory="/tmp" - declare -gA ini_associations declare -gA ini_unsaved_changes declare -gA ini_loaded - # Private Functions # -- # It's best to not call/modify these directly from your codebase # unless you know what you're doing! - # @private private_flex_ini_error # -- # Helper method to write consistent error logs private_flex_ini_error() { local msg="$1" - echo "[ FlexIni Error ] $msg" } - # @private private_flex_ini_mark_as_changed # -- # Marks an ini array as changed. @@ -36,7 +30,6 @@ private_flex_ini_mark_as_changed() { local ini_identifier=$(private_flex_ini_format_id "$1") ini_unsaved_changes["$ini_identifier"]=true } - # @private private_flex_ini_mark_as_unchanged # -- # Marks an ini array as unchanged. @@ -44,7 +37,6 @@ private_flex_ini_mark_as_unchanged() { local ini_identifier=$(private_flex_ini_format_id "$1") ini_unsaved_changes["$ini_identifier"]=false } - # @private private_flex_ini_required # -- # Helper function to identify required variables and return 1 @@ -52,26 +44,22 @@ private_flex_ini_mark_as_unchanged() { private_flex_ini_required() { local k="$1" local v="$2" - if [ -z "$v" ]; then private_flex_ini_error "value $k is required but was not provided" return 1 fi } - # @private private_flex_ini_require_loaded # -- # Function which returns 1 if the ini id has not # yet beenloaded. private_flex_ini_require_loaded() { local ini_identifier="$1" - if ! private_flex_ini_has_been_loaded "$ini_identifier"; then private_flex_ini_error "the ini id $ini_identifier has not-yet been loaded" return 1 fi } - # @private private_flex_ini_mark_as_loaded # -- # Marks an ini array as already loaded. @@ -79,14 +67,12 @@ private_flex_ini_mark_as_loaded() { local ini_identifier=$(private_flex_ini_format_id "$1") ini_loaded["$ini_identifier"]=true } - # @private private_flex_ini_has_been_loaded # -- # Returns 0 if the array has already been loaded, # returns 1 if it has not. private_flex_ini_has_been_loaded() { local ini_identifier=$(private_flex_ini_format_id "$1") - local loaded="${ini_loaded[$ini_identifier]}" if [ "$loaded" == "true" ]; then @@ -95,7 +81,6 @@ private_flex_ini_has_been_loaded() { return 1 fi } - # @private private_flex_ini_format_id # -- # Format the supplied ini id to make sure it's @@ -103,37 +88,29 @@ private_flex_ini_has_been_loaded() { private_flex_ini_format_id() { local default_ini_array_name="default" local ini_identifier="${1:-$default_ini_array_name}" - local formatted="${ini_identifier// /_}" echo "$formatted" | tr - _ } - # @private private_flex_ini_get_array_name # -- # Get the name of the array based on the ini id. private_flex_ini_get_array_name() { local ini_identifier=$(private_flex_ini_format_id "$1") local ini_name="${ini_identifier}_ini" - echo "$ini_name" } - # @private private_get_ini_file_path # -- # Get the file path for a specific ini id. private_get_ini_file_path() { local ini_identifier=$(private_flex_ini_format_id "$1") - local FILE_PATH="${ini_associations[$ini_identifier]}" - if [ -z "$FILE_PATH" ]; then log error "Error, no file associated with the ini id ${ini_identifier}" return 1 fi - echo "${FILE_PATH}" } - # @private private_flex_ini_init # -- # Initialize an ini file and load it into its array. This @@ -142,14 +119,10 @@ private_get_ini_file_path() { private_flex_ini_init() { local ini_file="$1" local ini_identifier=$(private_flex_ini_format_id "$2") - local ini=$(private_flex_ini_get_array_name "$ini_identifier") - declare -gA "$ini" - ini_associations["$ini_identifier"]="$ini_file" } - # @private private_flex_ini_create # -- # Create an ini file at the specified path. @@ -157,15 +130,12 @@ private_flex_ini_create() { local ini_file="$1" private_flex_ini_required "ini_file" "$ini_file" || return 1 - touch "$ini_file" || return 1 } - # Public Functions # -- # These functions are the ones you want to call from your scripts # since they are meant to work together and validate various items. - # @public flex_ini_load # -- # You'll need to call this method first in order to load up your @@ -174,37 +144,31 @@ flex_ini_load() { local ini_file="$1" local ini_identifier=$(private_flex_ini_format_id "$2") local force_reload="${3:-false}" - if [ "$force_reload" != "true" ] && private_flex_ini_has_been_loaded "$ini_identifier"; then return 0 fi - if [ "$force_reload" == "true" ] && private_flex_ini_has_been_loaded "$ini_identifier"; then flex_ini_clear "$ini_identifier" fi - if [ ! -f "$ini_file" ]; then if [ "$auto_create_ini_on_load" == "true" ]; then if ! private_flex_ini_create "$ini_file"; then - flex_ini_error "ini file could not be auto-created at ${ini_file}" + private_flex_ini_error "ini file could not be auto-created at ${ini_file}" return 1 fi else - flex_ini_error "ini file not found at ${ini_file} and auto_create_ini_on_load was not 'true' so we did not try to create it" + private_flex_ini_error "ini file not found at ${ini_file} and auto_create_ini_on_load was not 'true' so we did not try to create it" return 1 fi fi - private_flex_ini_init "$ini_file" "$ini_identifier" local ini=$(private_flex_ini_get_array_name "$ini_identifier") - local section="" local key="" local value="" local section_regex="^\[(.+)\]" local key_regex="^([^ =]+) *= *(.*) *$" local comment_regex="^;" - while IFS= read -r line; do if [[ $line =~ $comment_regex ]]; then continue @@ -218,11 +182,9 @@ flex_ini_load() { eval "$name_var" fi done <"$ini_file" - private_flex_ini_mark_as_unchanged "$ini_identifier" private_flex_ini_mark_as_loaded "$ini_identifier" } - # @public flex_ini_reload # -- # Reload an already-loaded ini ID. Please note, if you have not, in @@ -230,16 +192,13 @@ flex_ini_load() { flex_ini_reload() { local ini_identifier=$(private_flex_ini_format_id "$1") local destination_ini_path=$(private_get_ini_file_path "$ini_identifier") - if [ -z "$destination_ini_path" ]; then echo "[ Flex INI Error ] No INI filepath could be found from which to reload. Are you sure you loaded the config file for ${ini_identifier}?" + return 1 fi - flex_ini_clear "$ini_identifier" - flex_ini_load "$destination_ini_path" "$ini_identifier" } - # @public flex_ini_clear # -- # Clears an ini ID completely from our loaded configs. You @@ -247,16 +206,12 @@ flex_ini_reload() { # during debugging. flex_ini_clear() { local ini_identifier=$(private_flex_ini_format_id "$1") - private_flex_ini_require_loaded "$ini_identifier" || return 1 - unset ini_unsaved_changes["$ini_identifier"] unset ini_loaded["$ini_identifier"] - local current_array_name=$(private_flex_ini_get_array_name "$ini_identifier") unset "$current_array_name" } - # @public flex_ini_get # -- # Fetches a value from the specified ini array. @@ -264,27 +219,21 @@ flex_ini_get() { local key="$1" local ini_identifier=$(private_flex_ini_format_id "$2") local array_name=$(private_flex_ini_get_array_name "$ini_identifier") - private_flex_ini_required "key" "$key" || return 1 - local name_var='${'$array_name'['$key']}' local value=$(eval echo "$name_var") echo "$value" } - # @public flex_ini_has # -- # Returns 0 if the key exists, 1 if it does not. flex_ini_has() { local key="$1" local ini_identifier=$(private_flex_ini_format_id "$2") - private_flex_ini_required "key" "$key" || return 1 private_flex_ini_require_loaded "$ini_identifier" || return 1 - [[ $(flex_ini_get "$key" "$ini_identifier") ]] } - # @public flex_ini_update # -- # This creates or updates a value in your ini array. @@ -295,21 +244,17 @@ flex_ini_update() { local value="$2" local ini_identifier=$(private_flex_ini_format_id "$3") local array_name=$(private_flex_ini_get_array_name "$ini_identifier") - private_flex_ini_required "key" "$key" || return 1 private_flex_ini_require_loaded "$ini_identifier" || return 1 - local assign_var=$array_name'["'$key'"]="'"${value}"'"' eval "$assign_var" || return 1 - if [ "$auto_save_on_changes" == "true" ]; then flex_ini_save "$ini_identifier" else private_flex_ini_mark_as_changed "$ini_identifier" fi } - # @public flex_ini_delete # -- # Remove a value from your ini array. @@ -318,21 +263,17 @@ flex_ini_update() { flex_ini_delete() { local key="$1" local ini_identifier=$(private_flex_ini_format_id "$2") - private_flex_ini_required "key" "$key" || return 1 private_flex_ini_require_loaded "$ini_identifier" || return 1 - local array_name=$(private_flex_ini_get_array_name "$ini_identifier") local assign_var='unset '$array_name'["'$key'"]' eval "$assign_var" - - if [ "$auto_save_on_changes" == "true" ]; then + if [ "$auto_save_on_changes" == "true" ]; then flex_ini_save "$ini_identifier" else private_flex_ini_mark_as_changed "$ini_identifier" fi } - # @public flex_ini_save # -- # Save the values in the array back to the correct file. @@ -342,9 +283,7 @@ flex_ini_save() { local ini_identifier=$(private_flex_ini_format_id "$1") local override_path="$2" local default_destination_ini_path=$(private_get_ini_file_path "$ini_identifier") - private_flex_ini_require_loaded "$ini_identifier" || return 1 - # The save-as logic path first if [ -n "$override_path" ]; then # pre-create the file if it doesn't exist just to make sure we @@ -355,7 +294,6 @@ flex_ini_save() { return 1 fi fi - # set the destination ini path to whichever new path was specified local destination_ini_path="$override_path" # use the val from 'save as' to determine whether to back up @@ -368,33 +306,32 @@ flex_ini_save() { local should_back_up_before_save="${back_up_changes_on_save}" local is_save_as_operation=false fi - # If param is true, then fetch the user/group from the current ini # file and attempt to re-assign ownership when file is saved if # the current user is different from the owner of the file if [ "$reassign_file_permissions_when_possible" == "true" ]; then - local file_owner=$(stat --format '%U' "${destination_ini_path}") - local file_group=$(stat --format '%G' "${destination_ini_path}") + if [[ "$(uname)" == "Darwin" ]]; then + local file_owner=$(stat -f '%Su' "${destination_ini_path}") + local file_group=$(stat -f '%Sg' "${destination_ini_path}") + else + local file_owner=$(stat --format '%U' "${destination_ini_path}") + local file_group=$(stat --format '%G' "${destination_ini_path}") + fi local current_user=$(echo "$USER") if [ "$file_owner" != "$current_user" ]; then local should_reassign_file_permissions=true fi fi - local current_section="" local has_free_keys=false - local ini_file=$(mktemp "${tmp_directory}/flexini.XXXXXX") - for key in $(flex_ini_keys "$ini_identifier"); do [[ $key == *.* ]] && continue has_free_keys=true local value=$(flex_ini_get "$key" "$ini_identifier") echo "$key = $value" >>"$ini_file" done - [[ "${has_free_keys}" == "true" ]] && echo >>"$ini_file" - # Collect all section names and sort them declare -A sections for key in $(flex_ini_keys "$ini_identifier"); do @@ -402,7 +339,6 @@ flex_ini_save() { IFS="." read -r section_name key_name <<<"$key" sections["$section_name"]=1 done - # Process sections in alphabetical order for section_name in $(printf '%s\n' "${!sections[@]}" | sort); do # Check if this is a new section @@ -411,7 +347,6 @@ flex_ini_save() { echo "[$section_name]" >>"$ini_file" current_section="$section_name" fi - # Collect and sort keys for this section only section_keys=() # Clear the array first for key in $(flex_ini_keys "$ini_identifier"); do @@ -421,7 +356,6 @@ flex_ini_save() { section_keys+=("$key") fi done - # Sort keys within this section and write them for key in $(printf '%s\n' "${section_keys[@]}" | sort); do IFS="." read -r key_section key_name <<<"$key" @@ -429,44 +363,36 @@ flex_ini_save() { echo "$key_name = $value" >>"$ini_file" done done - # Back up the destination ini file if specified if [ "$should_back_up_before_save" == "true" ]; then cp -f "$destination_ini_path" "${destination_ini_path}.bak" || private_flex_ini_error "could not make the backup copy of the ini file at ${destination_ini_path}" fi - # Replace the destination ini file with the tmp file if ! mv -f "$ini_file" "$destination_ini_path"; then private_flex_ini_error "could not move tmp ini file to its destination at ${destination_ini_path} -- data was not saved to disk" return 1 fi - # Try to update the permissions of the file, if set if [ "$should_reassign_file_permissions" == "true" ]; then chown "${file_owner}":"${file_group}" "${destination_ini_path}" || private_flex_ini_error "our attempt to reassign file permissions to '${file_owner}:${file_group} failed for file at ${destination_ini_path}" fi - # Only mark the ini as unchanged if we saved it to # the original file. if [ "$is_save_as_operation" != "true" ]; then private_flex_ini_mark_as_unchanged "$ini_identifier" fi } - # @public flex_ini_save_as # -- # A helper function to initiate a save-as. flex_ini_save_as() { local override_path="$1" local ini_identifier=$(private_flex_ini_format_id "$2") - private_flex_ini_require_loaded "$ini_identifier" || return 1 - flex_ini_save "$ini_identifier" "$override_path" || return 1 } - # @public flex_ini_has_unsaved # -- # Returns 0 if the array has unsaved changes, @@ -475,16 +401,13 @@ flex_ini_has_unsaved() { local ini_identifier=$(private_flex_ini_format_id "$1") private_flex_ini_require_loaded "$ini_identifier" || return 1 - local has_unsaved="${ini_unsaved_changes["$ini_identifier"]}" - if [ "$has_unsaved" == "true" ]; then return 0 else return 1 fi } - # @public flex_ini_reset # -- # Removes all data from the stores. Be careful with this one! @@ -494,36 +417,28 @@ flex_ini_reset() { unset "$array_name" unset ini_associations["$i"] done - for i in "${!ini_unsaved_changes[@]}"; do unset ini_unsaved_changes["$i"] done - for i in "${!ini_loaded[@]}"; do unset ini_loaded["$i"] done } - # @public flex_ini_show # -- # Show all loaded key-value pairs (whether or not they've been saved). flex_ini_show() { local ini_identifier=$(private_flex_ini_format_id "$1") local file_path=$(private_get_ini_file_path "$ini_identifier") - private_flex_ini_require_loaded "$ini_identifier" || return 1 - echo "[ ${ini_identifier} ]" echo "--" - for key in $(flex_ini_keys "$ini_identifier"); do local value=$(flex_ini_get "$key" "$ini_identifier") echo "$key = $value" done - echo "" } - # @public flex_ini_keys # -- # Get an array of all keys in your ini array (whether or not they) @@ -531,10 +446,8 @@ flex_ini_show() { flex_ini_keys() { local ini_identifier=$(private_flex_ini_format_id "$1") local array_name=$(private_flex_ini_get_array_name "$ini_identifier") - private_flex_ini_require_loaded "$ini_identifier" || return 1 - local name_var='${!'$array_name'[@]}' local keys=($(eval echo "$name_var")) for a in "${keys[@]}"; do echo "$a"; done | sort -} +} \ No newline at end of file diff --git a/tests/src/test_flex_ini_save_macos_compatibility.sh b/tests/src/test_flex_ini_save_macos_compatibility.sh new file mode 100644 index 0000000..aec89dc --- /dev/null +++ b/tests/src/test_flex_ini_save_macos_compatibility.sh @@ -0,0 +1,51 @@ +test_flex_ini_save_macos_compatibility() { + local ini_one=$(create_ini) + + flex_ini_load "$ini_one" + flex_ini_update "test_key" "test_value" + + # Test that save works on both macOS and Linux + # This specifically tests the stat command compatibility + # The code should detect the OS and use appropriate stat flags + flex_ini_save + + # Verify the file was saved correctly + expect "$(flex_ini_get "test_key")" "test_value" + + # Test save_as functionality as well + local save_as_loc="${test_storage_dir}/macos_test_$$" + flex_ini_save_as "$save_as_loc" + + # Verify the save_as file exists and has correct content + if [ ! -f "$save_as_loc" ]; then + fail "Save as file was not created" + fi + + # Load the save_as file and verify content + flex_ini_load "$save_as_loc" "macos_test" + expect "$(flex_ini_get "test_key" "macos_test")" "test_value" + + # Test the specific stat commands used in the code + local test_file="${test_storage_dir}/stat_test_$$" + touch "$test_file" + + if [[ "$(uname)" == "Darwin" ]]; then + # Test macOS stat commands + local owner=$(stat -f '%Su' "$test_file") + local group=$(stat -f '%Sg' "$test_file") + echo "macOS stat: owner=$owner, group=$group" + [[ -n "$owner" ]] || fail "macOS stat owner command failed" + [[ -n "$group" ]] || fail "macOS stat group command failed" + else + # Test Linux stat commands + local owner=$(stat --format '%U' "$test_file") + local group=$(stat --format '%G' "$test_file") + echo "Linux stat: owner=$owner, group=$group" + [[ -n "$owner" ]] || fail "Linux stat owner command failed" + [[ -n "$group" ]] || fail "Linux stat group command failed" + fi + + # Clean up + flex_ini_reset + rm -f "$save_as_loc" "$test_file" +} From 2a91c164d8929c3c90873d24cfe1df0067966266 Mon Sep 17 00:00:00 2001 From: Matt Blum Date: Wed, 4 Mar 2026 10:51:25 -0600 Subject: [PATCH 2/6] fix github actions compatibility - Add conditional -g flag support for declare (bash 4.2+ compatibility) - Fix test runner filename parsing bug - Fix test_flex_ini_keys array capturing issue --- flex_ini.sh | 20 ++++++++++++++++---- tests/_run.sh | 4 ++-- tests/src/test_flex_ini_keys.sh | 3 ++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/flex_ini.sh b/flex_ini.sh index 90b88bd..24be13f 100644 --- a/flex_ini.sh +++ b/flex_ini.sh @@ -9,9 +9,16 @@ back_up_changes_on_save=true back_up_changes_on_save_as=false reassign_file_permissions_when_possible=false tmp_directory="/tmp" -declare -gA ini_associations -declare -gA ini_unsaved_changes -declare -gA ini_loaded +# Check if declare supports -g flag (bash 4.2+) +if declare -g test 2>/dev/null; then + declare -gA ini_associations + declare -gA ini_unsaved_changes + declare -gA ini_loaded +else + declare -A ini_associations + declare -A ini_unsaved_changes + declare -A ini_loaded +fi # Private Functions # -- # It's best to not call/modify these directly from your codebase @@ -120,7 +127,12 @@ private_flex_ini_init() { local ini_file="$1" local ini_identifier=$(private_flex_ini_format_id "$2") local ini=$(private_flex_ini_get_array_name "$ini_identifier") - declare -gA "$ini" + # Check if declare supports -g flag (bash 4.2+) + if declare -g test 2>/dev/null; then + declare -gA "$ini" + else + declare -A "$ini" + fi ini_associations["$ini_identifier"]="$ini_file" } # @private private_flex_ini_create diff --git a/tests/_run.sh b/tests/_run.sh index 4972ad6..bba4113 100755 --- a/tests/_run.sh +++ b/tests/_run.sh @@ -6,8 +6,8 @@ echo "" test_suites=() cd $test_parent_dir/src -for f in *; do - fn_name="${f/.sh/''}" +for f in *.sh; do + fn_name="${f%.sh}" test_suites+=("$fn_name") done cd ../.. diff --git a/tests/src/test_flex_ini_keys.sh b/tests/src/test_flex_ini_keys.sh index 10dcc12..f36bb41 100644 --- a/tests/src/test_flex_ini_keys.sh +++ b/tests/src/test_flex_ini_keys.sh @@ -5,7 +5,8 @@ test_flex_ini_keys() { flex_ini_update "testing" "two" flex_ini_update "tested" "three" - local res=($(flex_ini_keys)) + local res + readarray -t res < <(flex_ini_keys) expect "${#res[@]}" 3 expect_array_contains "test" "${res[@]}" From afb0f9656db7bc2dd8d5223701cd879ff674965f Mon Sep 17 00:00:00 2001 From: Matt Blum Date: Wed, 4 Mar 2026 10:54:05 -0600 Subject: [PATCH 3/6] fix github actions shell issue - Explicitly use bash shell in GitHub Actions workflow - Revert to original declare -gA syntax that was working before --- .github/workflows/test.yml | 3 ++- flex_ini.sh | 20 ++++---------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ca56548..cbc0fc2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,5 +16,6 @@ jobs: steps: - name: Check out repository code uses: actions/checkout@v4 - - run: ./tests/_run.sh + - run: bash ./tests/_run.sh + shell: bash - run: echo "🍏 This job's status is ${{ job.status }}." diff --git a/flex_ini.sh b/flex_ini.sh index 24be13f..90b88bd 100644 --- a/flex_ini.sh +++ b/flex_ini.sh @@ -9,16 +9,9 @@ back_up_changes_on_save=true back_up_changes_on_save_as=false reassign_file_permissions_when_possible=false tmp_directory="/tmp" -# Check if declare supports -g flag (bash 4.2+) -if declare -g test 2>/dev/null; then - declare -gA ini_associations - declare -gA ini_unsaved_changes - declare -gA ini_loaded -else - declare -A ini_associations - declare -A ini_unsaved_changes - declare -A ini_loaded -fi +declare -gA ini_associations +declare -gA ini_unsaved_changes +declare -gA ini_loaded # Private Functions # -- # It's best to not call/modify these directly from your codebase @@ -127,12 +120,7 @@ private_flex_ini_init() { local ini_file="$1" local ini_identifier=$(private_flex_ini_format_id "$2") local ini=$(private_flex_ini_get_array_name "$ini_identifier") - # Check if declare supports -g flag (bash 4.2+) - if declare -g test 2>/dev/null; then - declare -gA "$ini" - else - declare -A "$ini" - fi + declare -gA "$ini" ini_associations["$ini_identifier"]="$ini_file" } # @private private_flex_ini_create From 1ed91e60a033f13d15d93ceae0f9c3984c4f7a9b Mon Sep 17 00:00:00 2001 From: Matt Blum Date: Wed, 4 Mar 2026 10:57:19 -0600 Subject: [PATCH 4/6] add OS detection at top of file - Add _OS variable to detect macOS/Linux/unknown at startup - Use OS detection for stat command selection instead of inline uname checks - Update compatibility test to use OS detection - Support for future OS-specific features --- flex_ini.sh | 48 ++++++++++++++++--- .../test_flex_ini_save_macos_compatibility.sh | 6 ++- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/flex_ini.sh b/flex_ini.sh index 90b88bd..e86a1f0 100644 --- a/flex_ini.sh +++ b/flex_ini.sh @@ -9,9 +9,38 @@ back_up_changes_on_save=true back_up_changes_on_save_as=false reassign_file_permissions_when_possible=false tmp_directory="/tmp" -declare -gA ini_associations -declare -gA ini_unsaved_changes -declare -gA ini_loaded +# Detect the operating system +if [[ "$(uname)" == "Darwin" ]]; then + _OS="macos" +elif [[ "$(uname)" == "Linux" ]]; then + _OS="linux" +else + _OS="unknown" +fi + +# Check bash version and use appropriate declare syntax +_BASH_VERSION=$(bash --version | head -1 | grep -oE '[0-9]+\.[0-9]+' | head -1) +if [[ "${_BASH_VERSION%%.*}" -ge 4 ]]; then + # Bash 4.0+ supports associative arrays + if declare -g _test 2>/dev/null; then + # Bash 4.2+ supports -g flag + declare -gA ini_associations + declare -gA ini_unsaved_changes + declare -gA ini_loaded + _SUPPORTS_G_FLAG=true + else + # Bash 4.0-4.1 doesn't support -g flag + declare -A ini_associations + declare -A ini_unsaved_changes + declare -A ini_loaded + _SUPPORTS_G_FLAG=false + fi +else + # Bash 3.x doesn't support associative arrays at all + echo "Error: FlexIni requires bash 4.0 or higher for associative arrays" + echo "Current bash version: $_BASH_VERSION" + exit 1 +fi # Private Functions # -- # It's best to not call/modify these directly from your codebase @@ -120,7 +149,12 @@ private_flex_ini_init() { local ini_file="$1" local ini_identifier=$(private_flex_ini_format_id "$2") local ini=$(private_flex_ini_get_array_name "$ini_identifier") - declare -gA "$ini" + # Use appropriate declare syntax based on bash version + if [ "$_SUPPORTS_G_FLAG" = "true" ]; then + declare -gA "$ini" + else + declare -A "$ini" + fi ini_associations["$ini_identifier"]="$ini_file" } # @private private_flex_ini_create @@ -310,12 +344,14 @@ flex_ini_save() { # file and attempt to re-assign ownership when file is saved if # the current user is different from the owner of the file if [ "$reassign_file_permissions_when_possible" == "true" ]; then - if [[ "$(uname)" == "Darwin" ]]; then + if [ "$_OS" == "macos" ]; then local file_owner=$(stat -f '%Su' "${destination_ini_path}") local file_group=$(stat -f '%Sg' "${destination_ini_path}") - else + elif [ "$_OS" == "linux" ]; then local file_owner=$(stat --format '%U' "${destination_ini_path}") local file_group=$(stat --format '%G' "${destination_ini_path}") + else + echo "[ FlexIni Warning ] File permission reassignment not supported on $_OS" fi local current_user=$(echo "$USER") if [ "$file_owner" != "$current_user" ]; then diff --git a/tests/src/test_flex_ini_save_macos_compatibility.sh b/tests/src/test_flex_ini_save_macos_compatibility.sh index aec89dc..01b400a 100644 --- a/tests/src/test_flex_ini_save_macos_compatibility.sh +++ b/tests/src/test_flex_ini_save_macos_compatibility.sh @@ -29,20 +29,22 @@ test_flex_ini_save_macos_compatibility() { local test_file="${test_storage_dir}/stat_test_$$" touch "$test_file" - if [[ "$(uname)" == "Darwin" ]]; then + if [ "$_OS" == "macos" ]; then # Test macOS stat commands local owner=$(stat -f '%Su' "$test_file") local group=$(stat -f '%Sg' "$test_file") echo "macOS stat: owner=$owner, group=$group" [[ -n "$owner" ]] || fail "macOS stat owner command failed" [[ -n "$group" ]] || fail "macOS stat group command failed" - else + elif [ "$_OS" == "linux" ]; then # Test Linux stat commands local owner=$(stat --format '%U' "$test_file") local group=$(stat --format '%G' "$test_file") echo "Linux stat: owner=$owner, group=$group" [[ -n "$owner" ]] || fail "Linux stat owner command failed" [[ -n "$group" ]] || fail "Linux stat group command failed" + else + echo "Unknown OS: $_OS - skipping stat command tests" fi # Clean up From f067b853915bb48cab76785926b64387cdc19ba4 Mon Sep 17 00:00:00 2001 From: Matt Blum Date: Wed, 4 Mar 2026 11:00:00 -0600 Subject: [PATCH 5/6] add bash version check for GitHub Actions - Add script to check bash version and declare support - Temporarily add to workflow to diagnose macOS bash issues --- .github/workflows/test.yml | 2 ++ check_bash_version.sh | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 check_bash_version.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cbc0fc2..f230429 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,6 +16,8 @@ jobs: steps: - name: Check out repository code uses: actions/checkout@v4 + - name: Check bash version + run: bash check_bash_version.sh - run: bash ./tests/_run.sh shell: bash - run: echo "🍏 This job's status is ${{ job.status }}." diff --git a/check_bash_version.sh b/check_bash_version.sh new file mode 100644 index 0000000..1b82577 --- /dev/null +++ b/check_bash_version.sh @@ -0,0 +1,32 @@ +#!/bin/bash +echo "=== Bash Version Check ===" +echo "BASH_VERSION: $BASH_VERSION" +echo "Shell: $0" +echo "uname: $(uname)" +echo "bash --version:" +bash --version | head -3 +echo "" +echo "=== Testing declare flags ===" +echo "Testing declare -g:" +if declare -g test_var 2>/dev/null; then + echo "✓ declare -g works" + unset test_var +else + echo "✗ declare -g not supported" +fi + +echo "Testing declare -A:" +if declare -A test_array 2>/dev/null; then + echo "✓ declare -A works" + unset test_array +else + echo "✗ declare -A not supported" +fi + +echo "Testing declare -gA:" +if declare -gA test_garray 2>/dev/null; then + echo "✓ declare -gA works" + unset test_garray +else + echo "✗ declare -gA not supported" +fi From 84f49a3535b3744eeedd057acd8602552adfabe4 Mon Sep 17 00:00:00 2001 From: Matt Blum Date: Wed, 4 Mar 2026 11:02:44 -0600 Subject: [PATCH 6/6] require bash 4.0+ with clear error message - Add bash version check using BASH_VERSINFO - Provide helpful error message with brew install instructions for macOS - Install newer bash via brew in GitHub Actions for macOS - Simplify code by removing conditional declare logic --- .github/workflows/test.yml | 14 ++++++++++---- check_bash_version.sh | 32 -------------------------------- flex_ini.sh | 35 +++++++++-------------------------- 3 files changed, 19 insertions(+), 62 deletions(-) delete mode 100644 check_bash_version.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f230429..d9c2ad3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,8 +16,14 @@ jobs: steps: - name: Check out repository code uses: actions/checkout@v4 - - name: Check bash version - run: bash check_bash_version.sh - - run: bash ./tests/_run.sh - shell: bash + - name: Install newer bash on macOS + if: matrix.os == 'macos-latest' + run: brew install bash + - name: Run tests + run: | + if [[ "$(uname)" == "Darwin" ]]; then + /opt/homebrew/bin/bash ./tests/_run.sh + else + bash ./tests/_run.sh + fi - run: echo "🍏 This job's status is ${{ job.status }}." diff --git a/check_bash_version.sh b/check_bash_version.sh deleted file mode 100644 index 1b82577..0000000 --- a/check_bash_version.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -echo "=== Bash Version Check ===" -echo "BASH_VERSION: $BASH_VERSION" -echo "Shell: $0" -echo "uname: $(uname)" -echo "bash --version:" -bash --version | head -3 -echo "" -echo "=== Testing declare flags ===" -echo "Testing declare -g:" -if declare -g test_var 2>/dev/null; then - echo "✓ declare -g works" - unset test_var -else - echo "✗ declare -g not supported" -fi - -echo "Testing declare -A:" -if declare -A test_array 2>/dev/null; then - echo "✓ declare -A works" - unset test_array -else - echo "✗ declare -A not supported" -fi - -echo "Testing declare -gA:" -if declare -gA test_garray 2>/dev/null; then - echo "✓ declare -gA works" - unset test_garray -else - echo "✗ declare -gA not supported" -fi diff --git a/flex_ini.sh b/flex_ini.sh index e86a1f0..8142e89 100644 --- a/flex_ini.sh +++ b/flex_ini.sh @@ -18,29 +18,17 @@ else _OS="unknown" fi -# Check bash version and use appropriate declare syntax -_BASH_VERSION=$(bash --version | head -1 | grep -oE '[0-9]+\.[0-9]+' | head -1) -if [[ "${_BASH_VERSION%%.*}" -ge 4 ]]; then - # Bash 4.0+ supports associative arrays - if declare -g _test 2>/dev/null; then - # Bash 4.2+ supports -g flag - declare -gA ini_associations - declare -gA ini_unsaved_changes - declare -gA ini_loaded - _SUPPORTS_G_FLAG=true - else - # Bash 4.0-4.1 doesn't support -g flag - declare -A ini_associations - declare -A ini_unsaved_changes - declare -A ini_loaded - _SUPPORTS_G_FLAG=false - fi -else - # Bash 3.x doesn't support associative arrays at all +# Require bash 4.0+ for associative arrays +if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then echo "Error: FlexIni requires bash 4.0 or higher for associative arrays" - echo "Current bash version: $_BASH_VERSION" + echo "Current bash version: ${BASH_VERSION}" + echo "On macOS, install newer bash with: brew install bash" exit 1 fi + +declare -gA ini_associations +declare -gA ini_unsaved_changes +declare -gA ini_loaded # Private Functions # -- # It's best to not call/modify these directly from your codebase @@ -149,12 +137,7 @@ private_flex_ini_init() { local ini_file="$1" local ini_identifier=$(private_flex_ini_format_id "$2") local ini=$(private_flex_ini_get_array_name "$ini_identifier") - # Use appropriate declare syntax based on bash version - if [ "$_SUPPORTS_G_FLAG" = "true" ]; then - declare -gA "$ini" - else - declare -A "$ini" - fi + declare -gA "$ini" ini_associations["$ini_identifier"]="$ini_file" } # @private private_flex_ini_create