dotfiles/script/utils.sh

305 lines
5.2 KiB
Bash
Raw Normal View History

#!/usr/bin/env sh
2024-01-21 20:49:34 -08:00
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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
2024-01-21 20:49:34 -08:00
# 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 &
2024-01-21 20:49:34 -08:00
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
print_in_color()
{
string=$(echo "$1" | tr -s " ")
2024-01-21 20:49:34 -08:00
printf "%b" \
"$(tput setaf "$2" 2> /dev/null)" \
"$string" \
2024-01-21 20:49:34 -08:00
"$(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
2024-01-21 20:49:34 -08:00
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"
}
2024-01-21 20:49:34 -08:00
ask()
{
print_question "$1"
read -r
}
get_answer()
{
printf "%s" "$REPLY"
}
ask_for_confirmation()
{
print_question "$1 (y/n) "
REPLY="$(read_char)"
2024-01-21 20:49:34 -08:00
printf "\n"
}
answer_is_yes()
{
expr "$REPLY" : '[Yy]$'
2024-01-21 20:49:34 -08:00
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
show_spinner()
{
readonly FRAMES='/-\|'
2024-01-21 20:49:34 -08:00
readonly NUMBER_OF_FRAMES=${#FRAMES}
2024-01-21 20:49:34 -08:00
readonly CMDS="$2"
readonly MSG="$3"
readonly PID="$1"
2024-01-21 20:49:34 -08:00
i=0
frame_text=""
2024-01-21 20:49:34 -08:00
# 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"
2024-01-21 20:49:34 -08:00
# 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
2024-01-21 20:49:34 -08:00
}
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")")"
2024-01-21 20:49:34 -08:00
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
set_trap()
{
trap | grep "'$2' $1" >/dev/null 2>&1 \
|| trap '$2' "$1"
2024-01-21 20:49:34 -08:00
}
kill_all_subproccesses()
{
i=""
2024-01-21 20:49:34 -08:00
for i in $(jobs -p); do
kill "$i"
wait "$i" >/dev/null 2>&1
2024-01-21 20:49:34 -08:00
done
}
execute()
{
readonly CMDS="$1"
readonly MSG="${2:-$1}"
readonly TMP_FILE
TMP_FILE="$(mktemp /tmp/XXXXX)"
2024-01-21 20:49:34 -08:00
exit_code=0
cmds_pid=""
2024-01-21 20:49:34 -08:00
# 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 \
2024-01-21 20:49:34 -08:00
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
2024-01-21 20:49:34 -08:00
exit_code=$?
# Print output based on what happened.
print_result $exit_code "$MSG"
if [ $exit_code -ne 0 ]; then
2024-01-21 20:49:34 -08:00
print_error_stream < "$TMP_FILE"
fi
rm -rf "$TMP_FILE"
return $exit_code
}