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

### Changed

- Assigned reusable-library records to `base_bash_libs.<module>` 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.
- Include the local numeric timezone offset in default structured log
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
Expand Down
2 changes: 2 additions & 0 deletions bin/base-bash
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 15 additions & 15 deletions lib/bash/arg/lib_arg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -122,7 +122,7 @@ arg_parse() {
local __arg_parse_options=1

if (($# < 4)) || [[ "${4-}" != "--" ]]; then
log_error "arg_parse: usage: arg_parse <options_assoc> <positionals_array> <specs_array> -- [args...]"
log_error -l base_bash_libs.arg "arg_parse: usage: arg_parse <options_assoc> <positionals_array> <specs_array> -- [args...]"
return 2
fi

Expand Down Expand Up @@ -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

Expand All @@ -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

Expand All @@ -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
Expand Down
64 changes: 32 additions & 32 deletions lib/bash/file/lib_file.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -179,7 +179,7 @@ __file_section_marker_counts__() {
#
file_section_exists() {
if [[ $# -ne 3 ]]; then
log_error "file_section_exists: expected <target_file> <beginning_marker> <end_marker>."
log_error -l base_bash_libs.file "file_section_exists: expected <target_file> <beginning_marker> <end_marker>."
return 2
fi

Expand All @@ -205,7 +205,7 @@ file_section_exists() {
#
file_section_needs_update() {
if [[ $# -lt 3 ]]; then
log_error "file_section_needs_update: expected <target_file> <beginning_marker> <end_marker> [content_lines...]."
log_error -l base_bash_libs.file "file_section_needs_update: expected <target_file> <beginning_marker> <end_marker> [content_lines...]."
return 2
fi

Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 <target_file> <beginning_marker> <end_marker>"
log_info -l base_bash_libs.file "Usage: update_file_section -r <target_file> <beginning_marker> <end_marker>"
else
log_info "Usage: update_file_section <target_file> <beginning_marker> <end_marker> [new_lines...]"
log_info -l base_bash_libs.file "Usage: update_file_section <target_file> <beginning_marker> <end_marker> [new_lines...]"
fi
return 1
fi
Expand All @@ -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 <target_file> <beginning_marker> <end_marker>"
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 <target_file> <beginning_marker> <end_marker>"
return 1
fi
else
Expand All @@ -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

Expand All @@ -371,27 +371,27 @@ 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
fi

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
Expand All @@ -413,31 +413,31 @@ 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
__file_remove_temp_paths__ "$current_content_file"
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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/bash/file/tests/lib_file.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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 ]
Expand Down
Loading
Loading