From 522294208e2adff97be24139cbb4b5685058bb1a Mon Sep 17 00:00:00 2001 From: Martin Winkler Date: Wed, 4 May 2022 12:14:57 +0200 Subject: [PATCH] WIP rewrite with bash best practices --- sequencer.sh | 321 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100755 sequencer.sh diff --git a/sequencer.sh b/sequencer.sh new file mode 100755 index 0000000..576a5bc --- /dev/null +++ b/sequencer.sh @@ -0,0 +1,321 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2034 # variable not used + +# Exit on error. Append "|| true" if you expect an error. +set -o errexit +# Exit on error inside any functions or subshells. +set -o errtrace +# Do not allow use of undefined vars. Use ${VAR:-} to use an undefined VAR +set -o nounset +# Catch the error in case mysqldump fails (but gzip succeeds) in `mysqldump |gzip` +set -o pipefail +# Turn on traces, useful while debugging but commented out by default +# set -o xtrace + +## Globals +{ + ## Seq + readonly seq_name="${_sqn_alias:-${0##*/}}" + readonly seq_dir="$(cd -- "$(dirname -- "${0}")" && pwd)" + readonly seq_origin="$(cd -- "$(dirname -- "$(readlink -f -- "${0}")")" && pwd)" + readonly seq_file=$(basename -- "${0}") + readonly seq_fileName=${seq_file%%.*} + # shellcheck disable=SC2015 # && || is not if else + readonly seq_invocation="$(printf '%q' "${0}")$( (($#)) && printf ' %q' "$@" || true)" + + # May be overwritten by seq + seq_configFile="${seq_fileName}.cfg" + seq_configTemplate="${0%.*}.cfg.example" + + ## Sequencer + readonly sqr_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" + readonly sqr_file="$(basename -- "${BASH_SOURCE[0]}")" + readonly sqr_name="${sqr_file%%.*}" + readonly sqr_origin="$(cd -- "$(dirname -- \ + "$(readlink -f -- "${BASH_SOURCE[0]}")")" && pwd)" + + _sqr_interactive=1 + _sqr_debug=0 + _sqr_dry=0 + _sqr_logLastColor= + _sqr_verbose=0 + + # Colors + col_black= ; [ -t 1 ] && col_black='\033[0;30m' + col_darkgrey= ; [ -t 1 ] && col_darkgrey='\033[1;30m' + col_red= ; [ -t 1 ] && col_red='\033[0;31m' + col_lightred= ; [ -t 1 ] && col_lightred='\033[1;31m' + col_green= ; [ -t 1 ] && col_green='\033[0;32m' + col_lightgreen= ; [ -t 1 ] && col_lightgreen='\033[1;32m' + col_orange= ; [ -t 1 ] && col_orange='\033[0;33m' + col_yellow= ; [ -t 1 ] && col_yellow='\033[1;33m' + col_blue= ; [ -t 1 ] && col_blue='\033[0;34m' + col_lightblue= ; [ -t 1 ] && col_lightblue='\033[1;34m' + col_purple= ; [ -t 1 ] && col_purple='\033[0;35m' + col_lightpurple= ; [ -t 1 ] && col_lightpurple='\033[1;35m' + col_cyan= ; [ -t 1 ] && col_cyan='\033[0;36m' + col_lightcyan= ; [ -t 1 ] && col_lightcyan='\033[1;36m' + col_lightgray= ; [ -t 1 ] && col_lightgray='\033[0;37m' + col_white= ; [ -t 1 ] && col_white='\033[1;37m' + col_off= ;[ -t 1 ] && col_off='\033[0m' # No Color +} + +# Logging +{ + LOG_LEVEL="${LOG_LEVEL:-3}" # 4 = debug -> 0 = fatal (stop) + LOG_TIME="${LOG_TIME:-}" # 1 = show time stamps + + # sqr::log [LOG LEVEL] [LOG_COLOR] [OPTIONS] [LOG MESSAGE] + # Construct log messages + # + # [OPTIONS] + # -a : append text (no info and timestamp) + # Uses color from last sqr::log call without -a + # -- : End of options + # + sqr::log () { + sqr::debugPause + local appendText= + local arg= + local col_end="${col_off}" + + local log_level="${1:-}" + shift + local log_color="${1:-}" + shift + for arg in "${@}" ; do + case "${1:-}" in + --) + shift && break ;; + -a) + appendText=1 + shift ;; + esac + done + + _sqr_logLastColor="${log_color}" + [[ -z "${log_color}" ]] && col_end="" + + # all remaining arguments are to be printed + local log_line="" + + while IFS=$'\n' read -r log_line ; do + printf '%b' "${log_color}" + if [[ -n "${LOG_TIME}" ]] ; then + if (( appendText )) ; then + printf '%24s' "" + else + printf '%s ' "$(date -u +"%Y-%m-%d %H:%M:%S UTC")" + fi + fi + + if (( ! appendText )) ; then + printf " %3s " "[${log_level}]" + printf "%s" "${log_line}" # 1>&2 # send to stderr + else + # +3 : "[] " + printf "%$((${#log_level} + 4))s%s" "" "${log_line}" + fi + + printf '%b\n' "${col_end}" + done <<< "${@:-}" + sqr::debugContinue + } + + stop () { sqr::log "stop" "${col_red}" "${@}"; exit 1; } + error () { [[ "${LOG_LEVEL:-0}" -ge 1 ]] && sqr::log "e" "${col_red}" "${@}"; true; } + warning () { [[ "${LOG_LEVEL:-0}" -ge 2 ]] && sqr::log "w" "${col_orange}" "${@}"; true; } + info () { [[ "${LOG_LEVEL:-0}" -ge 3 ]] && sqr::log "i" "" "${@}"; true; } + debug () { [[ "${LOG_LEVEL:-0}" -ge 4 ]] && sqr::log "dbug" "${col_lightpurple}" "${@}"; true; } + + # internal printf same loglevel as info + sqr::print () { [[ "${LOG_LEVEL:-0}" -ge 3 ]] && printf "$@"; true; } + sqr::debugPause() { + if (( _sqr_debug )) ; then set +o xtrace; else true; fi + } + sqr::debugContinue() { + if (( _sqr_debug )) ; then set -o xtrace; else true; fi + } +} + +# Traps +{ + sqr::trap_exit () { + exists -f seq_trapExit && seq_trapExit + debug "Sequencer exit" + } + trap sqr::trap_exit EXIT + + # requires `set -o errtrace` + sqr::error_report() { + local error_code=${?} + error "Error in ${sqr_file} in function ${1} on line ${2}" + exit ${error_code} + } + + # Uncomment the following line for always providing an error backtrace + # trap 'sqr::error_report "${FUNCNAME:-.}" ${LINENO}' ERR +} + +# exists [-f] [--] [ELEMENT] +# [ELEMENT] +# : either a variable name or +# -f : function +exists() { + sqr::debugPause + local func= + local arg= + + for arg in "$@" ; do + case "${1:-}" in + --) + shift && break ;; + -f) + func="${2:-}" + esac + done + if [[ -n "${func}" ]] ; then + declare -F "${func}" &>>/dev/null + else + [[ -n "${!1:-}" ]] + fi + sqr::debugContinue +} + +# interactive +# Started with -q to use defaults for confirmations +interactive() { + (( _sqr_interactive )) +} +# quiet +# Log level smaller 3 (info) are treated as quiet request +quiet() { + [[ $LOG_LEVEL -lt 3 ]] +} +# silent +# Log level equals 0 (stop) +silent() { + [[ $LOG_LEVEL -eq 0 ]] +} + +### interactive +# confirm [OPTIONS] [--] [QUESTION] +# Default (empty character) = no +# +# [OPTIONS] +# -f : interactive even if quiet +# -n : no input help +# -y : default = yes +# -- : end of options +confirm() { + sqr::debugPause + local arg= + local rexReply='^[Yy]$' # default no + local inputHelp='[y/N] ' # default no + local noHelp=0 + local force=0 + + for arg in "${@}" ; do + case "${1:-}" in + --) + shift && break ;; + -f) + force=1 + shift ;; + -n) + noHelp=1 + shift ;; + -y) + rexReply='^[Yy]*$' # default yes + inputHelp='[Y/n] ' # default yes + shift ;; + esac + done + (( noHelp )) && inputHelp= + if interactive || (( force )) ; then + read -r -p "${1:-} ${inputHelp}" -n 1 + echo "" + else + REPLY='' + fi + sqr::debugContinue + [[ $REPLY =~ ${rexReply} ]] +} + +# ask [OPTION] [QUESTION] [DEFAULT] +# Will ask for input even if quiet, when [DEFAULT] is empty +# +# [OPTION] +# -e : allow empty input +# Ignored if [DEFAULT] is available +# -s : ask for secret (don't print input) +# Does not add a newline. Usage: +# pass=$(ask -s "Password"); echo +# -- : End of options +ask() { + sqr::debugPause + local hidden= + local empty=0 + local arg= + for arg in "$@" ; do + case "${1}" in + --) + shift && break ;; + -e) + empty=1 + shift ;; + -s) + hidden="-s" + shift ;; + esac + done + local answer= + if [[ -n "${2:-}" ]]; then + ! interactive && printf '%s\n' "${2}" && sqr::debugContinue && return 0 + read ${hidden?} -r -p "${1:-"User input"} ($2) " answer + else + read ${hidden?} -r -p "${1:-"User input"} " answer + fi + if [[ -z "$answer" ]] ; then + answer="${2:-}" + fi + printf '%s\n' "${answer}" + sqr::debugContinue + if (( ! empty )) ; then + [[ -n "${answer}" ]] + fi +} + +sqr::main() { + + # options check + for arg in "$@" ; do + case "$1" in + --quiet|-q) + _sqr_interactive=0 + shift ;; + -qq) + _sqr_interactive=0 + LOG_LEVEL=0 + shift ;; + --debug) + _sqr_debug="1" + shift ;; + esac + done + + # debug mode + if [[ "${_sqr_debug:-}" = "1" ]]; then + set -o xtrace + PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' + # Enable error backtracing + trap 'sqr::error_report "${FUNCNAME:-.}" ${LINENO}' ERR + fi + + sqr::print 'Running...\n' + confirm -y -- 'Continue?' + seq_config 2>/dev/null || true + step_1 "$@" +} + +sqr::main "$@"