815 lines
22 KiB
Bash
815 lines
22 KiB
Bash
#!/bin/bash
|
|
|
|
# fail ERROR-MESSAGE
|
|
#
|
|
# Outputs parameters (ERROR-MESSAGE) passed to 'fail' to stderr
|
|
#
|
|
# If a function 'fail_cleanup' exists, execute it after outputting
|
|
# error message. This allows scripts to perform any necessary script
|
|
# specific cleanup on failure.
|
|
#
|
|
# If 'no_exit' is set, or 'script_name' is 'bash',
|
|
# do not exit the process and return an error. This lets you chain
|
|
# error handlers or run the 'irepo' functions from a bash command line
|
|
# via:
|
|
# . /opt/scripts/lib/irepo
|
|
#
|
|
declare -g fail_failed=0
|
|
declare -g fail_min_stack=1
|
|
declare -g fail_skip_continue=0
|
|
declare -g fail_skip_filepath="/home/user/.${script_name}.skip_continue_target"
|
|
fail() {
|
|
local tmp=$?
|
|
if (( fail_failed )); then
|
|
exit 1
|
|
fi
|
|
fail_failed=${tmp}
|
|
# On exit 1 from fail, trap is called again. We do not want to display
|
|
# the stack frame for fail a second time, so set the fail_min_stack
|
|
# as the limit.
|
|
if (( ${#} != 0 )); then
|
|
echo -e "$*" | while read -r line; do
|
|
echo "FAIL: ${line}" >&2
|
|
done
|
|
fail_min_stack=0
|
|
else
|
|
echo -e "FAIL: ERR trap triggered: ${script_name} ${full_command}" >&2
|
|
fail_min_stack=1
|
|
fi
|
|
|
|
if (( fail_skip_continue )); then
|
|
echo ${BASH_LINENO[0]} > "${fail_skip_filepath}"
|
|
fi
|
|
|
|
# https://bashwizard.com/function-call-stack-and-backtraces/
|
|
declare CALLSTACK=
|
|
if (( ${#FUNCNAME[*]} > fail_min_stack )); then
|
|
CALLSTACK=$(local i ;
|
|
for (( i=0; i<${#FUNCNAME[*]}; i++ )); do
|
|
echo "FAIL: ${FUNCNAME[$i]}@${BASH_SOURCE[$i]}:${BASH_LINENO[$i-1]}"
|
|
done)
|
|
fi
|
|
|
|
echo "${CALLSTACK}" >&2
|
|
|
|
if [[ "${FAILURE_TO}" != "" ]]; then
|
|
echo "Attempting to email ${FAILURE_TO} of failure." >&2
|
|
declare _dir=$(pwd)
|
|
_temp=$(mktemp -d)
|
|
_filename="error-$(date +"%Y%m%d").log"
|
|
{
|
|
if [[ -e "${repositories}/.lock" ]]; then
|
|
echo -e "Lockfile contents:\n"
|
|
cat "${repositories}/.lock"
|
|
else
|
|
echo -e "No lockfile present during failure."
|
|
fi
|
|
echo ""
|
|
echo -e "Callstack:\n"
|
|
echo "${CALLSTACK}"
|
|
echo ""
|
|
echo -e "Environment:\n"
|
|
export
|
|
echo ""
|
|
} >> "${_temp}/${_filename}"
|
|
cd "${IREPO_HOME}/server"
|
|
if [[ -e "${log_file}" ]]; then
|
|
extra_attachments="--files=${log_file}"
|
|
else
|
|
extra_attachments=""
|
|
fi
|
|
export NODE_EXTRA_CA_CERTS="/usr/local/share/ca-certificates/IntelSHA256RootCA-base64.crt"
|
|
npm run mailer -- \
|
|
--to="${FAILURE_TO}" \
|
|
--from="${FAILURE_FROM:-james.p.ketrenos@intel.com}" \
|
|
--type="failure" \
|
|
--files="${_temp}/${_filename}" \
|
|
"${extra_attachments}"
|
|
cd "${_dir}"
|
|
rm -rf "${_temp}"
|
|
fi
|
|
|
|
# If we are in an interactive shell, do not call exit or the shell will
|
|
# terminate.
|
|
if [[ "${-}" =~ .*i.* ]]; then
|
|
kill -INT $$
|
|
fi
|
|
|
|
# execute fail_cleanup if it is declared
|
|
if [[ $(type -t fail_cleanup) == function ]]; then
|
|
fail_cleanup
|
|
fi
|
|
|
|
exit 1
|
|
}
|
|
|
|
trap "fail" ERR
|
|
|
|
fail_on_exit() {
|
|
if (( $# == 0 )); then
|
|
fail "fail_on_exit must be provided a return code"
|
|
fi
|
|
local ret=$1
|
|
local message=$2
|
|
message="${message:-unspecified command}"
|
|
if [[ ${ret} -ne 0 ]]; then
|
|
fail "${message} failed: ${ret}"
|
|
return ${ret}
|
|
fi
|
|
}
|
|
|
|
int() { [ "$1" -eq "$1" ] || fail "$1 is not an integer." ; echo $1 ; }
|
|
|
|
declare -g skip_continue_target=0
|
|
|
|
# skip_continue
|
|
#
|
|
# If called, skip_continue will enable line tracking in the fail ERR trap.
|
|
# On failure, the file '/home/user/.${script_name}.skip_continue_target'
|
|
# On exectuion, skip_continue will check the lineno contained in that
|
|
# file and only return SUCCESS (0) if the current Bash line number
|
|
# of the caller >= skip_continue_target.
|
|
#
|
|
# NOTE: This will not work with nested functions calling skip_continue
|
|
# from functions not in the main script.
|
|
skip_continue() {
|
|
fail_skip_continue=1
|
|
if (( fail_failed )); then
|
|
show_vals fail_failed
|
|
exit ${fail_failed}
|
|
fi
|
|
if (( ! skip_continue_target )) && [[ -f "${fail_skip_filepath}" ]]; then
|
|
skip_continue_target=$(cat "${fail_skip_filepath}")
|
|
fi
|
|
|
|
# Get the line of the skip_continue call
|
|
local line_no=${BASH_LINENO[0]}
|
|
line_no=$((line_no+1))
|
|
|
|
# Read two lines to see if the skip_continue has the command on
|
|
# the actual line, or on the following line. Set "line" to that
|
|
# line for display in the Running or Skipped output
|
|
mapfile lines < <(head -n ${line_no} "${BASH_SOURCE[1]}"| tail -n 2)
|
|
if [[ "${lines[0]}" =~ \&\&[[:space:]\\]*\#? ]]; then
|
|
line="${lines[1]}"
|
|
else
|
|
line="${lines[0]%*skip_continue &&}"
|
|
fi
|
|
|
|
if (( line_no >= skip_continue_target )); then
|
|
echo "Running (${line_no}): ${line%$'\n'}"
|
|
return 0
|
|
else
|
|
echo "Skipping (${line_no}): ${line%$'\n'}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# array_to_csv ARRAY [DELIM]
|
|
#
|
|
array_to_csv() {
|
|
declare -n _arr="${1}"
|
|
|
|
local delim="${2}"
|
|
local joined
|
|
delim=${delim:-,}
|
|
printf -v joined "%s${delim}" "${_arr[@]}"
|
|
joined="${joined%"${delim}"}"
|
|
joined="${joined#"${delim}"}"
|
|
echo "${joined}"
|
|
}
|
|
|
|
# csv_to_array ARRAY CSV [DELIM]
|
|
#
|
|
csv_to_array() {
|
|
declare -n _arr="${1}"
|
|
_arr="${2}"
|
|
declare _delim="${3:-,}"
|
|
mapfile -d "${_delim}" -t _arr <<< "${_arr}"
|
|
count=${#_arr[@]}
|
|
_arr[count-1]="${_arr[count-1]%$'\n'}"
|
|
}
|
|
|
|
|
|
# pop TARGET ARRAY
|
|
#
|
|
# removes the first element from ARRAY and stores in TARGET
|
|
pop() {
|
|
declare -n _target="${1}"
|
|
declare -n _arr="${2}"
|
|
if (( ${#_arr[@]} == 0 )); then
|
|
_target=""
|
|
return 1
|
|
fi
|
|
_target="${_arr[0]}"
|
|
_arr=("${_arr[@]:1}")
|
|
return 0
|
|
}
|
|
|
|
# parse_arguments COMMAND-LINE-ARGUMENTS
|
|
#
|
|
# Converts 'arguments' variable to long and short parameter definitions
|
|
#
|
|
# Parses COMMAND-LINE-ARGUMENTS and sets 'opts' as the parsed parameters
|
|
# to be used by caller in switch statement.
|
|
#
|
|
# NOTE:
|
|
# Optional parameters must be passed to the command line as
|
|
# '-param=VALUE' and not as '-param VALUE' or the optional parameter
|
|
# value will not be parsed and will be moved to unparsed parameters.
|
|
#
|
|
# Correct:
|
|
# app -has-optional=5 -other-param
|
|
#
|
|
# Incorrect:
|
|
# app -has-optional 5 -other-param
|
|
#
|
|
# Non-optional parameters can use space, however for consistency it is
|
|
# recommended that usage help show users to use '=' even for non-optional
|
|
# parameters.
|
|
#
|
|
# See 'arg_example' later in this file for sample usage.
|
|
#
|
|
parse_arguments() {
|
|
local arg_debug=0
|
|
|
|
if [[ ${#arguments[@]} == 0 ]]; then
|
|
fail "No command line arguments options in '\$arguments'"
|
|
fi
|
|
local sopts
|
|
local lopts
|
|
local arg
|
|
local _arguments
|
|
local mandatory=()
|
|
local valid=()
|
|
|
|
for arg in "${arguments[@]}"; do
|
|
arg="${arg%%\#*}" # Trim off any Help text
|
|
local -a argument_set
|
|
csv_to_array argument_set "${arg}" '|' # Convert to array
|
|
|
|
for argument in "${argument_set[@]}"; do
|
|
trimmed=${argument%:} # Trim off 'value required' indicator
|
|
trimmed=${trimmed%:} # Trim off 'value optional' indicator
|
|
if [[ "${argument:0:1}" == '*' ]]; then # Check if mandatory
|
|
argument="${argument:1}" # Strip off '*'
|
|
trimmed="${trimmed:1}" # Strip off '*'
|
|
mandatory_arg="${argument_set[*]//:/ }" # Remove :+ from argument_set
|
|
mandatory_arg="${mandatory_arg//$'*'}" # Remove '*'+ from argument_set
|
|
mandatory+=("${mandatory_arg}") # Add argument_set to mandatory
|
|
fi
|
|
valid+=("${trimmed}")
|
|
|
|
if [[ ${#trimmed} == 1 ]]; then
|
|
sopts="$argument${sopts}"
|
|
else
|
|
if [[ "${lopts}" == "" ]]; then
|
|
lopts=${argument}
|
|
else
|
|
lopts=${lopts},${argument}
|
|
fi
|
|
fi
|
|
done
|
|
done
|
|
|
|
(( arg_debug )) && echo "lopts: ${lopts}" >&2
|
|
(( arg_debug )) && echo "sopts: ${sopts}" >&2
|
|
|
|
# Only accept exact parameters, do not let getopt try and "fit"
|
|
for opt in "${@}"; do
|
|
if [[ ! "${opt}" =~ ^- ]]; then
|
|
continue
|
|
fi
|
|
if [[ "${opt}" == "--" ]]; then
|
|
break
|
|
fi
|
|
opt=${opt#-}
|
|
opt=${opt%%=*}
|
|
if [[ ! " ${valid[*]} " =~ [[:space:]]${opt}[[:space:]] ]]; then
|
|
echo "ERROR: '-${opt}' is not a valid parameter." >&2
|
|
return 1
|
|
fi
|
|
done
|
|
|
|
opts=$(getopt \
|
|
-a \
|
|
--longoptions "${lopts}" \
|
|
--name "$(basename "$0")" \
|
|
--options "${sopts}" \
|
|
-- "$@"
|
|
)
|
|
if [[ $? -ne 0 ]]; then
|
|
(( arg_debug )) && echo "opts: ${opts}"
|
|
fail "\nSee '$(basename "${0}") -h' for valid options.\n"
|
|
fi
|
|
|
|
# Enforce mandatory options
|
|
for arg in "${mandatory[@]}"; do
|
|
provided=0
|
|
for opt in ${opts}; do
|
|
if [[ "${opt}" == "--" ]]; then
|
|
break
|
|
fi
|
|
for acceptable in ${arg}; do
|
|
if [[ "${opt}" =~ ^-\-?"${acceptable}"$ ]]; then
|
|
provided=1
|
|
break
|
|
fi
|
|
done
|
|
done
|
|
if (( ! provided )); then
|
|
local last_arg=${arg% }
|
|
last_arg=${last_arg##* }
|
|
echo "ERROR: '-${last_arg}=' is a mandatory requirement and must be supplied." >&2
|
|
return 1
|
|
fi
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
# Example function demonstrating argument declaration and parsing.
|
|
#
|
|
# You can copy this into your entry script, change the name,
|
|
# and then call it in the start of your application passing in all
|
|
# of the command line arguments, for example:
|
|
#
|
|
# copy arg_example => process_arguments
|
|
# process_arguments ${@}
|
|
#
|
|
# And then adapt as fit for your application's parameters.
|
|
#
|
|
# To run the arg_example script, you can run irepo with
|
|
# arg_example_show set to a value. For example:
|
|
#
|
|
# arg_example_show=1 irepo -h -lp -required-parameter=5
|
|
#
|
|
arg_example() {
|
|
arguments=(
|
|
"h|help#This help text."
|
|
"lp" "long-parameter"
|
|
"rp:" "required-parameter:"
|
|
"hop::" "has-optional-parameter:"
|
|
)
|
|
|
|
parse_arguments "${@}"
|
|
eval set -- "$opts"
|
|
remaining=$#
|
|
while (( $# > 0 )); do
|
|
if [[ ${remaining} == 0 ]]; then
|
|
fail "case statement is not shifting off values correctly."
|
|
fi
|
|
remaining=$((remaining-1))
|
|
# Uncomment to help debug case statement:
|
|
# echo "Processing: \$1='$1' \$2='$2'"
|
|
case "${1}" in
|
|
# NOTE: Short options only have a single dash
|
|
-h|--help)
|
|
echo "...show help..."
|
|
;;
|
|
# NOTE: Long options always have a double dash, even though the
|
|
# application reads them from the command-line as a single dash
|
|
--lp|--long-parameter)
|
|
echo "...do work for long-parameter..."
|
|
;;
|
|
--rp|--required-parameter)
|
|
value=${2}
|
|
shift # Remove the argument from the processing list
|
|
echo "...do work for 'required-parameter=${value}'..."
|
|
;;
|
|
--hop|--has-optional-parameter)
|
|
value=default
|
|
if [[ "${2}" != "" ]]; then
|
|
value="${2}"
|
|
fi
|
|
shift # Remove the optional argument from the processing list
|
|
echo "...do work for 'has-optional-parameter=${value}'..."
|
|
;;
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
}
|
|
|
|
# show_vals VARIABLES
|
|
#
|
|
# Helper function for use in debugging. Typical usage:
|
|
#
|
|
# show_vals [OPTIONS] VARIABLE1 VARIABLE2 ... VARIABLEn
|
|
#
|
|
# Available options: -json -deshadow
|
|
# -json Outputs as JSON
|
|
# -deshadow Remove shadow_ prefix from any variable name
|
|
#
|
|
show_vals() {
|
|
declare -i json=0
|
|
declare -i deshadow=0
|
|
# Eventually replace this with actual arg parsing, but since
|
|
# only two options are possible, just check twice to parse both
|
|
if [[ "${1}" == "-json" ]]; then
|
|
json=1
|
|
shift
|
|
fi
|
|
if [[ "${1}" == "-deshadow" ]]; then
|
|
deshadow=1
|
|
shift
|
|
fi
|
|
if [[ "${1}" == "-json" ]]; then
|
|
json=1
|
|
shift
|
|
fi
|
|
if [[ "${1}" == "-deshadow" ]]; then
|
|
deshadow=1
|
|
shift
|
|
fi
|
|
local var
|
|
local value
|
|
declare -i first=1
|
|
if (( json )); then
|
|
echo "{"
|
|
fi
|
|
for var in "${@}"; do
|
|
if (( deshadow )); then
|
|
var_name="${var/shadow_}"
|
|
else
|
|
var_name="${var}"
|
|
fi
|
|
if (( first )); then
|
|
first=0
|
|
else
|
|
if ((json )); then
|
|
echo ","
|
|
fi
|
|
fi
|
|
if [[ "${!var}" == "" ]]; then
|
|
if (( json )); then
|
|
echo -n " \"${var_name}\": undefined"
|
|
else
|
|
echo "${var_name} = '' or unset"
|
|
fi
|
|
continue
|
|
fi
|
|
|
|
value=$(sed -nE "s#declare -a ${var}=\((.*)\)#\1#p" < \
|
|
<(declare -p "${var}"))
|
|
if (( json )); then
|
|
if [[ "${value}" == "" ]]; then
|
|
echo -n " \"${var_name}\": \"${!var}\""
|
|
else
|
|
eval declare -A values=(${value})
|
|
echo -n " \"${var_name}\": ["
|
|
declare -i first_arr=1
|
|
for item in "${values[@]}"; do
|
|
if (( first_arr )); then
|
|
first_arr=0
|
|
else
|
|
echo -n ", "
|
|
fi
|
|
echo -n "\"${item}\""
|
|
done
|
|
echo -n "]"
|
|
fi
|
|
else
|
|
if [[ "${value}" == "" ]]; then
|
|
echo "${var_name} = ${!var}"
|
|
else
|
|
echo "${var_name}[] = ${value}"
|
|
fi
|
|
fi
|
|
done
|
|
if (( json )); then
|
|
echo -e "\n}"
|
|
fi
|
|
}
|
|
|
|
export yes
|
|
|
|
# ask VAR OPTS PROMPT
|
|
#
|
|
# Will display PROMPT OPTS ? with OPTS split by | and default wrapped
|
|
# in (). VAR is set to selected option.
|
|
#
|
|
# If OPTS contains a capital letter, that is used as the default if
|
|
# ENTER is pressed.
|
|
#
|
|
# Usage:
|
|
# ask ret yN "Are you sure"
|
|
# if [[ "${ret}" == "n" ]]; then echo No; fi
|
|
# if [[ "${ret}" == "y" ]]; then echo Yes; fi
|
|
#
|
|
# NOTE: If 'yes' is set in the enviornment, and 'y' is in the option
|
|
# then 'y' value will be immediately returned after the prompt is shown.
|
|
#
|
|
# Will prompt: Are you sure y|(N)?
|
|
# If neither y, n, or ENTER is pressed, the prompt will loop.
|
|
# 'res' contains the character the user selected (lowercase)
|
|
ask() {
|
|
if [[ "${1}" == "-h" ]] || (( $# < 3 )); then
|
|
fail "ask RETVAR OPTS PROMPT"
|
|
fi
|
|
declare -n _ret="${1}"
|
|
local opts="${2}a" # Ny - N is default; ynC - C is default; Ynm - Y is default
|
|
shift 2
|
|
local prompt="${*}"
|
|
local index
|
|
local opts_str
|
|
local default
|
|
# Loop through each character in opts and build opt_str
|
|
# for display to read prompt.
|
|
while [[ "${opts:${index}:1}" != "" ]]; do
|
|
local c=${opts:${index}:1}
|
|
index=$((index+1))
|
|
if [[ ${c} =~ [A-Z] ]]; then
|
|
if [[ "${default}" != "" ]]; then
|
|
fail "Only one character can be default: ${opts} ${default} ${c}"
|
|
fi
|
|
if [[ "${opts_str}" != "" ]]; then
|
|
opts_str="${opts_str}|(${c})"
|
|
else
|
|
opts_str="(${c})"
|
|
fi
|
|
default=${c,,}
|
|
else
|
|
if [[ "${opts_str}" != "" ]]; then
|
|
opts_str="${opts_str}|${c}"
|
|
else
|
|
opts_str="${c}"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if (( yes )) && [[ ${opts} =~ [yY] ]]; then
|
|
# echo "${FUNCNAME[1]}@${BASH_SOURCE[1]}: ${prompt} ${opts_str}? y"
|
|
echo "${prompt} ${opts_str}? y"
|
|
_ret="y"
|
|
return 0
|
|
fi
|
|
|
|
local REPLY
|
|
while read -n1 -r -p "${prompt} ${opts_str}? " REPLY; do
|
|
case $REPLY in
|
|
"a")
|
|
fail "Abort"
|
|
;;
|
|
"")
|
|
if [[ "${default}" != "" ]]; then
|
|
_ret="${default,,}"
|
|
return 0
|
|
fi
|
|
;;
|
|
*)
|
|
echo ""
|
|
if [[ ${REPLY,,} =~ [${opts,,}] ]]; then
|
|
_ret="${REPLY,,}"
|
|
return 0
|
|
fi
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
|
|
# default_usage
|
|
#
|
|
# Display default usage based on ${arguments}
|
|
#
|
|
# 'arguments' is an ARRAY of strings.
|
|
#
|
|
# Each string is of the form [*]NAME[:](|NAME2[:])#Help text.
|
|
# If the leading '*' is present, this parameter is mandatory.
|
|
# If a name is followed by a single ':", that parameter takes a value.
|
|
# If name is followed by two colons ('::') the parameter is optional.
|
|
# |NAME2[:] provides the ability to list both short and long parameter
|
|
# options for a given parameter.
|
|
# The help text is displayed right aligned.
|
|
# If a variable NAME or shadow_NAME exists and is set, its value is
|
|
# displayed in the Help text as the 'Default'.
|
|
#
|
|
default_generate_help_text() {
|
|
for arg in "${@}"; do
|
|
local _help
|
|
local _default=
|
|
local _help_text="${arg#*\#}" # Strip ^.*# from _help_text
|
|
if [[ "${_help_text}" == "${arg}" ]]; then
|
|
_help_text=""
|
|
fi
|
|
mapfile -t _help_text < <(fold -s -w 50 <<< "${_help_text}")
|
|
|
|
if [[ "${arg:0:1}" == '*' ]]; then # Check if is_mandatory
|
|
arg="${arg:1}" # Strip off '*'
|
|
is_mandatory=1
|
|
else
|
|
is_mandatory=0
|
|
fi
|
|
|
|
arg="${arg%%\#*}" # Trim off any help text
|
|
local -a argument_set
|
|
csv_to_array argument_set "${arg}" '|' # Convert to array
|
|
|
|
for argument in "${argument_set[@]}"; do
|
|
if (( ${#argument_set[@]} > 1 )); then
|
|
argument_value_name=${argument_set[-1]}
|
|
else
|
|
argument_value_name=${argument}
|
|
fi
|
|
|
|
if [[ "${argument}" == "${argument_set[0]}" ]]; then
|
|
first_argument=1
|
|
else
|
|
first_argument=0
|
|
fi
|
|
if [[ "${argument}" == "${argument_set[-1]}" ]]; then
|
|
last_argument=1
|
|
else
|
|
last_argument=0
|
|
fi
|
|
if (( ${#argument_set[@]} > 1 )); then
|
|
more_than_one_argument=1
|
|
else
|
|
more_than_one_argument=0
|
|
fi
|
|
|
|
# If takes option, trim : off end of names
|
|
if [[ "${argument:0-1}" == ":" ]]; then
|
|
takes_value=1
|
|
argument_value_name=${argument_value_name:0:-1}
|
|
argument="${argument%:}" # Trim off the trailing :
|
|
else
|
|
takes_value=0
|
|
fi
|
|
|
|
# If option is optional, trim : off end of names
|
|
if [[ "${argument:0-1}" == ":" ]]; then
|
|
takes_value=0
|
|
takes_optional_value=1
|
|
argument_value_name=${argument_value_name:0:-1}
|
|
argument="${argument%:}" # Trim off the trailing :
|
|
else
|
|
takes_optional_value=0
|
|
fi
|
|
|
|
if (( is_mandatory )) && (( takes_optional_value )); then
|
|
fail "'${argument}' Mandatory (*) requirements can not take optional values (::)"
|
|
fi
|
|
|
|
# If this is the first argument, initialize _help from first item
|
|
# in _help_text. _default="". _default will be set later
|
|
# if this argument takes parameters.
|
|
if (( first_argument )); then
|
|
pop _help _help_text
|
|
_default=""
|
|
showed_help=0
|
|
fi
|
|
|
|
if (( takes_value )) || (( takes_optional_value )); then
|
|
# If a shadow_ variant exists, use that as the default,
|
|
# otherwise use the argument_value_name (with - changed to _)
|
|
declare -n _shadow="shadow_${argument_value_name//-/_}"
|
|
if [[ "${_shadow}" == "" ]]; then
|
|
declare -n _shadow="${argument_value_name//-/_}"
|
|
fi
|
|
# Make the display name for a value for
|
|
# foo-bar-rabbit into RABBIT
|
|
argument_value_name=${argument_value_name^^}
|
|
argument_value_name=${argument_value_name##*-}
|
|
|
|
# First argument. Initializes _default.
|
|
if (( first_argument )); then
|
|
if (( is_mandatory )); then
|
|
_default="Mandatory: ${argument_value_name} option must be provied."
|
|
else
|
|
if [[ "${_shadow}" != "" ]]; then
|
|
_default="Default: $(array_to_csv _shadow)"
|
|
else
|
|
_default="Default: no default"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# If this is the last argument in the set and the current help
|
|
# text is empty and there is no more _help_text, set the
|
|
# _help_text to default
|
|
if (( last_argument )) &&
|
|
[[ "${_help}" == "" ]] &&
|
|
(( ${#_help_text[@]} == 0 )) ; then
|
|
mapfile -t _help_text < <(fold -s -w 50 <<< "${_default}")
|
|
_default=""
|
|
pop _help _help_text
|
|
fi
|
|
|
|
if (( takes_optional_value )); then
|
|
equals="[=${argument_value_name}]"
|
|
else
|
|
equals="=${argument_value_name}"
|
|
fi
|
|
|
|
if (( first_argument )); then
|
|
# If there is more than one argument in the set, add a | to the
|
|
# output and do not display _default
|
|
if (( more_than_one_argument )); then
|
|
printf " %-30s %s\n" \
|
|
"-${argument}${equals}|" \
|
|
"${_help}"
|
|
showed_help=1
|
|
else
|
|
printf " %-30s %s\n" \
|
|
"-${argument}${equals}" \
|
|
"${_help}"
|
|
showed_help=1
|
|
fi
|
|
else # (( ! first_argument ))
|
|
# If not last optional argument, add a | to the output and do
|
|
# not display _help
|
|
if (( ! last_argument )); then
|
|
printf " %-30s %s\n" \
|
|
"-${argument}${equals}|" \
|
|
"${_help}"
|
|
showed_help=1
|
|
else
|
|
printf " %-30s %s\n" \
|
|
"-${argument}${equals}" \
|
|
"${_help}"
|
|
showed_help=1
|
|
fi
|
|
fi
|
|
else # (( ! takes_value )) && (( ! takes_optional_value ))
|
|
if (( first_argument )); then
|
|
option_string=""
|
|
# If there is more than one argument in the set, add a | to the
|
|
# output
|
|
if (( more_than_one_argument )); then
|
|
option_string="${option_string}-${argument}|"
|
|
else # (( last_argument ))
|
|
printf " %-30s %s\n" "-${argument}" "${_help}"
|
|
showed_help=1
|
|
fi
|
|
else # (( ! first_argument ))
|
|
# If last non-optional argument, display help
|
|
if (( last_argument )); then
|
|
printf " %-30s %s\n" "${option_string}-${argument}" "${_help}"
|
|
showed_help=1
|
|
else
|
|
option_string="${option_string}-${argument}|"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if (( showed_help )); then
|
|
#show_vals argument argument_set first_argument last_argument more_than_one_argument _help _help_text _default is_mandatory takes_value takes_optional_value showed_help _shadow
|
|
|
|
pop _help _help_text
|
|
fi
|
|
done # done for 'arg in "${argument_set[@]}"''
|
|
# If there is still a help string, display it.
|
|
while [[ "$_help" != "" ]] || (( ${#_help_text[@]} > 0 )); do
|
|
printf " %-30s %s\n" "" "${_help}"
|
|
if ! pop _help _help_text; then
|
|
if [[ "${_default}" == "" ]]; then
|
|
break
|
|
fi
|
|
mapfile -t _help_text < <(fold -s -w 50 <<< "${_default}")
|
|
_default=""
|
|
pop _help _help_text
|
|
fi
|
|
done
|
|
_help=""
|
|
_default=""
|
|
done
|
|
}
|
|
|
|
default_usage() {
|
|
local _mandatory=()
|
|
|
|
for arg in "${arguments[@]}"; do
|
|
if [[ "${arg:0:1}" == '*' ]]; then # Check if is_mandatory
|
|
arg="${arg%#*}" # Strip off help
|
|
arg="${arg:1:-1}" # Strip off '*'
|
|
csv_to_array arg "${arg}" '|'
|
|
_mandatory+=("${arg[-1]}")
|
|
fi
|
|
done
|
|
|
|
local _text="Usage: ${script_name} [OPTIONS] ${usage_extra}"
|
|
for _arg in "${_mandatory[@]}"; do
|
|
_text="${_text} -${_arg}=${_arg^^}"
|
|
done
|
|
mapfile -t _text < <(fold -s -w 72 <<< "${_text}")
|
|
first_line=1
|
|
for _line in "${_text[@]}"; do
|
|
if (( first_line )); then
|
|
echo -e "\n${_line}"
|
|
first_line=0
|
|
else
|
|
echo " ${_line}"
|
|
fi
|
|
done
|
|
|
|
echo -e "\nOptions:\n"
|
|
default_generate_help_text "${arguments[@]}"
|
|
|
|
echo ""
|
|
}
|