MIAOU-BASH is a collection of settings and helpers for leveraging BASH. Developer-friendly, it may be used as solo package with or without the miaou project.
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.

208 lines
5.4 KiB

#!/usr/bin/env bash
# constants
MIAOU_BASH_ERROR=$(mktemp -t "$(basename "$0").XXXXXXXX" --tmpdir="/run/user/$(id -u)")
MIAOU_DEBUG=${MIAOU_DEBUG:-false}
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_BASH_ERROR MIAOU_DEBUG ANSI
## functions
function is_true {
[[ "${1:-}" == 'true' ]]
}
# FIXME: define an alias to bypass ARG evaluation when unnecessary!
# example:
# alias toto="bash -c \"[[ \\\$MIAOU_DEBUG == 'true' ]] && echo yes \\\$* || echo nope\""
function miaou_debug {
is_true "${MIAOU_DEBUG}" && >/dev/tty builtin echo -e "\e[90mDEBUG <==> \e[33m$*\e[0m" || true
}
function tty_ansi {
local code="$1"
if [[ -v ANSI[$code] ]]; then
>/dev/tty echo -ne "\e[${ANSI[$code]}m"
else
>&2 echo "unknown ansi key: <$code>"
fi
}
function print_miaou_error {
local source="$1" line="$2" code="$3" type="$4" payload="${5:-}"
>&2 echo -n "${source}: line ${line}: miaou_error: code=${code} type=${type}"
[[ -n "$payload" ]] && >&2 echo " payload=${payload}" || >&2 echo
}
function on_error {
failure_code="${1:-200}"
>/dev/tty printf '\r' # force carriage return explicitly
miaou_debug "on_error: $failure_code ($$ -- $BASHPID)"
if [[ $$ == "$BASHPID" ]]; then
failure_type="COMMAND_ERROR"
print_miaou_error "${BASH_SOURCE[1]}" "${BASH_LINENO[0]}" "${failure_code}" "${failure_type}" '...'
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[@]}")
exit "$failure_code"
else
print_miaou_error "${BASH_SOURCE[1]}" "${BASH_LINENO[0]}" "${failure_code}" 'SUBSHELL_ERROR'
kill -PIPE $$
fi
}
function on_pipe {
local tmp_error regex source lineno
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]'
mapfile -t tmp_error <"$MIAOU_BASH_ERROR"
last_error="${tmp_error[-1]}"
regex='(.*): line ([0-9]+): miaou_error: code=([0-9]+) type=SUBSHELL_ERROR'
if [[ $last_error =~ $regex ]]; then
source=${BASH_REMATCH[1]}
lineno=${BASH_REMATCH[2]}
failure_code=${BASH_REMATCH[3]}
failure_type="SUBSHELL_ERROR"
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: "${failure_code}"
exit "$failure_code"
}
function dump_stderr {
miaou_debug --stderr--
mapfile -t tmp_error <"$MIAOU_BASH_ERROR"
tty_ansi FG_LIGHTBLUE
for i in "${tmp_error[@]}"; do echo "$i"; done
tty_ansi RESET
miaou_debug --end of stderr--
}
function dump_failure {
local i
miaou_debug --tty 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 in $(seq 1 $length_box); do echo -n '═'; done
echo '╗'
echo -n "$failure_type "
[[ -v failure_payload ]] && echo -n ' ' && tty_ansi FG_CYAN && echo -n " $failure_payload "
tty_ansi FG_YELLOW
printf "%5s" " $failure_code "
tty_ansi FG_MAGENTA
echo "║"
echo -n '╚'
for i in $(seq 1 4); do echo -n '═'; done
echo -n '╦'
for i in $(seq $((i + 2)) $length_box); do echo -n '═'; done
echo '╝'
echo " ║"
tty_ansi RESET
miaou_debug --end of tty error box--
}
function dump_stacktrace {
miaou_debug --stacktrace ${#error_source[@]}--
local i source lineno funcname
for i in "${!error_source[@]}"; do
tty_ansi FG_MAGENTA
echo -n " ╟─⟶"
[[ $i -gt 0 ]] && tty_ansi FG_GRAY
tty_ansi STYLE_ITALIC
source="${error_source[$i]}"
lineno="${error_lineno[$i]}"
funcname="${error_funcname[$i]}"
printf "%40s:%.4s" "$source" "$lineno"
tty_ansi RESET
printf " \t%s" "$funcname"
echo
done
miaou_debug --end of stacktrace--
}
function on_exit {
code="${1:-0}"
2>&3 >&3 dump_stderr
if [[ -v failure_type ]]; then
2>&3 >/dev/tty dump_failure
2>&3 >/dev/tty dump_stacktrace
fi
miaou_debug "on_exit $code"
}
## main
set -euo pipefail
set -E # HALT on subshell error
# set -T # DEBUG RETURN
[[ $# -lt 1 ]] && >&2 echo 'ERROR: script expected!' && builtin exit 1
BASH_ARGV0="$1" && shift # to pretend script runs by itself
trap 'on_error $?' ERR
trap 'on_exit $?' EXIT
trap 'on_pipe' PIPE
miaou_debug "--source from ${BASHPID}"
exec 3>/dev/stderr
# shellcheck source=/dev/null
2>"$MIAOU_BASH_ERROR" source "$BASH_ARGV0"
miaou_debug '--source success'