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
2 changes: 1 addition & 1 deletion base_api_manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ modules:
dependencies: std
optional_commands: none
artifacts: lib/bash/cli/lib_cli.sh,lib/bash/cli/README.md,lib/bash/cli/tests/lib_cli.bats
public_symbols: base_cli_command,base_cli_complete,base_cli_completion_script,base_cli_help,base_cli_model_init,base_cli_option,base_cli_parse,base_cli_positional,base_cli_result_count,base_cli_result_get,base_cli_result_get_positional,base_cli_run
public_symbols: base_cli_command,base_cli_complete,base_cli_completion_script,base_cli_help,base_cli_model_init,base_cli_option,base_cli_parse,base_cli_positional,base_cli_result_count,base_cli_result_get,base_cli_result_get_positional,base_cli_run,base_cli_validate_model
signature_source: lib/bash/cli/README.md
inputs: documented per symbol in the module README and API charter
outputs: documented per symbol; parsed results use BASE_BASH_LIBS_CLI_RESULT_* globals and named result variables
Expand Down
1 change: 1 addition & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ statuses, and side effects are normative in the linked module README and
- `base_cli_result_get` — signature: see [`lib/bash/cli/README.md`](../lib/bash/cli/README.md).
- `base_cli_result_get_positional` — signature: see [`lib/bash/cli/README.md`](../lib/bash/cli/README.md).
- `base_cli_run` — signature: see [`lib/bash/cli/README.md`](../lib/bash/cli/README.md).
- `base_cli_validate_model` — signature: see [`lib/bash/cli/README.md`](../lib/bash/cli/README.md).

### `app`

Expand Down
2 changes: 1 addition & 1 deletion docs/v2-api-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ signature/effects reference; this table makes coverage auditable.
| str | `base_str_lower`, `base_str_upper`, `base_str_ltrim`, `base_str_rtrim`, `base_str_trim`, `base_str_contains`, `base_str_starts_with`, `base_str_ends_with`, `base_str_split`, `base_str_join` | String transforms/predicates preserve caller values until validation succeeds; split/join use validated named outputs. |
| arg | `base_arg_parse` | Parses into caller-owned validated arrays/maps and leaves them unchanged on failure. |
| list | `base_list_append`, `base_list_prepend`, `base_list_remove`, `base_list_contains`, `base_list_unique`, `base_list_length` | Indexed-array mutators/predicates use caller-owned arrays; usage and operational errors return rather than exit. |
| cli | `base_cli_model_init`, `base_cli_command`, `base_cli_option`, `base_cli_positional`, `base_cli_help`, `base_cli_parse`, `base_cli_run`, `base_cli_complete`, `base_cli_completion_script`, `base_cli_result_get`, `base_cli_result_get_positional`, `base_cli_result_count` | A single declarative model drives nested parsing, aliases, defaults, required/enum/validator/conflict checks, deterministic help, and completion. Successful parses publish fixed `BASE_BASH_LIBS_CLI_RESULT_*` globals; usage and validation errors return status `2`. |
| cli | `base_cli_model_init`, `base_cli_validate_model`, `base_cli_command`, `base_cli_option`, `base_cli_positional`, `base_cli_help`, `base_cli_parse`, `base_cli_run`, `base_cli_complete`, `base_cli_completion_script`, `base_cli_result_get`, `base_cli_result_get_positional`, `base_cli_result_count` | A single declarative model drives nested parsing, aliases, defaults, required/enum/validator/conflict checks, deterministic help, and completion. `base_cli_validate_model` provides an explicit post-declaration handler-wiring check for tests and CI. Successful parses publish fixed `BASE_BASH_LIBS_CLI_RESULT_*` globals; usage and validation errors return status `2`. |
| app | `base_app_init`, `base_app_config_define`, `base_app_config_set_cli`, `base_app_config_load`, `base_app_config_get`, `base_app_config_provenance`, `base_app_config_report`, `base_app_add_standard_options`, `base_app_apply_standard_options`, `base_app_should_prompt`, `base_app_prompt`, `base_app_hook`, `base_app_run`, `base_app_status` | Optional typed configuration and lifecycle policy. Configuration is data-only with CLI > environment > project > user > default precedence; reports redact secrets. Hooks are named functions, LIFO, exactly-once, and preserve the application status. |
| launcher | `base_launcher_check_project`, `base_launcher_die`, `base_launcher_resolve_path`, `base_launcher_package_root`, `base_launcher_ensure_supported_bash`, `base_launcher_lib_dir_is_usable`, `base_launcher_resolve_lib_dir`, `base_launcher_source_stdlib`, `base_launcher_import_base_bash_lib`, `base_launcher_init`, `base_launcher_run_script`, `base_launcher_usage` | Entrypoint helpers may terminate only at the executable process boundary; path and usability helpers return status. `base_launcher_init` creates a deterministic minimal or standard scaffold and refuses divergent overwrites. `base_launcher_check_project` is non-mutating and emits human or JSON conformance records. `main` remains application-defined. |

Expand Down
2 changes: 2 additions & 0 deletions lib/bash/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ metadata in one source of truth. It is one sourceable file and requires
## Public API

- `base_cli_model_init MODEL [name=PROGRAM] [version=VERSION] [description=TEXT] [handler=FUNCTION]`
- `base_cli_validate_model MODEL` checks all declared handlers after the model
and application functions have been loaded; use it in tests or CI.
starts or replaces a model. `MODEL` is an in-process identifier and `name`
is the executable name shown in usage and completion output.
- `base_cli_command MODEL PATH DESCRIPTION [HANDLER] [aliases=A,B]` declares a
Expand Down
38 changes: 38 additions & 0 deletions lib/bash/cli/lib_cli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,44 @@ base_cli_model_init() {
return 0
}

# base_cli_validate_model - Verify that every declared command handler exists.
#
# Declaration remains order-independent: callers may declare a model before
# defining its handlers. Call this explicitly from tests or CI after all
# handlers have been loaded to fail early on wiring mistakes.
base_cli_validate_model() {
local model="${1-}" key path handler
local -a missing_handlers=()

(($# == 1)) || {
__base_bash_libs_cli_declaration_usage__ 'base_cli_validate_model: expected a model identifier.'
return 2
}
if ! __base_bash_libs_cli_valid_model__ "$model" || ! __base_bash_libs_cli_model_exists__ "$model"; then
__base_bash_libs_cli_declaration_usage__ "base_cli_validate_model: model '$model' is not initialized."
return 2
fi

handler="${__base_bash_libs_cli_models["$model|meta|handler"]-}"
if [[ -n "$handler" ]] && ! declare -F "$handler" > /dev/null 2>&1; then
missing_handlers+=("<root>:$handler")
fi
for key in "${!__base_bash_libs_cli_models[@]}"; do
[[ "$key" == "$model|command|handler|"* ]] || continue
path="${key#"$model|command|handler|"}"
handler="${__base_bash_libs_cli_models["$key"]-}"
[[ -n "$handler" ]] || continue
if ! declare -F "$handler" > /dev/null 2>&1; then
missing_handlers+=("$path:$handler")
fi
done
if ((${#missing_handlers[@]} > 0)); then
__base_bash_libs_cli_declaration_usage__ "base_cli_validate_model: handlers are not defined: ${missing_handlers[*]}"
return 2
fi
return 0
}

# base_cli_command - Adds a command path such as `admin/user` to a model.
# Usage: base_cli_command model path description [handler] [aliases=a,b]
base_cli_command() {
Expand Down
15 changes: 15 additions & 0 deletions lib/bash/cli/tests/lib_cli.bats
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,21 @@ declare_demo_model() {
[ "$handler_calls" -eq 1 ]
}

@test "explicit model validation catches undeclared handlers" {
base_cli_model_init validate name=validate handler=missing_root
base_cli_command validate run "Run" handler=missing_run

bats_run base_cli_validate_model validate

[ "$status" -eq 2 ]
[[ "$output" == *"<root>:missing_root"* ]]
[[ "$output" == *"run:missing_run"* ]]

missing_root() { :; }
missing_run() { :; }
base_cli_validate_model validate
}

@test "completion emits aliases and a self-contained completion adapter" {
declare_demo_model

Expand Down
Loading