WIP rewrite with bash best practices
This commit is contained in:
321
sequencer.sh
Executable file
321
sequencer.sh
Executable file
@@ -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 "$@"
|
Reference in New Issue
Block a user