From f7b5f2a12fcdbd62a7ee0b4dcfbeac14c5b7a9b8 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:43:24 -0700 Subject: [PATCH] Adopt component log categories --- CHANGELOG.md | 10 +++ bin/base-bash | 2 + lib/bash/arg/lib_arg.sh | 30 ++++----- lib/bash/file/lib_file.sh | 64 +++++++++--------- lib/bash/file/tests/lib_file.bats | 1 + lib/bash/gh/lib_gh.sh | 28 ++++---- lib/bash/git/lib_git.sh | 84 ++++++++++++------------ lib/bash/git/tests/lib_git.bats | 35 +++++++++- lib/bash/std/README.md | 50 +++++++++++--- lib/bash/std/lib_std.sh | 104 ++++++++++++++++-------------- lib/bash/std/tests/lib_std.bats | 50 ++++++++++++-- 11 files changed, 295 insertions(+), 163 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dcff55..011496b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ and versions are tracked in the repo-root `VERSION` file. ### Changed +- Assigned reusable-library records to `base_bash_libs.` categories and + defaulted the parent library gate to INFO, allowing application DEBUG output + without implicitly enabling library DEBUG diagnostics. - Added an optional `BASE_CLI_PRIMARY_LOG` diagnostic sink. Bash logging keeps terminal verbosity unchanged while persisting the DEBUG-level stream to the shared run primary log. @@ -22,6 +25,13 @@ and versions are tracked in the repo-root `VERSION` file. timestamps and an explicit `UTC` marker when `LOG_UTC=1`, keeping Bash log formatting aligned with Base's Python CLI logs. +### Deprecated + +- Deprecated the Bash-only `VERBOSE` level, `log_verbose*` helpers, and + `--verbose-wrapper`. Their 1.x behavior remains unchanged, with removal no + earlier than the next major release; DEBUG is the most detailed level for + new code. + ## [1.3.0] - 2026-07-16 ### Fixed diff --git a/bin/base-bash b/bin/base-bash index f13fbf8..9883bf3 100755 --- a/bin/base-bash +++ b/bin/base-bash @@ -142,6 +142,8 @@ base_bash_filter_runtime_args() { 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 diff --git a/lib/bash/arg/lib_arg.sh b/lib/bash/arg/lib_arg.sh index 23ca926..cabbaa6 100644 --- a/lib/bash/arg/lib_arg.sh +++ b/lib/bash/arg/lib_arg.sh @@ -38,32 +38,32 @@ __arg_parse_specs__() { if [[ "$__arg_spec" == "$__arg_remainder" || "$__arg_remainder" == "$__arg_tokens_part" || -z "$__arg_name" || -z "$__arg_kind" || -z "$__arg_tokens_part" ]]; then - log_error "arg_parse: malformed option spec '$__arg_spec'." + log_error -l base_bash_libs.arg "arg_parse: malformed option spec '$__arg_spec'." return 2 fi if ! [[ "$__arg_name" =~ $__arg_name_re ]]; then - log_error "arg_parse: option spec '$__arg_spec' name must be a valid Bash identifier." + log_error -l base_bash_libs.arg "arg_parse: option spec '$__arg_spec' name must be a valid Bash identifier." return 2 fi if [[ -n "${__arg_seen_names[$__arg_name]+set}" ]]; then - log_error "arg_parse: option spec name '$__arg_name' is duplicated." + log_error -l base_bash_libs.arg "arg_parse: option spec name '$__arg_name' is duplicated." return 2 fi __arg_seen_names["$__arg_name"]=1 if [[ "$__arg_kind" != "flag" && "$__arg_kind" != "value" && "$__arg_kind" != "repeatable" ]]; then - log_error "arg_parse: option spec '$__arg_name' must use kind 'flag', 'value', or 'repeatable'." + log_error -l base_bash_libs.arg "arg_parse: option spec '$__arg_name' must use kind 'flag', 'value', or 'repeatable'." return 2 fi if [[ "$__arg_kind" == "repeatable" ]]; then if [[ -z "$__arg_repeatable_names_name" ]]; then - log_error "arg_parse: repeatable option spec '$__arg_name' requires an output array contract." + log_error -l base_bash_libs.arg "arg_parse: repeatable option spec '$__arg_name' requires an output array contract." return 2 fi if ! __std_declares_array_kind__ "$__arg_name" "a"; then - log_error "arg_parse: repeatable option '$__arg_name' requires a caller-declared indexed array." + log_error -l base_bash_libs.arg "arg_parse: repeatable option '$__arg_name' requires a caller-declared indexed array." return 2 fi __std_assert_writable_output__ arg_parse "$__arg_name" || return 1 @@ -72,17 +72,17 @@ __arg_parse_specs__() { if [[ "$__arg_tokens_part" == "|"* || "$__arg_tokens_part" == *"|" || "$__arg_tokens_part" == *"||"* ]]; then - log_error "arg_parse: option spec '$__arg_spec' contains an empty option token." + log_error -l base_bash_libs.arg "arg_parse: option spec '$__arg_spec' contains an empty option token." return 2 fi IFS='|' read -r -a __arg_tokens <<<"$__arg_tokens_part" for __arg_token in "${__arg_tokens[@]}"; do if ! [[ "$__arg_token" =~ $__arg_token_re ]] || [[ "$__arg_token" == *"="* ]]; then - log_error "arg_parse: option spec '$__arg_spec' has invalid option token '$__arg_token'." + log_error -l base_bash_libs.arg "arg_parse: option spec '$__arg_spec' has invalid option token '$__arg_token'." return 2 fi if [[ -n "${__arg_seen_tokens[$__arg_token]+set}" ]]; then - log_error "arg_parse: option token '$__arg_token' is duplicated." + log_error -l base_bash_libs.arg "arg_parse: option token '$__arg_token' is duplicated." return 2 fi __arg_seen_tokens["$__arg_token"]=1 @@ -122,7 +122,7 @@ arg_parse() { local __arg_parse_options=1 if (($# < 4)) || [[ "${4-}" != "--" ]]; then - log_error "arg_parse: usage: arg_parse -- [args...]" + log_error -l base_bash_libs.arg "arg_parse: usage: arg_parse -- [args...]" return 2 fi @@ -151,11 +151,11 @@ arg_parse() { __arg_option_name="${__arg_token_name[$__arg_option_token]-}" if [[ -z "$__arg_option_kind" ]]; then - log_error "arg_parse: unknown option '$__arg_option_token'." + log_error -l base_bash_libs.arg "arg_parse: unknown option '$__arg_option_token'." return 2 fi if [[ "$__arg_option_kind" != "value" && "$__arg_option_kind" != "repeatable" ]]; then - log_error "arg_parse: option '$__arg_option_token' does not accept a value." + log_error -l base_bash_libs.arg "arg_parse: option '$__arg_option_token' does not accept a value." return 2 fi @@ -174,7 +174,7 @@ arg_parse() { __arg_option_name="${__arg_token_name[$__arg_option_token]-}" if [[ -z "$__arg_option_kind" ]]; then - log_error "arg_parse: unknown option '$__arg_option_token'." + log_error -l base_bash_libs.arg "arg_parse: unknown option '$__arg_option_token'." return 2 fi @@ -184,12 +184,12 @@ arg_parse() { fi if (($# == 0)); then - log_error "arg_parse: option '$__arg_option_token' requires a value." + log_error -l base_bash_libs.arg "arg_parse: option '$__arg_option_token' requires a value." return 2 fi if [[ -n "${1-}" ]]; then if [[ -n "${__arg_token_kind[$1]+set}" ]]; then - log_error "arg_parse: option '$__arg_option_token' requires a value before option '$1'." + log_error -l base_bash_libs.arg "arg_parse: option '$__arg_option_token' requires a value before option '$1'." return 2 fi fi diff --git a/lib/bash/file/lib_file.sh b/lib/bash/file/lib_file.sh index c302e9d..031b154 100644 --- a/lib/bash/file/lib_file.sh +++ b/lib/bash/file/lib_file.sh @@ -31,7 +31,7 @@ __file_resolve_target_path__() { while [[ -L "$path" ]]; do ((depth += 1)) if ((depth > 40)); then - log_error "Too many symlink levels while resolving '$2'." + log_error -l base_bash_libs.file "Too many symlink levels while resolving '$2'." return 1 fi @@ -55,7 +55,7 @@ __file_validate_markers__() { "$beginning_marker" == "$end_marker" || "$beginning_marker" == *$'\n'* || "$beginning_marker" == *$'\r'* || "$end_marker" == *$'\n'* || "$end_marker" == *$'\r'* ]]; then - log_error "Section markers must be non-empty, distinct, single-line values." + log_error -l base_bash_libs.file "Section markers must be non-empty, distinct, single-line values." return 2 fi return 0 @@ -158,11 +158,11 @@ __file_section_marker_counts__() { printf -v "$end_count_var" '%s' "$section_end_marker_count" if ((section_beginning_marker_count != section_end_marker_count)); then - log_error "Asymmetric markers in '$target_file': $section_beginning_marker_count start, $section_end_marker_count end. Manual repair needed." + log_error -l base_bash_libs.file "Asymmetric markers in '$target_file': $section_beginning_marker_count start, $section_end_marker_count end. Manual repair needed." return 2 fi if ((section_beginning_marker_count > 0)) && ! __file_section_markers_ordered__ "$target_file" "$beginning_marker" "$end_marker"; then - log_error "Misordered markers in '$target_file'. Manual repair needed." + log_error -l base_bash_libs.file "Misordered markers in '$target_file'. Manual repair needed." return 2 fi @@ -179,7 +179,7 @@ __file_section_marker_counts__() { # file_section_exists() { if [[ $# -ne 3 ]]; then - log_error "file_section_exists: expected ." + log_error -l base_bash_libs.file "file_section_exists: expected ." return 2 fi @@ -205,7 +205,7 @@ file_section_exists() { # file_section_needs_update() { if [[ $# -lt 3 ]]; then - log_error "file_section_needs_update: expected [content_lines...]." + log_error -l base_bash_libs.file "file_section_needs_update: expected [content_lines...]." return 2 fi @@ -222,23 +222,23 @@ file_section_needs_update() { ((beginning_marker_count > 0)) || return 0 if ! std_make_temp_file new_content_file base-file-section-new; then - log_error "Failed to create temporary content file for '$target_file'." + log_error -l base_bash_libs.file "Failed to create temporary content file for '$target_file'." return 2 fi if (($# > 0)); then if ! printf '%s\n' "$@" > "$new_content_file"; then - log_error "Failed to write replacement content for '$target_file'." + log_error -l base_bash_libs.file "Failed to write replacement content for '$target_file'." __file_remove_temp_paths__ "$new_content_file" return 2 fi elif ! : > "$new_content_file"; then - log_error "Failed to write replacement content for '$target_file'." + log_error -l base_bash_libs.file "Failed to write replacement content for '$target_file'." __file_remove_temp_paths__ "$new_content_file" return 2 fi if ! std_make_temp_file current_content_file base-file-section-current; then - log_error "Failed to create temporary current content file for '$target_file'." + log_error -l base_bash_libs.file "Failed to create temporary current content file for '$target_file'." __file_remove_temp_paths__ "$new_content_file" return 2 fi @@ -265,7 +265,7 @@ file_section_needs_update() { } } ' "$target_file" > "$current_content_file"; then - log_error "Failed to read existing section in '$target_file'." + log_error -l base_bash_libs.file "Failed to read existing section in '$target_file'." __file_remove_temp_paths__ "$current_content_file" "$new_content_file" return 2 fi @@ -320,11 +320,11 @@ update_file_section() { fi if [[ $# -lt 3 ]]; then - log_error "Insufficient arguments." + log_error -l base_bash_libs.file "Insufficient arguments." if [[ "$remove_section" == true ]]; then - log_info "Usage: update_file_section -r " + log_info -l base_bash_libs.file "Usage: update_file_section -r " else - log_info "Usage: update_file_section [new_lines...]" + log_info -l base_bash_libs.file "Usage: update_file_section [new_lines...]" fi return 1 fi @@ -333,8 +333,8 @@ update_file_section() { shift 3 # consume target_file, beginning_marker, end_marker if [[ "$remove_section" == true ]]; then if [[ $# -gt 0 ]]; then - log_error "When -r flag is used, no content arguments should be provided." - log_info "Usage: update_file_section -r " + log_error -l base_bash_libs.file "When -r flag is used, no content arguments should be provided." + log_info -l base_bash_libs.file "Usage: update_file_section -r " return 1 fi else @@ -344,7 +344,7 @@ update_file_section() { __file_literal_path__ target_file "$target_file" __file_validate_markers__ "$beginning_marker" "$end_marker" || return 1 if [[ ! -f "$target_file" ]]; then - log_debug "Target file '$target_file' does not exist." + log_debug -l base_bash_libs.file "Target file '$target_file' does not exist." return 0 fi @@ -371,19 +371,19 @@ update_file_section() { fi if [[ "$section_exists" == false && "$remove_section" == true ]]; then - log_debug "Section not present in '$target_file'; nothing to remove." + log_debug -l base_bash_libs.file "Section not present in '$target_file'; nothing to remove." return 0 fi local current_content_file="" new_content_file="" temp_file if [[ "$remove_section" == false ]]; then if ! std_make_temp_file new_content_file base-file-section-new; then - log_error "Failed to create temporary content file for '$target_file'." + log_error -l base_bash_libs.file "Failed to create temporary content file for '$target_file'." return 1 fi if ! printf '%s' "$new_content_string" > "$new_content_file"; then - log_error "Failed to write replacement content for '$target_file'." + log_error -l base_bash_libs.file "Failed to write replacement content for '$target_file'." __file_remove_temp_paths__ "$new_content_file" return 1 fi @@ -391,7 +391,7 @@ update_file_section() { if [[ "$section_exists" == true && "$remove_section" == false ]]; then if ! std_make_temp_file current_content_file base-file-section-current; then - log_error "Failed to create temporary current content file for '$target_file'." + log_error -l base_bash_libs.file "Failed to create temporary current content file for '$target_file'." __file_remove_temp_paths__ "$new_content_file" return 1 fi @@ -413,13 +413,13 @@ update_file_section() { print $0 } ' "$target_file" > "$current_content_file"; then - log_error "Failed to read existing section in '$target_file'." + log_error -l base_bash_libs.file "Failed to read existing section in '$target_file'." __file_remove_temp_paths__ "$current_content_file" "$new_content_file" return 1 fi if cmp -s "$current_content_file" "$new_content_file"; then - log_debug "Section already up to date in '$target_file'." + log_debug -l base_bash_libs.file "Section already up to date in '$target_file'." __file_remove_temp_paths__ "$current_content_file" "$new_content_file" return 0 fi @@ -427,17 +427,17 @@ update_file_section() { fi if [[ "$section_exists" == true ]]; then - log_info "Updating '$target_file'" + log_info -l base_bash_libs.file "Updating '$target_file'" else - log_info "Adding section to '$target_file'" + log_info -l base_bash_libs.file "Adding section to '$target_file'" fi if ! __file_make_target_temp__ temp_file "$target_file"; then - log_error "Failed to create temporary file for '$target_file'." + log_error -l base_bash_libs.file "Failed to create temporary file for '$target_file'." __file_remove_temp_paths__ "$new_content_file" return 1 fi if ! __preserve_file_mode__ "$target_file" "$temp_file"; then - log_error "Failed to preserve permissions for '$target_file'." + log_error -l base_bash_libs.file "Failed to preserve permissions for '$target_file'." __file_remove_temp_paths__ "$temp_file" "$new_content_file" return 1 fi @@ -494,20 +494,20 @@ update_file_section() { fi fi - log_error "Failed to process sections in '$target_file'." + log_error -l base_bash_libs.file "Failed to process sections in '$target_file'." __file_remove_temp_paths__ "$temp_file" "$new_content_file" return 1 else # Markers not found in the file if ! cp "$target_file" "$temp_file"; then - log_error "Failed to copy '$target_file' to '$temp_file'." + log_error -l base_bash_libs.file "Failed to copy '$target_file' to '$temp_file'." __file_remove_temp_paths__ "$temp_file" "$new_content_file" return 1 fi if [[ -s "$temp_file" ]] && [[ $(tail -c 1 "$temp_file" 2>/dev/null | wc -l) -eq 0 ]]; then if ! printf '\n' >> "$temp_file"; then - log_error "Failed to add trailing newline to '$temp_file'." + log_error -l base_bash_libs.file "Failed to add trailing newline to '$temp_file'." __file_remove_temp_paths__ "$temp_file" "$new_content_file" return 1 fi @@ -518,13 +518,13 @@ update_file_section() { printf '%s' "$new_content_string" printf '%s\n' "$end_marker" } >> "$temp_file"; then - log_error "Failed to add new section to '$target_file'." + log_error -l base_bash_libs.file "Failed to add new section to '$target_file'." __file_remove_temp_paths__ "$temp_file" "$new_content_file" return 1 fi if ! mv -f "$temp_file" "$target_file"; then - log_error "Failed to replace '$target_file' with '$temp_file'." + log_error -l base_bash_libs.file "Failed to replace '$target_file' with '$temp_file'." __file_remove_temp_paths__ "$temp_file" "$new_content_file" return 1 fi diff --git a/lib/bash/file/tests/lib_file.bats b/lib/bash/file/tests/lib_file.bats index e4015ef..0a8d5f8 100644 --- a/lib/bash/file/tests/lib_file.bats +++ b/lib/bash/file/tests/lib_file.bats @@ -261,6 +261,7 @@ EOF [ "$(cat "$target")" = $'before\n# BEGIN\nsame\ncontent\n# END\nafter' ] set_log_level DEBUG + set_log_category_level -l base_bash_libs.file DEBUG capture_command update_file_section "$target" "# BEGIN" "# END" "same" "content" [ "$status" -eq 0 ] diff --git a/lib/bash/gh/lib_gh.sh b/lib/bash/gh/lib_gh.sh index de40caa..ed0ad59 100644 --- a/lib/bash/gh/lib_gh.sh +++ b/lib/bash/gh/lib_gh.sh @@ -14,8 +14,8 @@ gh_require_cli() { local install_hint="${1:-}" command -v gh >/dev/null 2>&1 || { - log_error "Required command 'gh' was not found on PATH." - [[ -z "$install_hint" ]] || log_error "$install_hint" + log_error -l base_bash_libs.gh "Required command 'gh' was not found on PATH." + [[ -z "$install_hint" ]] || log_error -l base_bash_libs.gh "$install_hint" return 1 } } @@ -28,9 +28,9 @@ gh_auth_status_diagnostics() { auth_output="$(gh auth status -h github.com 2>&1)" || { while IFS= read -r line || [[ -n "$line" ]]; do - [[ -n "$line" ]] && log_error "gh auth status: $line" + [[ -n "$line" ]] && log_error -l base_bash_libs.gh "gh auth status: $line" done <<<"$auth_output" - [[ -z "$login_hint" ]] || log_error "$login_hint" + [[ -z "$login_hint" ]] || log_error -l base_bash_libs.gh "$login_hint" return 1 } } @@ -43,9 +43,9 @@ gh_report_command_failure() { if (($#)); then printf -v printable_args '%q ' "$@" printable_args="${printable_args% }" - log_error "GitHub command failed: gh $printable_args" + log_error -l base_bash_libs.gh "GitHub command failed: gh $printable_args" else - log_error "GitHub command failed: gh" + log_error -l base_bash_libs.gh "GitHub command failed: gh" fi gh_auth_status_diagnostics || true return "$status" @@ -66,7 +66,7 @@ gh_repo_from_remote_url() { local __gh_parsed_repo if [[ -z "$__gh_remote_url" || -z "$__gh_result_name" ]]; then - log_error "Usage: gh_repo_from_remote_url " + log_error -l base_bash_libs.gh "Usage: gh_repo_from_remote_url " return 1 fi assert_variable_name "$__gh_result_name" @@ -99,7 +99,7 @@ gh_infer_repo_from_origin() { local __gh_infer_repo __gh_infer_remote_url if [[ -z "$__gh_infer_repo_dir" || -z "$__gh_infer_result_name" ]]; then - log_error "Usage: gh_infer_repo_from_origin [--optional]" + log_error -l base_bash_libs.gh "Usage: gh_infer_repo_from_origin [--optional]" return 1 fi assert_variable_name "$__gh_infer_result_name" @@ -115,7 +115,7 @@ gh_infer_repo_from_origin() { printf -v "$__gh_infer_result_name" '%s' "" return 0 fi - log_error "Could not infer GitHub repository from '$__gh_infer_repo_dir' origin remote." + log_error -l base_bash_libs.gh "Could not infer GitHub repository from '$__gh_infer_repo_dir' origin remote." return 1 fi @@ -128,7 +128,7 @@ gh_repo_default_branch() { local __gh_repo_default_branch __gh_repo_status=0 if [[ -z "$__gh_repo" || -z "$__gh_repo_result_name" ]]; then - log_error "Usage: gh_repo_default_branch " + log_error -l base_bash_libs.gh "Usage: gh_repo_default_branch " return 1 fi assert_variable_name "$__gh_repo_result_name" @@ -141,7 +141,7 @@ gh_repo_default_branch() { return $? fi if [[ -z "$__gh_repo_default_branch" ]]; then - log_error "GitHub repository '$__gh_repo' does not report a default branch." + log_error -l base_bash_libs.gh "GitHub repository '$__gh_repo' does not report a default branch." return 1 fi @@ -187,7 +187,7 @@ gh_api_with_retry() { gh_require_cli || return 1 if [[ ! "$max_attempts" =~ ^[0-9]+$ ]] || ((max_attempts < 1)); then - log_warn "BASE_GH_API_MAX_ATTEMPTS must be a positive integer; using 2." + log_warn -l base_bash_libs.gh "BASE_GH_API_MAX_ATTEMPTS must be a positive integer; using 2." max_attempts=2 fi @@ -208,9 +208,9 @@ gh_api_with_retry() { fi if ((max_attempts == 2)); then - log_warn "GitHub API call failed on attempt $attempt; retrying once." + log_warn -l base_bash_libs.gh "GitHub API call failed on attempt $attempt; retrying once." else - log_warn "GitHub API call failed on attempt $attempt; retrying (attempt $((attempt + 1)) of $max_attempts)." + log_warn -l base_bash_libs.gh "GitHub API call failed on attempt $attempt; retrying (attempt $((attempt + 1)) of $max_attempts)." fi delay="$(__gh_api_retry_delay_seconds "$output")" __std_sleep_interval__ "$delay" diff --git a/lib/bash/git/lib_git.sh b/lib/bash/git/lib_git.sh index 5b60aaa..7a4729e 100644 --- a/lib/bash/git/lib_git.sh +++ b/lib/bash/git/lib_git.sh @@ -16,7 +16,7 @@ git_detect_default_branch() { local __git_detect_branch if [[ -z "$__git_detect_repo_dir" || -z "$__git_detect_result_name" ]]; then - log_error "Usage: git_detect_default_branch " + log_error -l base_bash_libs.git "Usage: git_detect_default_branch " return 1 fi assert_variable_name "$__git_detect_result_name" @@ -53,13 +53,13 @@ git_worktree_path_for_branch() { local -a git_cmd=(git) [[ -n "$branch" ]] || { - log_error "Usage: git_worktree_path_for_branch [repo_dir]" + log_error -l base_bash_libs.git "Usage: git_worktree_path_for_branch [repo_dir]" return 1 } [[ -z "$repo_dir" ]] || git_cmd=(git -C "$repo_dir") if ! output="$("${git_cmd[@]}" worktree list --porcelain 2>&1)"; then - log_error "Unable to list Git worktrees." + log_error -l base_bash_libs.git "Unable to list Git worktrees." return 1 fi while IFS= read -r line; do @@ -87,7 +87,7 @@ git_list_worktree_branches() { [[ -z "$repo_dir" ]] || git_cmd=(git -C "$repo_dir") if ! output="$("${git_cmd[@]}" worktree list --porcelain 2>&1)"; then - log_error "Unable to list Git worktrees." + log_error -l base_bash_libs.git "Unable to list Git worktrees." return 1 fi output+=$'\n' @@ -117,7 +117,7 @@ git_branch_upstream() { local branch="$2" if [[ -z "$repo_dir" || -z "$branch" ]]; then - log_error "Usage: git_branch_upstream " + log_error -l base_bash_libs.git "Usage: git_branch_upstream " return 1 fi @@ -130,7 +130,7 @@ git_branch_merged_to_ref() { local ref="$3" if [[ -z "$repo_dir" || -z "$branch" || -z "$ref" ]]; then - log_error "Usage: git_branch_merged_to_ref " + log_error -l base_bash_libs.git "Usage: git_branch_merged_to_ref " return 1 fi @@ -142,7 +142,7 @@ git_list_remote_branches() { local output ref _sha if ! output="$(git -C "$repo_dir" ls-remote --heads origin)"; then - log_error "Unable to list remote branches from origin." + log_error -l base_bash_libs.git "Unable to list remote branches from origin." return 1 fi while read -r _sha ref; do @@ -256,14 +256,14 @@ __git_pull_with_retry__() { local attempt=1 if [[ ! "$max_attempts" =~ ^[0-9]+$ ]] || ((max_attempts < 1)); then - log_warn "BASE_GIT_PULL_MAX_ATTEMPTS must be a positive integer; using 2." + log_warn -l base_bash_libs.git "BASE_GIT_PULL_MAX_ATTEMPTS must be a positive integer; using 2." max_attempts=2 fi while ((attempt <= max_attempts)); do if git pull --ff-only >"$git_log" 2>&1; then if ((attempt > 1)); then - log_debug "git pull succeeded on attempt $attempt." + log_debug -l base_bash_libs.git "git pull succeeded on attempt $attempt." fi return 0 fi @@ -273,11 +273,11 @@ __git_pull_with_retry__() { fi if ((max_attempts == 2)); then - log_warn "git pull failed on attempt $attempt; retrying once." + log_warn -l base_bash_libs.git "git pull failed on attempt $attempt; retrying once." else - log_warn "git pull failed on attempt $attempt; retrying (attempt $((attempt + 1)) of $max_attempts)." + log_warn -l base_bash_libs.git "git pull failed on attempt $attempt; retrying (attempt $((attempt + 1)) of $max_attempts)." fi - [[ -s "$git_log" ]] && log_debug_file "$git_log" + [[ -s "$git_log" ]] && log_debug_file -l base_bash_libs.git "$git_log" attempt=$((attempt + 1)) done } @@ -300,18 +300,18 @@ git_update_repo() { local git_log submodule_log="" if [[ -z "$git_repo" ]]; then - log_error "No git repository path provided." - log_info "Usage: git_update_repo /path/to/repo [allowed_dirty_path] [expected_branch]" + log_error -l base_bash_libs.git "No git repository path provided." + log_info -l base_bash_libs.git "Usage: git_update_repo /path/to/repo [allowed_dirty_path] [expected_branch]" return 1 fi if [[ ! -d "$git_repo" ]]; then - log_error "Git repo not found at '$git_repo'" + log_error -l base_bash_libs.git "Git repo not found at '$git_repo'" return 1 fi std_make_temp_file git_log git_log || { - log_error "Unable to create temporary git log file." + log_error -l base_bash_libs.git "Unable to create temporary git log file." return 1 } # git_update_repo intentionally works inside the target repository because @@ -324,7 +324,7 @@ git_update_repo() { # Check if it's a valid git repo if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then - log_error "'$git_repo' is not a Git repository." + log_error -l base_bash_libs.git "'$git_repo' is not a Git repository." __git_update_repo_finish__ "$git_log" true 1 return $? fi @@ -334,7 +334,7 @@ git_update_repo() { expected_branch="$(__git_expected_update_branch__ "$expected_branch")" current_branch=$(git rev-parse --abbrev-ref HEAD) if [[ "$current_branch" != "$expected_branch" ]]; then - log_debug "Current branch of '$git_repo' is '${current_branch}', not '$expected_branch'. Skipping update." + log_debug -l base_bash_libs.git "Current branch of '$git_repo' is '${current_branch}', not '$expected_branch'. Skipping update." __git_update_repo_finish__ "$git_log" true 0 return $? fi @@ -348,35 +348,35 @@ git_update_repo() { fi if [[ "$dirty" == true ]]; then if [[ -n "$allowed_dirty_path" ]] && __git_only_path_dirty__ "$allowed_dirty_path"; then - log_debug "Repo '$git_repo' only has tracked changes in '$allowed_dirty_path'; attempting git pull." + log_debug -l base_bash_libs.git "Repo '$git_repo' only has tracked changes in '$allowed_dirty_path'; attempting git pull." else - log_debug "Repo '$git_repo' has local changes; skipping auto-update. Commit or stash to enable git pull." + log_debug -l base_bash_libs.git "Repo '$git_repo' has local changes; skipping auto-update. Commit or stash to enable git pull." __git_update_repo_finish__ "$git_log" true 0 return $? fi fi if ! __git_pull_with_retry__ "$git_log"; then - log_error "git pull failed on repo '$git_repo'" - [[ -s "$git_log" ]] && log_info_file "$git_log" + log_error -l base_bash_libs.git "git pull failed on repo '$git_repo'" + [[ -s "$git_log" ]] && log_info_file -l base_bash_libs.git "$git_log" __git_update_repo_finish__ "$git_log" true 1 return $? fi # it is safe to run submodule commands even if the repo has no submodules if ! std_make_temp_file submodule_log git-submodule-log; then - log_error "Unable to create temporary submodule log file." + log_error -l base_bash_libs.git "Unable to create temporary submodule log file." __git_update_repo_finish__ "$git_log" true 1 return $? fi if ! { git submodule init && git submodule sync && git submodule update; } >"$submodule_log" 2>&1; then - log_error "git submodule update failed on repo '$git_repo'" - [[ -s "$submodule_log" ]] && log_info_file "$submodule_log" + log_error -l base_bash_libs.git "git submodule update failed on repo '$git_repo'" + [[ -s "$submodule_log" ]] && log_info_file -l base_bash_libs.git "$submodule_log" __git_update_repo_finish__ "$git_log" true 1 "$submodule_log" return $? fi - log_debug "Git repo '$git_repo' updated to latest '$expected_branch'" + log_debug -l base_bash_libs.git "Git repo '$git_repo' updated to latest '$expected_branch'" __git_update_repo_finish__ "$git_log" true 0 "$submodule_log" return $? } @@ -403,11 +403,11 @@ git_get_current_branch() { # --- Argument Validation --- if [[ -z "$__git_branch_target_dir" || -z "$__git_branch_result_name" ]]; then - log_error "Usage: git_get_current_branch " + log_error -l base_bash_libs.git "Usage: git_get_current_branch " return 1 fi if ! __is_valid_variable_name__ "$__git_branch_result_name"; then - log_error "git_get_current_branch: result variable name must be a valid Bash variable name." + log_error -l base_bash_libs.git "git_get_current_branch: result variable name must be a valid Bash variable name." return 1 fi __std_assert_writable_output__ git_get_current_branch "$__git_branch_result_name" || return 1 @@ -460,35 +460,35 @@ check_script_up_to_date() { fi if (($# != 1)); then - log_error "Usage: check_script_up_to_date [--fetch] " + log_error -l base_bash_libs.git "Usage: check_script_up_to_date [--fetch] " return 1 fi script_path="$1" if [[ ! -e "$script_path" ]]; then - log_warn "Script '$script_path' not found; skipping latest-version check." + log_warn -l base_bash_libs.git "Script '$script_path' not found; skipping latest-version check." return 0 fi if ! command -v git &> /dev/null; then - log_info "git not available; skipping latest-version check." + log_info -l base_bash_libs.git "git not available; skipping latest-version check." return 0 fi local script_dir repo_root prefix rel_path script_dir=$(dirname "$script_path") repo_root=$(git -C "$script_dir" rev-parse --show-toplevel 2>/dev/null) || { - log_info "Not in a git repo; skipping latest-version check." + log_info -l base_bash_libs.git "Not in a git repo; skipping latest-version check." return 0 } prefix=$(git -C "$script_dir" rev-parse --show-prefix 2>/dev/null) || { - log_info "Unable to resolve repo-relative path; skipping latest-version check." + log_info -l base_bash_libs.git "Unable to resolve repo-relative path; skipping latest-version check." return 0 } rel_path="${prefix}$(basename "$script_path")" if ! git -C "$repo_root" ls-files --error-unmatch "$rel_path" >/dev/null 2>&1; then - log_info "Script '$rel_path' is not tracked in git; skipping latest-version check." + log_info -l base_bash_libs.git "Script '$rel_path' is not tracked in git; skipping latest-version check." return 0 fi @@ -500,37 +500,37 @@ check_script_up_to_date() { dirty=true fi if [[ "$dirty" == true ]]; then - log_warn "Script '$rel_path' has local modifications; version may not match repo." + log_warn -l base_bash_libs.git "Script '$rel_path' has local modifications; version may not match repo." fi local upstream behind ahead upstream=$(git -C "$repo_root" rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null) || { - log_info "No upstream branch configured; skipping latest-version check." + log_info -l base_bash_libs.git "No upstream branch configured; skipping latest-version check." return 0 } if [[ "$fetch_before_check" == true ]]; then if git -C "$repo_root" fetch --quiet; then - log_info "Fetched upstream state before latest-version check." + log_info -l base_bash_libs.git "Fetched upstream state before latest-version check." else - log_warn "Unable to fetch upstream state; using local remote-tracking refs." + log_warn -l base_bash_libs.git "Unable to fetch upstream state; using local remote-tracking refs." fi else - log_debug "Using local remote-tracking refs; pass --fetch for a live remote check." + log_debug -l base_bash_libs.git "Using local remote-tracking refs; pass --fetch for a live remote check." fi behind=$(git -C "$repo_root" rev-list --count HEAD.."$upstream" 2>/dev/null) ahead=$(git -C "$repo_root" rev-list --count "$upstream"..HEAD 2>/dev/null) if [[ -n "$behind" && "$behind" -gt 0 ]]; then - log_warn "Repository is $behind commit(s) behind $upstream. Script may be out of date." + log_warn -l base_bash_libs.git "Repository is $behind commit(s) behind $upstream. Script may be out of date." if [[ "$dirty" == true ]]; then return 3 fi return 2 elif [[ -n "$ahead" && "$ahead" -gt 0 ]]; then - log_info "Repository is $ahead commit(s) ahead of $upstream." + log_info -l base_bash_libs.git "Repository is $ahead commit(s) ahead of $upstream." else - log_info "Repository is up to date with $upstream." + log_info -l base_bash_libs.git "Repository is up to date with $upstream." fi if [[ "$dirty" == true ]]; then diff --git a/lib/bash/git/tests/lib_git.bats b/lib/bash/git/tests/lib_git.bats index 2d0e3b5..9c9954a 100644 --- a/lib/bash/git/tests/lib_git.bats +++ b/lib/bash/git/tests/lib_git.bats @@ -251,6 +251,7 @@ EOF commit_all "$repo" "Initial commit" printf 'local change\n' > "$repo/data.txt" set_log_level DEBUG + set_log_category_level -l base_bash_libs.git DEBUG capture_command git_update_repo "$repo" @@ -258,7 +259,7 @@ EOF [[ "$output" == *"has local changes; skipping auto-update"* ]] } -@test "git_update_repo treats branch mismatch as a skip" { +@test "caller DEBUG does not enable reusable Git DEBUG by default" { local repo="$TEST_TMPDIR/repo" init_git_repo "$repo" @@ -268,6 +269,36 @@ EOF capture_command git_update_repo "$repo" "" release + [ "$status" -eq 0 ] + [[ "$output" != *"not 'release'. Skipping update"* ]] +} + +@test "Git child category DEBUG opt-in enables reusable Git diagnostics" { + local repo="$TEST_TMPDIR/repo" + + init_git_repo "$repo" + printf 'base\n' > "$repo/data.txt" + commit_all "$repo" "Initial commit" + set_log_level DEBUG + set_log_category_level -l base_bash_libs.git DEBUG + + capture_command git_update_repo "$repo" "" release + + [ "$status" -eq 0 ] + [[ "$output" == *"not 'release'. Skipping update"* ]] +} + +@test "base_bash_libs parent DEBUG opt-in enables reusable Git diagnostics" { + local repo="$TEST_TMPDIR/repo" + + init_git_repo "$repo" + printf 'base\n' > "$repo/data.txt" + commit_all "$repo" "Initial commit" + set_log_level DEBUG + set_log_category_level -l base_bash_libs DEBUG + + capture_command git_update_repo "$repo" "" release + [ "$status" -eq 0 ] [[ "$output" == *"not 'release'. Skipping update"* ]] } @@ -369,6 +400,7 @@ EOF commit_all "$repo" "Initial commit" printf 'local change\n' > "$repo/data.txt" set_log_level DEBUG + set_log_category_level -l base_bash_libs.git DEBUG capture_command git_update_repo "$repo" @@ -775,6 +807,7 @@ EOF create_tracked_repo_with_upstream "$repo" "$remote" "scripts/tool.sh" "#!/usr/bin/env bash" set_log_level DEBUG + set_log_category_level -l base_bash_libs.git DEBUG bats_run check_script_up_to_date "$script_path" diff --git a/lib/bash/std/README.md b/lib/bash/std/README.md index e0b4bb0..fb1c6b7 100644 --- a/lib/bash/std/README.md +++ b/lib/bash/std/README.md @@ -76,12 +76,14 @@ zero for success and nonzero for a false or invalid condition. and at least one configured sink accept the level. - `log_fatal`, `log_error`, `log_warn`, `log_info`, `log_debug`, `log_verbose `: write a structured message to stderr at the - named level. + named level. `log_verbose` is deprecated; use `log_debug`. - `log_info_file`, `log_debug_file`, `log_verbose_file [-l ] `: log a file's contents at the requested level when that level is enabled. + `log_verbose_file` is deprecated; use `log_debug_file`. - `log_info_enter`, `log_debug_enter`, `log_verbose_enter` and `log_info_leave`, `log_debug_leave`, `log_verbose_leave`: log the current - function boundary without arguments. + function boundary without arguments. The `log_verbose_*` forms are + deprecated; use the DEBUG forms. - `print_error`, `print_warn`, `print_info`, `print_success `: write an unstructured user-facing message to stderr. - `print_bold`, `print_message `: write an unstructured message to @@ -190,8 +192,8 @@ Sourcing `lib_std.sh` runs a small one-time initializer: - derives the caller's source directory in `__SCRIPT_DIR__` - exposes the package version in `BASE_BASH_LIBS_VERSION` - exposes the successful stdlib load marker in `BASE_BASH_LIBS_STDLIB_LOADED` -- consumes Base wrapper flags such as `--debug-wrapper`, `--verbose-wrapper`, - `--utc-wrapper`, and `--color` +- consumes Base wrapper flags such as `--debug-wrapper`, `--utc-wrapper`, and + `--color`; the compatibility flag `--verbose-wrapper` is deprecated - resets the caller's positional parameters to the filtered argument list Caller-visible globals: @@ -229,7 +231,6 @@ log_info "Installing package '$name'." log_warn "Cache directory does not exist: $cache_dir" log_error "Unable to read manifest '$manifest_path'." log_debug "resolved_home=$resolved_home" -log_verbose "raw_response=$response" ``` Available levels: @@ -239,7 +240,13 @@ Available levels: - `WARN` - `INFO` - `DEBUG` -- `VERBOSE` +- `VERBOSE` (deprecated compatibility level) + +`DEBUG` is the most detailed supported level for new code. `VERBOSE`, +`log_verbose`, `log_verbose_file`, `log_verbose_enter`, +`log_verbose_leave`, and `--verbose-wrapper` remain behaviorally compatible +through the 1.x line, but may be removed in the next major release. They do not +emit runtime deprecation warnings. Change terminal verbosity with: @@ -265,8 +272,8 @@ Terminal verbosity and category gates answer different questions: - `BASE_CLI_PRIMARY_LOG`, when set, receives accepted records through DEBUG even when the terminal remains at INFO. -Category gates are permissive by default for compatibility. Applications can -keep their own DEBUG output while limiting a reusable component: +The global default category gate is permissive for compatibility. Applications +can keep their own DEBUG output while limiting a reusable component: ```bash set_log_level DEBUG @@ -278,6 +285,33 @@ log_debug -l reusable_library "suppressed library diagnostic" log_debug -l reusable_library.network "enabled network diagnostic" ``` +The library's own records use these categories: + +- `base_bash_libs.std` +- `base_bash_libs.arg` +- `base_bash_libs.file` +- `base_bash_libs.git` +- `base_bash_libs.gh` + +The parent `base_bash_libs` gate defaults to INFO. Consequently, an application +can enable its own DEBUG terminal output without also enabling reusable-library +DEBUG records: + +```bash +set_log_level DEBUG + +# Opt in to every base-bash-libs DEBUG category: +set_log_category_level -l base_bash_libs DEBUG + +# Or keep the parent at INFO and enable one component: +set_log_category_level -l base_bash_libs INFO +set_log_category_level -l base_bash_libs.git DEBUG +``` + +`--debug-wrapper` enables both DEBUG terminal output and the +`base_bash_libs` DEBUG gate. The deprecated `--verbose-wrapper` continues to +enable both at VERBOSE during the compatibility window. + Use `log_is_enabled` to avoid constructing an expensive diagnostic unless a terminal or persistent sink will consume it: diff --git a/lib/bash/std/lib_std.sh b/lib/bash/std/lib_std.sh index 1cb94fa..124b22a 100644 --- a/lib/bash/std/lib_std.sh +++ b/lib/bash/std/lib_std.sh @@ -65,6 +65,7 @@ # # Notes: # - Global options --debug-wrapper/--verbose-wrapper/--utc-wrapper/--color are stripped from "$@" automatically. +# - --verbose-wrapper is deprecated compatibility surface; prefer --debug-wrapper. # - Wrappers may override the caller path seen by this library through BASE_BASH_BOOTSTRAP_SOURCE. # @@ -271,6 +272,7 @@ check_bash_version() { # __stdlib_init__() { __log_init__ + set_log_category_level -l base_bash_libs INFO # # Handle global arguments and strip them from the list before passing control to the main script. @@ -290,10 +292,13 @@ __stdlib_init__() { case "$arg" in --debug-wrapper) set_log_level DEBUG + set_log_category_level -l base_bash_libs DEBUG export LOG_DEBUG=1 ;; --verbose-wrapper) + # Deprecated compatibility option; prefer --debug-wrapper. set_log_level VERBOSE + set_log_category_level -l base_bash_libs VERBOSE export LOG_DEBUG=1 ;; --utc-wrapper) @@ -311,7 +316,7 @@ __stdlib_init__() { fi done __init_colors__ - log_debug "Command line: $0 ${__SCRIPT_ARGS__[*]}" + log_debug -l base_bash_libs.std "Command line: $0 ${__SCRIPT_ARGS__[*]}" return 0 } @@ -374,7 +379,7 @@ add_to_path() { case "$opt" in n) strict=0 ;; # don't care if directory exists or not before adding it to PATH p) prepend=1 ;; # prepend the directory to PATH instead of appending - *) log_error "add_to_path: invalid option '$opt'" + *) log_error -l base_bash_libs.std "add_to_path: invalid option '$opt'" return 1 ;; esac @@ -458,6 +463,7 @@ __log_init__() { # Note the '-g' option passed to declare is essential for global scope. unset _log_levels _loggers_level_map _log_category_level_map declare -gA _log_levels _loggers_level_map _log_category_level_map + # VERBOSE is deprecated compatibility surface; new callers should use DEBUG. _log_levels=([FATAL]=0 [ERROR]=1 [WARN]=2 [INFO]=3 [DEBUG]=4 [VERBOSE]=5) # Terminal output defaults to INFO. Category filtering is a separate, @@ -800,6 +806,7 @@ log_error() { __print_log__ ERROR "$@"; } log_warn() { __print_log__ WARN "$@"; } log_info() { __print_log__ INFO "$@"; } log_debug() { __print_log__ DEBUG "$@"; } +# Deprecated compatibility helper; prefer log_debug. log_verbose() { __print_log__ VERBOSE "$@"; } # @@ -807,6 +814,7 @@ log_verbose() { __print_log__ VERBOSE "$@"; } # log_info_file() { __print_log_file__ INFO "$@"; } log_debug_file() { __print_log_file__ DEBUG "$@"; } +# Deprecated compatibility helper; prefer log_debug_file. log_verbose_file() { __print_log_file__ VERBOSE "$@"; } # @@ -814,9 +822,11 @@ log_verbose_file() { __print_log_file__ VERBOSE "$@"; } # log_info_enter() { __print_log__ INFO "Entering function ${FUNCNAME[1]}"; } log_debug_enter() { __print_log__ DEBUG "Entering function ${FUNCNAME[1]}"; } +# Deprecated compatibility helper; prefer log_debug_enter. log_verbose_enter() { __print_log__ VERBOSE "Entering function ${FUNCNAME[1]}"; } log_info_leave() { __print_log__ INFO "Leaving function ${FUNCNAME[1]}"; } log_debug_leave() { __print_log__ DEBUG "Leaving function ${FUNCNAME[1]}"; } +# Deprecated compatibility helper; prefer log_debug_leave. log_verbose_leave() { __print_log__ VERBOSE "Leaving function ${FUNCNAME[1]}"; } # @@ -884,16 +894,16 @@ exit_if_error() { message="No message specified" fi if ! [[ $rc =~ $num_re ]]; then - log_error "'$rc' is not a valid exit code; it needs to be a number greater than zero. Treating it as 1." + log_error -l base_bash_libs.std "'$rc' is not a valid exit code; it needs to be a number greater than zero. Treating it as 1." rc=1 elif ! __std_decimal_integer_value__ normalized_rc "$rc"; then - log_error "'$rc' is not a valid decimal exit code. Treating it as 1." + log_error -l base_bash_libs.std "'$rc' is not a valid decimal exit code. Treating it as 1." rc=1 else rc="$normalized_rc" fi ((rc)) && { - log_fatal "$message" + log_fatal -l base_bash_libs.std "$message" dump_trace exit "$rc" } @@ -1097,7 +1107,7 @@ __std_run_impl__() { --timeout) shift if (($# == 0)) || ! __std_is_positive_integer__ "${1-}"; then - log_error "$helper_name: timeout seconds must be a positive integer." + log_error -l base_bash_libs.std "$helper_name: timeout seconds must be a positive integer." return 1 fi __std_decimal_integer_value__ timeout_seconds "$1" @@ -1106,7 +1116,7 @@ __std_run_impl__() { --max-attempts | --retry-attempts) shift if (($# == 0)) || ! __std_is_positive_integer__ "${1-}"; then - log_error "$helper_name: max attempts must be a positive integer." + log_error -l base_bash_libs.std "$helper_name: max attempts must be a positive integer." return 1 fi __std_decimal_integer_value__ max_attempts "$1" @@ -1115,7 +1125,7 @@ __std_run_impl__() { --retry-delay) shift if (($# == 0)) || ! __std_is_non_negative_integer__ "${1-}"; then - log_error "$helper_name: retry delay seconds must be a non-negative integer." + log_error -l base_bash_libs.std "$helper_name: retry delay seconds must be a non-negative integer." return 1 fi __std_decimal_integer_value__ retry_delay "$1" @@ -1127,7 +1137,7 @@ __std_run_impl__() { ;; *) if [[ "${1-}" == --* ]]; then - log_error "$helper_name: unknown option '$1'. Use -- before commands that begin with --." + log_error -l base_bash_libs.std "$helper_name: unknown option '$1'. Use -- before commands that begin with --." return 1 fi break @@ -1137,7 +1147,7 @@ __std_run_impl__() { # Check if the command is empty. if [[ $# -eq 0 ]]; then - log_error "$helper_name: No command provided." + log_error -l base_bash_libs.std "$helper_name: No command provided." return 1 fi @@ -1154,9 +1164,9 @@ __std_run_impl__() { # print a command and its arguments in a way that is unambiguous and # could be copied and pasted back into a shell. if [[ -n "$policy_description" ]]; then - log_info "[DRY-RUN] Would run with ${policy_description}: ${printable_command}" + log_info -l base_bash_libs.std "[DRY-RUN] Would run with ${policy_description}: ${printable_command}" else - log_info "[DRY-RUN] Would run: ${printable_command}" + log_info -l base_bash_libs.std "[DRY-RUN] Would run: ${printable_command}" fi return 0 fi @@ -1180,7 +1190,7 @@ __std_run_impl__() { if ((attempt < max_attempts)); then if ((! quiet)); then __std_run_status_message__ message "$exit_code" "$timeout_seconds" "$printable_command" - log_warn "${message} (attempt ${attempt} of ${max_attempts}; retrying)." + log_warn -l base_bash_libs.std "${message} (attempt ${attempt} of ${max_attempts}; retrying)." fi if ((retry_delay > 0)); then __std_sleep_interval__ "$retry_delay" @@ -1204,7 +1214,7 @@ __std_run_impl__() { exit_if_error "$exit_code" "$message" else if ((! quiet)); then - log_warn "$message (continuing)." + log_warn -l base_bash_libs.std "$message (continuing)." fi return $exit_code fi @@ -1307,7 +1317,7 @@ safe_mkdir() { case "$opt" in p) mkdir_args=(-p) ;; \?) - log_error "safe_mkdir: invalid option '-$OPTARG'" + log_error -l base_bash_libs.std "safe_mkdir: invalid option '-$OPTARG'" return 1 ;; esac @@ -1315,7 +1325,7 @@ safe_mkdir() { shift $((OPTIND - 1)) if (($# == 0)); then - log_warn "safe_mkdir: No directories provided to create." + log_warn -l base_bash_libs.std "safe_mkdir: No directories provided to create." return 0 fi @@ -1348,7 +1358,7 @@ safe_touch() { local file touch_path if (($# == 0)); then - log_warn "safe_touch: No files provided to touch." + log_warn -l base_bash_libs.std "safe_touch: No files provided to touch." return 0 fi @@ -1386,7 +1396,7 @@ safe_truncate() { local file if (($# == 0)); then - log_warn "safe_truncate: No files provided to truncate." + log_warn -l base_bash_libs.std "safe_truncate: No files provided to truncate." return 0 fi @@ -1438,14 +1448,14 @@ __std_run_cleanup_hooks__() { for hook in "${__std_cleanup_hooks[@]}"; do if ! "$hook"; then - log_warn "Cleanup hook '$hook' failed." + log_warn -l base_bash_libs.std "Cleanup hook '$hook' failed." fi done for cleanup_path in "${__std_cleanup_paths[@]}"; do [[ -e "$cleanup_path" || -L "$cleanup_path" ]] || continue if ! rm -rf -- "$cleanup_path"; then - log_warn "Cleanup path '$cleanup_path' could not be removed." + log_warn -l base_bash_libs.std "Cleanup path '$cleanup_path' could not be removed." fi done @@ -1477,11 +1487,11 @@ std_register_cleanup_hook() { local hook="${1-}" existing_hook if (($# != 1)); then - log_error "std_register_cleanup_hook: expected exactly one function name." + log_error -l base_bash_libs.std "std_register_cleanup_hook: expected exactly one function name." return 1 fi if ! __is_valid_variable_name__ "$hook" || ! declare -F "$hook" >/dev/null; then - log_error "std_register_cleanup_hook: '$hook' is not a defined cleanup function." + log_error -l base_bash_libs.std "std_register_cleanup_hook: '$hook' is not a defined cleanup function." return 1 fi @@ -1505,7 +1515,7 @@ std_unregister_cleanup_hook() { local -a remaining_hooks=() if (($# != 1)); then - log_error "std_unregister_cleanup_hook: expected exactly one function name." + log_error -l base_bash_libs.std "std_unregister_cleanup_hook: expected exactly one function name." return 1 fi @@ -1550,13 +1560,13 @@ std_register_cleanup_path() { local already_registered had_valid_path=0 status=0 if (($# == 0)); then - log_warn "std_register_cleanup_path: No paths provided." + log_warn -l base_bash_libs.std "std_register_cleanup_path: No paths provided." return 0 fi for path; do if ! __std_is_safe_cleanup_path__ "$path"; then - log_error "std_register_cleanup_path: refusing to register unsafe cleanup path '$path'." + log_error -l base_bash_libs.std "std_register_cleanup_path: refusing to register unsafe cleanup path '$path'." status=1 continue fi @@ -1596,13 +1606,13 @@ std_unregister_cleanup_path() { local -a paths_to_remove=() remaining_paths=() if (($# == 0)); then - log_warn "std_unregister_cleanup_path: No paths provided." + log_warn -l base_bash_libs.std "std_unregister_cleanup_path: No paths provided." return 0 fi for path; do if ! __std_is_safe_cleanup_path__ "$path"; then - log_error "std_unregister_cleanup_path: refusing to unregister unsafe cleanup path '$path'." + log_error -l base_bash_libs.std "std_unregister_cleanup_path: refusing to unregister unsafe cleanup path '$path'." status=1 continue fi @@ -1652,7 +1662,7 @@ __std_make_temp_path__() { done if (($# < 1 || $# > 2)); then - log_error "$__std_temp_helper_name: usage: $__std_temp_helper_name [--keep] [prefix]" + log_error -l base_bash_libs.std "$__std_temp_helper_name: usage: $__std_temp_helper_name [--keep] [prefix]" return 1 fi @@ -1660,31 +1670,31 @@ __std_make_temp_path__() { __std_temp_prefix="${2:-base-bash-libs}" if ! __is_valid_variable_name__ "$__std_temp_result_name"; then - log_error "$__std_temp_helper_name: result variable name must be a valid Bash variable name." + log_error -l base_bash_libs.std "$__std_temp_helper_name: result variable name must be a valid Bash variable name." return 1 fi __std_assert_writable_output__ "$__std_temp_helper_name" "$__std_temp_result_name" || return 1 if [[ -z "$__std_temp_prefix" || "$__std_temp_prefix" == */* ]]; then - log_error "$__std_temp_helper_name: prefix must be a non-empty filename prefix without '/'." + log_error -l base_bash_libs.std "$__std_temp_helper_name: prefix must be a non-empty filename prefix without '/'." return 1 fi __std_temp_root="${TMPDIR:-/tmp}" __std_temp_root="${__std_temp_root%/}" if [[ -z "$__std_temp_root" || ! -d "$__std_temp_root" ]]; then - log_error "$__std_temp_helper_name: TMPDIR is not a directory: ${TMPDIR:-/tmp}" + log_error -l base_bash_libs.std "$__std_temp_helper_name: TMPDIR is not a directory: ${TMPDIR:-/tmp}" return 1 fi __std_temp_template="$__std_temp_root/$__std_temp_prefix.XXXXXXXXXX" if [[ "$__std_temp_path_kind" == "dir" ]]; then __std_temp_path="$(mktemp -d "$__std_temp_template" 2>/dev/null)" || { - log_error "$__std_temp_helper_name: failed to create temporary directory." + log_error -l base_bash_libs.std "$__std_temp_helper_name: failed to create temporary directory." return 1 } else __std_temp_path="$(mktemp "$__std_temp_template" 2>/dev/null)" || { - log_error "$__std_temp_helper_name: failed to create temporary file." + log_error -l base_bash_libs.std "$__std_temp_helper_name: failed to create temporary file." return 1 } fi @@ -1739,7 +1749,7 @@ __std_assert_writable_output__() { attributes="${declaration#declare -}" attributes="${attributes%% *}" if [[ "$attributes" == *r* ]]; then - log_error "$function_name: result variable '$output_name' is readonly." + log_error -l base_bash_libs.std "$function_name: result variable '$output_name' is readonly." return 1 fi return 0 @@ -1847,11 +1857,11 @@ std_command_path() { local __std_command_result_name="${1-}" __std_command_name="${2-}" __std_command_resolved_path="" if (($# != 2)); then - log_error "std_command_path: usage: std_command_path " + log_error -l base_bash_libs.std "std_command_path: usage: std_command_path " return 1 fi if ! __is_valid_variable_name__ "$__std_command_result_name"; then - log_error "std_command_path: result variable name must be a valid Bash variable name." + log_error -l base_bash_libs.std "std_command_path: result variable name must be a valid Bash variable name." return 1 fi __std_assert_writable_output__ std_command_path "$__std_command_result_name" || return 1 @@ -2061,7 +2071,7 @@ assert_command_exists() { local cmd if (($# == 0)); then - log_warn "assert_command_exists: No commands provided to check." + log_warn -l base_bash_libs.std "assert_command_exists: No commands provided to check." return 0 fi @@ -2096,7 +2106,7 @@ assert_file_exists() { local file if (($# == 0)); then - log_warn "assert_file_exists: No files provided to check." + log_warn -l base_bash_libs.std "assert_file_exists: No files provided to check." return 0 fi @@ -2135,7 +2145,7 @@ assert_executable() { local executable if (($# == 0)); then - log_warn "assert_executable: No executable paths provided to check." + log_warn -l base_bash_libs.std "assert_executable: No executable paths provided to check." return 0 fi @@ -2170,7 +2180,7 @@ assert_dir_exists() { local dir if (($# == 0)); then - log_warn "assert_dir_exists: No directories provided to check." + log_warn -l base_bash_libs.std "assert_dir_exists: No directories provided to check." return 0 fi @@ -2250,14 +2260,14 @@ get_my_source_dir() { # ask_yes_no() { if (("$#" != 1)); then - log_error "ask_yes_no: invalid arguments" - log_info "Usage: ask_yes_no " + log_error -l base_bash_libs.std "ask_yes_no: invalid arguments" + log_info -l base_bash_libs.std "Usage: ask_yes_no " return 1 fi local message=$1 user_input tty_fd if ! exec {tty_fd}/dev/null; then - log_error "ask_yes_no: /dev/tty is not available" + log_error -l base_bash_libs.std "ask_yes_no: /dev/tty is not available" return 1 fi @@ -2298,14 +2308,14 @@ ask_yes_no() { # wait_for_enter() { if (("$#" > 1)); then - log_error "wait_for_enter: invalid arguments" - log_info "Usage: wait_for_enter [prompt_message]" + log_error -l base_bash_libs.std "wait_for_enter: invalid arguments" + log_info -l base_bash_libs.std "Usage: wait_for_enter [prompt_message]" return 1 fi local prompt=${1:-"Press Enter to continue"} tty_fd read_status if ! exec {tty_fd}/dev/null; then - log_error "wait_for_enter: /dev/tty is not available" + log_error -l base_bash_libs.std "wait_for_enter: /dev/tty is not available" return 1 fi @@ -2314,7 +2324,7 @@ wait_for_enter() { exec {tty_fd}<&- if ((read_status != 0)); then - log_error "wait_for_enter: failed to read from /dev/tty" + log_error -l base_bash_libs.std "wait_for_enter: failed to read from /dev/tty" return "$read_status" fi diff --git a/lib/bash/std/tests/lib_std.bats b/lib/bash/std/tests/lib_std.bats index 6915bf3..9bb0450 100644 --- a/lib/bash/std/tests/lib_std.bats +++ b/lib/bash/std/tests/lib_std.bats @@ -120,7 +120,7 @@ teardown() { PATH="$BASE_TEST_ORIG_PATH" } -@test "sourcing stdlib preserves original args and strips wrapper flags" { +@test "deprecated --verbose-wrapper preserves original args and enables VERBOSE compatibility" { local script="$TEST_TMPDIR/check-init.sh" create_script "$script" < "$target" + set_log_level VERBOSE + + log_verbose_file "$target" 2>"$stderr_file" + + [[ "$(cat "$stderr_file")" == *"VERBOSE"*"Contents of file '$target':"* ]] + [[ "$(cat "$stderr_file")" == *"verbose file contents"* ]] +} + @test "file logging helpers apply category gates and sink decisions" { local target="$TEST_TMPDIR/log-category-target.txt" local stderr_file="$TEST_TMPDIR/log-category-file.err" @@ -908,8 +950,8 @@ EOF set_log_level VERBOSE trace_me 2>"$stderr_file" - [[ "$(cat "$stderr_file")" == *"Entering function trace_me"* ]] - [[ "$(cat "$stderr_file")" == *"Leaving function trace_me"* ]] + [[ "$(cat "$stderr_file")" == *"VERBOSE"*"Entering function trace_me"* ]] + [[ "$(cat "$stderr_file")" == *"VERBOSE"*"Leaving function trace_me"* ]] } @test "print helpers emit expected text" {