diff --git a/README.md b/README.md index e519d93..869f39b 100644 --- a/README.md +++ b/README.md @@ -258,12 +258,14 @@ Miscellaneous functions - **`_detectLinuxDistro_`** Detects the host computer's distribution of Linux - **`_detectMacOSVersion_`** Detects the host computer's version of macOS - **`_detectOS_`** Detect the the host computer's operating system +- **`_endspin_`** Clears output from the _spinner_ - **`_execute_`** Executes commands with safety and logging options. Respects `DRYRUN` and `VERBOSE` flags. - **`_findBaseDir_`** Locates the real directory of the script being run. Similar to GNU readlink -n - **`_generateUUID_`** Generates a unique UUID -- **`_makeProgressBar_`** Prints a progress bar within a for/while loop +- **`_progressBar_`** Prints a progress bar within a for/while loop - **`_runAsRoot_`** Run the requested command as root (via sudo if requested) - **`_seekConfirmation_`** Seek user input for yes/no question +- **`_spinner_`** Creates a spinner within a for/while loop. - **`_trapCleanup_`** Cleans up after a trapped error. ## services.bash diff --git a/template.sh b/template.sh index 1dfcc20..1b1b3f2 100755 --- a/template.sh +++ b/template.sh @@ -53,6 +53,9 @@ _trapCleanup_() { local _script="${5:-}" local _sourced="${6:-}" + # Replace the cursor in-case 'tput civis' has been used + tput cnorm + if declare -f "fatal" &>/dev/null && declare -f "_printFuncStack_" &>/dev/null; then _funcstack="'$(printf "%s" "${_funcstack}" | sed -E 's/ / < /g')'" diff --git a/template_standalone.sh b/template_standalone.sh index 2e31f46..1e893f6 100755 --- a/template_standalone.sh +++ b/template_standalone.sh @@ -53,7 +53,7 @@ _setColors_() { blue=$(tput setaf 38) yellow=$(tput setaf 11) green=$(tput setaf 82) - red=$(tput setaf 196) + red=$(tput setaf 9) purple=$(tput setaf 171) gray=$(tput setaf 250) else @@ -61,7 +61,7 @@ _setColors_() { blue=$(tput setaf 38) yellow=$(tput setaf 3) green=$(tput setaf 2) - red=$(tput setaf 1) + red=$(tput setaf 9) purple=$(tput setaf 13) gray=$(tput setaf 7) fi @@ -307,6 +307,9 @@ _trapCleanup_() { local _script="${5:-}" local _sourced="${6:-}" + # Replace the cursor in-case 'tput civis' has been used + tput cnorm + if declare -f "fatal" &>/dev/null && declare -f "_printFuncStack_" &>/dev/null; then _funcstack="'$(printf "%s" "${_funcstack}" | sed -E 's/ / < /g')'" diff --git a/test/misc.bats b/test/misc.bats index 9746fff..5ddb92c 100755 --- a/test/misc.bats +++ b/test/misc.bats @@ -11,122 +11,119 @@ SOURCEFILE="${ROOTDIR}/utilities/misc.bash" ALERTS="${ROOTDIR}/utilities/alerts.bash" if test -f "${SOURCEFILE}" >&2; then - source "${SOURCEFILE}" + source "${SOURCEFILE}" else - echo "Sourcefile not found: ${SOURCEFILE}" >&2 - printf "Can not run tests.\n" >&2 - exit 1 + echo "Sourcefile not found: ${SOURCEFILE}" >&2 + printf "Can not run tests.\n" >&2 + exit 1 fi if test -f "${ALERTS}" >&2; then - source "${ALERTS}" - _setColors_ #Set color constants + source "${ALERTS}" + _setColors_ #Set color constants else - echo "Sourcefile not found: ${ALERTS}" >&2 - printf "Can not run tests.\n" >&2 - exit 1 + echo "Sourcefile not found: ${ALERTS}" >&2 + printf "Can not run tests.\n" >&2 + exit 1 fi setup() { + TESTDIR="$(temp_make)" + curPath="${PWD}" - TESTDIR="$(temp_make)" - curPath="${PWD}" + BATSLIB_FILE_PATH_REM="#${TEST_TEMP_DIR}" + BATSLIB_FILE_PATH_ADD='' - BATSLIB_FILE_PATH_REM="#${TEST_TEMP_DIR}" - BATSLIB_FILE_PATH_ADD='' + pushd "${TESTDIR}" >&2 - pushd "${TESTDIR}" >&2 + ######## DEFAULT FLAGS ######## + LOGFILE="${TESTDIR}/logs/log.txt" + QUIET=false + LOGLEVEL=ERROR + VERBOSE=false + FORCE=false + DRYRUN=false - ######## DEFAULT FLAGS ######## - LOGFILE="${TESTDIR}/logs/log.txt" - QUIET=false - LOGLEVEL=ERROR - VERBOSE=false - FORCE=false - DRYRUN=false - - set -o errtrace - set -o nounset - set -o pipefail + set -o errtrace + set -o nounset + set -o pipefail } teardown() { - set +o nounset - set +o errtrace - set +o pipefail + set +o nounset + set +o errtrace + set +o pipefail - popd >&2 - temp_del "${TESTDIR}" + popd >&2 + temp_del "${TESTDIR}" } ######## RUN TESTS ######## @test "Sanity..." { - run true - - assert_success - assert_output "" + run true + assert_success + assert_output "" } -_testExecute_() { - @test "_execute_: Debug command" { +@test "_execute_: Debug command" { DRYRUN=true run _execute_ "rm testfile.txt" assert_success assert_output --partial "[ dryrun] rm testfile.txt" - } +} - @test "_execute_: No command" { +@test "_execute_: No command" { run _execute_ assert_failure assert_output --regexp "\[ fatal\] Missing required argument to _execute_" - } +} - @test "_execute_: Bad command" { +@test "_execute_: Bad command" { run _execute_ "rm nonexistant.txt" assert_failure assert_output --partial "[warning] rm nonexistant.txt" - } +} - @test "_execute_ -e: Bad command" { +@test "_execute_ -e: Bad command" { run _execute_ -e "rm nonexistant.txt" assert_failure assert_output "error: rm nonexistant.txt" - } +} - @test "_execute_ -p: Return 0 on bad command" { +@test "_execute_ -p: Return 0 on bad command" { run _execute_ -p "rm nonexistant.txt" assert_success assert_output --partial "[warning] rm nonexistant.txt" - } +} - @test "_execute_: Good command" { +@test "_execute_: Good command" { touch "testfile.txt" run _execute_ "rm testfile.txt" assert_success assert_output --partial "[ info] rm testfile.txt" assert_file_not_exist "testfile.txt" - } +} - @test "_execute_: Good command - no output" { +@test "_execute_: Good command - no output" { touch "testfile.txt" run _execute_ -q "rm testfile.txt" assert_success refute_output --partial "[ info] rm testfile.txt" assert_file_not_exist "testfile.txt" - } +} - @test "_execute_ -s: Good command" { +@test "_execute_ -s: Good command" { touch "testfile.txt" run _execute_ -s "rm testfile.txt" assert_success assert_output --partial "[success] rm testfile.txt" assert_file_not_exist "testfile.txt" - } +} - @test "_execute_ -v: Good command" { +@test "_execute_ -v: Good command" { touch "testfile.txt" run _execute_ -v "rm -v testfile.txt" @@ -134,18 +131,18 @@ _testExecute_() { assert_line --index 0 "removed 'testfile.txt'" assert_line --index 1 --partial "[ info] rm -v testfile.txt" assert_file_not_exist "testfile.txt" - } +} - @test "_execute_ -n: Good command" { +@test "_execute_ -n: Good command" { touch "testfile.txt" run _execute_ -n "rm -v testfile.txt" assert_success assert_line --index 0 --partial "[ notice] rm -v testfile.txt" assert_file_not_exist "testfile.txt" - } +} - @test "_execute_ -ev: Good command" { +@test "_execute_ -ev: Good command" { touch "testfile.txt" run _execute_ -ve "rm -v testfile.txt" @@ -153,67 +150,74 @@ _testExecute_() { assert_line --index 0 "removed 'testfile.txt'" assert_line --index 1 --partial "rm -v testfile.txt" assert_file_not_exist "testfile.txt" - } } -_testExecute_ @test "_findBaseDir_" { - run _findBaseDir_ - assert_output --regexp "^/usr/local/Cellar/bats-core/[0-9]\.[0-9]\.[0-9]" + run _findBaseDir_ + assert_success + if [ -d /usr/local/Cellar/ ]; then + assert_output --regexp "^/usr/local/Cellar/bats-core/[0-9]\.[0-9]\.[0-9]" + elif [ -d /opt/homebrew/Cellar ]; then + assert_output --regexp "^/opt/homebrew/Cellar/bats-core/[0-9]\.[0-9]\.[0-9]" + fi } @test "_generateUUID_" { - run _generateUUID_ - assert_success - assert_output --regexp "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" + run _generateUUID_ + assert_success + assert_output --regexp "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" } -@test "_makeProgressBar_: verbose" { - verbose=true - run _makeProgressBar_ 100 - - assert_success - assert_output "" - verbose=false +@test "_spinner_: verbose" { + verbose=true + run _spinner_ + assert_success + assert_output "" } -@test "_makeProgressBar_: quiet" { - quiet=true - run _makeProgressBar_ 100 +@test "_spinner_: quiet" { + quiet=true + run _spinner_ + assert_success + assert_output "" +} - assert_success - assert_output "" - quiet=false +@test "_progressBar_: verbose" { + verbose=true + run _progressBar_ 100 + assert_success + assert_output "" +} + +@test "_progressBar_: quiet" { + quiet=true + run _progressBar_ 100 + assert_success + assert_output "" } @test "_seekConfirmation_: yes" { - run _seekConfirmation_ 'test' <<<"y" - - assert_success - assert_output --partial "[ input] test" + run _seekConfirmation_ 'test' <<<"y" + assert_success + assert_output --partial "[ input] test" } @test "_seekConfirmation_: no" { - run _seekConfirmation_ 'test' <<<"n" - - assert_failure - assert_output --partial "[ input] test" + run _seekConfirmation_ 'test' <<<"n" + assert_failure + assert_output --partial "[ input] test" } @test "_seekConfirmation_: Force" { - FORCE=true - - run _seekConfirmation_ "test" - assert_success - assert_output --partial "test" + FORCE=true + run _seekConfirmation_ "test" + assert_success + assert_output --partial "test" } @test "_seekConfirmation_: Quiet" { - QUIET=true - run _seekConfirmation_ 'test' <<<"y" - - assert_success - refute_output --partial "test" - - quiet=false + QUIET=true + run _seekConfirmation_ 'test' <<<"y" + assert_success + refute_output --partial "test" } diff --git a/utilities/alerts.bash b/utilities/alerts.bash index 5b6bc57..bb49131 100644 --- a/utilities/alerts.bash +++ b/utilities/alerts.bash @@ -22,7 +22,7 @@ _setColors_() { blue=$(tput setaf 38) yellow=$(tput setaf 11) green=$(tput setaf 82) - red=$(tput setaf 196) + red=$(tput setaf 9) purple=$(tput setaf 171) gray=$(tput setaf 250) else @@ -30,7 +30,7 @@ _setColors_() { blue=$(tput setaf 38) yellow=$(tput setaf 3) green=$(tput setaf 2) - red=$(tput setaf 1) + red=$(tput setaf 9) purple=$(tput setaf 13) gray=$(tput setaf 7) fi @@ -312,7 +312,7 @@ _columnizeOutput_() { done <<<"$(fold -w${_rightWrapLength} -s <<<"${_value}")" } -_clearLine_() { +_clearLine_() ( # DESC: # Clears output in the terminal on the specified line number. # ARGS: @@ -325,10 +325,12 @@ _clearLine_() { ! declare -f _isTerminal_ &>/dev/null && fatal "${FUNCNAME[0]} needs function _isTerminal_" + local _num="${1:-1}" local i + if _isTerminal_; then - for ((i = 0; i < $1; i++)); do + for ((i = 0; i < _num; i++)); do printf "\033[A\033[2K" done fi -} +) diff --git a/utilities/misc.bash b/utilities/misc.bash index 5619cae..974ad3a 100644 --- a/utilities/misc.bash +++ b/utilities/misc.bash @@ -356,26 +356,28 @@ _generateUUID_() { printf '\n' } -_makeProgressBar_() { +_progressBar_() { # DESC: - # Prints a progress bar within a for/while loop + # Prints a progress bar within a for/while loop. For this to work correctly you + # MUST know the exact number of iterations. If you don't know the exact number use _spinner_ # ARGS: # $1 (Required) - The total number of items counted # $2 (Optional) - The optional title of the progress bar - # OUTS: stdout: progress bar + # OUTS: + # stdout: progress bar # USAGE: - # for number in $(seq 0 100); do - # sleep 1 + # for i in $(seq 0 100); do + # sleep 0.1 # _makeProgressBar_ "100" "Counting numbers" # done - [[ $# == 0 ]] && return # Do nothing if no arguments are passed - (${QUIET:-}) && return - (${VERBOSE:-}) && return + [[ $# == 0 ]] && return # Do nothing if no arguments are passed + (${QUIET:-}) && return # Do nothing in quiet mode + (${VERBOSE:-}) && return # Do nothing if verbose mode is enabled [ ! -t 1 ] && return # Do nothing if the output is not a terminal [[ ${1} == 1 ]] && return # Do nothing with a single element - local n="${1}" + local _n="${1}" local _width=30 local _barCharacter="#" local _percentage @@ -383,40 +385,132 @@ _makeProgressBar_() { local _bar local _progressBarLine local _barTitle="${2:-Running Process}" - local n - ((n = n - 1)) + ((_n = _n - 1)) # Reset the count - [ -z "${progressBarProgress:-}" ] && progressBarProgress=0 - tput civis # Hide the cursor - trap 'tput cnorm; exit 1' SIGINT + [ -z "${PROGRESS_BAR_PROGRESS:-}" ] && PROGRESS_BAR_PROGRESS=0 + + # Hide the cursor + tput civis + + if [[ ! ${PROGRESS_BAR_PROGRESS} -eq ${_n} ]]; then - if [[ ! ${progressBarProgress} -eq ${n} ]]; then - #echo "progressBarProgress: $progressBarProgress" # Compute the percentage. - _percentage=$((progressBarProgress * 100 / $1)) + _percentage=$((PROGRESS_BAR_PROGRESS * 100 / $1)) + # Compute the number of blocks to represent the percentage. - _num=$((progressBarProgress * _width / $1)) + _num=$((PROGRESS_BAR_PROGRESS * _width / $1)) + # Create the progress bar string. _bar="" if [[ ${_num} -gt 0 ]]; then _bar=$(printf "%0.s${_barCharacter}" $(seq 1 "${_num}")) fi + # Print the progress bar. _progressBarLine=$(printf "%s [%-${_width}s] (%d%%)" " ${_barTitle}" "${_bar}" "${_percentage}") printf "%s\r" "${_progressBarLine}" - # echo -ne "${_progressBarLine}\r" - progressBarProgress=$((progressBarProgress + 1)) - else - # Clear the progress bar when complete - # echo -ne "\033[0K\r" - tput el # Clear the line - unset progressBarProgress + PROGRESS_BAR_PROGRESS=$((PROGRESS_BAR_PROGRESS + 1)) + + else + # Replace the cursor + tput cnorm + + # Clear the progress bar when complete + printf "\r\033[0K" + + unset PROGRESS_BAR_PROGRESS fi +} + +_spinner_() { + # DESC: + # Creates a spinner within a for/while loop. + # Don't forget to add _endspin_ at the end of the loop + # ARGS: + # $1 (Optional) - Text accompanying the spinner + # OUTS: + # stdout: progress bar + # USAGE: + # for i in $(seq 0 100); do + # sleep 0.1 + # _spinner_ "Counting numbers" + # done + # _endspin_ + + (${QUIET:-}) && return # Do nothing in quiet mode + (${VERBOSE:-}) && return # Do nothing in verbose mode + [ ! -t 1 ] && return # Do nothing if the output is not a terminal + + local _message + _message="${1:-Running process}" + + # Hide the cursor + tput civis + + [[ -z ${SPIN_NUM:-} ]] && SPIN_NUM=0 + + case ${SPIN_NUM:-} in + 0) _glyph="█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁" ;; + 1) _glyph="██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁" ;; + 2) _glyph="███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁" ;; + 3) _glyph="████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁" ;; + 4) _glyph="██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁" ;; + 5) _glyph="██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁" ;; + 6) _glyph="███████▁▁▁▁▁▁▁▁▁▁▁▁▁" ;; + 7) _glyph="████████▁▁▁▁▁▁▁▁▁▁▁▁" ;; + 8) _glyph="█████████▁▁▁▁▁▁▁▁▁▁▁" ;; + 9) _glyph="█████████▁▁▁▁▁▁▁▁▁▁▁" ;; + 10) _glyph="██████████▁▁▁▁▁▁▁▁▁▁" ;; + 11) _glyph="███████████▁▁▁▁▁▁▁▁▁" ;; + 12) _glyph="█████████████▁▁▁▁▁▁▁" ;; + 13) _glyph="██████████████▁▁▁▁▁▁" ;; + 14) _glyph="██████████████▁▁▁▁▁▁" ;; + 15) _glyph="███████████████▁▁▁▁▁" ;; + 16) _glyph="███████████████▁▁▁▁▁" ;; + 17) _glyph="███████████████▁▁▁▁▁" ;; + 18) _glyph="████████████████▁▁▁▁" ;; + 19) _glyph="█████████████████▁▁▁" ;; + 20) _glyph="█████████████████▁▁▁" ;; + 21) _glyph="██████████████████▁▁" ;; + 22) _glyph="██████████████████▁▁" ;; + 23) _glyph="███████████████████▁" ;; + 24) _glyph="███████████████████▁" ;; + 25) _glyph="███████████████████▁" ;; + 26) _glyph="████████████████████" ;; + 27) _glyph="████████████████████" ;; + 28) _glyph="█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁" ;; + esac + + # shellcheck disable=SC2154 + printf "\r${gray}[ info] %s... %s${reset}" "${_message}" "${_glyph}" + if [[ ${SPIN_NUM} -lt 28 ]]; then + ((SPIN_NUM = SPIN_NUM + 1)) + else + SPIN_NUM=0 + fi +} + +_endspin_() { + # DESC: + # Clears the line that showed the spinner and replaces the cursor. To be run after _spinner_ + # ARGS: + # None + # OUTS: + # stdout: Removes previous line + # USAGE: + # _endspin_ + + # Clear the spinner + printf "\r\033[0K" + + # Replace the cursor tput cnorm + + unset SPIN_NUM } _runAsRoot_() {