You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
676 lines
23 KiB
676 lines
23 KiB
#!/usr/bin/env bash
|
|
#!/usr/bin/env -S -- bash -x
|
|
|
|
# constants
|
|
|
|
MIAOU_DEBUG=${MIAOU_DEBUG:-false}
|
|
MIAOU_ERROR_REGEX='^(.*): line ([0-9]+): miaou_error:'
|
|
|
|
declare -A ANSI
|
|
ANSI[RESET]=0
|
|
|
|
ANSI[STYLE_ITALIC]=3
|
|
ANSI[STYLE_BOLD]=1
|
|
ANSI[STYLE_UNDERLINE]=4
|
|
ANSI[STYLE_STRIKETHROUGH]=9
|
|
|
|
ANSI[FG_BLACK]=30
|
|
ANSI[FG_RED]=31
|
|
ANSI[FG_GREEN]=32
|
|
ANSI[FG_YELLOW]=33
|
|
ANSI[FG_BLUE]=34
|
|
ANSI[FG_PURPLE]=35
|
|
ANSI[FG_CYAN]=36
|
|
ANSI[FG_WHITE]=37
|
|
ANSI[FG_GRAY]=90
|
|
ANSI[FG_LIGHTBLUE]=94
|
|
ANSI[FG_MAGENTA]=95
|
|
|
|
ANSI[BG_BLACK]=40
|
|
ANSI[BG_RED]=41
|
|
ANSI[BG_GREEN]=42
|
|
ANSI[BG_YELLOW]=43
|
|
ANSI[BG_BLUE]=44
|
|
ANSI[BG_PURPLE]=45
|
|
ANSI[BG_CYAN]=46
|
|
ANSI[BG_WHITE]=47
|
|
ANSI[BG_GRAY]=90
|
|
ANSI[BG_MAGENTA]=95
|
|
|
|
readonly MIAOU_DEBUG MIAOU_ERROR_REGEX ANSI
|
|
|
|
## functions
|
|
|
|
function is_true {
|
|
[[ "${1:-}" == 'true' ]]
|
|
}
|
|
|
|
# HINT: define an alias to bypass ARG evaluation when unnecessary!
|
|
# example: alias toto="bash -c \"[[ \\\$MIAOU_DEBUG == 'true' ]] && echo yes \\\$* || echo nope\""
|
|
function miaou_debug {
|
|
if is_true "${MIAOU_DEBUG}"; then
|
|
local saved_bash_rematch=("${BASH_REMATCH[@]}") # important when regex are used before calling miaou_debug
|
|
local i message="$*" padding=''
|
|
local compute_hierarchy=true
|
|
[[ ! -v miaou_debug_hierarchy ]] && miaou_debug_hierarchy=0
|
|
|
|
regex='^</' # starts with </
|
|
[[ $miaou_debug_hierarchy -gt 0 ]] &&
|
|
[[ "${message}" =~ $regex ]] &&
|
|
miaou_debug_hierarchy=$((miaou_debug_hierarchy - 1)) &&
|
|
compute_hierarchy=false
|
|
|
|
for ((i = 0; i < miaou_debug_hierarchy; i++)); do padding+=' '; done
|
|
>&4 builtin echo -e "\e[90m <== DEBUG ==> ${padding}\e[33m${message}\e[0m"
|
|
|
|
regex='[^/]>$' # does NOT end with />
|
|
is_true $compute_hierarchy &&
|
|
[[ "${message}" =~ $regex ]] &&
|
|
miaou_debug_hierarchy=$((miaou_debug_hierarchy + 1)) ||
|
|
true
|
|
BASH_REMATCH=("${saved_bash_rematch[@]}") # important when regex are used before calling miaou_debug
|
|
fi
|
|
}
|
|
|
|
function on_return {
|
|
local regex='return ([0-9]+)'
|
|
if [[ "$BASH_COMMAND" =~ $regex ]] && [[ "${BASH_REMATCH[1]}" -gt 0 ]]; then
|
|
failure_code="${BASH_REMATCH[1]}"
|
|
failure_type="RETURN"
|
|
failure_payload="${FUNCNAME[0]}"
|
|
print_miaou_error "${BASH_SOURCE[1]}" "${BASH_LINENO[0]}" "${failure_code}" "${failure_type}" "${failure_payload}"
|
|
miaou_debug "<on_return code=$failure_code payload=$failure_payload/>"
|
|
fi
|
|
}
|
|
|
|
# overridden function which permits backtracing of the original `exit` statement
|
|
function exit {
|
|
code=${1:-0}
|
|
if [[ $code -gt 0 ]]; then
|
|
error_source=("${BASH_SOURCE[@]}")
|
|
error_lineno=("${BASH_LINENO[@]}")
|
|
error_funcname=("${FUNCNAME[@]}")
|
|
unset 'error_source[-1]' 'error_lineno[-1]' 'error_funcname[-1]'
|
|
unset 'error_source[0]' 'error_lineno[-1]' 'error_funcname[0]'
|
|
error_funcname[-1]='<main>'
|
|
error_source=("${error_source[@]}")
|
|
error_lineno=("${error_lineno[@]}")
|
|
error_funcname=("${error_funcname[@]}")
|
|
|
|
failure_code=${1:-0}
|
|
failure_type="EXIT"
|
|
failure_payload="${error_funcname[0]}"
|
|
|
|
miaou_debug "<overridden_exit code=$failure_code/>"
|
|
print_miaou_error "${BASH_SOURCE[1]}" "${BASH_LINENO[0]}" "${failure_code}" "${failure_type}" "${failure_payload}"
|
|
fi
|
|
builtin exit "$code"
|
|
}
|
|
|
|
function tty_ansi {
|
|
local code
|
|
for code in "$@"; do
|
|
if [[ -v ANSI[$code] ]]; then
|
|
>&4 echo -ne "\e[${ANSI[$code]}m"
|
|
else
|
|
>&2 echo "unknown ansi key: <$code>"
|
|
break
|
|
fi
|
|
done
|
|
}
|
|
|
|
function print_miaou_error {
|
|
local source="$1" line="$2" code="$3" type="$4" payload="${5:-}"
|
|
local message
|
|
message="${source}: line ${line}: miaou_error: ${type} ${code}"
|
|
[[ -n "$payload" ]] && message+=" payload: ${payload}"
|
|
>&2 echo "$message"
|
|
}
|
|
|
|
function add_miaou_error {
|
|
local source="$1" line="$2" code="$3" type="$4" payload="${5:-}"
|
|
local message
|
|
message="${source}: line ${line}: miaou_error: ${type} ${code}"
|
|
[[ -n "$payload" ]] && message+=" payload: ${payload}"
|
|
tmp_error+=("$message")
|
|
}
|
|
|
|
function on_error {
|
|
trap - RETURN
|
|
failure_code="${1:-200}"
|
|
|
|
miaou_debug "<on_error code=${failure_code} command={${BASH_COMMAND}}>"
|
|
|
|
if [[ $$ == "$BASHPID" ]]; then
|
|
if [[ ! -v failure_type ]]; then
|
|
if [[ ${BASH_COMMAND} == 'false' ]]; then
|
|
failure_type="FALSE"
|
|
print_miaou_error "${BASH_SOURCE[1]}" "${BASH_LINENO[0]}" "${failure_code}" "${failure_type}"
|
|
else
|
|
failure_type="COMMAND_ERROR"
|
|
failure_payload="${BASH_COMMAND%%$'\n'*}" # first line only
|
|
print_miaou_error "${BASH_SOURCE[1]}" "${BASH_LINENO[0]}" "${failure_code}" "${failure_type}" "${failure_payload}"
|
|
|
|
load_tmp_error # cannot print_miaou_error after investigation
|
|
miaou_debug "<investigate_tmp_error pid=${BASHPID}>"
|
|
|
|
if [[ ${#tmp_error[@]} -gt 1 ]]; then
|
|
local last_error regex
|
|
last_error="${tmp_error[-2]}" # the second latest cause previous print_miaou_error above
|
|
|
|
regex='^(.*): line ([0-9]+): (.*): command not found$'
|
|
if [[ $failure_code == 127 && "$last_error" =~ $regex ]]; then
|
|
failure_type='COMMAND_NOT_FOUND'
|
|
failure_payload="${BASH_REMATCH[3]}"
|
|
miaou_debug "<command_not_found payload=${failure_payload}/>"
|
|
else
|
|
regex='^(.*): line ([0-9]+): (.*): No such file or directory$'
|
|
if [[ $failure_code == 127 && "$last_error" =~ $regex ]]; then
|
|
failure_type='FILE_NOT_FOUND'
|
|
failure_payload="${BASH_REMATCH[3]}"
|
|
miaou_debug "<file_not_found payload=${failure_payload}/>"
|
|
|
|
elif ! is_true "$CHILD_PROCESS"; then
|
|
# in main process only, not from children!
|
|
local regex2 source lineno funcname
|
|
last_error="${tmp_error[-1]}"
|
|
regex2='^[[:space:]]+miaou_trace: source=(.*) lineno=(.*) funcname=(.*)$'
|
|
if [[ "$last_error" =~ $regex2 ]]; then
|
|
miaou_debug "<miaou_trace>"
|
|
source="${BASH_REMATCH[1]}"
|
|
lineno="${BASH_REMATCH[2]}"
|
|
funcname="${BASH_REMATCH[3]}"
|
|
extra_source=("$source")
|
|
extra_lineno=("$lineno")
|
|
extra_funcname=("$funcname")
|
|
# miaou_debug "<miaou_trace found_first source=${extra_source} lineno=${extra_lineno} funcname=${extra_funcname}/>"
|
|
# go backward
|
|
for ((i = ${#tmp_error[@]} - 2; i >= 0; i--)); do
|
|
content=${tmp_error[$i]}
|
|
# miaou_debug "<index i=$i content=$content>"
|
|
if [[ "$content" =~ $regex2 ]]; then
|
|
source="${BASH_REMATCH[1]}"
|
|
lineno="${BASH_REMATCH[2]}"
|
|
funcname="${BASH_REMATCH[3]}"
|
|
extra_source=("$source" "${extra_source[@]}")
|
|
extra_lineno=("$lineno" "${extra_lineno[@]}")
|
|
extra_funcname=("$funcname" "${extra_funcname[@]}")
|
|
# miaou_debug '<miaou_trace found_remainders/>'
|
|
else
|
|
regex2='^(.*): line ([0-9]+): miaou_error: (.*) [0-9]+(.*)$'
|
|
if [[ "$content" =~ $regex2 ]]; then
|
|
failure_type='CHILD_ERROR'
|
|
failure_payload="${BASH_REMATCH[3]}"
|
|
local optional_payload
|
|
optional_payload="${BASH_REMATCH[4]}"
|
|
[[ -n "$optional_payload" ]] &&
|
|
optional_payload="${optional_payload# payload: }" &&
|
|
failure_payload="${failure_payload} ${optional_payload}"
|
|
miaou_debug "<child_error extra_traces=${#extra_source[@]} payload={${failure_payload}}/>"
|
|
else
|
|
miaou_debug '<child_error no_miaou_error weird=true/>'
|
|
fi
|
|
break
|
|
fi
|
|
# miaou_debug '</index>'
|
|
done
|
|
miaou_debug "</miaou_trace/>"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
miaou_debug '</investigate_tmp_error>'
|
|
fi
|
|
fi
|
|
|
|
error_source=("${BASH_SOURCE[@]}")
|
|
error_lineno=("${BASH_LINENO[@]}")
|
|
error_funcname=("${FUNCNAME[@]}")
|
|
unset 'error_source[-1]' 'error_lineno[-1]' 'error_funcname[-1]'
|
|
unset 'error_source[0]' 'error_lineno[-1]' 'error_funcname[0]'
|
|
error_funcname[-1]='<main>'
|
|
|
|
if [[ $failure_type == 'CHILD_ERROR' ]]; then
|
|
error_source=("${extra_source[@]}" "${error_source[@]}")
|
|
error_lineno=("${extra_lineno[@]}" "${error_lineno[@]}")
|
|
error_funcname=("${extra_funcname[@]}" "${error_funcname[@]}")
|
|
|
|
# add the funcname hint message from backward
|
|
local funcname hint_for_next=false
|
|
for ((i = 0; i < ${#error_funcname[@]}; i++)); do
|
|
is_true "$hint_for_next" &&
|
|
error_funcname[i]+=' (hint: prefer source than child process)' &&
|
|
hint_for_next=false
|
|
funcname="${error_funcname[$i]}"
|
|
[[ "$funcname" == '<main>' ]] && hint_for_next=true || true
|
|
done
|
|
|
|
else
|
|
error_source=("${error_source[@]}")
|
|
error_lineno=("${error_lineno[@]}")
|
|
error_funcname=("${error_funcname[@]}")
|
|
fi
|
|
|
|
if [[ -v failure_payload ]]; then
|
|
miaou_debug "</on_error exit=$failure_code type=${failure_type} payload={${failure_payload}}>"
|
|
else
|
|
miaou_debug "</on_error exit=$failure_code type=${failure_type}>"
|
|
fi
|
|
builtin exit "$failure_code"
|
|
else
|
|
print_miaou_error "${BASH_SOURCE[1]}" "${BASH_LINENO[0]}" "${failure_code}" 'SUBSHELL_ERROR' "${BASH_COMMAND}"
|
|
miaou_debug "</on_error pipe=$failure_code sub_pid=$BASHPID pid=$$ command={${BASH_COMMAND}}>"
|
|
kill -PIPE $$
|
|
fi
|
|
}
|
|
|
|
function self_upgrade {
|
|
sudo -E miaou-bash "$MIAOU_BASH_DIR"/lib/self-upgrade.bash
|
|
}
|
|
|
|
function uninstall {
|
|
sudo -E miaou-bash "$MIAOU_BASH_DIR"/lib/uninstall.bash
|
|
}
|
|
|
|
function populate_error_arrays {
|
|
|
|
local source=$1 line=$2 payload=$3
|
|
unset 'error_source[0]' 'error_lineno[0]' 'error_funcname[0]'
|
|
unset 'error_source[-1]' 'error_lineno[-1]' 'error_funcname[-1]'
|
|
unset 'error_source[-1]' 'error_lineno[-1]' 'error_funcname[-1]'
|
|
error_source=("$source" "${error_source[@]}")
|
|
error_lineno=("$line" "${error_lineno[@]}")
|
|
error_funcname=("${error_funcname[@]}" '<main>')
|
|
}
|
|
|
|
function on_pipe {
|
|
trap - RETURN
|
|
local tmp_error regex source lineno
|
|
miaou_debug "<on_pipe sub_pid=$BASHPID pid=$$>"
|
|
|
|
error_source=("${BASH_SOURCE[@]}")
|
|
error_lineno=("${BASH_LINENO[@]}")
|
|
error_funcname=("${FUNCNAME[@]}")
|
|
unset 'error_source[0]' 'error_lineno[0]' 'error_funcname[0]'
|
|
unset 'error_source[-1]' 'error_lineno[-1]' 'error_funcname[-1]'
|
|
unset 'error_source[-1]' 'error_lineno[-1]' 'error_funcname[-1]'
|
|
|
|
load_tmp_error
|
|
last_error="${tmp_error[-1]}"
|
|
|
|
regex='^(.*): line ([0-9]+): miaou_error: SUBSHELL_ERROR ([0-9]+) payload: (.*)$'
|
|
if [[ $last_error =~ $regex ]]; then
|
|
source=${BASH_REMATCH[1]}
|
|
lineno=${BASH_REMATCH[2]}
|
|
failure_code=${BASH_REMATCH[3]}
|
|
failure_payload=${BASH_REMATCH[4]}
|
|
failure_type="SUBSHELL_ERROR"
|
|
miaou_debug "<subshell_error payload={${failure_payload}}"
|
|
error_source=("$source" "${error_source[@]}")
|
|
error_lineno=("$lineno" "${error_lineno[@]}")
|
|
error_funcname=("${error_funcname[@]}" '<main>')
|
|
else
|
|
failure_code=141
|
|
failure_type="PIPE_ERROR"
|
|
fi
|
|
miaou_debug "</on_pipe>"
|
|
builtin exit "$failure_code"
|
|
}
|
|
|
|
function dump_stderr {
|
|
local tmp_error_count=${#tmp_error[@]}
|
|
miaou_debug "<stderr count=${tmp_error_count}>"
|
|
if [[ -v MAIN_FD_MIAOU && $tmp_error_count -gt 0 && "$MAIN_FD_STDERR" == false ]]; then
|
|
miaou_debug "<main_fd_miaou/>"
|
|
local last_error="${tmp_error[-1]}"
|
|
if [[ "$last_error" =~ $MIAOU_ERROR_REGEX ]]; then
|
|
tmp_error_count=$((tmp_error_count - 1))
|
|
miaou_debug "<hide_latest_miaou_error line=${last_error} count=$tmp_error_count/>"
|
|
unset 'tmp_error[-1]'
|
|
if [[ $tmp_error_count == 0 ]]; then
|
|
# IDEA: grab last_stdout and test no carriage_return, cf: ../test/stub/tee_last_line.bash
|
|
miaou_debug "<prevent_stdout_no_carriage_return />"
|
|
printf "\r"
|
|
fi
|
|
fi
|
|
fi
|
|
tty_ansi FG_LIGHTBLUE
|
|
for i in "${tmp_error[@]}"; do
|
|
is_true "$CHILD_PROCESS" && miaou_debug "<childerr content={$i}/>"
|
|
echo "$i"
|
|
done
|
|
tty_ansi RESET
|
|
miaou_debug '</stderr>'
|
|
}
|
|
|
|
function dump_failure {
|
|
local i
|
|
miaou_debug '<error_box>'
|
|
tty_ansi FG_MAGENTA
|
|
length_box=$((${#failure_type} + ${#failure_code} + (2 * 2) + 2))
|
|
[[ -v failure_payload ]] && length_box=$((length_box + ${#failure_payload} + (2 * 2) + 1))
|
|
echo -n '╔'
|
|
for ((i = 0; i < length_box; i++)); do echo -n '═'; done
|
|
echo '╗'
|
|
echo -n "║ $failure_type "
|
|
[[ -v failure_payload ]] && echo -n ' ' && tty_ansi BG_WHITE FG_BLACK && echo -n " $failure_payload " && tty_ansi RESET
|
|
tty_ansi FG_YELLOW
|
|
printf "%5s" " $failure_code "
|
|
tty_ansi FG_MAGENTA
|
|
echo "║"
|
|
|
|
if [[ ! -v error_source ]]; then
|
|
echo -n '╚'
|
|
for ((i = 0; i < length_box; i++)); do echo -n '═'; done
|
|
echo '╝'
|
|
miaou_debug '<continuation disabled/>'
|
|
else
|
|
echo -n '╚'
|
|
for ((i = 0; i < 4; i++)); do echo -n '═'; done
|
|
echo -n '╦'
|
|
for ((i = 5; i < length_box; i++)); do echo -n '═'; done
|
|
echo '╝'
|
|
echo " ║"
|
|
miaou_debug '<continuation enabled/>'
|
|
fi
|
|
tty_ansi RESET
|
|
miaou_debug '</error_box>'
|
|
}
|
|
|
|
function save_err_trace_to_main {
|
|
miaou_debug "<save_stacktrace_to_stderr count=${#error_source[@]} to=${MIAOU_BASH_ERROR_CHILDREN}>"
|
|
local i source lineno funcname
|
|
for i in "${tmp_error[@]}"; do >>"$MIAOU_BASH_ERROR_CHILDREN" echo "$i"; done
|
|
for i in "${!error_source[@]}"; do
|
|
source="${error_source[$i]}"
|
|
lineno="${error_lineno[$i]}"
|
|
funcname="${error_funcname[$i]}"
|
|
# miaou_debug "<trace source=${source} lineno=${lineno} funcname=${funcname}/>"
|
|
>>"$MIAOU_BASH_ERROR_CHILDREN" echo -e "\tmiaou_trace: source=${source} lineno=${lineno} funcname=${funcname}"
|
|
done
|
|
miaou_debug "</save_stacktrace_to_stderr>"
|
|
}
|
|
|
|
function dump_stacktrace {
|
|
miaou_debug "<stacktrace count=${#error_source[@]}>"
|
|
local i source lineno funcname
|
|
for i in "${!error_source[@]}"; do
|
|
tty_ansi FG_MAGENTA
|
|
echo -n " ╟──▷"
|
|
tty_ansi STYLE_ITALIC
|
|
source="${error_source[$i]}"
|
|
lineno="${error_lineno[$i]}"
|
|
funcname="${error_funcname[$i]}"
|
|
|
|
local hint regex='(^.*) \(hint: (.*)\)$'
|
|
if [[ "$funcname" =~ $regex ]]; then
|
|
hint="${BASH_REMATCH[2]}"
|
|
funcname="${BASH_REMATCH[1]} (hint: "
|
|
else
|
|
unset hint
|
|
[[ $i -gt 0 ]] && tty_ansi FG_GRAY
|
|
fi
|
|
|
|
printf "%40s:%.4s" "$source" "$lineno"
|
|
tty_ansi RESET
|
|
printf " \t%s" "$funcname"
|
|
|
|
if [[ -v hint ]]; then
|
|
tty_ansi FG_YELLOW
|
|
builtin echo -n "$hint"
|
|
tty_ansi RESET
|
|
builtin echo -n ')'
|
|
fi
|
|
|
|
echo
|
|
done
|
|
miaou_debug '</stacktrace>'
|
|
}
|
|
|
|
function load_tmp_error {
|
|
miaou_debug "<load_tmp_error from=$MIAOU_BASH_ERROR>"
|
|
if [[ ! -v tmp_error ]]; then
|
|
local count count_children tmp_error_children
|
|
if [[ -f "$MIAOU_BASH_ERROR_CURRENT_CHILDREN" ]]; then
|
|
miaou_debug "<tmp_children found=$MIAOU_BASH_ERROR_CURRENT_CHILDREN>"
|
|
mapfile -t tmp_error_children <"$MIAOU_BASH_ERROR_CURRENT_CHILDREN"
|
|
local count_children=${#tmp_error_children[@]}
|
|
if [[ $count_children == 0 ]]; then
|
|
miaou_debug "<tmp_error_children empty/>"
|
|
else
|
|
miaou_debug "<tmp_error_children count=${count_children}/>"
|
|
fi
|
|
miaou_debug "</tmp_children>"
|
|
rm "$MIAOU_BASH_ERROR_CURRENT_CHILDREN"
|
|
fi
|
|
|
|
mapfile -t tmp_error <"$MIAOU_BASH_ERROR"
|
|
|
|
if [[ -v count_children && $count_children -gt 0 ]]; then
|
|
tmp_error=("${tmp_error[@]}" "${tmp_error_children[@]}")
|
|
fi
|
|
local count=${#tmp_error[@]}
|
|
if [[ $count == 0 ]]; then
|
|
miaou_debug "<tmp_error empty/>"
|
|
else
|
|
miaou_debug "<tmp_error count=${count}>"
|
|
local i last_miaou_error_index=-1 content
|
|
local multiple_errors=()
|
|
for i in "${!tmp_error[@]}"; do
|
|
content="${tmp_error[i]}"
|
|
# miaou_debug "<tmperr index=$i content={$content}/>"
|
|
if [[ "$content" =~ $MIAOU_ERROR_REGEX ]]; then
|
|
if [[ $last_miaou_error_index -ge 0 ]]; then
|
|
multiple_errors+=("$last_miaou_error_index")
|
|
fi
|
|
last_miaou_error_index=$i
|
|
fi
|
|
done
|
|
if [[ "${#multiple_errors[@]}" -gt 0 ]]; then
|
|
miaou_debug "<prune multiple_errors=(${multiple_errors[*]}) />"
|
|
for i in "${multiple_errors[@]}"; do unset "tmp_error[$i]"; done
|
|
tmp_error=("${tmp_error[@]}")
|
|
miaou_debug "<tmp_error count=${#tmp_error[@]} clean/>"
|
|
fi
|
|
|
|
miaou_debug "</tmp_error>"
|
|
fi
|
|
else
|
|
local count=${#tmp_error[@]}
|
|
miaou_debug "<already_loaded count=$count/>"
|
|
fi
|
|
miaou_debug '</load_tmp_error>'
|
|
}
|
|
|
|
function on_exit {
|
|
trap - RETURN
|
|
failure_code="${1:-0}"
|
|
local last_error regex
|
|
miaou_debug "<on_exit code=$failure_code>"
|
|
|
|
load_tmp_error
|
|
|
|
if [[ -v failure_type && "$failure_type" == 'RETURN' ]]; then
|
|
last_error="${tmp_error[-1]}"
|
|
regex='^(.*): line ([0-9]+): miaou_error: RETURN [0-9]+'
|
|
if [[ "$last_error" =~ $regex ]]; then
|
|
miaou_debug "<return code=$failure_code/>"
|
|
local source line
|
|
source="${BASH_REMATCH[1]}"
|
|
line="${BASH_REMATCH[2]}"
|
|
error_source=("$source" "${error_source[@]}")
|
|
error_lineno=("$line" "${error_lineno[@]}")
|
|
error_funcname=("$failure_payload" "${error_funcname[@]}")
|
|
else
|
|
miaou_debug "<return type=unexpected/>"
|
|
fi
|
|
|
|
elif [[ -v failure_type && "$failure_type" == 'COMMAND_NOT_FOUND' ]]; then
|
|
unset 'tmp_error[-1]' 'tmp_error[-1]'
|
|
add_miaou_error "${BASH_SOURCE[2]}" "${BASH_LINENO[1]}" "${failure_code}" "${failure_type}" "${failure_payload}"
|
|
miaou_debug "<command_not_found change_stderr count=${#tmp_error[@]}/>"
|
|
|
|
elif [[ -v failure_type && "$failure_type" == 'FILE_NOT_FOUND' ]]; then
|
|
unset 'tmp_error[-1]' 'tmp_error[-1]'
|
|
add_miaou_error "${BASH_SOURCE[2]}" "${BASH_LINENO[1]}" "${failure_code}" "${failure_type}" "${failure_payload}"
|
|
miaou_debug "<file_not_found change_stderr count=${#tmp_error[@]}/>"
|
|
|
|
elif [[ -v failure_type && "$failure_type" == 'CHILD_ERROR' ]]; then
|
|
for ((i = 0; i < ${#extra_source[@]}; i++)); do unset 'tmp_error[-1]'; done
|
|
add_miaou_error "${BASH_SOURCE[2]}" "${BASH_LINENO[1]}" "${failure_code}" "${failure_type}" "${failure_payload}"
|
|
miaou_debug "<child_error change_stderr count=${#tmp_error[@]}/>"
|
|
|
|
elif [[ ! -v failure_type ]]; then
|
|
if [[ $failure_code -gt 0 && ${#tmp_error[@]} -gt 0 ]]; then
|
|
last_error="${tmp_error[-1]}"
|
|
miaou_debug "<investigate_tmp_error last={$last_error}>"
|
|
|
|
regex='^(.*): line ([0-9]+): (.*): unbound variable$'
|
|
if [[ $failure_code -eq 1 ]] && [[ "$last_error" =~ $regex ]]; then
|
|
failure_type='UNBOUND_VARIABLE'
|
|
failure_payload="${BASH_REMATCH[3]}"
|
|
failure_code=2
|
|
|
|
local source="${BASH_REMATCH[1]}" line="${BASH_REMATCH[2]}"
|
|
error_source=("${BASH_SOURCE[@]}")
|
|
error_lineno=("${BASH_LINENO[@]}")
|
|
error_funcname=("${FUNCNAME[@]}")
|
|
populate_error_arrays "$source" "$line" "$failure_payload"
|
|
|
|
unset 'tmp_error[-1]'
|
|
add_miaou_error "$source" "$line" "${failure_code}" "${failure_type}" "${failure_payload}"
|
|
miaou_debug "<unbound_variable source=${source} line=${line} payload=${failure_payload}/>"
|
|
else
|
|
regex='^(.*): line ([0-9]+): (.*): No such file or directory$'
|
|
if [[ $failure_code -eq 1 ]] && [[ "$last_error" =~ $regex ]]; then
|
|
failure_type='SOURCE_NOT_FOUND'
|
|
failure_payload="${BASH_REMATCH[3]}"
|
|
failure_code=3
|
|
|
|
local source="${BASH_REMATCH[1]}" line="${BASH_REMATCH[2]}"
|
|
error_source=("${BASH_SOURCE[@]}")
|
|
error_lineno=("${BASH_LINENO[@]}")
|
|
error_funcname=("${FUNCNAME[@]}")
|
|
populate_error_arrays "$source" "$line" "$failure_payload"
|
|
|
|
unset 'tmp_error[-1]'
|
|
add_miaou_error "$source" "$line" "${failure_code}" "${failure_type}" "${failure_payload}"
|
|
miaou_debug "<source_not_found source=${source} line=${line} payload=${failure_payload}/>"
|
|
else
|
|
failure_type='UNKNOWN ERROR'
|
|
failure_payload="$BASH_COMMAND"
|
|
fi
|
|
fi
|
|
miaou_debug "</investigate_tmp_error>"
|
|
fi
|
|
fi
|
|
|
|
if ! is_true $CHILD_PROCESS; then
|
|
2>&3 >&3 dump_stderr
|
|
if [[ -v failure_type && $failure_code -gt 0 ]]; then
|
|
2>&3 >&4 dump_failure
|
|
if [[ -v error_source ]]; then
|
|
2>&3 >&4 dump_stacktrace
|
|
fi
|
|
fi
|
|
elif [[ -v failure_type ]]; then
|
|
2>&3 >&4 save_err_trace_to_main
|
|
fi
|
|
|
|
if [[ $failure_code -gt 0 ]]; then
|
|
miaou_debug "</on_exit code=$failure_code>"
|
|
else
|
|
miaou_debug "</on_exit success>"
|
|
fi
|
|
cleanup
|
|
}
|
|
|
|
function build_miaou_bash_error {
|
|
local directory
|
|
directory="/run/user/$UID/$(basename "$0")"
|
|
|
|
CHILD_PROCESS=false
|
|
[[ -f "${directory}/${PPID}.tmp" ]] && CHILD_PROCESS=true
|
|
|
|
mkdir -p "$directory"
|
|
MIAOU_BASH_ERROR_CURRENT_CHILDREN="${directory}/$$.children.tmp"
|
|
MIAOU_BASH_ERROR_CHILDREN="${directory}/${PPID}.children.tmp"
|
|
MIAOU_BASH_ERROR="${directory}/$$.tmp"
|
|
rm -f "$MIAOU_BASH_ERROR"
|
|
|
|
readonly MIAOU_BASH_ERROR_CURRENT_CHILDREN MIAOU_BASH_ERROR_CHILDREN MIAOU_BASH_ERROR CHILD_PROCESS
|
|
}
|
|
|
|
function cleanup {
|
|
miaou_debug '<cleanup />'
|
|
trap - ERR EXIT PIPE RETURN
|
|
rm -f "$MIAOU_BASH_ERROR"
|
|
}
|
|
|
|
function usage {
|
|
echo "$(basename "$0") <SCRIPT> [...SCRIPT_ARGS] | --version | --help | --self-upgrade | --uninstall"
|
|
}
|
|
|
|
function parse_options {
|
|
[[ $# == 0 ]] && >&2 echo 'ERROR: script expected!' && builtin exit 2
|
|
|
|
if [[ $# == 1 ]]; then
|
|
case "$1" in
|
|
--help) usage && builtin exit ;;
|
|
--version) cat "${MIAOU_BASH_DIR:-/opt/miaou-bash}/.semver_git_tag" && builtin exit ;;
|
|
--self-upgrade)
|
|
self_upgrade
|
|
builtin exit
|
|
;;
|
|
--uninstall)
|
|
uninstall
|
|
builtin exit
|
|
;;
|
|
-*) >&2 echo "ERROR: option '$1' unknown!" && usage && builtin exit 2 ;;
|
|
esac
|
|
fi
|
|
|
|
[[ $1 =~ ^- ]] && >&2 echo "ERROR: too many args $#, either single option or SCRIPT + args accepted!" && builtin exit 2
|
|
SCRIPT="$1"
|
|
[[ ! -v SCRIPT ]] && >&2 echo 'ERROR: script expected!' && builtin exit 2
|
|
[[ ! -f "$SCRIPT" ]] && >&2 echo "ERROR: script '$SCRIPT' not found!" && builtin exit 2
|
|
true
|
|
}
|
|
|
|
function read {
|
|
2>&1 builtin read "$@"
|
|
}
|
|
|
|
## main
|
|
|
|
set -euo pipefail
|
|
set -E # HALT on pipe error
|
|
set -T # RETURN
|
|
|
|
parse_options "$@"
|
|
build_miaou_bash_error
|
|
trap 'on_error $?' ERR
|
|
trap 'on_exit $?' EXIT
|
|
trap 'on_pipe' PIPE
|
|
trap 'on_return' RETURN
|
|
|
|
BASH_ARGV0="$SCRIPT" && shift
|
|
if is_true "$CHILD_PROCESS"; then
|
|
# any nested children
|
|
exec 4>/dev/tty 2>"$MIAOU_BASH_ERROR"
|
|
miaou_debug "<child file=$BASH_ARGV0 pid=${BASHPID} parent=${PPID}>"
|
|
else
|
|
# the MAIN Process
|
|
if [[ -e /proc/$$/fd/3 ]]; then
|
|
exec 4>&3 3>&- # swap fd 3 4 when fd 3 sent off
|
|
else
|
|
MAIN_FD_MIAOU=true
|
|
[[ -t 2 ]] && MAIN_FD_STDERR=false || MAIN_FD_STDERR=true
|
|
exec 4>/dev/tty
|
|
fi
|
|
exec 3>/dev/stderr 2>"$MIAOU_BASH_ERROR"
|
|
miaou_debug "<main file=$BASH_ARGV0 pid=${BASHPID}/>"
|
|
fi
|
|
|
|
# shellcheck source=/dev/null
|
|
source "$SCRIPT"
|