diff --git a/docs/why-base-bash-libs.md b/docs/why-base-bash-libs.md index 3ead281..e0e2d27 100644 --- a/docs/why-base-bash-libs.md +++ b/docs/why-base-bash-libs.md @@ -19,6 +19,86 @@ Beacon is useful evidence because it consumes only the public API committed in | Repeat checked directory, file, and command handling | `base_std_safe_mkdir`, `base_std_safe_truncate`, and `base_std_run` provide consistent failure behavior | Bundle layout, manifest format, and domain-specific diagnostics | | Display the dependency that actually ran | `base_require_version` and the immutable `BASE_BASH_LIBS_VERSION`, `BASE_BASH_LIBS_COMMIT`, `BASE_BASH_LIBS_DIRTY_STATE`, and `BASE_BASH_LIBS_PROVENANCE` values expose package identity | Beacon's independent application version and release lifecycle | +## Code-level before and after + +The following examples are illustrative, not Beacon's history. They show the +kind of plumbing a small Bash application would otherwise have to maintain. +The `before` version is intentionally short but runnable; the `after` version +uses the same public contracts that Beacon exercises. + + +### Command parsing and dispatch + +Before, an application has to keep option parsing, usage text, validation, and +dispatch consistent by hand: + +```bash +set -eu + +usage() { + printf 'usage: %s [-o PATH] {status|collect}\n' "${0##*/}" +} + +output='' +while getopts ':o:h' option; do + case "$option" in + o) output="$OPTARG" ;; + h) usage; exit 0 ;; + :) printf 'missing value for -%s\n' "$OPTARG" >&2; usage >&2; exit 2 ;; + \?) printf 'unknown option: -%s\n' "$OPTARG" >&2; usage >&2; exit 2 ;; + esac +done +shift "$((OPTIND - 1))" + +case "${1-}" in + status) printf 'status=ready\n' ;; + collect) printf 'collecting=%s\n' "${output:-default}" ;; + *) usage >&2; exit 2 ;; +esac +``` + +After, the CLI contract generates help, option validation, and dispatch from +one declaration boundary: + +```bash +base_cli_model_init beacon name=beacon version=0.1.0 \ + description="Offline support-bundle collector" handler=beacon_dispatch +base_cli_command beacon status "Show fixture and framework readiness" handler=beacon_dispatch +base_cli_command beacon collect "Create a redacted support bundle" handler=beacon_dispatch +base_cli_option beacon "" output value --output help="Support bundle directory" metavar=PATH +``` + + + +### Temporary-directory cleanup + +Before, an application owns a trap and must preserve it when another cleanup +path or signal handler is added later: + +```bash +stage="$(mktemp -d)" +cleanup_stage() { + rm -rf -- "$stage" +} +trap cleanup_stage EXIT +printf 'staging=%s\n' "$stage" +``` + +After, Base Bash composes cleanup ownership and lets the application transfer a +published path explicitly: + +```bash +base_std_make_temp_dir stage beacon-demo +printf 'staging=%s\n' "$stage" +base_std_unregister_cleanup_path "$stage" +``` + +The lifecycle contract is executable evidence, not just prose: +`bats --filter 'cleanup evidence failure' tests/lifecycle.bats` demonstrates +that a cleanup-log failure does not replace the application's successful exit +status. + + For example, `beacon_collect` asks `base_std_make_temp_dir` for a staging directory. The framework registers that path with its composed cleanup lifecycle. Beacon can return from any later failure without adding a second diff --git a/tests/docs-before-after.sh b/tests/docs-before-after.sh new file mode 100755 index 0000000..fe0eb76 --- /dev/null +++ b/tests/docs-before-after.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +set -euo pipefail + +docs_before_after_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P)" +docs_before_after_source="$docs_before_after_root/docs/why-base-bash-libs.md" +docs_before_after_tmp="$(mktemp -d "${TMPDIR:-/tmp}/beacon-before-after.XXXXXX")" + +docs_before_after_cleanup() { + rm -rf -- "$docs_before_after_tmp" +} +trap docs_before_after_cleanup EXIT + +docs_before_after_extract() { + local marker_start="$1" marker_end="$2" destination="$3" + awk -v marker_start="$marker_start" -v marker_end="$marker_end" ' + $0 == marker_start { in_region = 1; next } + $0 == marker_end { exit } + in_region && /^```bash$/ { in_code = 1; next } + in_region && /^```$/ { in_code = 0; next } + in_region && in_code { print } + ' "$docs_before_after_source" > "$destination" + [[ -s "$destination" ]] + /usr/bin/env bash -n "$destination" + shellcheck --shell=bash "$destination" +} + +docs_before_after_extract \ + '' \ + '' \ + "$docs_before_after_tmp/cli.sh" +docs_before_after_extract \ + '' \ + '' \ + "$docs_before_after_tmp/cleanup.sh" + +printf 'Before-and-after documentation examples passed ShellCheck.\n' diff --git a/tests/docs-contracts.sh b/tests/docs-contracts.sh index ab7804b..8ac6333 100755 --- a/tests/docs-contracts.sh +++ b/tests/docs-contracts.sh @@ -19,6 +19,10 @@ grep -Fq '[adoption decision guide](should-i-use-base-bash-libs.md)' "$docs_cont grep -Fq '[five-minute Beacon tutorial](five-minute-tutorial.md)' "$docs_contract_decision" grep -Fq '[Beacon documentation and recommended reading path](docs/README.md)' "$docs_contract_readme" grep -Fq '[use Base Bash in your project](docs/use-in-your-project.md)' "$docs_contract_readme" +grep -Fq '## Code-level before and after' "$docs_contract_why" +grep -Fq 'BEGIN BEFORE AFTER CLI' "$docs_contract_why" +grep -Fq 'BEGIN BEFORE AFTER CLEANUP' "$docs_contract_why" +grep -Fq "bats --filter 'cleanup evidence failure' tests/lifecycle.bats" "$docs_contract_why" docs_contract_expected_example="$( sed -n '/^$/,/^$/p' \ diff --git a/tests/docs-examples.sh b/tests/docs-examples.sh index 164e8b8..e10e5d0 100755 --- a/tests/docs-examples.sh +++ b/tests/docs-examples.sh @@ -31,4 +31,6 @@ shellcheck --shell=bash "$example_script" /usr/bin/env bash "$example_script" ) +/usr/bin/env bash "$docs_root/tests/docs-before-after.sh" + printf 'Runnable documentation examples passed.\n' diff --git a/tests/validate.sh b/tests/validate.sh index 5cf11c1..234f6b8 100755 --- a/tests/validate.sh +++ b/tests/validate.sh @@ -98,7 +98,7 @@ command -v shellcheck > /dev/null 2>&1 || { printf 'shellcheck is required.\n' >&2 exit 1 } -shellcheck bin/beacon examples/minimal-cli lib/beacon.sh scripts/release-artifact scripts/verify-vendor scripts/resolve-framework-ref tests/validate.sh tests/bash-42-smoke.sh tests/candidate-smoke.sh tests/docs-examples.sh tests/docs-contracts.sh tests/external-consumer-walkthrough.sh tests/minimal-consumer.sh tests/release-artifact.sh || exit $? +shellcheck bin/beacon examples/minimal-cli lib/beacon.sh scripts/release-artifact scripts/verify-vendor scripts/resolve-framework-ref tests/validate.sh tests/bash-42-smoke.sh tests/candidate-smoke.sh tests/docs-examples.sh tests/docs-before-after.sh tests/docs-contracts.sh tests/external-consumer-walkthrough.sh tests/minimal-consumer.sh tests/release-artifact.sh || exit $? ./tests/docs-examples.sh || exit $? ./tests/docs-contracts.sh || exit $?