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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ and versions are tracked in the repo-root `VERSION` file.

### Changed

- Made `lib_std.sh` sourcing passive and introduced the explicit, idempotent
`base_bash_libs_init` lifecycle API. Wrapper flags now return through a
caller-owned array without hidden positional-parameter mutation; launchers,
examples, and companion-library tests initialize explicitly.
- Made timed foreground-TTY invocations fail closed with a safe diagnostic;
callers must provide a pipe or explicit non-terminal stdin for the v2 hard
descendant guarantee.
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ Source the installed stdlib from the Homebrew prefix:
```bash
base_bash_libs_prefix="$(brew --prefix basefoundry/base/base-bash-libs)"
source "$base_bash_libs_prefix/libexec/lib/bash/std/lib_std.sh"
declare -a app_args=()
base_bash_libs_init app_args --source "${BASH_SOURCE[0]}" -- "$@"
printf 'base-bash-libs version: %s\n' "$BASE_BASH_LIBS_VERSION"
```

Expand Down Expand Up @@ -115,6 +117,8 @@ Source the stdlib from that checkout:
```bash
base_bash_libs_dir="$PWD/vendor/base-bash-libs"
source "$base_bash_libs_dir/lib/bash/std/lib_std.sh"
declare -a app_args=()
base_bash_libs_init app_args --source "${BASH_SOURCE[0]}" -- "$@"
printf 'base-bash-libs version: %s\n' "$BASE_BASH_LIBS_VERSION"
```

Expand Down Expand Up @@ -145,6 +149,8 @@ project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
base_bash_libs_dir="$project_root/vendor/base-bash-libs"

source "$base_bash_libs_dir/lib/bash/std/lib_std.sh"
declare -a app_args=()
base_bash_libs_init app_args --source "${BASH_SOURCE[0]}" -- "$@"
import "$base_bash_libs_dir/lib/bash/file/lib_file.sh"
import "$base_bash_libs_dir/lib/bash/git/lib_git.sh"
import "$base_bash_libs_dir/lib/bash/gh/lib_gh.sh"
Expand Down
37 changes: 4 additions & 33 deletions bin/base-bash
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#
# Responsibilities:
# - run a Bash script with lib/bash/std/lib_std.sh preloaded
# - preserve stdlib wrapper-flag behavior for the target script
# - explicitly initialize stdlib runtime state and filter wrapper flags
# - provide package-relative imports without establishing Base runtime state
#

Expand Down Expand Up @@ -127,36 +127,9 @@ base_bash_resolve_lib_dir() {
}

base_bash_source_stdlib() {
local script_path="$1"
local stdlib_path="$BASE_BASH_LIBS_DIR/std/lib_std.sh"
shift

# Consumed by lib_std.sh while it is being sourced below.
# shellcheck disable=SC2034
BASE_BASH_BOOTSTRAP_SOURCE="$script_path"
# shellcheck source=/dev/null
source "$stdlib_path" "$@" || exit $?
unset BASE_BASH_BOOTSTRAP_SOURCE
}

base_bash_filter_runtime_args() {
local parse_options=1

base_bash_runtime_args=()

while (($#)); do
if ((parse_options)) && [[ "$1" == "--" ]]; then
base_bash_runtime_args+=("$1")
parse_options=0
# Keep consuming the deprecated --verbose-wrapper compatibility flag
# during the 1.x window so it never leaks into command argv.
elif ((parse_options)) && [[ "$1" == --debug-wrapper || "$1" == --verbose-wrapper || "$1" == --utc-wrapper || "$1" == --color ]]; then
:
else
base_bash_runtime_args+=("$1")
fi
shift
done
source "$stdlib_path" || exit $?
}

import_base_bash_lib() {
Expand All @@ -183,17 +156,15 @@ base_bash_run_script() {
local script_path="$1"
local package_root="$2"
local -a runtime_args=()
local -a base_bash_runtime_args=()
shift 2

[[ -f "$script_path" ]] || base_bash_die "Script '$script_path' was not found."

BASE_BASH_LIBS_DIR="$(base_bash_resolve_lib_dir "$package_root")" || exit $?
export BASE_BASH_LIBS_DIR

base_bash_filter_runtime_args "$@"
base_bash_source_stdlib "$script_path" "$@"
runtime_args=("${base_bash_runtime_args[@]}")
base_bash_source_stdlib
base_bash_libs_init runtime_args --source "$script_path" -- "$@" || exit $?

unset -f main

Expand Down
3 changes: 3 additions & 0 deletions examples/cookbook-args-lists-strings.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" || exit 1
# shellcheck source=/dev/null
source "$repo_root/lib/bash/std/lib_std.sh"

declare -a app_args=()
base_bash_libs_init app_args --source "${BASH_SOURCE[0]}" -- "$@"

import "$repo_root/lib/bash/arg/lib_arg.sh"
import "$repo_root/lib/bash/list/lib_list.sh"
import "$repo_root/lib/bash/str/lib_str.sh"
Expand Down
3 changes: 3 additions & 0 deletions examples/cookbook-cleanup-temp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" || exit 1
# shellcheck source=/dev/null
source "$repo_root/lib/bash/std/lib_std.sh"

declare -a app_args=()
base_bash_libs_init app_args --source "${BASH_SOURCE[0]}" -- "$@"

base_bash_libs_require_version 1.0.0

workspace_dir=""
Expand Down
3 changes: 3 additions & 0 deletions examples/std-usage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" || exit 1
# shellcheck source=/dev/null
source "$repo_root/lib/bash/std/lib_std.sh"

declare -a app_args=()
base_bash_libs_init app_args --source "${BASH_SOURCE[0]}" -- "$@"

import "$repo_root/lib/bash/file/lib_file.sh"

example_file="${TMPDIR:-/tmp}/base-bash-libs-example.$$"
Expand Down
11 changes: 6 additions & 5 deletions lib/bash/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ modules.
## Caller Runtime Contract

All public modules support Bash 4.2 or newer with every combination of caller-
selected `errexit`, `nounset`, and `pipefail`. Sourcing a module does not change
those settings, any other `set` or `shopt` option, `IFS`, `OPTIND`, the working
directory, the umask, traps, or ordinary positional arguments. The stdlib's
documented wrapper flags are the exception: its initializer removes recognized
wrapper flags and publishes the filtered positional arguments.
selected `errexit`, `nounset`, and `pipefail`. Sourcing a module is passive: it
does not change those settings, any other `set` or `shopt` option, `IFS`,
`OPTIND`, the working directory, the umask, traps, exports, or ordinary
positional arguments. After sourcing `lib_std.sh`, callers explicitly invoke
`base_bash_libs_init` to initialize runtime state and receive wrapper-filtered
arguments in a caller-owned array.

Public API calls preserve the same process state unless their documented
purpose is to change it. Examples of intentional mutation include PATH helpers,
Expand Down
2 changes: 2 additions & 0 deletions lib/bash/arg/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Aliasing is rejected before any caller-owned output is changed.

```bash
source "/absolute/path/to/lib/bash/std/lib_std.sh"
declare -a app_args=()
base_bash_libs_init app_args --source "${BASH_SOURCE[0]}" --
source "/absolute/path/to/lib/bash/arg/lib_arg.sh"

declare -A options=()
Expand Down
13 changes: 12 additions & 1 deletion lib/bash/arg/tests/lib_arg.bats
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,21 @@ load ../../tests/test_helper.sh
setup() {
setup_test_tmpdir
source "$BASE_BASH_DIR/std/lib_std.sh"
declare -a setup_args=()
base_bash_libs_init setup_args --source "$BASE_BASH_DIR/arg/tests/lib_arg.bats" --
source "$BASE_BASH_DIR/arg/lib_arg.sh"
}

create_script() {
local script_path="$1"
cat > "$script_path"
local content source_line init_lines
content="$(cat)"
source_line="source \"$BASE_BASH_DIR/std/lib_std.sh\""
if [[ "$content" == *"$source_line"* ]]; then
init_lines=$'declare -a base_bash_libs_test_args=()\nbase_bash_libs_init base_bash_libs_test_args -- "$@"\nset -- "${base_bash_libs_test_args[@]}"'
content="${content/"$source_line"/"$source_line"$'\n'"$init_lines"}"
fi
printf '%s\n' "$content" > "$script_path"
chmod +x "$script_path"
}

Expand Down Expand Up @@ -47,6 +56,8 @@ create_script() {
case "$mode" in *u*) set -u ;; esac
case "$mode" in *p*) set -o pipefail ;; esac
source "$2"
declare -a app_args=()
base_bash_libs_init app_args --
source "$3"
arg_parse
exit $?
Expand Down
2 changes: 2 additions & 0 deletions lib/bash/file/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ settings; they do not impose a strict-mode policy on the calling script.

```bash
source "/absolute/path/to/lib/bash/std/lib_std.sh"
declare -a app_args=()
base_bash_libs_init app_args --source "${BASH_SOURCE[0]}" --
source "/absolute/path/to/lib/bash/file/lib_file.sh"

update_file_section ~/.bash_profile "# BEGIN APP" "# END APP" \
Expand Down
10 changes: 10 additions & 0 deletions lib/bash/file/tests/lib_file.bats
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ load ../../tests/test_helper.sh
setup() {
setup_test_tmpdir
source "$BASE_BASH_DIR/std/lib_std.sh"
declare -a setup_args=()
base_bash_libs_init setup_args --source "$BASE_BASH_DIR/file/tests/lib_file.bats" --
source "$BASE_BASH_DIR/file/lib_file.sh"
}

Expand Down Expand Up @@ -39,6 +41,8 @@ file_mode() {
cat > "$script" <<EOF
#!/usr/bin/env bash
source "$BASE_BASH_DIR/std/lib_std.sh"
declare -a app_args=()
base_bash_libs_init app_args --source "\${BASH_SOURCE[0]}" -- "\$@"
source "$BASE_BASH_DIR/file/lib_file.sh"
printf 'line-one' > "\$1"
update_file_section "\$1" "# BEGIN" "# END" "first"
Expand Down Expand Up @@ -103,6 +107,8 @@ EOF
#!/usr/bin/env bash
set -euo pipefail
source "$BASE_BASH_DIR/std/lib_std.sh"
declare -a app_args=()
base_bash_libs_init app_args --source "\${BASH_SOURCE[0]}" -- "\$@"
source "$BASE_BASH_DIR/file/lib_file.sh"
update_file_section
printf 'after\n'
Expand All @@ -127,6 +133,8 @@ EOF
#!/usr/bin/env bash
set -euo pipefail
source "$BASE_BASH_DIR/std/lib_std.sh"
declare -a app_args=()
base_bash_libs_init app_args --source "\${BASH_SOURCE[0]}" -- "\$@"
source "$BASE_BASH_DIR/file/lib_file.sh"
update_file_section "\$1" "# BEGIN" "# END"
printf 'strict=preserved\n'
Expand Down Expand Up @@ -307,6 +315,8 @@ EOF
cat > "$script" <<EOF
#!/usr/bin/env bash
source "$BASE_BASH_DIR/std/lib_std.sh"
declare -a app_args=()
base_bash_libs_init app_args --source "\${BASH_SOURCE[0]}" -- "\$@"
source "$BASE_BASH_DIR/file/lib_file.sh"
trap 'printf "caller\n" >> "$log_file"' EXIT
before_trap="\$(trap -p EXIT)"
Expand Down
2 changes: 2 additions & 0 deletions lib/bash/gh/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Source the stdlib before this library:

```bash
source "/path/to/base-bash-libs/lib/bash/std/lib_std.sh"
declare -a app_args=()
base_bash_libs_init app_args --source "${BASH_SOURCE[0]}" --
import "/path/to/base-bash-libs/lib/bash/gh/lib_gh.sh"
```

Expand Down
22 changes: 22 additions & 0 deletions lib/bash/gh/tests/lib_gh.bats
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ setup() {
mkdir -p "$TEST_TMPDIR/bin"
PATH="$TEST_TMPDIR/bin:$BASE_TEST_ORIG_PATH"
source "$BASE_BASH_DIR/std/lib_std.sh"
declare -a setup_args=()
base_bash_libs_init setup_args --source "$BASE_BASH_DIR/gh/tests/lib_gh.bats" --
source "$BASE_BASH_DIR/gh/lib_gh.sh"
}

Expand Down Expand Up @@ -163,6 +165,8 @@ gh_api_retry_observed() {
case "$mode" in *u*) set -u ;; esac
case "$mode" in *p*) set -o pipefail ;; esac
source "$2"
declare -a app_args=()
base_bash_libs_init app_args --
source "$3"
"$4"
rc=$?
Expand Down Expand Up @@ -301,6 +305,8 @@ EOF
case "$mode" in *u*) set -u ;; esac
case "$mode" in *p*) set -o pipefail ;; esac
source "$2"
declare -a app_args=()
base_bash_libs_init app_args --
source "$3"
PATH="$4:$PATH"
gh_run issue list
Expand All @@ -318,6 +324,8 @@ EOF
case "$mode" in *u*) set -u ;; esac
case "$mode" in *p*) set -o pipefail ;; esac
source "$2"
declare -a app_args=()
base_bash_libs_init app_args --
source "$3"
PATH="$4:$PATH"
gh_run --sensitive --safe-display "strict protected operation" -- issue list
Expand Down Expand Up @@ -402,6 +410,8 @@ EOF

bats_run "$BASH" -c '
source "$1"
declare -a app_args=()
base_bash_libs_init app_args --
source "$2"
PATH="$3"
gh_require_cli "$4"
Expand Down Expand Up @@ -572,6 +582,8 @@ EOF
#!/usr/bin/env bash
set -euo pipefail
source "$BASE_BASH_DIR/std/lib_std.sh"
declare -a app_args=()
base_bash_libs_init app_args --source "\${BASH_SOURCE[0]}" -- "\$@"
source "$BASE_BASH_DIR/gh/lib_gh.sh"
PATH="$TEST_TMPDIR/bin:$BASE_TEST_ORIG_PATH"
gh_run issue create --title Example
Expand Down Expand Up @@ -610,6 +622,8 @@ EOF

bats_run "$BASH" -c '
source "$1"
declare -a app_args=()
base_bash_libs_init app_args --
source "$2"
PATH="$3"
gh_run issue list
Expand Down Expand Up @@ -1711,6 +1725,8 @@ EOF
cat > "$script" <<'EOF'
#!/usr/bin/env bash
source "$1"
declare -a app_args=()
base_bash_libs_init app_args --
source "$2"
TMPDIR="$3"
STATUS_FILE="$4"
Expand Down Expand Up @@ -1753,6 +1769,8 @@ EOF
#!/usr/bin/env bash
set -u
source "$1"
declare -a app_args=()
base_bash_libs_init app_args --
source "$2"
TMPDIR="$3"
TRAP_MARKER="$4"
Expand Down Expand Up @@ -1904,6 +1922,8 @@ EOF
#!/usr/bin/env bash
set -e
source "$BASE_BASH_DIR/std/lib_std.sh"
declare -a app_args=()
base_bash_libs_init app_args --source "\${BASH_SOURCE[0]}" -- "\$@"
source "$BASE_BASH_DIR/gh/lib_gh.sh"
PATH="$TEST_TMPDIR/bin:$PATH"
gh_api_with_retry repos/owner/missing
Expand Down Expand Up @@ -1931,6 +1951,8 @@ EOF

bats_run "$BASH" -c '
source "$1"
declare -a app_args=()
base_bash_libs_init app_args --
source "$2"
set -C
value="$(gh_api_with_retry --max-attempts 1 -- repos/owner/repo)" || exit $?
Expand Down
2 changes: 2 additions & 0 deletions lib/bash/git/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ helpers manage branch selection, retries, and cleanup.

```bash
source "/absolute/path/to/lib/bash/std/lib_std.sh"
declare -a app_args=()
base_bash_libs_init app_args --source "${BASH_SOURCE[0]}" --
source "/absolute/path/to/lib/bash/git/lib_git.sh"

branch=""
Expand Down
Loading
Loading