PRbL/functions

1496 lines
44 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# pyr0ball script functions library
# Initial Vars
functionsrev=2.0.2
#scriptname="${BASH_SOURCE[0]##*/}"
#rundir="${BASH_SOURCE[0]%/*}"
#runuser="$(whoami)"
pretty_date="$(date +%Y-%m-%d_%H-%M-%S)"
short_date="$(date +%Y-%m-%d)"
# rundir_absolute=$(pushd $rundir ; pwd ; popd)
# escape_dir=$(printf %q "${rundir_absolute}")
# logfile="${rundir}/${pretty_date}_${scriptname}.log"
# ensure ctrl-c to cancel script ends the entire process and not just the current function
# trap ctrl_c INT # Commented out by default to prevent abnormal background exits
# return any cursor mods to normal on exit
cleanup(){
tput cnorm
}
trap cleanup EXIT
# Escape characters (if your shell uses a different one, you can modify it here)
# By default this is using the usual bash escape code
ESC=$( printf '\033')
# Detect OS type
case $OSTYPE in
linux-gnu*|cygwin|msys) ESC=$( printf '\033') ;;
darwin* ) ESC=$( printf '\e') ;;
esac
# Utilities (Setting up colors and bounding boxes)
# boxtype changes what borders are used for the boxborder functions.
# options are "single" "double" "char"
# for the "char" option, change the "borderchar" variable to
# whichever unicode character you wish to use
# boxtype="${boxtype:-single}" # Sets default 'single' if not already set
# borderchar="${borderchar:-#}" # Sets default '#' if not already set
boxtype="norm" # Sets default 'single' if not already set
borderchar="#" # Sets default '#' if not already set
# Colorization options
if [[ "$TERM" != "linux" ]] ; then
red="${ESC}[38;5;1m" # red
grn="${ESC}[38;5;2m" # green
ylw="${ESC}[38;5;3m" # yellow
blu="${ESC}[38;5;27m" # blue
lbl="${ESC}[38;5;69m" # light blue
mag="${ESC}[38;5;5m" # magenta
cyn="${ESC}[38;5;6m" # cyan
pur="${ESC}[38;5;135m" # purple
ong="${ESC}[38;5;166m" # orange
lyl="${ESC}[38;5;228m" # light yellow
lrd="${ESC}[38;5;196m" # light red
gry="${ESC}[38;5;240m" # Grey
norm=$"${ESC}[39m" # default/normal
#
bld="${ESC}[1m" # bold
unb="${ESC}[21m" # un-bold
dim="${ESC}[2m" # dim
und="${ESC}[22m" # un-dim
unl="${ESC}[4m" # underline
nln="${ESC}[24" # not-underline
blk="${ESC}[5" # blinking
unbl="${ESC}[25" # stop blinking
inv="${ESC}[7m" # invert
rsinv="${ESC}[27" # reset http://www.endmemo.com/unicode/unicodeconverter.phpnvert
hid="${ESC}[8" # hidden
unh="${ESC}[28" # unhide
dfl="${ESC}[0m" # restore default
fi
check_unicode_support() {
# Check if LANG indicates UTF-8
[[ "${LANG,,}" == *".utf-8"* || "${LANG,,}" == *".utf8"* ]] || return 1
# Check if terminal can display Unicode
echo -ne "\u2714" >/dev/null 2>&1 || return 1
return 0
}
box_chars_simple() {
# Simple ASCII box characters
top_border="-"
bottom_border="-"
left_border="|"
right_border="|"
left_top_border="+"
right_top_border="+"
left_bottom_border="+"
right_bottom_border="+"
box_break_line="-"
}
set_box_chars() {
if check_unicode_support; then
case "$OSTYPE" in
linux-gnu*|cygwin|msys)
return_arrow=$(echo -e "\u2BAC")
enter_arrow=$(echo -e "\u21B5")
green_check=${grn}$(echo -e "\u2714")${dfl}
red_x=${lrd}$(echo -e "\u00D7")${dfl}
selected_opt=$(echo -e "\u25C9")
deselected_opt=$(echo -e "\u25CE")
light_h='─'
norm_h='━'
double_h='═'
light_v='│'
norm_v='┃'
double_v='║'
;;
darwin*)
return_arrow="⮬"
return_arrow=$(echo -e "⮬")
enter_arrow=$(echo -e "↵")
green_check=${grn}$(echo -e "✔")${dfl}
red_x=${lrd}$(echo -e "×")${dfl}
selected_opt=$(echo -e "◉")
deselected_opt=$(echo -e "◎")
light_h='─'
norm_h='━'
double_h='═'
light_v='│'
norm_v='┃'
double_v='║'
;;
*)
# Fallback to simple ASCII if OS is unknown
return_arrow=">"
green_check="+"
red_x="x"
selected_opt="*"
deselected_opt="o"
;;
esac
else
# ASCII fallbacks
return_arrow=">"
enter_arrow="<"
green_check="+"
red_x="x"
selected_opt="*"
deselected_opt="o"
box_chars_simple
fi
}
# For drawing pretty boxes
update_terminal_width() {
local min_width=40 # Minimum usable width
local default_width=80
# Try to get terminal width, fallback to default if failed
terminal_width=$(tput cols 2>/dev/null || echo "$default_width")
# Ensure minimum width
if (( terminal_width < min_width )); then
terminal_width=$min_width
fi
# Set box width with 1 character margin
BOXWIDTH=$((terminal_width - 1))
}
# Add trap for terminal resize
trap 'update_terminal_width' WINCH
set_borders() {
top_border=$1
bottom_border=$2
left_border=$3
right_border=$4
left_top_border=$5
right_top_border=$6
left_bottom_border=$7
right_bottom_border=$8
box_break_line=$9
}
# Box border type single-line
box-norm() {
case $OSTYPE in
linux-gnu*|cygwin|msys)
set_borders "${norm_h}" "${norm_h}" "${norm_v}" "${norm_v}" "┏" "┓" "┗" "┛" "▫"
;;&
darwin* )
set_borders "${norm_h}" "${norm_h}" "${norm_v}" "${norm_v}" "┏" "┓" "┗" "┛" "▫"
;;
esac
}
# Box border type double-line
box-double() {
case $OSTYPE in
linux-gnu*|cygwin|msys)
set_borders "${double_h}" "${double_h}" "${double_v}" "${double_v}" "╔" "╗" "╚" "╝" "▫"
;;&
darwin* )
set_borders "${double_h}" "${double_h}" "${double_v}" "${double_v}" "╔" "╗" "╚" "╝" "▫"
;;
esac
}
# Box border type thick-line
box-heavy(){
case $OSTYPE in
linux-gnu*|cygwin|msys)
set_borders "▀" "▄" "▌" "▐" "▛" "▜" "▙" "▟" "▫"
;;&
darwin* )
set_borders "▀" "▄" "▌" "▐" "▛" "▜" "▙" "▟" "▫"
;;
esac
}
# Box border type thin-line
box-light(){
case $OSTYPE in
linux-gnu*|cygwin|msys)
set_borders "${light_h}" "${light_h}" "${light_v}" "${light_v}" "▫" "▫" "▫" "▫" "⎯"
;;&
darwin* )
set_borders "${light_h}" "${light_h}" "${light_v}" "${light_v}" "▫" "▫" "▫" "▫" "⎯"
;;
esac
}
box-rounded(){
case $OSTYPE in
linux-gnu*|cygwin|msys)
set_borders "${light_h}" "${light_h}" "${light_v}" "${light_v}" "╭" "╮" "╰" "╯" "▢"
;;&
darwin* )
set_borders "${light_h}" "${light_h}" "${light_v}" "${light_v}" "╭" "╮" "╰" "╯" "▢"
;;
esac
}
box-singlechar(){
set_borders "$borderchar" "$borderchar" "$borderchar" "$borderchar" "$borderchar" "$borderchar" "$borderchar" "$borderchar" "-"
}
set-boxtype() {
# Accept a parameter if provided, otherwise use the global variable
local style="${1:-$boxtype}"
# Update the global variable to match what we're using
boxtype="$style"
# Ensure characters are defined
set_box_chars
update_terminal_width
# Set the box style
case $style in
norm) box-norm ;;
double) box-double ;;
heavy) box-heavy ;;
light) box-light ;;
rounded) box-rounded ;;
char) box-singlechar ;;
*)
# Handle unknown box types by falling back to normal
echo "Warning: Unknown box type '$style', falling back to 'norm'" >&2
boxtype="norm"
box-norm
;;
esac
}
box-singlechar(){
set_borders "$borderchar" "$borderchar" "$borderchar" "$borderchar" "$borderchar" "$borderchar" "$borderchar" "$borderchar" "-"
}
set-boxtype(){
case $boxtype in
norm) box-norm ;;
double) box-double ;;
heavy) box-heavy ;;
light) box-light ;;
rounded) box-rounded ;;
char) box-singlechar ;;
esac
}
update_terminal_width
set-boxtype
repchar() {
# More robust parameter checking
if [[ $# -lt 2 ]]; then
echo "Error: repchar() Incorrect usage. Usage: repchar <char> <count>" >&2
# Return a safe fallback instead of failing
echo "----"
return 0
fi
local char="$1"
local count="$2"
# Validate count is a number
if ! [[ "$count" =~ ^[0-9]+$ ]]; then
echo "Error: repchar() count must be a number. Got: '$count'" >&2
# Return a safe fallback instead of failing
echo "----"
return 0
fi
# Create and output the repeated character string
printf -v result "%*s" "$count" ""
echo "${result// /$char}"
}
test_boxtop() {
echo "Debug - BOXWIDTH: $BOXWIDTH" >&2
echo "Debug - top_border: '$top_border'" >&2
echo "Debug - left_top_border: '$left_top_border'" >&2
local rep_result=$(repchar "$top_border" "$((BOXWIDTH-1))")
echo "Debug - repchar result: '$rep_result'" >&2
printf "%s%s%s\n" "${brdr}$left_top_border" "$rep_result" "$right_top_border${dfl}"
}
boxtop() {
printf "%s%s%s\n" "${brdr}$left_top_border" "$(repchar "$top_border" "$((BOXWIDTH-1))")" "$right_top_border${dfl}"
}
boxbottom() {
printf "%s%s%s\n" "${brdr}$left_bottom_border" "$(repchar "$bottom_border" "$((BOXWIDTH-1))")" "$right_bottom_border${dfl}"
}
boxlinelog() {
local content="$1"
logger printf "%s%s\r${ESC}[%sC%s\n" "$left_border" "$content" "$BOXWIDTH" "$right_border"
}
boxline() {
local content="$1"
if ! printf "%s%s\r${ESC}[%sC%s\n" "$left_border" "$content" "$BOXWIDTH" "$right_border" 2>/dev/null; then
# Fallback to simple output if fancy formatting fails
printf "%s %s %s\n" "$left_border" "$content" "$right_border"
fi
}
boxseparator(){
printf '%s%s%s\n' "${brdr}" "$(repchar "$box_break_line" $((BOXWIDTH-3)))" "${dfl}"
}
boxborder() {
boxtop
for line in "$@" ; do
boxline "$line"
done
boxbottom
}
boxlinelog() {
local _cmd=${@:1}
#echo -e "$left_border $(logger echo -e "${1}")\r${ESC}[${BOXWIDTH}C$right_border"
logger echo -e "$left_border $_cmd\r${ESC}[${BOXWIDTH}C$right_border"
}
boxborderlog(){
local _cmd=()
boxtop
for line in "$@" ; do
boxlinelog "$line"
done
boxbottom
}
subboxborder() {
level=${1:-0}
local indent=$((level*2))
local border_char="━"
local top_border=""
local bottom_border=""
# Set the border characters based on the level of nesting
if ((level==0)); then
left_border="$left_top_border"
right_border="$right_top_border"
top_border="$rep_char $border_char $indent"
bottom_border="$rep_char $border_char $indent"
else
left_border="$left_border"
right_border="$right_border"
top_border="$rep_char $border_char $indent"
bottom_border="$rep_char $border_char $indent"
fi
# Generate the top border
boxline "$(boxborder "$top_border")"
((level++))
# Generate the output lines
for line in "${@:2}" ; do
boxline "$(repchar " " $indent)$(boxline "$line")"
boxseparator
done
# Generate the bottom border
((level--))
boxline "$(boxborder "$bottom_border")"
}
# Helper function to get visual length of string (accounting for ANSI escape codes)
get_visual_length() {
local text="$1"
# Remove ANSI escape sequences and count remaining characters
local stripped
stripped=$(printf "%s" "$text" | sed 's/\x1b\[[0-9;]*m//g')
printf "%s" "${#stripped}"
}
# For printing center-justified text. To change the padding character, replace the ' ' <~~ whitespace in front of the '{' in the padding variable
center() {
local text="$1"
# Early return for non-interactive/debug mode
if (( !interactive )) || (( xtrace )); then
printf "%s %s\n" "$text"
return
fi
# Calculate available inner width (total width minus borders)
local inner_width=$(( BOXWIDTH - 2 ))
local text_length=$(get_visual_length "$text")
# If text is too long, just print it with borders
if (( text_length >= inner_width )); then
printf "%s%s%s\n" "$left_border" "$text" "$right_border"
return
fi
# Calculate padding sizes
local padding_left=$(( (inner_width - text_length) / 2 ))
local padding_right=$(( inner_width - text_length - padding_left ))
# Create padding strings
local left_pad right_pad
printf -v left_pad "%*s" "$padding_left" ""
printf -v right_pad "%*s" "$padding_right" ""
# Print with consistent borders
printf "%s%s%s%s%s\n" \
"$left_border" \
"$left_pad" \
"$text" \
"$right_pad" \
"$right_border"
}
# For printing spanned text, e.g. single-pair lists ($name...$title)
spanner() {
local left="$1"
local right="$2"
local spacer="${3:-.}" # Default to dot if no spacer provided
# Early return for non-interactive/debug mode
if (( !interactive )) || (( xtrace )); then
printf "%s %s\n" "$left" "$right"
return
fi
# Calculate available inner width (total width minus borders)
local inner_width=$(( BOXWIDTH - 2 ))
local left_length=$(get_visual_length "$left")
local right_length=$(get_visual_length "$right")
# Minimum space between left and right text
local min_space=1
# Calculate space needed for spacer
local spacer_width=$(( inner_width - left_length - right_length ))
# If not enough space, just print with minimal formatting
if (( spacer_width < min_space )); then
printf "%s%s %s%s\n" \
"$left_border" \
"$left" \
"$right" \
"$right_border"
return
fi
# Create spacer string
local spacer_string
printf -v spacer_string "%*s" "$spacer_width" ""
spacer_string=${spacer_string// /$spacer}
# Print with consistent borders
printf "%s%s%s%s%s\n" \
"$left_border" \
"$left" \
"$spacer_string" \
"$right" \
"$right_border"
}
# Workflow and logging
pushd(){
command pushd "$@" > /dev/null
}
popd(){
command popd "$@" > /dev/null
}
popdfail(){
ec=$?
popd
$(exit $ec) #restore the exit code
fail "$@"
}
run(){
_cmd=$@
if [[ $dry_run != true ]] ; then
logger $_cmd
else
logger boxline "DryRun: $_cmd"
fi
}
logger(){
if [[ -w "$logfile" ]]; then
"$@" 2>&1 | tee -a "$logfile" | while IFS= read -r line; do
# strip any escaped strings for logging output
_line="${line//[$'\t\r\x01-\x1F\x7F-\xFF']}"
# only print out non-empty lines to log
[[ -n $_line ]] && printf '[%s][%s] %s\n' "$scriptname" "$pretty_date" "$_line" >> "$logfile"
done
else
"$@"
fi
}
# File handling functions
take-backup(){
name="$1"
if [[ $update_run != true ]] ; then
# Check if a backup file or symbolic link already exists
if [[ -e "$name.bak" || -L "$name.bak" ]]; then
run boxline " $name.bak backup already exists"
else
# Check if the file is a hidden file (starts with a dot)
if [[ "$name" == .* ]]; then
# Add a dot to the beginning of the backup file name
backup_name=".${name}.bak"
else
# Create the backup file name by appending ".bak" to the original file name
backup_name="${name}.bak"
fi
# Copy the file to the backup file with preservation of file attributes
run cp -p "$name" "$backup_name"
# Add the original file to the list of backup files
backup_files+=("$name")
# Log the original file name to the backup file list file
run echo "$name" >> "$rundir/backup_files.list"
fi
fi
}
restore-backup(){
run echo "${#backup_files[@]}"
for file in "${backup_files[@]}" ; do
run cp "$file".bak $file
run echo "$file is restored"
done
backup_files=()
if [ -f $rundir/backup_files.list ] ; then
run rm $rundir/backup_files.list
fi
}
install-file(){
local _source="$1"
local _destination="$2"
local _source_root="$3"
local _filename=${_source##*/}
local _destination_file=${_destination}/${_filename#${_source_root}}
installed_files+=("${_destination_file}")
if [[ $update_run == true ]] ; then
run boxline "$scriptname: added file ${_destination_file} to list"
else
run cp -p $_source $_destination_file && boxline "Installed ${_filename}" || warn "Unable to install ${_filename}"
run echo "${_destination_file}" >> $rundir/installed_files.list
fi
}
install-dir() {
local _source="$1"
local _destination="$2"
installed_dirs+=("$_source -> $_destination")
# Iterate through all files in the source directory recursively
while IFS= read -r -d '' source_file; do
# Construct the destination file path by removing the source directory path
# and appending it to the destination directory path
local _filename="${source_file#${_source}}"
local destination_file="${_destination}/${source_file#${_source}}"
# Create the parent directory of the destination file if it doesn't exist
# Log the destination file path to the logfile
#echo "$destination_file" >> "$logfile"
installed_files+=($destination_file)
if [[ $update_run == true ]] ; then
run boxline "$scriptname: added file ${destination_file} to list"
else
run mkdir -p "$(dirname "$destination_file")"
run cp -p ${_source}${_filename} $destination_file && boxline "Installed ${_filename}" || warn "Unable to install ${_filename}"
run echo "${destination_file}" >> $rundir/installed_files.list
fi
done < <(find "$_source" -type f -print0)
}
# Utilities
success(){
_line="$@"
echo -e "\n"
boxtop
boxline "${scriptname} ${grn}SUCCESS${dfl} ${_line}"
boxbottom
exit 0
}
warn(){
ec=$?
_line="$@"
echo -e "\n"
boxtop
boxline "${lrd}WARNING${lyl}[${ong}code=${red}$ec${lyl}]: ${_line}${dfl}"
boxbottom
}
fail(){
ec=$?
_line="$@"
echo -e "\n"
boxtop
boxline "${lrd}FAILED${lyl}[${ong}code=${red}$ec${lyl}]: ${_line}${dfl}"
boxbottom
exit $ec
}
clone-repo(){
# git clone <source url> <destination>
local _url=$1
local _destination=$2
if [ ! -z $_destination ] ; then
run mkdir -p $_destination
fi
run git clone --recurse-submodules $_url $_destination
}
# Executes: grep "$1" | grep "$2" | grep "$3" | ...
chained-grep(){
local pattern="$1"
if [[ -z "$pattern" ]]; then
cat
return
fi
shift
grep -- "$pattern" | chained-grep "$@"
}
# Remove leading/trailing whitespace
trim(){
local var=${@:2}
var="${var#"${var%%[![:space:]]*}"}"
var="${var%"${var##*[![:space:]]}"}"
printf '%s' "$var"
}
strip-html-tags(){
local input="$1"
local stripped=$(echo "$input" | sed 's/<[^>]*>//g' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
echo "$stripped"
}
# Retrieve and run a script from url
run-from-url(){
url=$1
# boxline "url=$url"
args=${*:2}
# boxline "args=$args"
# check the interpreter using the shebang at the top of the file
interpreter=$(curl -ks $url | head -n 1 | sed -n -e 's\/\#//g' 2>/dev/null)
# boxline "interpreter=$interpreter"
# In case of missing shebang, default to local shells
if [ -z $interpreter ] ; then
if [ -x /bin/bash ] ; then
interpreter="/bin/bash"
elif [ -x /bin/sh ] ; then
interpreter="/bin/sh"
elif [ -x /bin/zsh ] ; then
interpreter="/bin/zsh"
fi
fi
logger boxline "Running online script $url with args $args"
if [[ "$interpreter" == "/bin/bash" ]] ; then
run curl -H 'Cache-Control: no-cache' -kLs $url | $interpreter -s -- $args
else
run curl -H 'Cache-Control: no-cache' -kLs $url > ${url##*/}
run $interpreter ${url##*/} $args
ec=$?
run rm ${url##*/}
fi
return $ec
}
check-git-repository(){
repodir=${1:-$rundir}
if [ -z $repodir ] ; then
warn "check-git-repository() Incorrect usage. Usage: check-git-repository </path/to/dir>"
return 2
fi
if git -C "$repodir" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo "Directory '$repodir' is a valid Git repository."
return 0
# Your script logic here
else
echo "Directory '$repodir' is not a valid Git repository"
return 1
fi
}
# fancy script exits
ctrl_c(){
echo -e "\n"
fail "User interrupted with Ctrl-C"
}
# Cosmetics
PRBL_LOGO="
${ESC}[38;5;202m_!*(xL}}xvr!,.${dfl}
${ESC}[38;5;202m>}KM3${ong}nLL}}}}}LLTh${ESC}[38;5;202m%5u>.${dfl}
${ESC}[38;5;202m<FE${ong}nvi${ESC}[38;5;202mT3Egg8888888DGn${ong}ivn${ESC}[38;5;202mOKr.${dfl}
${ESC}[38;5;202m.vEP${ong}?v${ESC}[38;5;202mZ88888${ong}Q${ESC}[38;5;178mB${ylw}B####B${ESC}[38;5;178mQ${ESC}[38;5;202m88888Zx${ong}rj${ESC}[38;5;202mNT.${dfl}
${ESC}[38;5;202m!MW${ong}^L${ESC}[38;5;202mD888${ESC}[38;5;178mQ${ylw}#@@${ESC}[38;5;178m@${ong}@${ESC}[38;5;202m######${ESC}[38;5;178m@${ylw}@@#B${ESC}[38;5;202m888g${ong}n>P${ESC}[38;5;202m8}.${dfl}
${ESC}[38;5;202m?g${ong}2!${ESC}[38;5;202m%888${ESC}[38;5;178mB${ylw}B${ESC}[38;5;178mev*${ong}<!!!!!!!!!>${ESC}[38;5;178m***^${ong}!!!!!.L${ESC}[38;5;202mB&?${dfl}
${ESC}[38;5;202m>gF${ong}=${ESC}[38;5;202mRg8${ong}8${ylw}#5${dfl} ${ESC}[38;5;202m,${ong}^}${ESC}[38;5;202m#Be-${dfl}
${ESC}[38;5;202mPg${ong}'${ESC}[38;5;202ma88${ong}8${ylw}#B ${ESC}[38;5;202m,unnnnn}- .ZO${ong}_${ESC}[38;5;202mN${ESC}[38;5;208m#B${ESC}[38;5;202mgr${dfl}
${ESC}[38;5;202m-gZ${ong}-${ESC}[38;5;202mg88${ong}8${ylw}B, ${ESC}[38;5;202m.U88${ong}!${ESC}[38;5;202mj${ESC}[38;5;208m#B#${ESC}[38;5;202m${ESC}[38;5;202mQs.${dfl}
${ESC}[38;5;202m.NM${ong}-${ESC}[38;5;202mD88${ong}N${ylw}! .${ESC}[38;5;202mj888${ong}=${ESC}[38;5;202mX${ESC}[38;5;208mQQ${ESC}[38;5;202mQ${ong}O${ESC}[38;5;202mQQ=${dfl}
${ESC}[38;5;202m28${ong}>u${ESC}[38;5;202m8g< ${ESC}[38;5;178m,${ong}!!!!!!!!!!!!!!!!${ESC}[38;5;178m>Lp${ESC}[38;5;202mQ888K${ong},${ESC}[38;5;202mQQ${ESC}[38;5;208mg${ESC}[38;5;202my ,V>${dfl}
${ESC}[38;5;202m_OM${ong}:K${ESC}[38;5;202m? ${ylw}.&#${ong}Q${ESC}[38;5;202m88888888888888${ESC}[38;5;178m#${ylw}@@${ESC}[38;5;178m#${ESC}[38;5;202m888M${ong}:p${ESC}[38;5;202m#${ESC}[38;5;208m#Q${ESC}[38;5;202m8G! .${dfl}
${ESC}[38;5;202m,WM${ong}!.!!${ESC}[38;5;202m^p${ESC}[38;5;178m#${ylw}@@${ESC}[38;5;178m#${ong}#${ESC}[38;5;202mQ88g8888Q${ong}B${ESC}[38;5;178m#${ylw}@@${ESC}[38;5;178m#Q${ESC}[38;5;202m888e${ong}!W${ESC}[38;5;202m#${ESC}[38;5;208m####${ESC}[38;5;202mBBg2!${dfl}
${ESC}[38;5;202m.u&n${ong}^y${ESC}[38;5;202mg88${ong}8${ESC}[38;5;178mQ${ylw}##@${ESC}[38;5;178m@${ong}@${ESC}[38;5;202m@@@${ong}@${ESC}[38;5;178m@@${ylw}@#${ESC}[38;5;178mB8${ong}8${ESC}[38;5;202m88K${ong}*}${ESC}[38;5;202mQ${ESC}[38;5;208m###${ESC}[38;5;202mBQ${ong}Q${ESC}[38;5;202mQ${ESC}[38;5;208mBB${ESC}[38;5;202m##gL.${dfl}
${ESC}[38;5;202m_nD${ong}j?${ESC}[38;5;202mxmPggg${ESC}[38;5;178m8${ylw}88Q${ESC}[38;5;178mQQQ${ylw}Q8${ESC}[38;5;178m88${ong}88${ESC}[38;5;202m8Wx${ong}?z${ESC}[38;5;202mQ${ESC}[38;5;208m#########${ESC}[38;5;202mQ${ESC}[38;5;208m#${ong}@${ESC}[38;5;202mQ8Z.${dfl}
${ESC}[38;5;202m.^y${ong}Oj${ESC}[38;5;202mLLL${ESC}[38;5;208m}${ESC}[38;5;202mjP${ong}%OEOM${ESC}[38;5;202m5suLxL${ong}jQ${ESC}[38;5;202m#${ESC}[38;5;208m######${ESC}[38;5;214mQ${ESC}[38;5;208m###${ESC}[38;5;202mB88&2${ong}vvn${ESC}[38;5;202mWQ(${dfl}
${ESC}[38;5;202m.!}${ong}%QQNWKessaG%gB${ESC}[38;5;202m###${ESC}[38;5;208m########${ESC}[38;5;202mB${ESC}[38;5;214mQ@@@${ESC}[38;5;208m#${ESC}[38;5;202mR?. r<${dfl}
${ESC}[38;5;202m.!LP${ESC}[38;5;208m8B${ESC}[38;5;214m##${ESC}[38;5;202m#Q${ong}5${ESC}[38;5;202mV%8${ESC}[38;5;208mB##B88${ong}QQ${ESC}[38;5;202mB${ESC}[38;5;208m##B${ESC}[38;5;202mB${ESC}[38;5;214m#@@@@@${ong}Q${ESC}[38;5;202me?,${dfl}
${ESC}[38;5;202m_*${ESC}[38;5;208mTW${ESC}[38;5;214m#${ESC}[38;5;202mP_ !K${ESC}[38;5;208mQ${ESC}[38;5;214m#${ESC}[38;5;202m@${ong}BM^^}${ESC}[38;5;202mOB${ESC}[38;5;208m##BB#${ESC}[38;5;202m#${ESC}[38;5;214m@@@#B${ESC}[38;5;202ma<${dfl}
${ESC}[38;5;202m-${ESC}[38;5;208m<${ESC}[38;5;202m!. :j${ESC}[38;5;208mg${ESC}[38;5;214m##${ESC}[38;5;202mh: _ry${ESC}[38;5;208mW8BBB${ESC}[38;5;202mB#${ESC}[38;5;214m#@@@@@${ESC}[38;5;202m#}.${dfl}
${ESC}[38;5;202m'!?${ESC}[38;5;214mn${ESC}[38;5;202mn?=. .-_,${ong}=${ESC}[38;5;202m^x${ESC}[38;5;214mAQ#@@${ESC}[38;5;202mB>${dfl}
${ESC}[38;5;202m>s${ESC}[38;5;214mQ@${ESC}[38;5;202m@?${dfl}
${ESC}[38;5;202m_n${ESC}[38;5;214mB${ESC}[38;5;202m#,${dfl}
${ESC}[38;5;202m-hv${dfl}
"
prbl-logo(){
echo "${PRBL_LOGO}"
}
# Function for spinner status
# usage: spin "command [args]"
set_spinner() {
case $1 in
spinner1)
FRAME="⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
FRAME_INTERVAL=0.05
;;
spinner2)
FRAME="-\|/"
FRAME_INTERVAL=0.1
;;
spinner3)
FRAME="◐◓◑◒"
FRAME_INTERVAL=0.2
;;
spinner4)
FRAME="⇐⇖⇑⇗⇒⇘⇓⇙"
FRAME_INTERVAL=0.1
;;
spinner5)
FRAME="◇◈◆◈"
FRAME_INTERVAL=0.2
;;
spinner6)
FRAME="⚬⚭⚮⚯⚮⚭"
FRAME_INTERVAL=0.15
;;
spinner7)
FRAME="░▒▓█▓▒"
FRAME_INTERVAL=0.2
;;
spinner8)
FRAME="☉◎◉◎☉"
FRAME_INTERVAL=0.1
;;
spinner9)
FRAME="♡♥❤♥♡"
FRAME_INTERVAL=0.15
;;
spinner10)
FRAME="▁▂▃▄▅▆▇█▇▆▅▄▃▂▁"
FRAME_INTERVAL=0.1
;;
spinner11)
FRAME="←↖↑↗→↘↓↙"
FRAME_INTERVAL=0.1
;;
spinner12)
FRAME="▖▘▝▗"
FRAME_INTERVAL=0.2
;;
spinner13)
FRAME="▙▛▜▟"
FRAME_INTERVAL=0.2
;;
spinner14)
FRAME="▚▞"
FRAME_INTERVAL=0.5
;;
spinner15)
FRAME="☰☱☳☶☵☳☶☴☰☰"
FRAME_INTERVAL=0.1
;;
spinner16)
FRAME="▤▧▥▨"
FRAME_INTERVAL=0.2
;;
spinner17)
FRAME="▉▊▋▌▍▎▏▎▍▌▋▊▉"
FRAME_INTERVAL=0.1
;;
spinner18)
FRAME="⣾⣽⣻⢿⡿⣟⣯⣷"
FRAME_INTERVAL=0.1
;;
spinner19)
FRAME="┤┘┴└├┌┬┐"
FRAME_INTERVAL=0.1
;;
*)
warn "No spinner is defined for $1"
esac
}
spin() {
CMD=$@
tput civis
sc=0
$CMD &
_PID=$!
while [ -d /proc/$_PID ] ; do
for i in "${FRAME[@]}" ; do
printf "\b${FRAME:sc++:1}"
((sc==${#FRAME})) && sc=0
#echo -ne "\b${FRAME[$i]}"
sleep $FRAME_INTERVAL
done
done
printf "\r\n"
tput cnorm
}
spinstart() {
sstop=false
tput civis
sc=0
until [[ $sstop != false ]] ; do
for i in "${FRAME[@]}" ; do
printf "\b${FRAME:sc++:1}"
((sc==${#FRAME})) && sc=0
#echo -ne "\b${FRAME[$i]}"
sleep $FRAME_INTERVAL
done
done
}
spinstop() {
export sstop=true
printf "\r\n"
tput cnorm
}
# https://github.com/fearside/ProgressBar/blob/master/progressbar.sh
progress-bar() {
# Process data
let _progress=(${1}*100/${2}*100)/100
let _done=(${_progress}*4)/10
let _left=40-$_done
# Build progressbar string lengths
_done=$(printf "%${_done}s")
_left=$(printf "%${_left}s")
# 1.2 Build progressbar strings and print the ProgressBar line
# 1.2.1 Output example:
# 1.2.1.1 Progress : [########################################] 100%
printf "\rProgress : [${_done// /#}${_left// /-}] ${_progress}%%"
}
# Menu functions
# Renders a text based list of options that can be selected by the
# user using up, down and enter keys and returns the chosen option.
#
# Arguments : list of options, maximum of 256
# "opt1" "opt2" ...
# Return value: selected index (0 for opt1, 1 for opt2 ...)
select_option(){
# little helpers for terminal print control and key input
cursor_blink_on() { printf "$ESC[?25h"; }
cursor_blink_off() { printf "$ESC[?25l"; }
cursor_to() { printf "$ESC[$1;${2:-1}H"; }
print_option() { printf " $1 "; }
print_selected() { printf " $ESC[7m $1 $ESC[27m"; }
get_cursor_row() { IFS=';' read -sdR -p $'\E[6n' ROW COL; echo ${ROW#*[}; }
key_input() { read -s -n3 key 2>/dev/null >&2
if [[ $key = $ESC[A ]]; then echo up; fi
if [[ $key = $ESC[B ]]; then echo down; fi
if [[ $key = "" ]]; then echo enter; fi; }
# initially print empty new lines (scroll down if at bottom of screen)
for opt; do printf "\n"; done
# determine current screen position for overwriting the options
local lastrow=`get_cursor_row`
local startrow=$(($lastrow - $#))
# ensure cursor and input echoing back on upon a ctrl+c during read -s
trap "cursor_blink_on; stty echo; printf '\n'; exit" 2
cursor_blink_off
local selected=0
while true; do
# print options by overwriting the last lines
local idx=0
for opt; do
cursor_to $(($startrow + $idx))
if [ $idx -eq $selected ]; then
print_selected "$opt"
else
print_option "$opt"
fi
((idx++))
done
# user key control
case `key_input` in
enter) break;;
up) ((selected--));
if [ $selected -lt 0 ]; then selected=$(($# - 1)); fi;;
down) ((selected++));
if [ $selected -ge $# ]; then selected=0; fi;;
esac
done
# cursor position back to normal
cursor_to $lastrow
printf "\n"
cursor_blink_on
return $selected
}
# Example for above select_option
#echo "Select one option using up/down keys and enter to confirm:"
#echo
#options=("one" "two" "three")
#select_option "${options[@]}"
#choice=$?
#echo "Choosen index = $choice"
#echo " value = ${options[$choice]}"
select_opt(){
select_option "$@" 1>&2
local result=$?
echo $result
return $result
}
# Examples for above select_opt
#case `select_opt "Yes" "No" "Cancel"` in
# 0) echo "selected Yes";;
# 1) echo "selected No";;
# 2) echo "selected Cancel";;
#esac
#options=("Yes" "No" "${array[@]}") # join arrays to add some variable array
#case `select_opt "${options[@]}"` in
# 0) echo "selected Yes";;
# 1) echo "selected No";;
# *) echo "selected ${options[$?]}";;
#esac
multiselect(){
# little helpers for terminal print control and key input
cursor_blink_on() { printf "$ESC[?25h"; }
cursor_blink_off() { printf "$ESC[?25l"; }
cursor_to() { printf "$ESC[$1;${2:-1}H"; }
print_inactive() { printf "$2 $1 "; }
print_active() { printf "$2 $ESC[7m $1 $ESC[27m"; }
get_cursor_row() { IFS=';' read -sdR -p $'\E[6n' ROW COL; echo ${ROW#*[}; }
local return_value=$1
local -n options=$2
local -n defaults=$3
local selected=()
for ((i=0; i<${#options[@]}; i++)); do
if [[ ${defaults[i]} = "true" ]]; then
selected+=("true")
else
selected+=("false")
fi
printf "\n"
done
# determine current screen position for overwriting the options
local lastrow=`get_cursor_row`
local startrow=$(($lastrow - ${#options[@]}))
# ensure cursor and input echoing back on upon a ctrl+c during read -s
trap "cursor_blink_on; stty echo; printf '\n'; exit" 2
cursor_blink_off
key_input() {
local key
IFS= read -rsn1 key 2>/dev/null >&2
if [[ $key = "" ]]; then echo enter; fi;
if [[ $key = $'\x20' ]]; then echo space; fi;
if [[ $key = "k" ]]; then echo up; fi;
if [[ $key = "j" ]]; then echo down; fi;
if [[ $key = $'\x1b' ]]; then
read -rsn2 key
if [[ $key = [A || $key = k ]]; then echo up; fi;
if [[ $key = [B || $key = j ]]; then echo down; fi;
fi
}
toggle_option() {
local option=$1
if [[ ${selected[option]} == true ]]; then
selected[option]=false
else
selected[option]=true
fi
}
print_options() {
# print options by overwriting the last lines
local idx=0
for option in "${options[@]}"; do
#local prefix="[ ]"
local prefix="${gry}${deselected_opt}${dfl}"
if [[ ${selected[idx]} == true ]]; then
#prefix="[\e[38;5;46m✔\e[0m]"
prefix="${selected_opt}"
fi
cursor_to $(($startrow + $idx))
if [ $idx -eq $1 ]; then
print_active "$option" "$prefix"
else
print_inactive "$option" "$prefix"
fi
((idx++))
done
}
local active=0
while true; do
print_options $active
# user key control
case `key_input` in
space) toggle_option $active;;
enter) print_options -1; break;;
up) ((active--));
if [ $active -lt 0 ]; then active=$((${#options[@]} - 1)); fi;;
down) ((active++));
if [ $active -ge ${#options[@]} ]; then active=0; fi;;
esac
done
# cursor position back to normal
cursor_to $lastrow
printf "\n"
cursor_blink_on
eval $return_value='("${selected[@]}")'
}
# Example for above multiselect functions
#my_options=( "Option 1" "Option 2" "Option 3" )
#preselection=( "true" "true" "false" )
#multiselect result my_options preselection
#idx=0
#for option in "${my_options[@]}"; do
# echo -e "$option\t=> ${result[idx]}"
# ((idx++))
#done
### IP validation function
# returns 0 (true) for valid, 1 (false) for invalid
valid-ip()
{
local ip=$1
local stat=1
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
OIFS=$IFS
IFS='.'
ip=($ip)
IFS=$OIFS
[[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \
&& ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]
stat=$?
fi
return $stat
}
# Version comparison function
# usage:
# vercomp [first version] [second version]
# output:
# 0 =
# 1 >
# 2 <
vercomp() {
if [[ $1 == $2 ]]
then
return 0
fi
local IFS=.
local i ver1=($1) ver2=($2)
# fill empty fields in ver1 with zeros
for ((i=${#ver1[@]}; i<${#ver2[@]}; i++))
do
ver1[i]=0
done
for ((i=0; i<${#ver1[@]}; i++))
do
if [[ -z ${ver2[i]} ]]
then
# fill empty fields in ver2 with zeros
ver2[i]=0
fi
if ((10#${ver1[i]} > 10#${ver2[i]}))
then
# version 1 is greater than version 2
return 1
fi
if ((10#${ver1[i]} < 10#${ver2[i]}))
then
# version 1 is less than version 2
return 2
fi
done
return 0
}
check-prbl-version(){
# TODO: Finish writing this function
vercomp
}
compare-versions(){
vercomp $1 $2
case $? in
0) op='=';;
1) op='>';;
2) op='<';;
esac
if [[ $op != $3 ]]
then
echo "Fail: Expected '$3', Actual '$op', Arg1 '$1', Arg2 '$2'"
else
echo "Pass: '$1 $op $2'"
fi
}
identify-system(){
if type lsb_release >/dev/null 2>&1; then
ID=$(lsb_release -si)
OS=$(lsb_release -si)
VER=$(lsb_release -sr)
elif [ -f /etc/os-release ]; then
. /etc/os-release
ID=${ID:-$VERSION_ID}
ID=${ID:-$VERSION}
OS=$NAME
VER=$VERSION_ID
VER=${VER:-$VERSION}
elif [ -f /etc/lsb-release ]; then
. /etc/lsb-release
ID=$DISTRIB_ID
OS=$DISTRIB_ID
VER=$DISTRIB_RELEASE
VER=${VER:-$LSB_VERSION}
elif [ -f /etc/debian_version ]; then
ID=debian
OS=Debian
VER=$(cat /etc/debian_version)
elif [ -f /etc/redhat-release ]; then
OS=$(awk '{print $1}' /etc/redhat-release)
else
OS=$(uname -s)
VER=$(uname -r)
fi
}
ID(){
boxborder \
"ID: $ID" \
"OS: $OS" \
"VER: $VER"
}
install-packages(){
declare -a packages=()
declare -a offline_packages=()
for arg in "$@"; do
if [[ -f $arg ]]; then
offline_packages+=("$arg")
else
packages+=("$arg")
fi
done
identify-system
case $ID in
amzn | centos | fedora | rhel)
[ "${#packages[@]}" -gt 0 ] && run sudo yum install -y "${packages[@]}"
[ "${#offline_packages[@]}" -gt 0 ] && run sudo rpm -i "${packages[@]}"
;;
arch | Arch | manjaro)
if [ "${#packages[@]}" -gt 0 ]; then
run sudo pacman -Syu
run sudo pacman -S "${packages[@]}"
fi
[ "${#offline_packages[@]}" -gt 0 ] && run sudo pacman -U "${offline_packages[@]}"
;;
debian | ubuntu | Ubuntu)
if [ "${#packages[@]}" -gt 0 ]; then
run sudo apt-get update
run sudo apt-get install -y "${packages[@]}"
fi
[ "${#offline_packages[@]}" -gt 0 ] && run sudo dpkg -i "${offline_packages[@]}"
;;
opensuse | sles)
if [ "${#packages[@]}" -gt 0 ]; then
run sudo zypper refresh
run sudo zypper install "${packages[@]}"
fi
[ "${#offline_packages[@]}" -gt 0 ] && run sudo rpm -i "${offline_packages[@]}"
;;
slackware)
if [ "${#packages[@]}" -gt 0 ]; then
for package in "${packages[@]}"; do
run sudo installpkg "$package"
done
fi
if [ "${#offline_packages[@]}" -gt 0 ]; then
for package_file in "${offline_packages[@]}"; do
run sudo installpkg "$package_file"
done
fi
;;
brew)
[ "${#packages[@]}" -gt 0 ] && run brew install "${packages[@]}"
if [ "${#offline_packages[@]}" -gt 0 ]; then
for package_file in "${offline_packages[@]}"; do
run brew install "$package_file"
done
fi
;;
esac
}
check-packages() {
# Utilizes system package manager to check for installed packages
# Declare arrays for online and offline packages
declare -a packages=()
declare -a offline_packages=()
# Separate online and offline packages
for arg in "$@"; do
if [[ -f $arg ]]; then
offline_packages+=("$arg")
else
packages+=("$arg")
fi
done
# Identify the system
identify-system
# Check packages based on system type
case $ID in
amzn | centos | fedora | rhel)
# Check online packages
if [ "${#packages[@]}" -gt 0 ]; then
for package in "${packages[@]}"; do
if yum list installed "$package" >/dev/null 2>&1; then
installed_version=$(yum list installed "$package" | grep "$package" | awk '{print $2}')
available_version=$(yum list available "$package" | grep "$package" | awk '{print $2}')
if [ "$installed_version" = "$available_version" ]; then
return 0
elif [ "$installed_version" \> "$available_version" ]; then
return 3
else
return 1
fi
else
return 2
fi
done
fi
# Check offline packages
if [ "${#offline_packages[@]}" -gt 0 ]; then
for package_file in "${offline_packages[@]}"; do
if rpm -q --quiet "$package_file"; then
installed_version=$(rpm -q "$package_file" --qf "%{VERSION}")
return 0
else
return 2
fi
done
fi
;;
arch | Arch | manjaro)
# Check online packages
if [ "${#packages[@]}" -gt 0 ]; then
for package in "${packages[@]}"; do
if pacman -Q "$package" >/dev/null 2>&1; then
installed_version=$(pacman -Q "$package" | awk '{print $2}')
available_version=$(pacman -Si "$package" | grep "Version" | awk '{print $3}')
if [ "$installed_version" = "$available_version" ]; then
return 0
elif [ "$installed_version" \> "$available_version" ]; then
return 3
else
return 1
fi
else
return 2
fi
done
fi
# Check offline packages
if [ "${#offline_packages[@]}" -gt 0 ]; then
for package_file in "${offline_packages[@]}"; do
if pacman -Qp "$package_file" >/dev/null 2>&1; then
installed_version=$(pacman -Qp "$package_file" | awk '{print $2}')
return 0
else
return 2
fi
done
fi
;;
debian | ubuntu | Ubuntu)
# Check online packages
if [ "${#packages[@]}" -gt 0 ]; then
for package in "${packages[@]}"; do
if dpkg -s "$package" >/dev/null 2>&1; then
installed_version=$(dpkg -s "$package" | grep -E "^Version:" | awk '{print $2}')
available_version=$(apt-cache show "$package" | grep -E "^Version:" | awk '{print $2}')
if [ "$installed_version" = "$available_version" ]; then
return 0
elif [ "$installed_version" \> "$available_version" ]; then
return 3
else
return 1
fi
else
return 2
fi
done
fi
# Check offline packages
if [ "${#offline_packages[@]}" -gt 0 ]; then
for package_file in "${offline_packages[@]}"; do
if dpkg -I "$package_file" >/dev/null 2>&1; then
installed_version=$(dpkg -I "$package_file" | grep -E "^ Version:" | awk '{print $3}')
return 0
else
return 2
fi
done
fi
;;
opensuse | sles)
# Check online packages
if [ "${#packages[@]}" -gt 0 ]; then
for package in "${packages[@]}"; do
if zypper info "$package" >/dev/null 2>&1; then
installed_version=$(zypper info "$package" | grep "Version" | awk '{print $3}')
available_version=$(zypper se -i "$package" | grep "Version" | awk '{print $3}')
if [ "$installed_version" = "$available_version" ]; then
return 0
elif [ "$installed_version" \> "$available_version" ]; then
return 3
else
return 1
fi
else
return 2
fi
done
fi
# Check offline packages
if [ "${#offline_packages[@]}" -gt 0 ]; then
for package_file in "${offline_packages[@]}"; do
if rpm -q --quiet "$package_file"; then
installed_version=$(rpm -q "$package_file" --qf "%{VERSION}")
return 0
else
return 2
fi
done
fi
;;
slackware)
# Check online packages
if [ "${#packages[@]}" -gt 0 ]; then
for package in "${packages[@]}"; do
if slackpkg search "$package" >/dev/null 2>&1; then
installed_version=$(slackpkg search "$package" | awk '{print $3}')
return 0
else
return 2
fi
done
fi
# Check offline packages
if [ "${#offline_packages[@]}" -gt 0 ]; then
for package_file in "${offline_packages[@]}"; do
if slackpkg search "$package_file" >/dev/null 2>&1; then
installed_version=$(slackpkg search "$package_file" | awk '{print $3}')
return 0
else
return 2
fi
done
fi
;;
brew)
# Check online packages
if [ "${#packages[@]}" -gt 0 ]; then
for package in "${packages[@]}"; do
if brew list "$package" >/dev/null 2>&1; then
installed_version=$(brew list --versions "$package" | awk '{print $2}')
available_version=$(brew info "$package" | head -n 1 | awk '{print $3}')
if [ "$installed_version" = "$available_version" ]; then
return 0
elif [ "$installed_version" \> "$available_version" ]; then
return 3
else
return 1
fi
else
return 2
fi
done
fi
# Check offline packages
if [ "${#offline_packages[@]}" -gt 0 ]; then
for package_file in "${offline_packages[@]}"; do
if brew list "$package_file" >/dev/null 2>&1; then
installed_version=$(brew list --versions "$package_file" | awk '{print $2}')
return 0
else
return 2
fi
done
fi
;;
esac
}
set_box_chars
update_terminal_width
set-boxtype