diff --git a/bash.bashrc b/bash.bashrc index 39ac40f..391fd57 100644 --- a/bash.bashrc +++ b/bash.bashrc @@ -1,163 +1,169 @@ #!/bin/bash function __prompt_command { - local EXIT="$?" # This needs to be first - - local R='\[\e[0m\]' - local Red='\[\e[31m\]' - local Gre='\[\e[32m\]' - local Yel='\[\e[93m\]' - local Blu='\[\e[34m\]' - local Cya='\[\e[36m\]' - local Mag='\[\e[95m\]' - local Gra='\[\e[90m\]' - - local DIVERGENT_GIT_SYMBOL="${Yel}➾${R}" - local SYNCHRONIZED_GIT_SYMBOL="${Gre}✔${R}" - local PUSHED_NEEDED_SYMBOL='⥱' - local TAG_NEEDED_SYMBOL='⤽' - - # set xterm title - echo -ne "\033]0;${USER}@${HOSTNAME}: ${PWD}\007" - - PS1='' - - if [[ -n ${container_hostname:-} ]]; then - PS1+="${Gra}[LXC:${R}" - local host - host=$(builtin echo $container_hostname | tr ' ' ':') - case ${host:0:4} in - 'beta') - PS1+="${Yel}" - ;; - 'prod') - PS1+="${Red}" - ;; - *) ;; - esac - PS1+="${host}${Gra}] " - fi - - if [[ "$(id -u)" -eq 0 ]]; then - PS1+="$Yel\u$R" - PROMPT='$' - else - PS1+="$Gre\u$R" - PROMPT='#' - fi - - PS1+="${Gra}@" - - case ${HOSTNAME:0:4} in + local EXIT="$?" # This needs to be first + + local R='\[\e[0m\]' + local Red='\[\e[31m\]' + local Gre='\[\e[32m\]' + local Yel='\[\e[93m\]' + local Blu='\[\e[34m\]' + local Cya='\[\e[36m\]' + local Mag='\[\e[95m\]' + local Gra='\[\e[90m\]' + + local DIVERGENT_GIT_SYMBOL="${Yel}➾${R}" + local SYNCHRONIZED_GIT_SYMBOL="${Gre}✔${R}" + local PUSHED_NEEDED_SYMBOL='⥱' + local TAG_NEEDED_SYMBOL='⤽' + + # set xterm title + echo -ne "\033]0;${USER}@${HOSTNAME}: ${PWD}\007" + + PS1='' + + if [[ -n ${container_hostname:-} ]]; then + PS1+="${Gra}[LXC:${R}" + local host + host=$(builtin echo $container_hostname | tr ' ' ':') + case ${host:0:4} in 'beta') - PS1+="${Yel}" - ;; + PS1+="${Yel}" + ;; 'prod') - PS1+="${Red}" - ;; - *) - PS1+="${R}" - ;; + PS1+="${Red}" + ;; + *) ;; esac - PS1+="$HOSTNAME" - - local pwd='~' - [ "$PWD" != "$HOME" ] && pwd=${PWD/#$HOME\//\~\/} - PS1+=" ${Cya}${pwd}$(__vte_osc7)${R}" - - if hash git 2>&-; then - # git command exists - - if git rev-parse --git-dir >/dev/null 2>&1; then - # current dir is version controlled - - local branch tag_release dirty - branch=$(git branch 2>/dev/null | grep -e ^* | cut -d ' ' -f2) - PS1+=" ${R}[${Mag}${branch}${R}|" - - tag_release=$(git describe --tags 2>/dev/null | cut -d'-' -f1) - dirty=$(git status -s | wc -l) - if [[ $dirty -ne 0 ]]; then - PS1+="${Yel}${DIVERGENT_GIT_SYMBOL}${R}|${tag_release}${Yel}…$dirty" + PS1+="${host}${Gra}] " + fi + + if [[ "$(id -u)" -eq 0 ]]; then + PS1+="$Yel\u$R" + PROMPT='$' + else + PS1+="$Gre\u$R" + PROMPT='#' + fi + + PS1+="${Gra}@" + + case ${HOSTNAME:0:4} in + 'beta') + PS1+="${Yel}" + ;; + 'prod') + PS1+="${Red}" + ;; + *) + PS1+="${R}" + ;; + esac + PS1+="$HOSTNAME" + + local pwd='~' + [ "$PWD" != "$HOME" ] && pwd=${PWD/#$HOME\//\~\/} + PS1+=" ${Cya}${pwd}$(__vte_osc7)${R}" + + if hash git 2>&-; then + # git command exists + + if git rev-parse --git-dir >/dev/null 2>&1; then + # current dir is version controlled + + local branch tag_release dirty + branch=$(git branch 2>/dev/null | grep -e ^* | cut -d ' ' -f2) + PS1+=" ${R}[${Mag}${branch}${R}|" + + tag_release=$(git describe --tags 2>/dev/null | cut -d'-' -f1) + dirty=$(git status -s | wc -l) + if [[ $dirty -ne 0 ]]; then + PS1+="${Yel}${DIVERGENT_GIT_SYMBOL}${R}|${tag_release}${Yel}…$dirty" + else + local ahead + ahead=$(git rev-list --branches --not --remotes | wc -l) + + if [[ $ahead -ne 0 ]]; then + PS1+="${DIVERGENT_GIT_SYMBOL}|${tag_release}${Yel}${PUSHED_NEEDED_SYMBOL}${ahead}" + else + if [[ -z $tag_release ]]; then + PS1+="${SYNCHRONIZED_GIT_SYMBOL}" + else + local commit_ahead + commit_ahead=$(git log "$tag_release"..HEAD --oneline | wc -l) + + if [[ $commit_ahead -gt 0 ]]; then + PS1+="${DIVERGENT_GIT_SYMBOL}|${tag_release}${Cya}${TAG_NEEDED_SYMBOL}${commit_ahead}" else - local ahead - ahead=$(git rev-list --branches --not --remotes | wc -l) - - if [[ $ahead -ne 0 ]]; then - PS1+="${DIVERGENT_GIT_SYMBOL}|${tag_release}${Yel}${PUSHED_NEEDED_SYMBOL}${ahead}" - else - if [[ -z $tag_release ]]; then - PS1+="${SYNCHRONIZED_GIT_SYMBOL}" - else - local commit_ahead - commit_ahead=$(git log "$tag_release"..HEAD --oneline | wc -l) - - if [[ $commit_ahead -gt 0 ]]; then - PS1+="${DIVERGENT_GIT_SYMBOL}|${tag_release}${Cya}${TAG_NEEDED_SYMBOL}${commit_ahead}" - else - PS1+="${SYNCHRONIZED_GIT_SYMBOL}|${Cya}${tag_release}" - fi - fi - fi + PS1+="${SYNCHRONIZED_GIT_SYMBOL}|${Cya}${tag_release}" fi - PS1+="${R}]" + fi fi + fi + PS1+="${R}]" fi + fi - if [ $EXIT != 0 ]; then - PS1+="$Mag" # Add red if exit code non 0 - else - PS1+="$Gra" - fi + if [ $EXIT != 0 ]; then + PS1+="$Mag" # Add red if exit code non 0 + else + PS1+="$Gra" + fi - PS1+=" $PROMPT ${R}" + PS1+=" $PROMPT ${R}" } function __vte_osc7 { - # HINT: special character vte to get TILIX show proper hostname and pwd - # Use \[...\] around the parts of PS1 that have length 0. - # https://unix.stackexchange.com/questions/28827/why-is-my-bash-prompt-getting-bugged-when-i-browse-the-history#28828 - - VTE_INFO="${HOSTNAME:-}" - if [[ -n ${container_hostname:-} ]]; then - local host - host=$(builtin echo $container_hostname | tr ' ' ':') - VTE_INFO="$host:$HOSTNAME" - fi - printf "\[\033]7;file://%s%s\a\]" "${VTE_INFO}" "$(__vte_urlencode "${PWD}")" + # HINT: special character vte to get TILIX show proper hostname and pwd + # Use \[...\] around the parts of PS1 that have length 0. + # https://unix.stackexchange.com/questions/28827/why-is-my-bash-prompt-getting-bugged-when-i-browse-the-history#28828 + + VTE_INFO="${HOSTNAME:-}" + if [[ -n ${container_hostname:-} ]]; then + local host + host=$(builtin echo $container_hostname | tr ' ' ':') + VTE_INFO="$host:$HOSTNAME" + fi + printf "\[\033]7;file://%s%s\a\]" "${VTE_INFO}" "$(__vte_urlencode "${PWD}")" } function __vte_urlencode { - # This is important to make sure string manipulation is handled - # byte-by-byte. - LC_ALL=C - str="$1" - while [ -n "$str" ]; do - safe="${str%%[!a-zA-Z0-9/:_\.\-\!\'\(\)~]*}" - printf "%s" "$safe" - str="${str#"$safe"}" - if [ -n "$str" ]; then - printf "%%%02X" "'$str" - str="${str#?}" - fi - done + # This is important to make sure string manipulation is handled + # byte-by-byte. + LC_ALL=C + str="$1" + while [ -n "$str" ]; do + safe="${str%%[!a-zA-Z0-9/:_\.\-\!\'\(\)~]*}" + printf "%s" "$safe" + str="${str#"$safe"}" + if [ -n "$str" ]; then + printf "%%%02X" "'$str" + str="${str#?}" + fi + done } ###------ ### MAIN ###------ +MIAOU_BASH_DIR=$(realpath /opt/miaou-bash) +export MIAOU_BASH_DIR + +## Add path for TOOLBOX if any +[[ -d "/TOOLBOX" ]] && PATH=$PATH:/TOOLBOX || true + +# Add path to any tools folder in /opt/miaou-* +for i in {/opt,$HOME}/miaou-*/tools; do [[ -d "$i" ]] && PATH=$PATH:$i; done || true + # If not running interactively, don't do anything case $- in *i*) ;; *) return ;; esac -MIAOU_BASH_DIR=$(realpath /opt/miaou-bash) -export MIAOU_BASH_DIR - HOSTNAME=$(hostname -f) # don't put duplicate lines or lines starting with space in the history. @@ -180,9 +186,9 @@ shopt -s checkwinsize # set a fancy prompt (non-color, unless we know we "want" color) case "$TERM" in xterm-color | *-256color) - color_prompt=yes - export COLOR_OPTIONS=' --color=auto' - ;; + color_prompt=yes + export COLOR_OPTIONS=' --color=auto' + ;; esac export LS_COLORS="rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.sql.gz=01;35:*.mariadb.gz=01;35:*.postgres.gz=01;35:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:" @@ -192,11 +198,11 @@ export LESS="r" # this, if it's already enabled in /etc/bash.bashrc and /etc/profile # sources /etc/bash.bashrc). if ! shopt -oq posix; then - if [ -f /usr/share/bash-completion/bash_completion ]; then - . /usr/share/bash-completion/bash_completion - elif [ -f /etc/bash_completion ]; then - . /etc/bash_completion - fi + if [ -f /usr/share/bash-completion/bash_completion ]; then + . /usr/share/bash-completion/bash_completion + elif [ -f /etc/bash_completion ]; then + . /etc/bash_completion + fi fi # prevent CTRL+S to freeze in vim, CTRL+Q to unfreeze! @@ -232,9 +238,3 @@ alias ssu4="ss -ntel4p | perl -pne 'if(/uid:(\\d+)/){@a=getpwuid(\$1);s/uid:(\\d # Alias definitions. [[ -f "/etc/bash.aliases" ]] && source "/etc/bash.aliases" || true [[ -f "$HOME/.bash_aliases" ]] && source "$HOME/.bash_aliases" || true - -## Add path for TOOLBOX if any -[[ -d "/TOOLBOX" ]] && PATH=$PATH:/TOOLBOX || true - -# Add path to any tools folder in /opt/miaou-* -for i in {/opt,$HOME}/miaou-*/tools; do [[ -d "$i" ]] && PATH=$PATH:$i; done || true diff --git a/bin/miaou-bash b/bin/miaou-bash index cf27b61..568dd3c 100755 --- a/bin/miaou-bash +++ b/bin/miaou-bash @@ -1,14 +1,17 @@ #!/usr/bin/env bash # CONSTANTS -BASH_BACK_ERROR=$(mktemp -t "$(basename $0).XXXXXXXX" --tmpdir=/run/user/$(id -u)) + +BASH_BACK_NAME=$(basename "${0}") +BASH_BACK_ERROR=$(mktemp -t "${BASH_BACK_NAME}.XXXXXXXX" --tmpdir="/run/user/$(id -u)") BASH_BACK_DEBUG=${BASH_BACK_DEBUG:-false} +readonly BASH_BACK_NAME BASH_BACK_ERROR BASH_BACK_DEBUG # 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 + code=$(echo "${BASH_COMMAND}" | cut -d' ' -f2) # regex does not work here unfortunately! if [[ -n "$code" ]] && [[ "$code" -ne 0 ]]; then trap - DEBUG >&2 echo "${BASH_SOURCE[2]}: line ${BASH_LINENO[0]}: return $code from ${FUNCNAME[1]}" @@ -16,12 +19,12 @@ function on_return { fi } -# overridden function which permits stacktracing of original `return` statement +# overridden function which permits backtracing 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 + >&2 echo "${BASH_SOURCE[2]}:${BASH_LINENO[0]} exit $code" + builtin exit "$code" } function on_exit { @@ -31,19 +34,19 @@ function on_exit { stack_functions=("${FUNCNAME[@]}") stack_sources=("${BASH_SOURCE[@]}") - unset stack_lines[-1] - unset stack_lines[-1] - unset stack_functions[0] - unset stack_functions[-1] + 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] + 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] + unset 'stack_functions[1]' + unset 'stack_sources[1]' + unset 'stack_lines[0]' fi stack_lines=("${stack_lines[@]}") @@ -53,9 +56,9 @@ function on_exit { stack_summary=() if [[ $code -gt 0 ]]; then - last_error=$(tail -n1 $BASH_BACK_ERROR) + last_error=$(tail -n1 "$BASH_BACK_ERROR") last_source=${stack_sources[0]} - last_source=${last_source//./"\."} # replace . by \. + last_source=${last_source//./"\."} # replace '.' with '\.' regex1="^$last_source: line ([0-9]+): (.*)\$" regex2="^$last_source:([0-9]+) (.*)\$" if [[ "$last_error" =~ $regex1 || "$last_error" =~ $regex2 ]]; then @@ -92,7 +95,7 @@ function on_exit { fi cleanup - builtin exit $code + builtin exit "$code" } function dump_summary { @@ -104,8 +107,8 @@ function dump_summary { function dump_stderr { last_trim=${1:-false} - mapfile -t tmp_error <$BASH_BACK_ERROR - $last_trim && unset tmp_error[-1] && tmp_error=("${tmp_error[@]}") + 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 @@ -122,7 +125,7 @@ function success { function cleanup { trap - ERR TERM INT EXIT exec 3>&- 4>&- - rm $BASH_BACK_ERROR + rm "$BASH_BACK_ERROR" } # MAIN @@ -133,9 +136,13 @@ set -TEue -o pipefail trap 'on_exit $?' ERR TERM INT EXIT trap 'on_return' DEBUG -# magic FD -exec 3> >(tee $BASH_BACK_ERROR >/dev/null) 4>/dev/stderr +# magic FD + delayed tee +exec 3> >(tee "$BASH_BACK_ERROR" >/dev/null) 4>/dev/stderr + +# magic $0 +BASH_ARGV0="$1" # to pretend script runs by itself $BASH_BACK_DEBUG && >/dev/tty echo '---stdout---' -source $1 2>&3 +# shellcheck source=/dev/null +source "$1" 2>&3 success diff --git a/install.sh b/install.sh index e9c0786..40b882d 100755 --- a/install.sh +++ b/install.sh @@ -3,21 +3,21 @@ ## CONSTANTS readonly CURDIR=$PWD -readonly REQUIRED_PKGS=(file git bc vim jq) +readonly REQUIRED_PKGS=(file git bc vim jq bat gum) readonly MAIN_TAR_GZ_URL="https://git.artcode.re/miaou/miaou-bash/archive/main.tar.gz" ## FUNCTIONS function push_directory { - pushd "$1" || (echo "unable to push directory <$1>" && exit 1) + pushd "$1" || (echo "unable to push directory <$1>" && exit 1) } function pop_directory { - popd || (echo "unable to pop from previous directory" && exit 1) + popd || (echo "unable to pop from previous directory" && exit 1) } function root_required { - [ "$(id -u)" -ne 0 ] && echo 'root privilege required' && exit 1 + [ "$(id -u)" -ne 0 ] && echo 'root privilege required' && exit 1 } ## MAIN @@ -25,28 +25,28 @@ function root_required { root_required if [[ $CURDIR != '/opt/miaou-bash' ]]; then - # download and fullfill /opt/miaou-bash, then run it from folder + # download and fullfill /opt/miaou-bash, then run it from folder - if [[ -L /opt/miaou-bash ]]; then - /opt/miaou-bash/install.sh - else - rm -rf /opt/miaou-bash + if [[ -L /opt/miaou-bash ]]; then + /opt/miaou-bash/install.sh + else + rm -rf /opt/miaou-bash - push_directory "$(mktemp -d)" - curl --no-progress-meter -O $MAIN_TAR_GZ_URL - tar -xzf main.tar.gz - mv miaou-bash /opt/ - pop_directory - rm -rf "$TEMP" + push_directory "$(mktemp -d)" + curl --no-progress-meter -O $MAIN_TAR_GZ_URL + tar -xzf main.tar.gz + mv miaou-bash /opt/ + pop_directory + rm -rf "$TEMP" - push_directory /opt/miaou-bash && ./install.sh + push_directory /opt/miaou-bash && ./install.sh - fi + fi else - MIAOU_BASH_DIR=$CURDIR - export MIAOU_BASH_DIR - source "$MIAOU_BASH_DIR/lib/functions.bash" - "$MIAOU_BASH_DIR/tools/idem_apt_install" "${REQUIRED_PKGS[@]}" - ./init.sh + MIAOU_BASH_DIR=$CURDIR + export MIAOU_BASH_DIR + source "$MIAOU_BASH_DIR/lib/functions.bash" + "$MIAOU_BASH_DIR/tools/idem_apt_install" "${REQUIRED_PKGS[@]}" + ./init.sh fi diff --git a/lib/functions.bash b/lib/functions.bash index 4421929..2254607 100644 --- a/lib/functions.bash +++ b/lib/functions.bash @@ -5,35 +5,35 @@ ## =============== function _array_contains { - local -n array=$1 - local element=$2 - [[ " ${array[@]} " =~ " ${element} " ]] && return 0 || return 1 + local -n array=$1 + local element=$2 + [[ " ${array[@]} " =~ " ${element} " ]] && return 0 || return 1 } function _array_intersect { - local -n arr1=$1 - local -n arr2=$2 - local -n result=$3 + local -n arr1=$1 + local -n arr2=$2 + local -n result=$3 - declare -A include - for item in "${arr2[@]}"; do include["$item"]=1; done + declare -A include + for item in "${arr2[@]}"; do include["$item"]=1; done - result=() - for item in "${arr1[@]}"; do [[ -n ${include["$item"]} ]] && result+=("$item"); done + result=() + for item in "${arr1[@]}"; do [[ -n ${include["$item"]} ]] && result+=("$item"); done } function _array_subtract { - local -n arr1=$1 # First array - local -n arr2=$2 # Array to subtract - local -n result=$3 # Output array + local -n arr1=$1 # First array + local -n arr2=$2 # Array to subtract + local -n result=$3 # Output array - [[ ${#arr2[@]} == 0 ]] && result=("${arr1[@]}") && return + [[ ${#arr2[@]} == 0 ]] && result=("${arr1[@]}") && return - declare -A exclude - for item in "${arr2[@]}"; do exclude["$item"]=1; done + declare -A exclude + for item in "${arr2[@]}"; do exclude["$item"]=1; done - result=() - for item in "${arr1[@]}"; do [[ -z ${exclude["$item"]} ]] && result+=("$item"); done + result=() + for item in "${arr1[@]}"; do [[ -z ${exclude["$item"]} ]] && result+=("$item"); done } ## ============== @@ -41,8 +41,8 @@ function _array_subtract { ## ============== function _pluralize_simple { - echo -n "$1 " - [[ $1 -eq 1 || $1 -eq -1 ]] && echo ${2} || echo ${2}s + echo -n "$1 " + [[ $1 -eq 1 || $1 -eq -1 ]] && echo ${2} || echo ${2}s } ## ============== @@ -50,13 +50,32 @@ function _pluralize_simple { ## ============== function _confirm_destructive { - local message="${1:-This cannot be undone!}" - echo "⚠️ WARNING: $message" - read -p "Type YES to confirm: " response - case "$response" in - [yY][eE][sS] | [yY]) return 0 ;; - *) echo "canceled!" && return 1 ;; - esac + local message="${1:-This cannot be undone!}" + echo "⚠️ WARNING: $message" + read -p "Type YES to confirm: " response + case "$response" in + [yY][eE][sS] | [yY]) return 0 ;; + *) echo "canceled!" && return 1 ;; + esac +} + +## ================= +## OPTIONS Functions +## ================= + +function _flatten_short_options { + local -n args=$1 + local result=() + for word in ${args[@]}; do + [[ $word == -- ]] && break + if [[ $word =~ ^-[a-z][a-z] ]]; then + word=${word:1} + for ((i = 0; i < ${#word}; i++)); do result+=("-${word:i:1}"); done + else + result+=("$word") + fi + done + args=("${result[@]}") } ## ============== @@ -69,45 +88,45 @@ function _confirm_destructive { # ARRAY=( a b c ) # containsElement ARRAY 'a' => 0 function containsElement { - isArray "$1" || (echo >&2 "ERROR: <$1> not an array!" && return 2) - array_inter="$1[@]" - array=("${!array_inter}") - for i in "${array[@]}"; do [[ "$i" == "$2" ]] && return 0; done - return 1 + isArray "$1" || (echo >&2 "ERROR: <$1> not an array!" && return 2) + array_inter="$1[@]" + array=("${!array_inter}") + for i in "${array[@]}"; do [[ "$i" == "$2" ]] && return 0; done + return 1 } ## return 0 (true) if arg1 (passed by name) is an array function isArray { - [[ "$(declare -p "$1" 2>/dev/null)" =~ "declare -a" ]] || return 1 + [[ "$(declare -p "$1" 2>/dev/null)" =~ "declare -a" ]] || return 1 } function isDebian { - grep ^ID=debian /etc/os-release + grep ^ID=debian /etc/os-release } function isArch { - grep ^ID=arch /etc/os-release + grep ^ID=arch /etc/os-release } function os-release { - grep ^ID= /etc/os-release | cut -d '=' -f2 + grep ^ID= /etc/os-release | cut -d '=' -f2 } function askConfirmation { - case "$1" in - y | Y | yes | YES) - QUESTION="(Y/n)?" - DEFAULT=0 - ;; - *) - QUESTION="(y/N)?" - DEFAULT=1 - ;; - esac - read -rp "$QUESTION : " choice - case "$choice" in - y | Y | yes | YES) return 0 ;; #true - n | no | N | NO) return 1 ;; #false - *) return $DEFAULT ;; - esac + case "$1" in + y | Y | yes | YES) + QUESTION="(Y/n)?" + DEFAULT=0 + ;; + *) + QUESTION="(y/N)?" + DEFAULT=1 + ;; + esac + read -rp "$QUESTION : " choice + case "$choice" in + y | Y | yes | YES) return 0 ;; #true + n | no | N | NO) return 1 ;; #false + *) return $DEFAULT ;; + esac }