#!/bin/bash util__gate_printable() { # # True if stdin had non-whitespace values # local value value=$(cat) grep -q '[[:print:]]' <<<"$value" || return 1 echo "$value" } util__isa_bool() { # # True if $1 is a boolean value # test "$1" == false && return 0 test "$1" == true && return 0 return 1 } util__isa_enum() { # # True if $1 is one of $2.. # local value=$1; shift local alloweds=("$@") local allowed for allowed in "${alloweds[@]}"; do test "$value" == "$allowed" && return 0 done return 1 } util__isa_int() { # # True if $1 is an integer # # Note that $1 does not have to be decimal; in POSIX shells, # eg. 0xf1 is a valid hexadecimal integer and 0755 is a valid # octal integer. # { test -z "$1" && return 1 test "$1" -ge 0 && return 0 return 1 } 2>/dev/null } util__isa_name() { # # True if $1 is a name in most languages # echo "$1" | grep -qx '[[:alpha:]_][[:alnum:]_]*' } util__isa_posint() { # # True if $1 is a positive integer # { test -z "$1" && return 1 test "$1" -ge 0 && return 0 return 1 } 2>/dev/null } util__join() { # # Use $1 to join items $2.. # local dlm=$1; shift local items=("$@") local item local head=true for item in "${items[@]}"; do $head || echo -n "$dlm" $head && head=false echo -n "$item" done } util__loadarr() { # # Save output lines from command $2.. to array named $1 # # Usage: # util__loadarr ARRAY CMD [ARG].. # # Unlike `mapfile -t <<<"$(command)", this produces zero # items if command output was empty. # local __util__arr_fromcmd__oarr=$1; shift local __util__arr_fromcmd__cmd=("$@") local __util__arr_fromcmd__tmp local __util__arr_fromcmd__es __util__arr_val_oarr "$__util__arr_fromcmd__oarr" || return 2 __util__arr_fromcmd__tmp=$(mktemp /tmp/util__loadarr.__util__arr_fromcmd__tmp.XXXXX) "${__util__arr_fromcmd__cmd[@]}" > "$__util__arr_fromcmd__tmp"; __util__arr_fromcmd__es=$? mapfile -t "$__util__arr_fromcmd__oarr" <"$__util__arr_fromcmd__tmp" rm "$__util__arr_fromcmd__tmp" return $__util__arr_fromcmd__es } util__loadenv() { # # Safely load value from envvar $1 # local name=$1 local value value=${!name} test -n "$value" || return 1 echo "$value" } util__loadenv_bool() { # # Safely load integer from envvar $1 # local name=$1 __util__safe_loadenv \ "$name" \ util__isa_bool \ "not a boolean (true|false): $name=$value" } util__loadenv_enum() { # # Safely load enum from envvar $1, allowing values $2.. # local name=$1; shift local allowed_values=("$@") local value local desc_allowed value=${!name} test -n "$value" || return 1 util__isa_enum "$value" "${allowed_values[@]}" || { desc_allowed=$(util__join "|" "${allowed_values[@]}") warn "not one of supported values ($desc_allowed): $name = $value" return 3 } echo "$value" } util__loadenv_int() { # # Safely load integer from envvar $1 # local name=$1 __util__safe_loadenv \ "$name" \ util__isa_int \ "not an integer" } util__loadenv_name() { # # Safely load positive integer from envvar $1 # local name=$1 __util__safe_loadenv \ "$name" \ util__isa_name \ "not a valid name (alphanumeric, starting with letter)" } util__loadenv_posint() { # # Safely load positive integer from envvar $1 # local name=$1 __util__safe_loadenv \ "$name" \ util__isa_posint \ "not a positive integer" } util__sort_by_length() { # # Sort items on stdin by length # while IFS= read -r item; do echo "${#item} $item" done \ | sort -n \ | cut -d' ' -f2- } util__warnbox() { # # Emit warning $2 box-decorated with char $1, follow by lines $3.. # local char=$1; shift local subj=$1; shift local body=("$@") case "${#body[*]}" in 0) __util__warnbox1 "$char" "$subj" ;; *) __util__warnbox_sub "$char" "$subj" "${body[@]}" ;; esac } __util__warnbox1() { # # Emit warning $2 box-decorated with char $1 # local char=$1 local msg=$2 local line local midline="$char$char $msg $char$char" line=$(__util__make_hr "$char" "$midline") warn "$line" \ "$midline" \ "$line" } __util__warnbox_sub() { # # Emit warning $2 box-decorated with char $1, follow by lines $3.. # local char=$1; shift # decoration character local subj=$1; shift # local of the message subject local body=("$@") # lines of the message body local longest # longest line from subject and body local width # length of ^^ local hr # horizontal ruler local vr="$char$char" # vertical ruler point local padded_body # body lines; padded local padded_subj # body line; padded local line # each of ^^^ local out_lines=() # final lines longest=$(__util__longest "$subj" "${body[@]}") width=${#longest} padded_subj=$(__util__rpad "${width}" "$subj") util__loadarr padded_body __util__rpad "$width" "${body[@]}" debug_var longest width padded_subj padded_body hr=$(__util__make_hr "$char" "$vr $longest $vr") out_lines+=( "$hr" "$vr $padded_subj $vr" "$hr" ) for line in "${padded_body[@]}"; do out_lines+=("$vr $line $vr") done out_lines+=("$hr") warn "${out_lines[@]}" } __util__longest() { # # Choose longest of $@ # printf '%s\n' "$@" \ | util__sort_by_length \ | tail -n 1 } __util__make_hr() { # # Repeat char $1 to the length of $2 # local char=$1 local stuff=$2 local length=${#stuff} __util__repchar "$char" "$length" } __util__repchar() { # # Repeat char $1 $2 times # local char=$1 local times=$2 local togo=$times while test "$togo" -gt 0; do echo -n "$char" ((togo--)) done echo } __util__rpad() { # # Right-pad all of $2.. to length $1 with space # local wid=$1; shift local rest=("$@") printf "%-${wid}s"'\n' "${rest[@]}" } __util__safe_loadenv() { # # Safely load envvar $1 with validator $2 and error message $3 # local name=$1 local validator=$2 local msg=$3 local value value=${!name} debug_var name value test -n "$value" || return 1 "$validator" "$value" || { warn "$msg: $name=$value" return 3 } echo "$value" } __util__isa_name() { # # True if $1 is a name in most languages # echo "$1" | grep -qx '[[:alpha:]_][[:alnum:]_]*' } __util__arr_val_oarr() { # # Validate output array named $1 # local name=$1 __util__isa_name "$name" || { warn "invalid array name: $name"; return 2; } }