dotfiles/script/utils.sh
2024-02-03 12:05:02 -08:00

283 lines
5.2 KiB
Bash
Executable file

#!/usr/bin/env sh
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
skip_questions() {
while :; do
case $1 in
-y | --yes) return 0 ;;
*) break ;;
esac
shift 1
done
return 1
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ask_for_sudo() {
sudo -v >/dev/null 2>&1
# Update existing 'sudo' timestamp until this script has finished.
#
# https://gist.github.com/cowboy/3118588
while true; do
sudo -n true
sleep 60
kill -0 "$$" || exit
done >/dev/null 2>&1 &
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
print_in_color() {
string=$(echo "$1" | tr -s " ")
printf "%b" \
"$(tput setaf "$2" 2>/dev/null)" \
"$string" \
"$(tput sgr0 2>/dev/null)"
}
print_in_red() {
print_in_color "$1" 1
}
print_in_yellow() {
print_in_color "$1" 3
}
print_in_green() {
print_in_color "$1" 2
}
print_in_purple() {
print_in_color "$1" 5
}
print_title() {
print_in_purple "\n • $1\n\n"
}
print_success() {
print_in_green " [✔] $1\n"
}
print_warning() {
print_in_yellow " [!] $1\n"
}
print_error() {
print_in_red " [✖] $1 $2\n"
}
print_question() {
print_in_yellow " [?] $1\n"
}
print_result() {
if [ "$1" = 0 ]; then
print_success "$2"
else
print_error "$2"
fi
return "$1"
}
print_error_stream() {
while read -r line; do
print_error "↳ ERROR: $line"
done
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# https://stackoverflow.com/a/32270158
# POSIX read -n 1
# Usage: var="$(read_char)"
read_char() {
old=$(stty -g)
stty raw -echo min 0
printf '%s' "$(dd bs=1 count=1 2>/dev/null)"
stty "$old"
}
ask() {
print_question "$1"
read -r
}
get_answer() {
printf "%s" "$REPLY"
}
ask_for_confirmation() {
print_question "$1 (y/n) "
REPLY="$(read_char)"
printf "\n"
}
answer_is_yes() {
expr "$REPLY" : '[Yy]$'
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
show_spinner() {
readonly FRAMES='/-\|'
readonly NUMBER_OF_FRAMES=${#FRAMES}
readonly CMDS="$2"
readonly MSG="$3"
readonly PID="$1"
i=0
frame_text=""
# Provide more space so that the text hopefully doesn't reach the bottom line
# of the terminal window.
#
# This is a workaround for escape sequences not tracking the buffer position
# (accounting for scrolling).
#
# See also: https://unix.stackexchange.com/a/278888
printf "\n\n\n"
tput cuu 3
tput sc
while kill -0 "$PID" >/dev/null 2>&1; do
i=$((i + 1))
num=$((i % NUMBER_OF_FRAMES))
frame="$(echo $FRAMES | cut -c ${num}-$((num + 1)))"
frame_text=" [$frame] $MSG"
# Print frame text.
printf "%s" "$frame_text"
sleep 0.2
# Clear frame text.
tput rc
done
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cmd_exists() {
command -v "$1" >/dev/null 2>&1
}
is_git_repository() {
git rev-parse >/dev/null 2>&1
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# https://stackoverflow.com/a/29835459
# POSIX compliant way of finding BASH_SOURCE
# Usage: sh_source "$0"
rreadlink() (
target=$1
fname=
targetDir=
CDPATH=
{
\unalias command
\unset-f command
} >/dev/null 2>&1
# shellcheck disable=SC2034
[ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on
while :; do
[ -L "$target" ] || [ -e "$target" ] || {
command printf '%s\n' "ERROR: $target does not exist." >&2
return 1
}
# shellcheck disable=SC2164
command cd "$(command dirname -- "$target")"
fname=$(command basename -- "$target")
[ "$fname" = '/' ] && fname=''
if [ -L "$fname" ]; then
target=$(command ls -l "$fname")
target=${target#* -> }
continue
fi
break
done
targetDir=$(command pwd -P)
if [ "$fname" = '.' ]; then
command printf '%s\n' "${targetDir%/}"
elif [ "$fname" = '..' ]; then
command printf '%s\n' "${targetDir%/}/$fname"
fi
)
sh_source() {
printf '%s' "$(dirname -- "$(rreadlink "$1")")"
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
set_trap() {
trap | grep "'$2' $1" >/dev/null 2>&1 ||
trap '$2' "$1"
}
kill_all_subproccesses() {
i=""
for i in $(jobs -p); do
kill "$i"
wait "$i" >/dev/null 2>&1
done
}
execute() {
readonly CMDS="$1"
readonly MSG="${2:-$1}"
readonly TMP_FILE
TMP_FILE="$(mktemp /tmp/XXXXX)"
exit_code=0
cmds_pid=""
# If the current process is ended, also end all its subproccesses.
set_trap "EXIT" "kill_all_subproccesses"
# Execute commands in background
# shellcheck disable=SC2261
eval "$CMDS" \
>/dev/null 2>&1 \
2>"$TMP_FILE" &
cmds_pid=$!
# Show a spinner if the commands require more time to complete.
show_spinner "$cmds_pid" "$CMDS" "$MSG"
# Wait for the commands to no longer be executing in the background, and then
# get their exit code.
wait "$cmds_pid" >/dev/null 2>&1
exit_code=$?
# Print output based on what happened.
print_result $exit_code "$MSG"
if [ $exit_code -ne 0 ]; then
print_error_stream <"$TMP_FILE"
fi
rm -rf "$TMP_FILE"
return $exit_code
}