From 346274bc902c24aee2d0ef880e8d5f9e95f245c3 Mon Sep 17 00:00:00 2001 From: pvincent Date: Mon, 13 Jul 2026 00:49:03 +0400 Subject: [PATCH] bin/miaou-bash --- bin/miaou-bash | 141 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100755 bin/miaou-bash diff --git a/bin/miaou-bash b/bin/miaou-bash new file mode 100755 index 0000000..cf27b61 --- /dev/null +++ b/bin/miaou-bash @@ -0,0 +1,141 @@ +#!/usr/bin/env bash + +# CONSTANTS +BASH_BACK_ERROR=$(mktemp -t "$(basename $0).XXXXXXXX" --tmpdir=/run/user/$(id -u)) +BASH_BACK_DEBUG=${BASH_BACK_DEBUG:-false} + +# FUNCTIONS + +function on_return { + if [[ "${BASH_COMMAND}" == 'return '* ]]; then + code=$(echo "${BASH_COMMAND}" | cut -d' ' -f2) # regex does not work here unfortunately! thus use of basic cut + if [[ -n "$code" ]] && [[ "$code" -ne 0 ]]; then + trap - DEBUG + >&2 echo "${BASH_SOURCE[2]}: line ${BASH_LINENO[0]}: return $code from ${FUNCNAME[1]}" + fi + fi +} + +# overridden function which permits stacktracing of original `return` statement +function exit { + code=${1:-0} + >&2 echo "${BASH_SOURCE[2]}:${BASH_LINENO[0]} exit $@" + # >&2 echo "${BASH_SOURCE[2]}: line ${BASH_LINENO[0]}: exit $@" + builtin exit $code +} + +function on_exit { + code=${1:-0} + + stack_lines=("${BASH_LINENO[@]}") + stack_functions=("${FUNCNAME[@]}") + stack_sources=("${BASH_SOURCE[@]}") + + unset stack_lines[-1] + unset stack_lines[-1] + unset stack_functions[0] + unset stack_functions[-1] + stack_functions[-1]='
' + unset stack_sources[0] + unset stack_sources[-1] + + if [[ "${stack_functions[1]}" == exit && "${stack_sources[1]}" == "${BASH_SOURCE[0]}" ]]; then + # the exit case scenario + unset stack_functions[1] + unset stack_sources[1] + unset stack_lines[0] + fi + + stack_lines=("${stack_lines[@]}") + stack_functions=("${stack_functions[@]}") + stack_sources=("${stack_sources[@]}") + + stack_summary=() + + if [[ $code -gt 0 ]]; then + last_error=$(tail -n1 $BASH_BACK_ERROR) + last_source=${stack_sources[0]} + last_source=${last_source//./"\."} # replace . by \. + regex1="^$last_source: line ([0-9]+): (.*)\$" + regex2="^$last_source:([0-9]+) (.*)\$" + if [[ "$last_error" =~ $regex1 || "$last_error" =~ $regex2 ]]; then + line=${BASH_REMATCH[1]} + message=${BASH_REMATCH[2]} + regex=': unbound variable$' + [[ $code == 1 ]] && [[ "$message" =~ $regex ]] && stack_lines[0]=$line + + regex="^return ([0-9]*) from (.*)\$" + if [[ "$message" =~ $regex ]]; then + return_code="${BASH_REMATCH[1]}" + funcname="${BASH_REMATCH[2]}" + # >&4 echo "RETURN DETECTED line ${stack_sources[0]} $line $funcname" + stack_sources=("${stack_sources[0]}" "${stack_sources[@]}") + stack_lines=("$line" "${stack_lines[@]}") + stack_functions=("$funcname" "${stack_functions[@]}") + stack_summary+=("ERROR $code: [return $return_code]") + else + stack_summary+=("ERROR $code: [$message]") + fi + else + regex="^ .*:[0-9]* .*\$" + [[ "$last_error" =~ $regex ]] || stack_summary+=("ERROR $code:") # + fi + + for i in "${!stack_functions[@]}"; do + stack_detail=$(printf "\t%30s\t%s\n" "${stack_sources[$i]}:${stack_lines[$i]}" "${stack_functions[$i]}") + stack_summary+=("$stack_detail") + done + >&4 dump_stderr true + dump_summary + else + >&4 dump_stderr false + fi + + cleanup + builtin exit $code +} + +function dump_summary { + $BASH_BACK_DEBUG && >/dev/tty echo '---stacktrace---' + for i in "${stack_summary[@]}"; do + >&4 echo -e "$i" + done +} + +function dump_stderr { + last_trim=${1:-false} + mapfile -t tmp_error <$BASH_BACK_ERROR + $last_trim && unset tmp_error[-1] && tmp_error=("${tmp_error[@]}") + if [[ "${#tmp_error[@]}" -gt 0 ]]; then + $BASH_BACK_DEBUG && >/dev/tty echo '---stderr---' + for i in "${tmp_error[@]}"; do + echo -e "$i" + done + fi +} + +function success { + >&2 dump_stderr + cleanup +} + +function cleanup { + trap - ERR TERM INT EXIT + exec 3>&- 4>&- + rm $BASH_BACK_ERROR +} + +# MAIN + +set -TEue -o pipefail +[[ $# -ne 1 ]] && >&2 echo 'ERROR: script expected!' && builtin exit 1 + +trap 'on_exit $?' ERR TERM INT EXIT +trap 'on_return' DEBUG + +# magic FD +exec 3> >(tee $BASH_BACK_ERROR >/dev/null) 4>/dev/stderr + +$BASH_BACK_DEBUG && >/dev/tty echo '---stdout---' +source $1 2>&3 +success