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 @@ -24,7 +24,7 @@ environment:
- BASE_BASH_LIBS_LOG_DEBUG|caller-control|enable debug diagnostics
- BASE_BASH_LIBS_LOG_UTC|caller-control|render log timestamps in UTC
- BASE_BASH_LIBS_GIT_PULL_MAX_ATTEMPTS|caller-control|maximum git pull attempts
- NO_COLOR|caller-input|disable color output when set
- NO_COLOR|caller-input|disable automatic color output when set; explicit always mode overrides it
- TMPDIR|caller-input|temporary directory policy
- GH_TOKEN|caller-input|GitHub CLI authentication input
- TZ|caller-input|caller-selected timezone for diagnostics
Expand Down
3 changes: 2 additions & 1 deletion docs/v2-api-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ argv. This mapping is the v2 migration reference:
| `--debug-wrapper` | Enables DEBUG logging and is removed from application argv. |
| `--verbose-wrapper` | Preserves the deprecated VERBOSE compatibility level and is removed from application argv. |
| `--utc-wrapper` | Exports UTC logging for the initialized runtime and is removed from application argv. |
| `--color` | Requests terminal colors and is removed from application argv. |
| `--color` | Requests automatic terminal colors (stderr TTY and `NO_COLOR` unset) and is removed from application argv. A `--color MODE` pair remains available to the app's own parser. |
| `--color-mode MODE` | Selects `auto`, `always`, or `never` for wrappers that do not use the standard app-option model; removed from application argv and takes precedence over app-level `--color`. `always` overrides `NO_COLOR`. |
| `base_init --` | Stops wrapper parsing; the separator and all following values remain literal application argv. (The launcher's own script-selection `--` is not forwarded.) |

No other launcher option is implicitly forwarded. Applications that need an
Expand Down
10 changes: 7 additions & 3 deletions lib/bash/app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,13 @@ zero. No configuration value is evaluated as shell code.
`--verbose`, `--quiet`, `--color`, `--dry-run`, `--non-interactive`,
`--config`, and `--user-config` options to a declarative CLI model.
`--color` accepts `auto`, `always`, or `never`; the bare legacy launcher
`--color` flag remains supported when it is not followed by one of those
modes. `--quiet` sets the default logger threshold to `WARN`, while
`--verbose` sets it to `DEBUG`; the options are mutually exclusive.
`--color` flag remains supported, and wrappers can use `--color-mode MODE`
when they need an explicit policy independent of the app's own CLI model.
An explicit wrapper mode takes precedence over the app's `--color` selection.
`auto` colors only when stderr is a terminal and `NO_COLOR` is unset;
`always` forces ANSI colors even for captured stderr or a set `NO_COLOR`, and
`never` disables them. `--quiet` sets the default logger threshold to `WARN`,
while `--verbose` sets it to `DEBUG`; the options are mutually exclusive.
`base_app_apply_standard_options` publishes the parsed policy in
`BASE_BASH_LIBS_APP_*` globals and applies the logging/color policy.
Applications should call
Expand Down
32 changes: 12 additions & 20 deletions lib/bash/app/lib_app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -677,15 +677,16 @@ base_app_config_report() {

# base_app_add_standard_options - Opts a CLI model into common policy options.
base_app_add_standard_options() {
local cli_model="${1-}" path="${2-}"
local cli_model="${1-}" path="${2-}" color_modes

(($# == 2)) || {
__base_bash_libs_app_error__ 'base_app_add_standard_options: usage: base_app_add_standard_options CLI_MODEL COMMAND_PATH'
return 2
}
color_modes="$(__base_bash_libs_std_color_modes__)" || return $?
base_cli_option "$cli_model" "$path" verbose flag --verbose -v help='Enable verbose diagnostics' || return $?
base_cli_option "$cli_model" "$path" quiet flag --quiet -q conflicts=verbose help='Suppress informational output' || return $?
base_cli_option "$cli_model" "$path" color value --color default=auto enum=auto,always,never metavar=MODE help='Color policy' || return $?
base_cli_option "$cli_model" "$path" color value --color default=auto enum="$color_modes" metavar=MODE help='Color policy' || return $?
base_cli_option "$cli_model" "$path" dry_run flag --dry-run help='Plan without mutating' || return $?
base_cli_option "$cli_model" "$path" noninteractive flag --non-interactive help='Never prompt' || return $?
base_cli_option "$cli_model" "$path" config value --config metavar=FILE help='Use a project configuration file' || return $?
Expand All @@ -694,22 +695,26 @@ base_app_add_standard_options() {

# base_app_apply_standard_options - Publishes parsed common options as policy state.
base_app_apply_standard_options() {
local model="${1-}" value
local model="${1-}" value cli_color

(($# == 1)) || {
__base_bash_libs_app_error__ 'base_app_apply_standard_options: usage: base_app_apply_standard_options MODEL'
return 2
}
__base_bash_libs_app_model_exists__ "$model" || return 1
cli_color="${BASE_BASH_LIBS_CLI_RESULT_OPTIONS[color]-auto}"
if ! __base_bash_libs_std_color_mode_is_valid__ "$cli_color"; then
__base_bash_libs_app_error__ "base_app_apply_standard_options: invalid color mode '$cli_color'."
return 2
fi
value="${__base_bash_libs_std_wrapper_color_mode:-$cli_color}"
# These are caller-visible policy globals consumed by application code.
# shellcheck disable=SC2034
BASE_BASH_LIBS_APP_VERBOSE="${BASE_BASH_LIBS_CLI_RESULT_OPTIONS[verbose]-0}"
# shellcheck disable=SC2034
BASE_BASH_LIBS_APP_QUIET="${BASE_BASH_LIBS_CLI_RESULT_OPTIONS[quiet]-0}"
BASE_BASH_LIBS_APP_DRY_RUN="${BASE_BASH_LIBS_CLI_RESULT_OPTIONS[dry_run]-0}"
BASE_BASH_LIBS_APP_NONINTERACTIVE="${BASE_BASH_LIBS_CLI_RESULT_OPTIONS[noninteractive]-0}"
value="${BASE_BASH_LIBS_CLI_RESULT_OPTIONS[color]-auto}"
BASE_BASH_LIBS_APP_COLOR="$value"
if [[ -n "${BASE_BASH_LIBS_STD_LOG_LEVELS[INFO]+set}" ]]; then
case "${BASE_BASH_LIBS_APP_QUIET}:${BASE_BASH_LIBS_APP_VERBOSE}" in
1:0) base_std_set_log_level WARN || return $? ;;
Expand All @@ -724,21 +729,8 @@ base_app_apply_standard_options() {
__base_bash_libs_app_error__ 'base_app_apply_standard_options: quiet and verbose cannot both be enabled.'
return 2
fi
case "$value" in
auto | always)
# shellcheck disable=SC2034
BASE_BASH_LIBS_STD_COLOR_ENABLED=1
;;
never)
# shellcheck disable=SC2034
BASE_BASH_LIBS_STD_COLOR_ENABLED=0
;;
*)
__base_bash_libs_app_error__ "base_app_apply_standard_options: invalid color mode '$value'."
return 2
;;
esac
__base_bash_libs_std_init_colors__
__base_bash_libs_std_apply_color_mode__ "$value" || return $?
BASE_BASH_LIBS_APP_COLOR="$value"
BASE_BASH_LIBS_DRY_RUN="$BASE_BASH_LIBS_APP_DRY_RUN"
export BASE_BASH_LIBS_APP_DRY_RUN BASE_BASH_LIBS_APP_NONINTERACTIVE BASE_BASH_LIBS_APP_COLOR
export BASE_BASH_LIBS_DRY_RUN
Expand Down
67 changes: 67 additions & 0 deletions lib/bash/app/tests/lib_app.bats
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,73 @@ assert_demo_snapshot() {
[ "${BASE_BASH_LIBS_STD_LOGGER_LEVELS[default]}" -eq 4 ]
}

@test "standard color modes distinguish auto always and never" {
local stderr_file="$TEST_TMPDIR/color.stderr"
local -a init_args=()

base_init init_args --
base_cli_model_init color name=color
base_cli_command color run "Run"
base_app_init color_policy name=color
base_app_add_standard_options color run

unset NO_COLOR
base_cli_parse color -- run --color auto
base_app_apply_standard_options color_policy
base_std_print_error auto 2>"$stderr_file"
[[ "$(<"$stderr_file")" != *$'\033['* ]]

export NO_COLOR=1
base_cli_parse color -- run --color always
base_app_apply_standard_options color_policy
base_std_print_error always 2>"$stderr_file"
[[ "$(<"$stderr_file")" == *$'\033['* ]]

base_cli_parse color -- run --color never
base_app_apply_standard_options color_policy
base_std_print_error never 2>"$stderr_file"
[[ "$(<"$stderr_file")" != *$'\033['* ]]
unset NO_COLOR
}

@test "invalid standard color policy fails before publishing color state" {
local status

base_cli_model_init invalid_color name=invalid-color
base_cli_command invalid_color run "Run"
base_app_init invalid_color_policy
base_cli_parse invalid_color -- run
__base_bash_libs_std_color_mode=never
BASE_BASH_LIBS_APP_COLOR=never
BASE_BASH_LIBS_CLI_RESULT_OPTIONS[color]=unsupported

if base_app_apply_standard_options invalid_color_policy >/dev/null 2>"$TEST_TMPDIR/color-error"; then
status=0
else
status=$?
fi

[ "$status" -eq 2 ]
[ "$__base_bash_libs_std_color_mode" = never ]
[ "$BASE_BASH_LIBS_APP_COLOR" = never ]
grep -F "invalid color mode 'unsupported'" "$TEST_TMPDIR/color-error"
}

@test "explicit wrapper color mode overrides the app's standard color option" {
base_cli_model_init wrapper_color name=wrapper-color
base_cli_command wrapper_color run "Run"
base_app_init wrapper_color_policy
base_app_add_standard_options wrapper_color run
base_cli_parse wrapper_color -- run --color always
__base_bash_libs_std_wrapper_color_mode=never

base_app_apply_standard_options wrapper_color_policy

[ "$BASE_BASH_LIBS_APP_COLOR" = never ]
[ "$__base_bash_libs_std_color_mode" = never ]
[ -z "$BASE_BASH_LIBS_STD_COLOR_RED" ]
}

@test "base_app_prompt validates usage and applies the prompt policy contract" {
local prompt_calls=()
base_app_init prompt
Expand Down
12 changes: 10 additions & 2 deletions lib/bash/std/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,8 +419,16 @@ base_std_print_message "plain stdout message"
`log_*`, `base_std_print_error`, `base_std_print_warn`, `base_std_print_info`, and `base_std_print_success` write to
stderr. `base_std_print_bold` and `base_std_print_message` write to stdout.

Colors are only enabled for terminal stderr when `--color` is passed. Set
`NO_COLOR` to disable colored output even when `--color` is present.
The legacy `base_init --color` control requests automatic color and only enables
it when stderr is a terminal and `NO_COLOR` is unset. Wrappers can use
`base_init --color-mode auto|always|never` to select an explicit standard
color policy; this explicit wrapper setting takes precedence over an app-level
`--color MODE`. `always` overrides `NO_COLOR`, and `never` disables color.
The application's own `--color MODE` pair remains untouched for its parser.
Arguments after the application's separator remain application-owned. Applications using
`base_app_add_standard_options` can select `auto`, `always`, or `never`:
`always` explicitly forces ANSI output even when stderr is captured or
`NO_COLOR` is set; `never` disables it.

## Error Handling

Expand Down
99 changes: 84 additions & 15 deletions lib/bash/std/lib_std.sh
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
# Notes:
# - Call base_init <result_array> [--source <script>] [--] [argv...]
# before using stateful helpers. It strips --debug-wrapper,
# --verbose-wrapper, --utc-wrapper, and --color into the result array.
# --verbose-wrapper, --utc-wrapper, --color, and --color-mode into the result array.
# - --verbose-wrapper is deprecated compatibility surface; prefer --debug-wrapper.
# - BASE_BASH_LIBS_BOOTSTRAP_SOURCE is accepted as a source-path fallback by the
# explicit initializer, not consumed while this file is sourced.
Expand Down Expand Up @@ -471,6 +471,8 @@ __base_bash_libs_std_initialize_runtime_state__() {

__base_bash_libs_std_log_init__
declare -g BASE_BASH_LIBS_STD_COLOR_ENABLED=0
declare -g __base_bash_libs_std_color_mode=auto
declare -g __base_bash_libs_std_wrapper_color_mode=""
declare -ga __base_bash_libs_std_cleanup_hooks=()
declare -ga __base_bash_libs_std_cleanup_paths=()
declare -ga __base_bash_libs_std_cleanup_entries=()
Expand Down Expand Up @@ -518,7 +520,8 @@ base_init() {
local __base_bash_libs_std_init_result_name="${1-}" __base_bash_libs_std_init_source_path=""
local __base_bash_libs_std_init_script_dir="" __base_bash_libs_std_init_arg
local __base_bash_libs_std_init_input_index
local __base_bash_libs_std_init_parse_config=1 __base_bash_libs_std_init_color_requested=0
local __base_bash_libs_std_init_parse_config=1 __base_bash_libs_std_init_color_mode_requested=""
local __base_bash_libs_std_init_wrapper_color_mode_requested=""
local __base_bash_libs_std_init_configure_runtime=0
local -a __base_bash_libs_std_init_input_args=() __base_bash_libs_std_init_filtered_args=()

Expand Down Expand Up @@ -577,7 +580,9 @@ base_init() {
fi

__base_bash_libs_std_init_parse_config=1
for __base_bash_libs_std_init_input_index in "${!__base_bash_libs_std_init_input_args[@]}"; do
for ((__base_bash_libs_std_init_input_index = 0; \
__base_bash_libs_std_init_input_index < ${#__base_bash_libs_std_init_input_args[@]}; \
__base_bash_libs_std_init_input_index++)); do
__base_bash_libs_std_init_arg="${__base_bash_libs_std_init_input_args[__base_bash_libs_std_init_input_index]}"
if ((__base_bash_libs_std_init_parse_config)) && [[ "$__base_bash_libs_std_init_arg" == "--" ]]; then
__base_bash_libs_std_init_filtered_args+=("$__base_bash_libs_std_init_arg")
Expand Down Expand Up @@ -607,18 +612,36 @@ base_init() {
;;
--color)
# `--color` was historically a bare launcher flag. A
# standard application may also own `--color MODE`; keep the
# wrapper form when it is bare or followed by an ordinary
# application argument, but let the documented modes reach
# the application parser unchanged.
if [[ "${__base_bash_libs_std_init_input_args[__base_bash_libs_std_init_input_index + 1]-}" == auto ||
"${__base_bash_libs_std_init_input_args[__base_bash_libs_std_init_input_index + 1]-}" == always ||
"${__base_bash_libs_std_init_input_args[__base_bash_libs_std_init_input_index + 1]-}" == never ]]; then
# standard application may also own `--color MODE`; keep that
# pair for its parser and offer `--color-mode` to wrappers
# that need an unambiguous explicit policy.
if __base_bash_libs_std_color_mode_is_valid__ \
"${__base_bash_libs_std_init_input_args[__base_bash_libs_std_init_input_index + 1]-}"; then
__base_bash_libs_std_init_filtered_args+=("$__base_bash_libs_std_init_arg")
else
__base_bash_libs_std_init_color_requested=1
__base_bash_libs_std_init_color_mode_requested=auto
__base_bash_libs_std_init_wrapper_color_mode_requested=""
fi
;;
--color-mode)
__base_bash_libs_std_init_color_mode_requested="${__base_bash_libs_std_init_input_args[__base_bash_libs_std_init_input_index + 1]-}"
if ! __base_bash_libs_std_color_mode_is_valid__ "$__base_bash_libs_std_init_color_mode_requested"; then
printf 'base_init: --color-mode expects one of: %s.\n' \
"$(__base_bash_libs_std_color_modes__)" >&2
return 2
fi
__base_bash_libs_std_init_wrapper_color_mode_requested="$__base_bash_libs_std_init_color_mode_requested"
__base_bash_libs_std_init_input_index=$((__base_bash_libs_std_init_input_index + 1))
;;
--color-mode=*)
__base_bash_libs_std_init_color_mode_requested="${__base_bash_libs_std_init_arg#*=}"
if ! __base_bash_libs_std_color_mode_is_valid__ "$__base_bash_libs_std_init_color_mode_requested"; then
printf 'base_init: invalid color mode %s; expected one of: %s.\n' \
"'$__base_bash_libs_std_init_color_mode_requested'" "$(__base_bash_libs_std_color_modes__)" >&2
return 2
fi
__base_bash_libs_std_init_wrapper_color_mode_requested="$__base_bash_libs_std_init_color_mode_requested"
;;
*)
__base_bash_libs_std_init_filtered_args+=("$__base_bash_libs_std_init_arg")
;;
Expand All @@ -629,8 +652,12 @@ base_init() {
done

if ((__base_bash_libs_std_init_configure_runtime)); then
BASE_BASH_LIBS_STD_COLOR_ENABLED="$__base_bash_libs_std_init_color_requested"
__base_bash_libs_std_init_colors__
if [[ -n "$__base_bash_libs_std_init_color_mode_requested" ]]; then
__base_bash_libs_std_apply_color_mode__ "$__base_bash_libs_std_init_color_mode_requested" || return $?
__base_bash_libs_std_wrapper_color_mode="$__base_bash_libs_std_init_wrapper_color_mode_requested"
else
__base_bash_libs_std_init_colors__ || return $?
fi
base_std_set_log_category_level -l base_bash_libs INFO
# Re-apply explicit debug levels after the default category gate.
for __base_bash_libs_std_init_arg in "${__base_bash_libs_std_init_input_args[@]+${__base_bash_libs_std_init_input_args[@]}}"; do
Expand Down Expand Up @@ -1098,12 +1125,54 @@ __base_bash_libs_std_print_log_record__() {
}

#
# __base_bash_libs_std_color_modes__ - Prints the supported standard color modes.
__base_bash_libs_std_color_modes__() {
printf 'auto,always,never'
}

__base_bash_libs_std_color_mode_is_valid__() {
case "${1-}" in
auto | always | never) return 0 ;;
*) return 1 ;;
esac
}

__base_bash_libs_std_apply_color_mode__() {
local mode="${1-}"

if ! __base_bash_libs_std_color_mode_is_valid__ "$mode"; then
printf 'ERROR: invalid color mode %s.\n' "'$mode'" >&2
return 2
fi
__base_bash_libs_std_color_mode="$mode"
if [[ "$mode" == auto ]]; then
BASE_BASH_LIBS_STD_COLOR_ENABLED=1
fi
__base_bash_libs_std_init_colors__
}

# __base_bash_libs_std_init_colors__ - Initialize colors used for logging
# This is called from base_init.
#
__base_bash_libs_std_init_colors__() {
# If --color was not passed, NO_COLOR is set, or the log stream is not a terminal, disable colors.
if [[ "$BASE_BASH_LIBS_STD_COLOR_ENABLED" != 1 || -n "${NO_COLOR+x}" || ! -t 2 ]]; then
local __base_bash_libs_std_colors_enabled=0

if ! __base_bash_libs_std_color_mode_is_valid__ "${__base_bash_libs_std_color_mode:-auto}"; then
printf 'ERROR: invalid color mode %s.\n' "'${__base_bash_libs_std_color_mode-}'" >&2
return 2
fi
case "${__base_bash_libs_std_color_mode:-auto}" in
always)
__base_bash_libs_std_colors_enabled=1
;;
auto)
if [[ "$BASE_BASH_LIBS_STD_COLOR_ENABLED" == 1 && -z "${NO_COLOR+x}" && -t 2 ]]; then
__base_bash_libs_std_colors_enabled=1
fi
;;
never) ;;
esac
if ((__base_bash_libs_std_colors_enabled == 0)); then
BASE_BASH_LIBS_STD_COLOR_BOLD=""
BASE_BASH_LIBS_STD_COLOR_RED=""
BASE_BASH_LIBS_STD_COLOR_GREEN=""
Expand Down
Loading
Loading