Commit 1b2dd788 authored by Emeric Verschuur's avatar Emeric Verschuur

[3rd] Update bashopts to the version 2.0.0

parent 0ca1748a
......@@ -19,6 +19,8 @@
set -e
BASHOPTS_VERSION=2.0.0
bashopts_exit_handle() {
local err=$?
set +o xtrace
......@@ -45,29 +47,34 @@ trap 'bashopts_exit_handle' ERR
set -o errtrace
# display a error (fatal)
bashopts_critical() {
>&2 printf "[ERROR] %s\n" "$@"
exit 1
bashopts_log() {
if [ -n "$bashopts_log_handler" ]; then
$bashopts_log_handler "$@"
return;
fi
local level=$1;
shift || bashopts_log C "Usage bashopts_log <level> message"
case "${level,,}" in
c|critical)
>&2 printf "[CRIT] %s\n" "$@"
exit 1
;;
e|error)
>&2 printf "[ERRO] %s\n" "$@"
;;
w|warning)
>&2 printf "[WARN] %s\n" "$@"
;;
*)
bashopts_log C "Invalid log level: $level"
;;
esac
}
if [ ! "${BASH_VERSINFO[0]}" -ge 4 ]; then
bashopts_critical "bashopts require BASH version 4 or greater"
bashopts_log C "bashopts require BASH version 4 or greater"
fi
# display a error (non fatal)
bashopts_error() {
>&2 printf "[ERROR] %s\n" "$@"
}
# display a warning (non fatal)
bashopts_warning() {
>&2 printf "[WARNING] %s\n" "$@"
}
bashopts_regex_escape () {
echo $1 | sed 's/[][()\.^$\/?*+]/\\&/g'
}
# extract the value part of a declaration ("the value")
bashopts_get_def() {
declare | grep "^$1=" | sed -E 's/^[^=]+=//g'
......@@ -88,7 +95,7 @@ bashopts_check_opt_name() {
echo $1
return 0
fi
bashopts_error "'$1' is not a valid variable name"
bashopts_log E "'$1' is not a valid variable name"
return 1
}
......@@ -101,23 +108,23 @@ bashopts_check_number() {
echo $1
return 0
fi
bashopts_error "'$1' is not a valid number"
bashopts_log E "Option $op: '$1' is not a valid number"
return 1
}
# check and format a boolean value
bashopts_check_boolean() {
case "$1" in
''|f|false|F|FALSE|False|0)
case "${1,,}" in
''|f|false|0)
echo "false"
return 0
;;
t|true|T|TRUE|True|1)
t|true|1)
echo "true"
return 0
;;
*)
bashopts_error "'$1' is not a valid boolean value"
bashopts_log E "Option $op: '$1' is not a valid boolean value"
return 1
;;
esac
......@@ -129,6 +136,21 @@ bashopts_check_string() {
return 0
}
# check and format a enumeration value
bashopts_check_enumeration() {
local line expr values
while read -r line; do
expr="^($line)\$"
if [[ "$1" =~ $expr ]]; then
echo "${line##*|}"
return 0
fi
values+=("'${line##*|}'")
done <<< "${2:-${bashopts_optprop_enum_values[$op]}}"
bashopts_log E "Option $op: Invalid value '$1' (accepted values are: ${values[*]})"
return 1
}
# check nothing
bashopts_check_nothing() {
echo "$1"
......@@ -136,7 +158,7 @@ bashopts_check_nothing() {
}
# declare the options property arrays
for f in name default expression short_opt long_opt description type method check setting interactive req_value; do
for f in name default expression short_opt long_opt description type enum_values method check setting interactive req_value; do
eval declare -x -A bashopts_optprop_$f
done
......@@ -155,7 +177,7 @@ bashopts_tool_name=$0
bashopts_setup() {
local arg arglist no_default_opts non_interactive disable_interactive
if ! arglist=$(getopt -o "n:d:u:s:yxp" -n "$0 " -- "$@"); then
bashopts_critical "Usage bashopts_setup:" \
bashopts_log C "Usage bashopts_setup:" \
" -n <val> Tool name" \
" -d <val> Tool description" \
" -u <val> Tool usage description" \
......@@ -178,14 +200,14 @@ bashopts_setup() {
-x) disable_interactive="true";;
-p) bashopts_tool_settings_force_write="true";;
--) break;;
*) bashopts_critical "Fatal error";;
*) bashopts_log C "Fatal error";;
esac
done
if [ -z "$bashopts_tool_name" ]; then
bashopts_critical "Undefined tool name"
bashopts_log C "Undefined tool name"
fi
if [ -z "$bashopts_tool_description" ]; then
bashopts_critical "Undefined tool description"
bashopts_log C "Undefined tool description"
fi
bashopts_tool_usage=${bashopts_tool_usage:-"$bashopts_tool_name [options and commands] [-- [extra args]]"}
# add the default options
......@@ -203,16 +225,17 @@ bashopts_setup() {
# STEP 2: add options
bashopts_declare() {
local arg arglist options
if ! arglist=$(getopt -o "n:v:e:o:l:d:t:m:k:rsi" -n "$0 " -- "$@"); then
bashopts_critical "Usage bashopts_declare:" \
local arg arglist options options_enum_values
if ! arglist=$(getopt -o "n:v:x:o:l:d:t:e:m:k:rsi" -n "$0 " -- "$@"); then
bashopts_log C "Usage bashopts_declare:" \
" -n <val> Name" \
" -v <val> Default value" \
" -e <val> Bash expression: like default but this expression is computed and can contain variables and other bash expression" \
" -x <val> Bash expression: like default but this expression is computed and can contain variables and other bash expression" \
" -o <val> Short option" \
" -l <val> Long option" \
" -d <val> Description" \
" -t <val> Value type: string (default), number, boolean" \
" -t <val> Value type: string, enumeration, number, boolean (default)" \
" -e <val> Enum element: restrict accepted values with a list of '-e <element>' options (you have to set one '-e <val>' by elements)" \
" -m <val> Method: set (DEFAULT: simple value), add (list with several values)" \
" -k <val> Custom check method (bash function)" \
" -r Value required" \
......@@ -227,53 +250,68 @@ bashopts_declare() {
shift
case "$arg" in
-n) options[name]=$(bashopts_check_opt_name $1 || exit 1); shift;;
-v) options[default]=$1; shift;;
-e) options[expression]=$1; shift;;
-o) options[short_opt]=$1; shift;;
-l) options[long_opt]=$1; shift;;
-d) options[description]=$1; shift;;
-t) options[type]=$1; shift;;
-m) options[method]=$1; shift;;
-k) options[check]=$1; shift;;
-v) options[default]=$1; shift;;
-x) options[expression]=$1; shift;;
-o) options[short_opt]=$1; shift;;
-l) options[long_opt]=$1; shift;;
-d) options[description]=$1; shift;;
-t) options[type]=$1; shift;;
-e) options_enum_values+=($1); shift;;
-m) options[method]=$1; shift;;
-k) options[check]=$1; shift;;
-s) options[setting]="true";;
-i) options[interactive]="true";;
-r) options[req_value]="true";;
--) break;;
*) bashopts_critical "Fatal error";;
*) bashopts_log C "Fatal error";;
esac
done
# Check incompatible -v and -r options
if [ -n "${options[default]}" ] && [ "${options[req_value]}" == "true" ]; then
bashopts_critical "bashopts_declare: -r and -v options cannot be activated at the same time"
bashopts_log C "bashopts_declare: -r and -v options cannot be activated at the same time"
fi
# format the type and check/format the default value
case "${options[type]}" in
''|bool|boolean)
case "${options[type],,}" in
''|b|bool|boolean)
options[type]="boolean"
;;
str|string)
e|enum|enumeration)
options[type]="enumeration"
if [ ${#options_enum_values[@]} -lt 2 ]; then
bashopts_log C "bashopts_declare: ${options[name]} enumeration need at least two elements (two '-e <val>' calls at least)"
fi
options[enum_values]="$(IFS=$'\n'; echo "${options_enum_values[*]}")"
;;
s|str|string)
options[type]="string"
;;
num|number)
n|num|number)
options[type]="number"
;;
*)
bashopts_critical "Invalid type ${options[type]}"
bashopts_log C "Invalid type ${options[type]}"
;;
esac
# Check for incompatibility with old version (-e opt moved to -x)
if [ "${options[type]}" != "enumeration" ] && [ ${#options_enum_values[@]} -gt 0 ]; then
bashopts_log C "bashopts_declare: The former '-e' option is now moved to '-x'" \
" => the new '-e' is reserved for enumeration elements"
fi
# Setup check value method
if ! [[ -v options[check] ]]; then
options[check]="bashopts_check_${options[type]}"
fi
# format the option method
case "${options[method]}" in
case "${options[method],,}" in
''|s|set)
# default: simple value - override
options[method]="set"
if [ "${options[type]}" != "string" ] || [[ -v options[default] ]]; then
# Check the default value format
options[default]="$(${options[check]} "${options[default]}")" \
|| bashopts_critical "Invalid default value for ${options[name]} option"
if ! options[default]="$(${options[check]} "${options[default]}" "${options[enum_values]}")"; then
bashopts_log W "Invalid default value for ${options[name]} option, this value will stay unset"
unset options[default]
fi
fi
;;
a|add)
......@@ -281,30 +319,30 @@ bashopts_declare() {
options[method]="add"
;;
*)
bashopts_critical "Invalid method ${options[method]}"
bashopts_log C "Invalid method ${options[method]}"
;;
esac
# Check option name
if [[ -v bashopts_optprop_name[${options[name]}] ]]; then
bashopts_critical "Dupplicate option name '${options[name]}'"
bashopts_log C "Dupplicate option name '${options[name]}'"
fi
# check the short option
if [[ -v options[short_opt] ]]; then
if ! [[ ${options[short_opt]} =~ ^[a-zA-Z0-9_-]$ ]]; then
bashopts_critical "Invalid short option ${options[short_opt]}"
bashopts_log C "Invalid short option ${options[short_opt]}"
fi
if [[ -v bashopts_arg2op[-${options[short_opt]}] ]]; then
bashopts_critical "Dupplicate short option '${options[short_opt]}'"
bashopts_log C "Dupplicate short option '${options[short_opt]}'"
fi
bashopts_arg2op[-${options[short_opt]}]=${options[name]}
fi
# check the long option
if [[ -v options[long_opt] ]]; then
if ! [[ ${options[long_opt]} =~ ^[a-zA-Z0-9_-]{2,}$ ]]; then
bashopts_critical "Invalid long option ${options[long_opt]}"
bashopts_log C "Invalid long option ${options[long_opt]}"
fi
if [[ -v bashopts_arg2op[--${options[long_opt]}] ]]; then
bashopts_critical "Dupplicate long option '${options[long_opt]}'"
bashopts_log C "Dupplicate long option '${options[long_opt]}'"
fi
bashopts_arg2op[--${options[long_opt]}]=${options[name]}
fi
......@@ -315,6 +353,28 @@ bashopts_declare() {
bashopts_optlist+=(${options[name]})
}
bashopts_get_valid_value_list() {
local op
case "$1" in
-*)
op=${bashopts_arg2op[$1]}
;;
*)
op=$1
;;
esac
case "${bashopts_optprop_type[$op]}" in
boolean)
echo -e "true\nfalse"
;;
enumeration)
while read -r line; do
echo "\"${line##*|}\""
done <<< "${bashopts_optprop_enum_values[$op]}"
;;
esac
}
# maximum of two values
bashopts_math_max() {
echo $(($1>$2?$1:$2))
......@@ -328,14 +388,14 @@ bashopts_math_min() {
# join array element
bashopts_join_by() {
local IFS="$1"
shift || bashopts_critical "Usage: bashopts_join_by <character> [elt1 [elt2...]]"
shift || bashopts_log C "Usage: bashopts_join_by <character> [elt1 [elt2...]]"
echo "$*"
}
# dump an option value by its name
bashopts_dump_value() {
local op=$1
shift || bashopts_critical "Usage: bashopts_dump_value op_name"
shift || bashopts_log C "Usage: bashopts_dump_value op_name"
[[ -v "$op" ]] || return 0
if [ "${bashopts_optprop_method[$op]}" == "set" ]; then
if [ "${bashopts_optprop_type[$op]}" == "string" ]; then
......@@ -366,7 +426,7 @@ bashopts_dump_value() {
# display the formated help
bashopts_diplay_help() {
local elts optargs_max_len=8 val dval ncol
local elts optargs_max_len=8 val ncol line
declare -A optargs
ncol=$(tput cols || true) 2> /dev/null
# compute the good arguments comumn size
......@@ -393,25 +453,33 @@ bashopts_diplay_help() {
echo
echo "OPTIONS:"
for op in "${bashopts_optlist[@]}"; do
elts=""
dval=""
if ! [[ $op =~ ^__.*__$ ]]; then
# display additional information the each properties
# discarding special options like --help
if [[ -v bashopts_optprop_expression[$op] ]]; then
elts="- [\$$op] (type:${bashopts_optprop_type[$op]}, default:\"${bashopts_optprop_expression[$op]//\"/\\\"}\")"
elif [[ -v bashopts_optprop_default[$op] ]]; then
if [ "${bashopts_optprop_type[$op]}" == "string" ]; then
elts="- [\$$op] (type:${bashopts_optprop_type[$op]}, default:\"${bashopts_optprop_default[$op]//\"/\\\"}\")"
# display arguments, value if available, description, and additional info if available
printf " %-${optargs_max_len}s %s\n" "${optargs[$op]}" "${bashopts_optprop_description[$op]} $(
if ! [[ $op =~ ^__.*__$ ]]; then
# display additional information the each properties
# discarding special options like --help
echo -n "- [\$$op] (type:${bashopts_optprop_type[$op]}"
if [[ -v bashopts_optprop_expression[$op] ]]; then
echo -n ", default:\"${bashopts_optprop_expression[$op]//\"/\\\"}\""
elif [[ -v bashopts_optprop_default[$op] ]]; then
if [[ "${bashopts_optprop_type[$op]}" =~ ^(string|enumeration)$ ]]; then
echo -n ", default:\"${bashopts_optprop_default[$op]//\"/\\\"}\""
else
echo -n ", default:${bashopts_optprop_default[$op]}"
fi
else
elts="- [\$$op] (type:${bashopts_optprop_type[$op]}, default:${bashopts_optprop_default[$op]})"
elts=")"
fi
if [ "${bashopts_optprop_type[$op]}" == "enumeration" ]; then
echo -n ", accepted values:$(
while read -r line; do
echo -n " '${line##*|}'"
done <<< "${bashopts_optprop_enum_values[$op]}"
)"
fi
else
elts="- [\$$op] (type:${bashopts_optprop_type[$op]})"
echo -n ")"
fi
fi
# display arguments, value if available, description, and additional info if available
printf " %-${optargs_max_len}s %s\n" "${optargs[$op]}" "${bashopts_optprop_description[$op]} $elts"
)"
done
test "$1" != "-e" || exit $2
}
......@@ -507,7 +575,7 @@ bashopts_parse_args() {
esac
;;
*)
bashopts_critical "Fatal error: args"
bashopts_log C "Fatal error: args"
;;
esac
done
......@@ -519,7 +587,7 @@ bashopts_parse_args() {
# display an array: [val1, val2, ...]
bashopts_dump_array() {
local type=$1
shift || bashopts_critical "Usage: bashopts_dump_array type elt1 [elt2...]"
shift || bashopts_log C "Usage: bashopts_dump_array type elt1 [elt2...]"
echo -n "["
if [ "$type" == "string" ]; then
echo -n "\"${1//\"/\\\"}\""
......@@ -538,11 +606,20 @@ bashopts_dump_array() {
echo -n "]"
}
bashopts_read_json_array() {
local line
while read -r line; do
eval "$1+=($line)"
done <<< "$(jq '.[]' <<< "$2")" && return 0 || \
bashopts_log E "Invalid JSON array"
return 1
}
# Process a specified option
bashopts_process_option() {
local dval tval ival op arg arglist check val_req edit_req
if ! arglist=$(getopt -o "n:k:r" -n "bashopts_process_option " -- "$@"); then
bashopts_critical "Usage bashopts_process_opt" \
bashopts_log C "Usage bashopts_process_opt" \
" -n <val> property name" \
" -k <val> override value check function" \
" -r At least one value required"
......@@ -557,11 +634,11 @@ bashopts_process_option() {
-k) check=$1; shift;;
-r) val_req="true";;
--) break;;
*) bashopts_critical "Fatal error";;
*) bashopts_log C "Fatal error";;
esac
done
test -n "$op" || \
bashopts_critical "bashopts_process_option: missing -n option"
bashopts_log C "bashopts_process_option: missing -n option"
if [ -z "$check" ]; then
check="${bashopts_optprop_check[$op]}"
fi
......@@ -585,7 +662,7 @@ bashopts_process_option() {
# Edition no more really required if already defined
edit_req="false"
elif [ "${bashopts_optprop_setting[$op]}" == "true" ] \
&& [ -f "$(readlink -f $bashopts_tool_settings_path)" ] \
&& [ -f "$(readlink -m "$bashopts_tool_settings_path")" ] \
&& grep -E -q "^$op=" $bashopts_tool_settings_path; then
eval "tval=$(grep -E "^${op}=" $bashopts_tool_settings_path | sed -E "s/^[^=]+=//g")"
fi
......@@ -594,17 +671,17 @@ bashopts_process_option() {
for (( i=0; i<${#tval[@]}; i++)); do
if ! $check "${tval[$i]}" > /dev/null; then
if [ "$BASHOPTS_INTERACTIVE" != "true" ]; then
bashopts_critical "Non interactive mode: Exit due to one or more error"
bashopts_log C "Non interactive mode: Exit due to one or more error"
fi
# (re)enable edition
edit_req="true"
break
fi
done
elif [ "$val_req" == "true" ]; then
bashopts_error "At least one value required"
elif [ "$val_req" == "true" ] && [ "$__BASHOPTS_DISPLAY_HELP__" != "true" ]; then
bashopts_log E "At least one value required"
if [ "$BASHOPTS_INTERACTIVE" != "true" ]; then
bashopts_critical "Non interactive mode: Exit due to one or more error"
bashopts_log C "Non interactive mode: Exit due to one or more error"
fi
# (re)enable edition
edit_req="true"
......@@ -618,25 +695,52 @@ bashopts_process_option() {
if [ "$BASHOPTS_INTERACTIVE" == "true" ]; then
# interactive edition
while true; do
echo "* ${bashopts_optprop_description[$op]}"
# Display the property description
echo "* ${bashopts_optprop_description[$op]}$(
# Add possible value list for enumeration type
if [ "${bashopts_optprop_type[$op]}" == "enumeration" ]; then
echo -n " (accepted values:$(
while read -r line; do
echo -n " '${line##*|}'"
done <<< "${bashopts_optprop_enum_values[$op]}"
)"
echo -n ")"
fi
)"
# Add info for array properties
if [ "${bashopts_optprop_method[$op]}" == "add" ]; then
echo " -> List property format: 'single val.' or BASH array '(v1 v2 v3)' or JSON array '[v1, v2, v3]'"
fi
echo -n " $(bashopts_dump_array {bashopts_optprop_type[$op]} "${tval[@]}"): "
read ival || return 1
read ival || bashopts_log C "Unexpected error, aborting..."
if [ -n "$ival" ]; then
if [ "${bashopts_optprop_method[$op]}" == "add" ]; then
# array value
if ! eval "tval=$ival" 2>/dev/null; then
bashopts_error "'$ival' must be written in BASH array format: '( \"val 1\" \"val2\" \"val3...\" )'"
unset tval
continue
fi
tval=()
case "${ival:0:1}" in
'[')
bashopts_read_json_array tval "$ival" || continue
;;
'(')
if ! eval "tval=$ival" 2>/dev/null; then
bashopts_log E "Invalid BASH array"
continue
fi
;;
*)
tval+=("$ival")
;;
esac
else
# non array/normal value
tval=$ival
fi
elif [ "${bashopts_optprop_method[$op]}" == "add" ] && ! [[ -v tval ]]; then
tval=()
fi
# check format
if [ "${#tval[@]}" -eq 0 ] && [ "$val_req" == "true" ]; then
bashopts_error "At least one value required"
bashopts_log E "At least one value required"
unset tval
continue
fi
......@@ -654,7 +758,7 @@ bashopts_process_option() {
unset tval
fi
fi
if [[ -v tval ]]; then
if declare -p tval > /dev/null 2>&1; then
# edit OK, break
break
fi
......@@ -682,9 +786,9 @@ bashopts_process_option() {
# force_write is true
echo "$(bashopts_get_def_full $op)" >> $bashopts_tool_settings_path
fi
) || bashopts_warning "Please check the settings file"
) || bashopts_log W "Please check the settings file"
else
bashopts_warning "No settings file specified"
bashopts_log W "No settings file specified"
fi
fi
if [ "$op" == "BASHOPTS_NON_INTERACTIVE" ] && ! [[ -v BASHOPTS_INTERACTIVE ]]; then
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment