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
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,12 @@ import "$base_bash_libs_dir/lib/bash/str/lib_str.sh"

After `lib_std.sh` is sourced, `BASE_BASH_LIBS_VERSION` contains the package
version from the repository/package `VERSION` file. Downstream scripts can use
that readonly constant when they need to require a minimum library version.
that readonly constant when they need to display the loaded library version.
Use `base_bash_libs_require_version` to require a minimum library version:

```bash
base_bash_libs_require_version 1.1.0
```

See `examples/std-usage.sh` for a small standalone script that sources the
stdlib, imports the file helpers, logs progress, and runs a checked command.
Expand Down
13 changes: 13 additions & 0 deletions lib/bash/std/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,19 @@ When a Base wrapper preloads the stdlib for another command, it can set
`BASE_BASH_BOOTSTRAP_SOURCE` so `__SCRIPT_DIR__` still points at the command
script rather than the wrapper.

## Version Requirements

Use `base_bash_libs_require_version` when a downstream script depends on APIs
added after the first public release:

```bash
base_bash_libs_require_version 1.1.0
```

The helper compares dotted numeric versions, returns silently when the loaded
library is new enough, and exits with a clear fatal error when the loaded
`BASE_BASH_LIBS_VERSION` is too old.

## Logging

Use structured logging for operational messages:
Expand Down
60 changes: 60 additions & 0 deletions lib/bash/std/lib_std.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
# std_command_path var cmd # Resolve an external command path without exiting.
# std_function_exists fn # Predicate for defined Bash functions.
# assert_variable_name name # Validate Bash variable-name arguments.
# base_bash_libs_require_version min_version
# # Exit clearly if the loaded library is too old.
# add_to_path [-n] [-p] dir # Append/prepend unique PATH entries.
# set_log_level [LEVEL] # Adjust default logger (FATAL..VERBOSE).
# log_info/debug/... msgs # Structured logging (color in interactive shells).
Expand Down Expand Up @@ -121,6 +123,64 @@ BASE_BASH_LIBS_VERSION="$(__lib_std_read_package_version__ "$__BASE_BASH_LIBS_RO
readonly BASE_BASH_LIBS_VERSION
unset -f __lib_std_read_package_version__

__base_bash_libs_is_dotted_numeric_version__() {
local version="${1-}" version_re='^[0-9]+([.][0-9]+)*$'
[[ "$version" =~ $version_re ]]
}

__base_bash_libs_version_at_least__() {
local actual_version="$1" minimum_version="$2"
local -a actual_parts=() minimum_parts=()
local index max_parts actual_part minimum_part actual_number minimum_number

IFS=. read -r -a actual_parts <<<"$actual_version"
IFS=. read -r -a minimum_parts <<<"$minimum_version"

max_parts="${#actual_parts[@]}"
if ((${#minimum_parts[@]} > max_parts)); then
max_parts="${#minimum_parts[@]}"
fi

for ((index = 0; index < max_parts; index++)); do
actual_part="${actual_parts[$index]:-0}"
minimum_part="${minimum_parts[$index]:-0}"
actual_number=$((10#$actual_part))
minimum_number=$((10#$minimum_part))

if ((actual_number > minimum_number)); then
return 0
fi
if ((actual_number < minimum_number)); then
return 1
fi
done

return 0
}

#
# base_bash_libs_require_version - Requires a minimum base-bash-libs version.
#
# Usage:
# base_bash_libs_require_version 1.1.0
#
base_bash_libs_require_version() {
local minimum_version="${1-}"

assert_arg_count "$#" 1

if ! __base_bash_libs_is_dotted_numeric_version__ "$minimum_version" ||
! __base_bash_libs_is_dotted_numeric_version__ "$BASE_BASH_LIBS_VERSION"; then
fatal_error "base_bash_libs_require_version expects dotted numeric versions."
fi

if ! __base_bash_libs_version_at_least__ "$BASE_BASH_LIBS_VERSION" "$minimum_version"; then
fatal_error "base-bash-libs $minimum_version or newer is required; loaded version is $BASE_BASH_LIBS_VERSION."
fi

return 0
}

#
# Memorize the original script arguments at the very beginning.
# This allows the library to parse global options before the main script does.
Expand Down
40 changes: 40 additions & 0 deletions lib/bash/std/tests/lib_std.bats
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,46 @@ EOF
readonly -p BASE_BASH_LIBS_VERSION >/dev/null
}

@test "base_bash_libs_require_version accepts the loaded version and older versions" {
local stdout_file="$TEST_TMPDIR/version-check.out"

base_bash_libs_require_version "$BASE_BASH_LIBS_VERSION" >"$stdout_file"
base_bash_libs_require_version "0.1.0" >>"$stdout_file"

[ ! -s "$stdout_file" ]
}

@test "base_bash_libs_require_version exits when the loaded version is too old" {
local script="$TEST_TMPDIR/version-too-old.sh"

create_script "$script" <<EOF
#!/usr/bin/env bash
source "$STDLIB_PATH"
base_bash_libs_require_version "999.0.0"
EOF

bats_run bash "$script"

[ "$status" -ne 0 ]
[[ "$output" == *"base-bash-libs 999.0.0 or newer is required"* ]]
[[ "$output" == *"loaded version is $BASE_BASH_LIBS_VERSION"* ]]
}

@test "base_bash_libs_require_version exits for invalid version strings" {
local script="$TEST_TMPDIR/version-invalid.sh"

create_script "$script" <<EOF
#!/usr/bin/env bash
source "$STDLIB_PATH"
base_bash_libs_require_version "1.two.0"
EOF

bats_run bash "$script"

[ "$status" -ne 0 ]
[[ "$output" == *"base_bash_libs_require_version expects dotted numeric versions"* ]]
}

@test "stdlib exposes readonly loaded marker" {
[ "${BASE_BASH_LIBS_STDLIB_LOADED:-}" = "1" ]
readonly -p BASE_BASH_LIBS_STDLIB_LOADED >/dev/null
Expand Down
Loading