From 261bbee1a444cb713a3a6ba2109d9d7388f5d23c Mon Sep 17 00:00:00 2001 From: yage Date: Wed, 7 Feb 2024 15:14:36 -0700 Subject: [PATCH] introduce --verbosity flag rather than mixed logging settings; correct color misnaming to ANSI convention; added sanity-check; simplified hello-world; created FZF_USER_INPUT to replace the confusing FZF_HEAD and FZF_TAIL --- run | 45 ++--- zsh/hello-world | 4 +- zsh/lib/scwrypts/run.module.zsh | 45 ++++- zsh/lib/utils/colors.zsh | 34 ++-- zsh/lib/utils/dependencies.zsh | 9 +- zsh/lib/utils/environment.zsh | 1 + zsh/lib/utils/io.fzf.zsh | 57 ++++++ zsh/lib/utils/io.print.zsh | 38 ++++ zsh/lib/utils/io.usage.zsh | 65 +++++++ zsh/lib/utils/io.zsh | 295 +++++++++++++------------------- zsh/sanity-check | 52 ++++++ 11 files changed, 419 insertions(+), 226 deletions(-) create mode 100644 zsh/lib/utils/io.fzf.zsh create mode 100644 zsh/lib/utils/io.print.zsh create mode 100644 zsh/lib/utils/io.usage.zsh create mode 100755 zsh/sanity-check diff --git a/run b/run index 14cd51f..2b7c8e2 100755 --- a/run +++ b/run @@ -18,9 +18,13 @@ __RUN() { runtime -y, --yes auto-accept all [yn] prompts through current scwrypt -e, --env set environment; overwrites SCWRYPTS_ENV - -q, --quiet run in quiet mode - -n, --no-log skip the log file and run in quiet mode - -v, --verbose override quiet mode settings and print all debug dialogue + -n shorthand for "--verbosity 0" + -v, --verbosity [0-4] set scwrypts log level to one of the following: + 0 : only command output and critical failures; skips logfile + 1 : add success / failure messages + 2 : (default) include status update messages + 3 : include warning messages + 4 : include debug messages alternate commands -h, --help display this message and exit @@ -44,9 +48,9 @@ __RUN() { local VARSPLIT SEARCH_GROUP SEARCH_TYPE SEARCH_NAME local ALLOW_LOGFILE=1 - local VERBOSE=1 + local SCWRYPTS_LOG_LEVEL=2 - [ $CI ] && [ ! $SCWRYPTS_CI_FORCE_NON_VERBOSE ] && VERBOSE=2 + [ $CI ] && [ ! $SCWRYPTS_CI_FORCE_NON_VERBOSE ] && SCWRYPTS_LOG_LEVEL=3 local ERROR=0 @@ -108,9 +112,8 @@ __RUN() { ;; -y | --yes ) export __SCWRYPTS_YES=1 ;; - -q | --quiet ) VERBOSE=0 ;; - -n | --no-log ) VERBOSE=0 ; [ ! $SUBSCWRYPT ] && SUBSCWRYPT=0 ;; - -v | --verbose ) VERBOSE=2 ;; + -n | --no-log ) SCWRYPTS_LOG_LEVEL=0 ;; + -v | --verbosity ) SCWRYPTS_LOG_LEVEL=$2 ;; -e | --env ) [ ! $2 ] && ERROR "missing value for argument $1" && break @@ -210,7 +213,6 @@ __RUN() { [[ $(echo $SCWRYPTS_AVAILABLE | wc -l) -eq 2 ]] && { SCWRYPT_SELECTION=$(echo $SCWRYPTS_AVAILABLE | tail -n1) - [[ $VERBOSE -eq 2 ]] || VERBOSE=0 } || { SCWRYPT_SELECTION=$(echo $SCWRYPTS_AVAILABLE | FZF "select a script to run" --header-lines 1) } @@ -271,12 +273,13 @@ __RUN() { local LOGFILE=$(__GET_LOGFILE) local HEADER=$( - [[ $VERBOSE -gt 0 ]] || return 0 + [[ $SCWRYPTS_LOG_LEVEL -ge 2 ]] || return 0 [ $SUBSCWRYPT ] && return 0 echo '=====================================================================' - echo "script : $SCWRYPT_GROUP $SCWRYPT_TYPE $SCWRYPT_NAME" - echo "run at : $(date)" - echo "config : $ENV_NAME" + echo "script : $SCWRYPT_GROUP $SCWRYPT_TYPE $SCWRYPT_NAME" + echo "run at : $(date)" + echo "config : $ENV_NAME" + echo "verbosity : $SCWRYPTS_LOG_LEVEL" [ ! $LOGFILE ] && echo '\033[1;33m------------------------------------------\033[0m' ) @@ -291,22 +294,19 @@ __RUN() { } } + set -o pipefail { [ $HEADER ] && echo $HEADER - [[ $VERBOSE -gt 0 ]] && echo '\033[1;33m--- BEGIN OUTPUT -------------------------\033[0m' + [[ $SCWRYPTS_LOG_LEVEL -ge 2 ]] && echo '\033[1;33m--- BEGIN OUTPUT -------------------------\033[0m' (eval "$RUN_STRING $(printf "%q " "$@")") - EXIT_CODE=$? - [[ $VERBOSE -gt 0 ]] && echo '\033[1;33m--- END OUTPUT ---------------------------\033[0m' + export EXIT_CODE=$? + [[ $SCWRYPTS_LOG_LEVEL -ge 2 ]] && echo '\033[1;33m--- END OUTPUT ---------------------------\033[0m' [[ $EXIT_CODE -eq 0 ]] && EXIT_COLOR='32m' || EXIT_COLOR='31m' + [[ $SCWRYPTS_LOG_LEVEL -ge 2 ]] && echo "terminated with\\033[1;$EXIT_COLOR code $EXIT_CODE\\033[0m" - [[ $VERBOSE -gt 0 ]] && echo "terminated with\\033[1;$EXIT_COLOR code $EXIT_CODE\\033[0m" + return $EXIT_CODE } 2>&1 | tee --append "$LOGFILE" - - exit $(\ - sed -n 's/^terminated with.*code \([0-9]*\).*$/\1/p' $LOGFILE \ - | tail -n1 - ) } ##################################################################### @@ -343,6 +343,7 @@ __VALIDATE_UPSTREAM_TIMELINE() { __GET_LOGFILE() { [ $SUBSCWRYPT ] && return 0 + [[ $SCWRYPTS_LOG_LEVEL -eq 0 ]] && return 0 [[ $SCWRYPT_NAME =~ scwrypts/logs ]] && return 0 [[ $SCWRYPT_NAME =~ interactive ]] && return 0 diff --git a/zsh/hello-world b/zsh/hello-world index 73f48fa..5352069 100755 --- a/zsh/hello-world +++ b/zsh/hello-world @@ -1,4 +1,6 @@ #!/bin/zsh +##################################################################### + MAIN() { - SUCCESS 'hello world!' + SUCCESS 'Hello, World!' } diff --git a/zsh/lib/scwrypts/run.module.zsh b/zsh/lib/scwrypts/run.module.zsh index 62dbcf1..c5a1d10 100644 --- a/zsh/lib/scwrypts/run.module.zsh +++ b/zsh/lib/scwrypts/run.module.zsh @@ -119,12 +119,51 @@ SCWRYPTS__GET_RUNSTRING__zsh() { || SCWRYPT_FILENAME="$GROUP_PATH/$SCWRYPT_TYPE/$SCWRYPT_NAME" \ ; - echo "source '$SCWRYPT_FILENAME'; CHECK_ENVIRONMENT; MAIN" + printf " + source '$SCWRYPT_FILENAME' + CHECK_ENVIRONMENT + ERRORS=0 + + export USAGE=\" + usage: - + + args: - + + options: - + -h, --help display this message and exit + -v, --verbose output debugging information + + description: - + \" + + [ ! \$USAGE__usage ] && export USAGE__usage='[...options...]' + + () { + local MAIN_ARGS=() + local VARSPLIT + while [[ \$# -gt 0 ]] + do + case \$1 in + -[a-z][a-z]* ) + VARSPLIT=\$(echo \"\$1 \" | sed 's/^\\\\(-.\\\\)\\\\(.*\\\\) /\\\\1 -\\\\2/') + set -- throw-away \$(echo \" \$VARSPLIT \") \${@:2} + ;; + -h | --help ) USAGE; exit 0 ;; + -v | --verbose ) INFO \"setting SCWRYPTS_LOG_LEVEL to 'debug'\"; export SCWRYPTS_LOG_LEVEL=debug ;; + * ) MAIN_ARGS+=(\$1) ;; + esac + shift 1 + done + MAIN \${MAIN_ARGS[@]} + } " + return 0 } -SCWRYPTS__GET_RUNSTRING__zsh_v3_compatibility() { +SCWRYPTS__GET_RUNSTRING__zsh_v3() { + WARNING "scwrypts zsh/v3 runstrings are now deprecated; please update to scwrypts v4 format" + __CHECK_DEPENDENCY zsh || return 1 [ $(eval echo '$SCWRYPTS_TYPE__'$SCWRYPT_GROUP) ] \ @@ -132,8 +171,6 @@ SCWRYPTS__GET_RUNSTRING__zsh_v3_compatibility() { || echo "source $GROUP_PATH/$SCWRYPT_TYPE/$SCWRYPT_NAME" \ ; - WARNING "scwrypts zsh/v3 runstrings are now deprecated; please update to scwrypts v4 format" - return 0 } diff --git a/zsh/lib/utils/colors.zsh b/zsh/lib/utils/colors.zsh index 72b9d27..adc2565 100644 --- a/zsh/lib/utils/colors.zsh +++ b/zsh/lib/utils/colors.zsh @@ -2,22 +2,22 @@ __BLACK='\033[0;30m' __DARK_GRAY='\033[1;30m' __RED='\033[0;31m' -__LIGHT_RED='\033[1;31m' +__BRIGHT_RED='\033[1;31m' __GREEN='\033[0;32m' -__LIGHT_GREEN='\033[1;32m' +__BRIGHT_GREEN='\033[1;32m' -__ORANGE='\033[0;33m' -__YELLOW='\033[1;33m' +__YELLOW='\033[0;33m' +__BRIGHT_YELLOW='\033[1;33m' -__BLUE='\033[1;34m' -__DARK_BLUE='\033[0;34m' +__BLUE='\033[0;34m' +__BRIGHT_BLUE='\033[1;34m' -__PURPLE='\033[1;35m' -__DARK_PURPLE='\033[0;35m' +__MAGENTA='\033[0;35m' +__BRIGHT_MAGENTA='\033[1;35m' -__CYAN='\033[1;36m' -__DARK_CYAN='\033[0;36m' +__CYAN='\033[0;36m' +__BRIGHT_CYAN='\033[1;36m' __WHITE='\033[1;37m' __LIGHT_GRAY='\033[0;37m' @@ -27,17 +27,17 @@ __COLOR_RESET='\033[0m' __GET_RANDOM_COLOR() { local COLORS=( $__RED - $__LIGHT_RED + $__BRIGHT_RED $__GREEN - $__LIGHT_GREEN - $__ORANGE + $__BRIGHT_GREEN $__YELLOW + $__BRIGHT_YELLOW $__BLUE - $__DARK_BLUE - $__PURPLE - $__DARK_PURPLE + $__BRIGHT_BLUE + $__MAGENTA + $__BRIGHT_MAGENTA $__CYAN - $__DARK_CYAN + $__BRIGHT_CYAN $__WHITE ) print "$__COLOR_RESET${COLORS[$(shuf -i 1-${#COLORS[@]} -n 1)]}" diff --git a/zsh/lib/utils/dependencies.zsh b/zsh/lib/utils/dependencies.zsh index 9075548..ad478d4 100644 --- a/zsh/lib/utils/dependencies.zsh +++ b/zsh/lib/utils/dependencies.zsh @@ -1,13 +1,14 @@ __CHECK_DEPENDENCIES() { - local DEP ERROR=0 + local DEP ERRORS=0 + local SCWRYPTS_LOG_LEVEL=1 [ ! $E ] && E=ERROR DEPENDENCIES=($(echo $DEPENDENCIES | sed 's/ \+/\n/g' | sort -u)) - for DEP in ${DEPENDENCIES[@]}; do __CHECK_DEPENDENCY $DEP || ((ERROR+=1)); done - __CHECK_COREUTILS || ((ERROR+=$?)) + for DEP in ${DEPENDENCIES[@]}; do __CHECK_DEPENDENCY $DEP || ((ERRORS+=1)); done + __CHECK_COREUTILS || ((ERRORS+=$?)) - return $ERROR + return $ERRORS } __CHECK_DEPENDENCY() { diff --git a/zsh/lib/utils/environment.zsh b/zsh/lib/utils/environment.zsh index 8e26d56..bd7a85a 100644 --- a/zsh/lib/utils/environment.zsh +++ b/zsh/lib/utils/environment.zsh @@ -1,4 +1,5 @@ __CHECK_REQUIRED_ENV() { + local SCWRYPTS_LOG_LEVEL=1 local VAR ERROR=0 REQUIRED_ENV=($(echo $REQUIRED_ENV | sed 's/\s\+/\n/g' | sort -u)) for VAR in ${REQUIRED_ENV[@]}; do __CHECK_ENV_VAR $VAR || ((ERROR+=1)); done diff --git a/zsh/lib/utils/io.fzf.zsh b/zsh/lib/utils/io.fzf.zsh new file mode 100644 index 0000000..8db69c0 --- /dev/null +++ b/zsh/lib/utils/io.fzf.zsh @@ -0,0 +1,57 @@ +FZF() { + [ $CI ] && FAIL 1 'currently in CI, but FZF requires user input' + + local FZF_ARGS=() + + FZF_ARGS+=(-i) + FZF_ARGS+=(--ansi) + FZF_ARGS+=(--bind=ctrl-c:cancel) + FZF_ARGS+=(--height=50%) + FZF_ARGS+=(--layout=reverse) + + local SELECTION=$(fzf ${FZF_ARGS[@]} --prompt "$1 : " ${@:2}) + PROMPT "$1" + + [ $BE_QUIET ] || { + [[ $SCWRYPTS_LOG_LEVEL -ge 1 ]] && echo $SELECTION >&2 + } + echo $SELECTION + [ $SELECTION ] +} + +FZF_USER_INPUT() { # allow user to type custom answers; reconfirm if ambiguous with select + local FZF_OUTPUT=$(BE_QUIET=1 FZF $@ --print-query | sed '/^$/d' | sort -u) + [[ $SCWRYPTS_LOG_LEVEL -ge 1 ]] && echo $FZF_OUTPUT | head -n1 >&2 + [ ! $FZF_OUTPUT ] && return 1 + + [[ $(echo "$FZF_OUTPUT" | wc -l) -eq 1 ]] \ + && { echo "$FZF_OUTPUT"; return 0; } + + local FZF_OUTPUT=$( + echo "$FZF_OUTPUT" \ + | sed "1s/\$/^$(printf "$__LIGHT_GRAY\\033[3m")<- what you typed$(printf $__COLOR_RESET)/" \ + | sed "2s/\$/^$(printf "$__LIGHT_GRAY\\033[3m")<- what you selected$(printf $__COLOR_RESET)/" \ + | column -ts '^' \ + | BE_QUIET=1 FZF "$@ (clarify)" \ + ) + + [[ $SCWRYPTS_LOG_LEVEL -ge 1 ]] && echo $FZF_OUTPUT >&2 + FZF_OUTPUT=$(echo $FZF_OUTPUT | sed 's/\s\+<- what you .*$//') + echo $FZF_OUTPUT + [ $FZF_OUTPUT ] +} + +##################################################################### +### vvv DEPRECATED vvv ############################################## +##################################################################### + +FZF_HEAD() { # prefer user input over selected + WARNING 'FZF_HEAD is deprecated; please switch to FZF_USER_INPUT (drop-in fix!)' + FZF $@ --print-query | sed '/^$/d' | head -n1; +} +FZF_TAIL() { # prefer selected over user input + WARNING 'FZF_TAIL is deprecated; please switch to FZF_USER_INPUT (drop-in fix!)' + FZF $@ --print-query | sed '/^$/d' | tail -n1; +} + +##################################################################### diff --git a/zsh/lib/utils/io.print.zsh b/zsh/lib/utils/io.print.zsh new file mode 100644 index 0000000..beea503 --- /dev/null +++ b/zsh/lib/utils/io.print.zsh @@ -0,0 +1,38 @@ +PRINT() { + local MESSAGE + local LAST_LINE_END='\n' + local STDERR=1 + local STDOUT=0 + + local LTRIM=1 + while [[ $# -gt 0 ]] + do + case $1 in + -n | --no-trim-tabs ) LTRIM=0 ;; + -x | --no-line-end ) LAST_LINE_END='' ;; + -o | --use-stdout ) STDOUT=1; STDERR=0 ;; + * ) MESSAGE+="$(echo $1) " ;; + esac + shift 1 + done + + MESSAGE="$(echo "$MESSAGE" | sed 's/%/%%/g')" + + local STYLED_MESSAGE="$({ + printf "${COLOR}" + while IFS='' read line + do + [[ $PREFIX =~ ^[[:space:]]\+$ ]] && printf '\n' + + printf "${PREFIX} : $(echo "$line" | sed 's/^ \+//; s/ \+$//')" + + PREFIX=$(echo $PREFIX | sed 's/./ /g') + done <<< $MESSAGE + })" + STYLED_MESSAGE="${COLOR}$(echo "$STYLED_MESSAGE" | sed 's/%/%%/g')${__COLOR_RESET}${LAST_LINE_END}" + + [[ $STDERR -eq 1 ]] && printf $STYLED_MESSAGE >&2 + [[ $STDOUT -eq 1 ]] && printf $STYLED_MESSAGE + + return 0 +} diff --git a/zsh/lib/utils/io.usage.zsh b/zsh/lib/utils/io.usage.zsh new file mode 100644 index 0000000..789c337 --- /dev/null +++ b/zsh/lib/utils/io.usage.zsh @@ -0,0 +1,65 @@ +USAGE() { # formatter for USAGE variable + [ ! $USAGE ] && return 0 + local USAGE_LINE=$(echo $USAGE | grep -i '^[ ]*usage *:' | sed 's/^[ ]*//') + + [ $USAGE__usage ] && echo $USAGE_LINE | grep -q 'usage: -' \ + && USAGE_LINE=$(echo $USAGE_LINE | sed "s/usage: -/usage: $USAGE__usage/") + + [ $__SCWRYPT ] \ + && USAGE_LINE=$( + echo $USAGE_LINE \ + | sed "s;^[^:]*:;& scwrypts $SCWRYPT_NAME --;" \ + | sed 's/ \{2,\}/ /g; s/scwrypts -- scwrypts/scwrypts/' \ + ) + + local THE_REST=$(echo $USAGE | grep -vi '^[ ]*usage *:' ) + + local DYNAMIC_USAGE_ELEMENT + # + # create dynamic usage elements (like 'args') by defining USAGE__ + # then using the syntax ": -" in your USAGE variable + # + # e.g. + # + # USAGE__args=" + # subcommand arg 1 arg 1 description + # subcommand arg 2 some other description + # " + # + # USAGE=" + # usage: some-command [...args...] + # + # args: - + # -h, --help some arguments are applicable everywhere + # " + # + for DYNAMIC_USAGE_ELEMENT in $(echo $THE_REST | sed -n 's/^\([^:]*\): -$/\1/p') + do + DYNAMIC_USAGE_ELEMENT_TEXT=$(eval echo '$USAGE__'$DYNAMIC_USAGE_ELEMENT) + #[ $DYNAMIC_USAGE_ELEMENT_TEXT ] || continue + + + case $DYNAMIC_USAGE_ELEMENT in + description ) + DYNAMIC_USAGE_ELEMENT_TEXT=$(echo "$DYNAMIC_USAGE_ELEMENT_TEXT" | perl -p0e 's/^[\n\s]+//') + DYNAMIC_USAGE_ELEMENT_TEXT="$__YELLOW\\033[03m$DYNAMIC_USAGE_ELEMENT_TEXT\\033[0m" + ;; + * ) + DYNAMIC_USAGE_ELEMENT_TEXT=$(echo $DYNAMIC_USAGE_ELEMENT_TEXT | sed 's/[^ ]/ &/') + ;; + esac + + THE_REST=$(echo $THE_REST | perl -pe "s$DYNAMIC_USAGE_ELEMENT: -$DYNAMIC_USAGE_ELEMENT:\n$DYNAMIC_USAGE_ELEMENT_TEXT\n\n") + done + + # allow for dynamic 'description: -' but delete the 'description:' header line + THE_REST=$(echo $THE_REST | sed '/^[ ]*description:$/d') + + echo "$__BLUE$USAGE_LINE$__COLOR_RESET\n\n$THE_REST" \ + | sed "s/^\t\+//; s/\s\+$//; s/^\\s*$//;" \ + | sed '/./,$!d; :a; /^\n*$/{$d;N;ba;};' \ + | perl -p0e 's/\n{2,}/\n\n/g' \ + | perl -p0e 's/:\n{2,}/:\n/g' \ + | perl -p0e 's/([a-z]+:)\n([a-z]+:)/\2/g' \ + >&2 +} diff --git a/zsh/lib/utils/io.zsh b/zsh/lib/utils/io.zsh index a4698f1..0a48095 100644 --- a/zsh/lib/utils/io.zsh +++ b/zsh/lib/utils/io.zsh @@ -1,216 +1,115 @@ -PRINT() { - local MESSAGE - local LAST_LINE_END='\n' - local STDERR=1 - local STDOUT=0 - - local LTRIM=1 - while [[ $# -gt 0 ]] - do - case $1 in - -n | --no-trim-tabs ) LTRIM=0 ;; - -x | --no-line-end ) LAST_LINE_END='' ;; - -o | --use-stdout ) STDOUT=1; STDERR=0 ;; - * ) MESSAGE+="$(echo $1) " ;; - esac - shift 1 - done - - MESSAGE="$(echo "$MESSAGE" | sed 's/%/%%/g')" - - local STYLED_MESSAGE="$({ - printf "${COLOR}" - while IFS='' read line - do - [[ $PREFIX =~ ^[[:space:]]\+$ ]] && printf '\n' - - printf "${PREFIX} : $(echo "$line" | sed 's/^ \+//; s/ \+$//')" - - PREFIX=$(echo $PREFIX | sed 's/./ /g') - done <<< $MESSAGE - })" - STYLED_MESSAGE="${COLOR}$(echo "$STYLED_MESSAGE" | sed 's/%/%%/g')${__COLOR_RESET}${LAST_LINE_END}" - - [[ $STDERR -eq 1 ]] && printf $STYLED_MESSAGE >&2 - [[ $STDOUT -eq 1 ]] && printf $STYLED_MESSAGE - - return 0 -} +##################################################################### +### basic colorized print messages ################################## +##################################################################### +source "${0:a:h}/io.print.zsh" [ ! $ERRORS ] && ERRORS=0 -ERROR() { PREFIX="ERROR ✖" COLOR=$__RED PRINT "$@"; ((ERRORS+=1)); } -SUCCESS() { PREFIX="SUCCESS ✔" COLOR=$__GREEN PRINT "$@"; } -WARNING() { PREFIX="WARNING " COLOR=$__ORANGE PRINT "$@"; } -STATUS() { PREFIX="STATUS " COLOR=$__BLUE PRINT "$@"; } -REMINDER() { PREFIX="REMINDER " COLOR=$__PURPLE PRINT "$@"; } -INFO() { PREFIX="INFO ℹ" COLOR=$__WHITE PRINT "$@"; } - -PROMPT() { - PREFIX="PROMPT " COLOR=$__CYAN PRINT "$@" - PREFIX="USER ⌨" COLOR=$__CYAN PRINT '' --no-line-end -} - -FAIL() { ERROR "${@:2}"; exit $1; } -ABORT() { FAIL 69 'user abort'; } - -CHECK_ERRORS() { - local FAIL_OUT=1 - local DISPLAY_USAGE=1 - - while [[ $# -gt 0 ]] - do - case $1 in - --no-fail ) FAIL_OUT=0 ;; - --no-usage ) DISPLAY_USAGE=0 ;; - esac - shift 1 - done - - [ ! $ERRORS ] && ERRORS=0 - [[ $ERRORS -eq 0 ]] && return 0 - - [[ $DISPLAY_USAGE -eq 1 ]] && USAGE - - [[ $FAIL_OUT -eq 1 ]] && exit $ERRORS +ERROR() { # command encountered an error + [[ $SCWRYPTS_LOG_LEVEL -ge 1 ]] \ + && PREFIX="ERROR ✖" COLOR=$__RED PRINT "$@" + ((ERRORS+=1)) return $ERRORS } -USAGE() { # formatter for USAGE variable - [ ! $USAGE ] && return 0 - local USAGE_LINE=$(echo $USAGE | grep -i '^[ ]*usage *:' | sed 's/^[ ]*//') +SUCCESS() { # command completed successfully + [[ $SCWRYPTS_LOG_LEVEL -ge 1 ]] \ + && PREFIX="SUCCESS ✔" COLOR=$__GREEN PRINT "$@" +} - [ $USAGE__usage ] && echo $USAGE_LINE | grep -q 'usage: -' \ - && USAGE_LINE=$(echo $USAGE_LINE | sed "s/usage: -/usage: $USAGE__usage/") +REMINDER() { # include sysadmin reminder or other important notice to users + [[ $SCWRYPTS_LOG_LEVEL -ge 1 ]] \ + && PREFIX="REMINDER " COLOR=$__BRIGHT_MAGENTA PRINT "$@" +} - [ $__SCWRYPT ] \ - && USAGE_LINE=$( - echo $USAGE_LINE \ - | sed "s;^[^:]*:;& scwrypts $SCWRYPT_NAME --;" \ - | sed 's/ \{2,\}/ /g; s/scwrypts -- scwrypts/scwrypts/' \ - ) +STATUS() { # general status updates (prefer this to generic 'echo') + [[ $SCWRYPTS_LOG_LEVEL -ge 2 ]] \ + && PREFIX="STATUS " COLOR=$__BLUE PRINT "$@" +} - local THE_REST=$(echo $USAGE | grep -vi '^[ ]*usage *:' ) +WARNING() { # warning-level messages; not errors + [[ $SCWRYPTS_LOG_LEVEL -ge 3 ]] \ + && PREFIX="WARNING " COLOR=$__YELLOW PRINT "$@" +} - local DYNAMIC_USAGE_ELEMENT - # - # create dynamic usage elements (like 'args') by defining USAGE__ - # then using the syntax ": -" in your USAGE variable - # - # e.g. - # - # USAGE__args=" - # subcommand arg 1 arg 1 description - # subcommand arg 2 some other description - # " - # - # USAGE=" - # usage: some-command [...args...] - # - # args: - - # -h, --help some arguments are applicable everywhere - # " - # - for DYNAMIC_USAGE_ELEMENT in $(echo $THE_REST | sed -n 's/^\([^:]*\): -$/\1/p') +DEBUG() { # helpful during development or (sparingly) to help others' development + [[ $SCWRYPTS_LOG_LEVEL -gt 4 ]] \ + && PREFIX="DEBUG ℹ" COLOR=$__WHITE PRINT "$@" +} + +PROMPT() { # you probably want to use yN or INPUT from below + [[ $SCWRYPTS_LOG_LEVEL -ge 1 ]] \ + && PREFIX="PROMPT " COLOR=$__CYAN PRINT "$@" \ + && PREFIX="USER ⌨" COLOR=$__BRIGHT_CYAN PRINT '' --no-line-end \ + ; +} + +FAIL() { SCWRYPTS_LOG_LEVEL=1 ERROR "${@:2}"; exit $1; } +ABORT() { FAIL 69 'user abort'; } + +##################################################################### +### check for reported errors and format USAGE contents ############# +##################################################################### + +CHECK_ERRORS() { + local FAIL_OUT=true + local DISPLAY_USAGE=true + + [ ! $ERRORS ] && ERRORS=0 + + while [[ $# -gt 0 ]] do - DYNAMIC_USAGE_ELEMENT_TEXT=$(eval echo '$USAGE__'$DYNAMIC_USAGE_ELEMENT) + case $1 in + --fail ) FAIL_OUT=true ;; + --no-fail ) FAIL_OUT=false ;; - [[ ! $DYNAMIC_USAGE_ELEMENT =~ ^description$ ]] \ - && DYNAMIC_USAGE_ELEMENT_TEXT=$(echo $DYNAMIC_USAGE_ELEMENT_TEXT | sed 's/[^ ]/ &/') - - THE_REST=$(echo $THE_REST | perl -pe "s/$DYNAMIC_USAGE_ELEMENT: -/$DYNAMIC_USAGE_ELEMENT:\n$DYNAMIC_USAGE_ELEMENT_TEXT\n\n/") + --usage ) DISPLAY_USAGE=true ;; + --no-usage ) DISPLAY_USAGE=false ;; + esac + shift 1 done - # allow for dynamic 'description: -' but delete the 'description:' header line - THE_REST=$(echo $THE_REST | sed '/^[ ]*description:$/d') + [[ $ERRORS -eq 0 ]] && return 0 - echo "$__DARK_BLUE$USAGE_LINE$__COLOR_RESET\n\n$THE_REST" \ - | sed "s/^\t\+//; s/\s\+$//; s/^\\s*$//;" \ - | sed '/./,$!d; :a; /^\n*$/{$d;N;ba;};' \ - | perl -p0e 's/\n{2,}/\n\n/g' \ - | perl -p0e 's/:\n{2,}/:\n/g' \ - >&2 + [[ $DISPLAY_USAGE =~ true ]] && USAGE + + [[ $FAIL_OUT =~ true ]] && exit $ERRORS || return $ERRORS } -INPUT() { - PROMPT "${@:2}" - READ $1 - local VALUE=$(eval echo '$'$1) - [ $VALUE ] -} +source "${0:a:h}/io.usage.zsh" -Yn() { +##################################################################### +### facilitate user prompt and input ################################ +##################################################################### + +Yn() { # ask a yes-or-no question (default yes) PROMPT "$@ [Yn]" - [ $CI ] && { echo y; return 0; } + [ $CI ] && { echo y >&2; return 0; } [ $__SCWRYPTS_YES ] && [[ $__SCWRYPTS_YES -eq 1 ]] && { echo y; return 0; } local Yn; READ -k Yn; echo >&2 [[ $Yn =~ [nN] ]] && return 1 || return 0 } -yN() { +yN() { # ask a yes-or-no question (default no) PROMPT "$@ [yN]" - [ $CI ] && { echo y; return 0; } + [ $CI ] && { echo y >&2; return 0; } [ $__SCWRYPTS_YES ] && [[ $__SCWRYPTS_YES -eq 1 ]] && { echo y; return 0; } local yN; READ -k yN; echo >&2 [[ $yN =~ [yY] ]] && return 0 || return 1 } -CAPTURE() { - [ ! $USAGE ] && USAGE=" - usage: stdout-varname stderr-varname [...cmd and args...] - - captures stdout and stderr on separate variables for a command - " - { - IFS=$'\n' read -r -d '' $2; - IFS=$'\n' read -r -d '' $1; - } < <((printf '\0%s\0' "$(${@:3})" 1>&2) 2>&1) +INPUT() { # read a single line of user input + PROMPT "${@:2}" + READ $1 + local VALUE=$(eval echo '$'$1) + [ $VALUE ] } -##################################################################### +source "${0:a:h}/io.fzf.zsh" # allow user to select from a list of inputs -GETSUDO() { - echo "\\033[1;36mPROMPT  : checking sudo password...\\033[0m" >&2 - sudo echo hi >/dev/null 2>&1 /dev/tty; } - -FZF() { - [ $CI ] && { - ERROR 'currently in CI, but FZF requires user input' - exit 1 - } - - local FZF_ARGS=() - - FZF_ARGS+=(-i) - FZF_ARGS+=(--ansi) - FZF_ARGS+=(--bind=ctrl-c:cancel) - FZF_ARGS+=(--height=50%) - FZF_ARGS+=(--layout=reverse) - - local SELECTION=$(fzf ${FZF_ARGS[@]} --layout=reverse --prompt "$1 : " ${@:2}) - PROMPT "$1" - echo $SELECTION >&2 - echo $SELECTION -} -FZF_HEAD() { FZF $@ --print-query | sed '/^$/d' | head -n1; } # prefer user input over selected -FZF_TAIL() { FZF $@ --print-query | sed '/^$/d' | tail -n1; } # prefer selected over user input - -READ() { - [ $CI ] && { - INFO 'currently in CI, skipping READ' - return 0 - } - read $@ /dev/tty; } + YQ() { yq --version | grep -q mikefarah || { yq $@ @@ -229,3 +134,37 @@ YQ() { yq eval '... comments=""' | yq $@ } + +##################################################################### +### other i/o utilities ############################################# +##################################################################### + +CAPTURE() { + [ ! $USAGE ] && USAGE=" + usage: stdout-varname stderr-varname [...cmd and args...] + + captures stdout and stderr on separate variables for a command + " + { + IFS=$'\n' read -r -d '' $2; + IFS=$'\n' read -r -d '' $1; + } < <((printf '\0%s\0' "$(${@:3})" 1>&2) 2>&1) +} + + +GETSUDO() { + echo "\\033[1;36mPROMPT  : checking sudo password...\\033[0m" >&2 + sudo echo hi >/dev/null 2>&1