Loading README.md +15 −13 Original line number Diff line number Diff line Loading @@ -8,7 +8,7 @@ There are mainly 5 steps: - Setup bashopts with ```bashopts_setup``` function - Declare options/arguments with ```bashopts_declare``` (one call per option) - Parse arguments with ```bashopts_parse_args``` - Process arguments with ```bashopts_process_args``` - Process arguments with ```bashopts_process_opts``` ### Load the library Load all the function from the ```bashopts.sh``` file and you can add the line ```trap 'bashopts_exit_handle' ERR``` to display backtrace on error Loading @@ -26,7 +26,9 @@ bashopts_setup option list: - ```-d <val>```: Tool description - ```-u <val>```: Tool usage (optional) - ```-s <val>```: Define a setting file path (optional) - ```-f```: Force edition for interactive options (default: a prompt is displayed only if not given via the command line) - ```-y```: Set non interactive mode as the default mode (optional) - ```-x```: Disable entirely interactive mode (optional) - ```-p```: Force value storage even if the value is equal to the default one (optional) Code example: ```bash Loading Loading @@ -71,7 +73,7 @@ bashopts_parse_args "$@" ``` ### Argument processing Process all the options with ```bashopts_process_args``` Process all the options with ```bashopts_process_opts``` This function will fill all options values using the following order: - Value from the command line Loading @@ -81,9 +83,9 @@ This function will fill all options values using the following order: - The default option value (may be defined with the ```bashopts_declare -v```) bashopts_process_args usage: bashopts_process_opts usage: ```bash bashopts_process_args bashopts_process_opts ``` ### Option exploitation Loading @@ -105,20 +107,20 @@ bashopts_process_args trap 'bashopts_exit_handle' ERR # Initialize the library bashopts_setup -n "$0" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc" bashopts_setup -n "example.sh" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc" # Declare the options bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\$first_name \$last_name" bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\"\$first_name \$last_name\"" bashopts_declare -n age -l number -d "Age" -t number bashopts_declare -n email_list -t string -m add -l email -d "Email adress" # Parse arguments bashopts_parse_args "$@" # Process argument bashopts_process_args # Process options bashopts_process_opts # Display the values echo Loading @@ -139,17 +141,17 @@ $ ./example.sh --help Output: ``` NAME: ./example.sh - This is myapp tool description displayed on help message example.sh - This is myapp tool description displayed on help message USAGE: [options and commands] [-- [extra args]] example.sh [options and commands] [-- [extra args]] OPTIONS: -h,--help Display this help -n,--non-interactive true Non interactive mode - [$bashopts_non_interactive] (type:boolean, default:false) -n,--non-interactive false Non interactive mode - [$bashopts_non_interactive] (type:boolean, default:false) -f,--first "John" First name - [$first_name] (type:string, default:"") -l,--last "Smith" Last name - [$last_name] (type:string, default:"") --display-name "John Smith" Display name - [$display_name] (type:string, default:"$first_name $last_name") --display-name "John Smith" Display name - [$display_name] (type:string, default:"\"$first_name $last_name\"") --number 0 Age - [$age] (type:number, default:0) --email Email adress - [$email_list] (type:string, default:"") ``` Loading bashopts.sh +218 −153 Original line number Diff line number Diff line Loading @@ -44,8 +44,8 @@ trap 'bashopts_exit_handle' ERR # expansions and subshells set -o errtrace # display a error (fatal) error bashopts_error() { # display a error (fatal) bashopts_critical() { local l >&2 echo -n "[ERROR] " for l in "$@"; do Loading @@ -54,7 +54,16 @@ bashopts_error() { exit 1 } # display a warning (non fatal) error # display a error (non fatal) bashopts_error() { local l >&2 echo -n "[ERROR] " for l in "$@"; do >&2 echo -e "$l" done } # display a warning (non fatal) bashopts_warning() { local l >&2 echo -n "[WARNING] " Loading @@ -67,16 +76,6 @@ bashopts_regex_escape () { echo $1 | sed 's/[][()\.^$\/?*+]/\\&/g' } # check if the value is a boolean bashopts_isboolean() { [[ $1 =~ ^(true|false)$ ]] || return 1 } # check if the value is a number bashopts_isnumber() { [[ $1 =~ ^-?[0-9]+([.][0-9]+)?$ ]] || return 1 } # extract the value part of a declaration ("the value") bashopts_get_def() { declare | grep "^$1=" | sed -E 's/^[^=]+=//g' Loading @@ -92,43 +91,60 @@ bashopts_get_def_full() { } # check and format an option name value bashopts_chkvarname() { if [[ $1 =~ ^[a-zA-Z0-9_]+$ ]]; then bashopts_check_opt_name() { if [[ "$1" =~ ^[a-zA-Z0-9_]+$ ]]; then echo $1 return 0 fi bashopts_error "$1 is not a valid variable name" bashopts_error "'$1' is not a valid variable name" return 1 } # check and format a number value bashopts_chknum() { bashopts_check_number() { if [ -z "$1" ]; then echo 0 return 0 elif [[ $1 =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then elif [[ "$1" =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then echo $1 return 0 fi bashopts_error "$1 is not a valid number" bashopts_error "'$1' is not a valid number" return 1 } # check and format a boolean value bashopts_chkbool() { bashopts_check_boolean() { case "$1" in ''|f|false|F|FALSE|0) ''|f|false|F|FALSE|False|0) echo "false" return 0 ;; t|true|T|TRUE|1) t|true|T|TRUE|True|1) echo "true" return 0 ;; *) bashopts_error "$1 is not a valid boolean value" bashopts_error "'$1' is not a valid boolean value" return 1 ;; esac } # check and format a string value bashopts_check_string() { echo "$1" return 0 } # check nothing bashopts_check_nothing() { echo "$1" return 0 } # declare the options property arrays for f in name default expression short_opt long_opt description type method setting interactive req_value; do for f in name default expression short_opt long_opt description type method check setting interactive req_value; do eval declare -x -A bashopts_optprop_$f done Loading @@ -147,7 +163,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_error "Usage bashopts_setup:" \ bashopts_critical "Usage bashopts_setup:" \ " -n <val> Tool name" \ " -d <val> Tool description" \ " -u <val> Tool usage description" \ Loading @@ -155,7 +171,6 @@ bashopts_setup() { " -y Set non interactive mode as the default mode" \ " -x Disable entirely interactive mode" \ " -p Force value storage even if the value is equal to the default one" return 1 fi eval set -- "$arglist"; # Store the global bashopts properties Loading @@ -171,14 +186,14 @@ bashopts_setup() { -x) disable_interactive="true";; -p) bashopts_tool_settings_force_write="true";; --) break;; *) bashopts_error "Fatal error";; *) bashopts_critical "Fatal error";; esac done if [ -z "$bashopts_tool_name" ]; then bashopts_error "Undefined tool name" bashopts_critical "Undefined tool name" fi if [ -z "$bashopts_tool_description" ]; then bashopts_error "Undefined tool description" bashopts_critical "Undefined tool description" fi bashopts_tool_usage=${bashopts_tool_usage:-"$bashopts_tool_name [options and commands] [-- [extra args]]"} # add the default options Loading @@ -197,8 +212,8 @@ bashopts_setup() { # STEP 2: add options bashopts_declare() { local arg arglist options if ! arglist=$(getopt -o "n:v:e:o:l:d:t:m:rsi" -n "$0 " -- "$@"); then bashopts_error "Usage bashopts_declare:" \ if ! arglist=$(getopt -o "n:v:e:o:l:d:t:m:k:rsi" -n "$0 " -- "$@"); then bashopts_critical "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" \ Loading @@ -207,10 +222,10 @@ bashopts_declare() { " -d <val> Description" \ " -t <val> Value type: string (default), number, boolean" \ " -m <val> Method: set (DEFAULT: simple value), add (list with several values)" \ " -k <val> Custom check method (bash function)" \ " -r Value required" \ " -i Enable interactive edition" \ " -s Store in setting" return 1 fi eval set -- "$arglist"; declare -A options Loading @@ -219,7 +234,7 @@ bashopts_declare() { arg=$1 shift case "$arg" in -n) options[name]=$(bashopts_chkvarname $1); shift;; -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;; Loading @@ -227,18 +242,18 @@ bashopts_declare() { -d) options[description]=$1; shift;; -t) options[type]=$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_error "Fatal error";; *) bashopts_critical "Fatal error";; esac done # format the type and check/format the default value case "${options[type]}" in ''|bool|boolean) options[type]="boolean" options[default]=$(bashopts_chkbool ${options[default]}) ;; str|string) options[type]="string" Loading @@ -247,12 +262,17 @@ bashopts_declare() { num|number) options[type]="number" options[req_value]="true" options[default]=$(bashopts_chknum ${options[default]}) ;; *) bashopts_error "Invalid type ${options[type]}" bashopts_critical "Invalid type ${options[type]}" ;; esac # Setup check value method if ! [[ -v options[check] ]]; then options[check]="bashopts_check_${options[type]}" fi # Check the default value format options[default]="$(${options[check]} "${options[default]}")" || exit 1 # format the option method case "${options[method]}" in ''|s|set) Loading @@ -264,30 +284,30 @@ bashopts_declare() { options[method]="add" ;; *) bashopts_error "Invalid method ${options[method]}" bashopts_critical "Invalid method ${options[method]}" ;; esac # Check option name if [[ -v bashopts_optprop_name[${options[name]}] ]]; then bashopts_error "Dupplicate option name '${options[name]}'" bashopts_critical "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_error "Invalid short option ${options[short_opt]}" bashopts_critical "Invalid short option ${options[short_opt]}" fi if [[ -v bashopts_arg2op[-${options[short_opt]}] ]]; then bashopts_error "Dupplicate short option '${options[short_opt]}'" bashopts_critical "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_error "Invalid long option ${options[long_opt]}" bashopts_critical "Invalid long option ${options[long_opt]}" fi if [[ -v bashopts_arg2op[--${options[long_opt]}] ]]; then bashopts_error "Dupplicate long option '${options[long_opt]}'" bashopts_critical "Dupplicate long option '${options[long_opt]}'" fi bashopts_arg2op[--${options[long_opt]}]=${options[name]} fi Loading @@ -311,14 +331,14 @@ bashopts_math_min() { # join array element bashopts_join_by() { local IFS="$1" shift || bashopts_error "Usage: bashopts_join_by <character> [elt1 [elt2...]]" shift || bashopts_critical "Usage: bashopts_join_by <character> [elt1 [elt2...]]" echo "$*" } # dump an option value by its name bashopts_dump_value() { local op=$1 shift || bashopts_error "Usage: bashopts_dump_value op_name" shift || bashopts_critical "Usage: bashopts_dump_value op_name" [[ -v "$op" ]] || return 0 if [ "${bashopts_optprop_method[$op]}" == "set" ]; then if [ "${bashopts_optprop_type[$op]}" == "string" ]; then Loading Loading @@ -460,28 +480,17 @@ bashopts_parse_args() { val="$1" shift op=${bashopts_arg2op[$arg]} if [ -z "$val" ] && [ "${bashopts_optprop_type[$op]}" != "boolean" ]; then if [ -z "$val" ]; then if [ "${bashopts_optprop_type[$op]}" == "boolean" ]; then # boolean argument with no value is considered as true val="true" else # empty value tell to unset the value or clear the array unset $op continue fi case "${bashopts_optprop_type[$op]}" in boolean) if [ -z "$val" ]; then # boolean argument with no value is considered as true val="true" fi # check and format boolean value val=$(bashopts_chkbool $val) ;; string) # nothing to do ;; number) # check and format number value val=$(bashopts_chknum $val) ;; esac val="$(${bashopts_optprop_check[$op]} "$val")" || exit 1 case "${bashopts_optprop_method[$op]}" in set) # normal case: override the value Loading @@ -494,7 +503,7 @@ bashopts_parse_args() { esac ;; *) bashopts_error "Fatal error: args" bashopts_critical "Fatal error: args" ;; esac done Loading @@ -506,7 +515,7 @@ bashopts_parse_args() { # display an array: [val1, val2, ...] bashopts_dump_array() { local type=$1 shift || bashopts_error "Usage: bashopts_dump_array type elt1 [elt2...]" shift || bashopts_critical "Usage: bashopts_dump_array type elt1 [elt2...]" echo -n "[" if [ "$type" == "string" ]; then echo -n "\"${1//\"/\\\"}\"" Loading @@ -525,76 +534,117 @@ bashopts_dump_array() { echo -n "]" } # STEP 4: process arg bashopts_process_args() { local dval ival if [ "$__bashopts_display_help__" == "true" ]; then bashopts_interactive="false" 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" \ " -n <val> property name" \ " -k <val> override value check function" \ " -r At least one value required" fi for op in "${bashopts_optlist[@]}"; do unset dval eval set -- "$arglist"; # parse all the parameters while true; do arg=$1 shift case "$arg" in -n) op=$1; shift;; -k) check=$1; shift;; -r) val_req="true";; --) break;; *) bashopts_critical "Fatal error";; esac done test -n "$op" || \ bashopts_critical "bashopts_process_option: missing -n option" if [ -z "$check" ]; then check="${bashopts_optprop_check[$op]}" fi # eval or get default value if [[ -v bashopts_optprop_expression[$op] ]]; then eval "dval=${bashopts_optprop_expression[$op]}" elif [ "${bashopts_optprop_method[$op]}" == "add" ]; then dval=() else dval=${bashopts_optprop_default[$op]} fi if ! [[ -v $op ]]; then unset tval # read the value from file if ! [[ -v $op ]] && [ "${bashopts_optprop_setting[$op]}" == "true" ] \ dval="${bashopts_optprop_default[$op]}" fi # Init edit_req edit_req=${bashopts_optprop_interactive[$op]} if [[ -v $op ]]; then # Extract value from option name eval "tval=$(bashopts_get_def $op)" # Edition no more really required if already defined edit_req="false" elif [ "${bashopts_optprop_setting[$op]}" == "true" ] \ && [ -f "$(readlink -f $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 if [[ -v tval ]]; then # Check current value(s) 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" fi # (re)enable edition edit_req="true" break fi done elif [ "$val_req" == "true" ]; then bashopts_error "At least one value required" if [ "$bashopts_interactive" != "true" ]; then bashopts_critical "Non interactive mode: Exit due to one or more error" fi # (re)enable edition edit_req="true" fi if ! [[ -v tval ]] || [ "$edit_req" == "true" ]; then if [[ ! -v tval ]] && [ -n "$dval" ]; then # set default value eval "tval=$(bashopts_get_def dval)" fi if [ "${bashopts_optprop_interactive[$op]}" == "true" ] && [ "$bashopts_interactive" == "true" ]; then if [ "$edit_req" == "true" ]; then if [ "$bashopts_interactive" == "true" ]; then # interactive edition while true; do echo "* ${bashopts_optprop_description[$op]}" echo -n " $(bashopts_dump_array {bashopts_optprop_type[$op]} "${tval[@]}"): " read ival if [ -z "$ival" ]; then # keep the previous/default value break; fi read ival || return 1 if [ -n "$ival" ]; then if [ "${bashopts_optprop_method[$op]}" == "add" ]; then # array value if ! eval "tval=$ival" 2>/dev/null; then echo "'$ival' is not a valid BASH array (format: '( \"val 1\" \"val2\" \"val3...\" )')" bashopts_error "'$ival' must be written in BASH array format: '( \"val 1\" \"val2\" \"val3...\" )'" unset tval continue fi else # non array/norman value # non array/normal value tval=$ival fi # check boolean/number (array or not) case "${bashopts_optprop_type[$op]}" in boolean) for v in "${tval[@]}"; do if ! bashopts_isboolean $v; then echo "'$v' is not a valid boolean value" fi # check format if [ "${#tval[@]}" -eq 0 ] && [ "$val_req" == "true" ]; then bashopts_error "At least one value required" fi if [ "${bashopts_optprop_method[$op]}" == "add" ]; then # array value for (( i=0; i<${#tval[@]}; i++)); do if ! tval[$i]="$($check "${tval[$i]}")"; then unset tval break fi done ;; number) for v in "${tval[@]}"; do if ! bashopts_isnumber $v; then echo "'$v' is not a valid number value" else # non array/normal value if ! tval="$($check "$tval")"; then unset tval break fi done ;; esac fi if [[ -v tval ]]; then # edit OK, break break Loading @@ -602,10 +652,11 @@ bashopts_process_args() { # otherwise, loop... done fi fi fi if [[ -v tval ]]; then eval "$op=$(bashopts_get_def tval)" fi fi if [ "${bashopts_optprop_setting[$op]}" == "true" ]; then if [ -n "$bashopts_tool_settings_path" ]; then # vrite the value to the setting file Loading @@ -632,9 +683,23 @@ bashopts_process_args() { bashopts_interactive="true" fi fi } # STEP 4: process arg bashopts_process_opts() { local op if [ "$__bashopts_display_help__" == "true" ]; then bashopts_interactive="false" fi for op in "${bashopts_optlist[@]}"; do bashopts_process_option -n $op done if [ "$__bashopts_display_help__" == "true" ]; then bashopts_diplay_help exit 0 fi } bashopts_process_args() { bashopts_process_opts "$@" } example.sh +3 −3 Original line number Diff line number Diff line Loading @@ -12,15 +12,15 @@ bashopts_setup -n "example.sh" -d "This is myapp tool description displayed on h # Declare the options bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\$first_name \$last_name" bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\"\$first_name \$last_name\"" bashopts_declare -n age -l number -d "Age" -t number bashopts_declare -n email_list -t string -m add -l email -d "Email adress" # Parse arguments bashopts_parse_args "$@" # Process argument bashopts_process_args # Process options bashopts_process_opts # Display the values echo Loading test.sh +110 −19 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
README.md +15 −13 Original line number Diff line number Diff line Loading @@ -8,7 +8,7 @@ There are mainly 5 steps: - Setup bashopts with ```bashopts_setup``` function - Declare options/arguments with ```bashopts_declare``` (one call per option) - Parse arguments with ```bashopts_parse_args``` - Process arguments with ```bashopts_process_args``` - Process arguments with ```bashopts_process_opts``` ### Load the library Load all the function from the ```bashopts.sh``` file and you can add the line ```trap 'bashopts_exit_handle' ERR``` to display backtrace on error Loading @@ -26,7 +26,9 @@ bashopts_setup option list: - ```-d <val>```: Tool description - ```-u <val>```: Tool usage (optional) - ```-s <val>```: Define a setting file path (optional) - ```-f```: Force edition for interactive options (default: a prompt is displayed only if not given via the command line) - ```-y```: Set non interactive mode as the default mode (optional) - ```-x```: Disable entirely interactive mode (optional) - ```-p```: Force value storage even if the value is equal to the default one (optional) Code example: ```bash Loading Loading @@ -71,7 +73,7 @@ bashopts_parse_args "$@" ``` ### Argument processing Process all the options with ```bashopts_process_args``` Process all the options with ```bashopts_process_opts``` This function will fill all options values using the following order: - Value from the command line Loading @@ -81,9 +83,9 @@ This function will fill all options values using the following order: - The default option value (may be defined with the ```bashopts_declare -v```) bashopts_process_args usage: bashopts_process_opts usage: ```bash bashopts_process_args bashopts_process_opts ``` ### Option exploitation Loading @@ -105,20 +107,20 @@ bashopts_process_args trap 'bashopts_exit_handle' ERR # Initialize the library bashopts_setup -n "$0" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc" bashopts_setup -n "example.sh" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc" # Declare the options bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\$first_name \$last_name" bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\"\$first_name \$last_name\"" bashopts_declare -n age -l number -d "Age" -t number bashopts_declare -n email_list -t string -m add -l email -d "Email adress" # Parse arguments bashopts_parse_args "$@" # Process argument bashopts_process_args # Process options bashopts_process_opts # Display the values echo Loading @@ -139,17 +141,17 @@ $ ./example.sh --help Output: ``` NAME: ./example.sh - This is myapp tool description displayed on help message example.sh - This is myapp tool description displayed on help message USAGE: [options and commands] [-- [extra args]] example.sh [options and commands] [-- [extra args]] OPTIONS: -h,--help Display this help -n,--non-interactive true Non interactive mode - [$bashopts_non_interactive] (type:boolean, default:false) -n,--non-interactive false Non interactive mode - [$bashopts_non_interactive] (type:boolean, default:false) -f,--first "John" First name - [$first_name] (type:string, default:"") -l,--last "Smith" Last name - [$last_name] (type:string, default:"") --display-name "John Smith" Display name - [$display_name] (type:string, default:"$first_name $last_name") --display-name "John Smith" Display name - [$display_name] (type:string, default:"\"$first_name $last_name\"") --number 0 Age - [$age] (type:number, default:0) --email Email adress - [$email_list] (type:string, default:"") ``` Loading
bashopts.sh +218 −153 Original line number Diff line number Diff line Loading @@ -44,8 +44,8 @@ trap 'bashopts_exit_handle' ERR # expansions and subshells set -o errtrace # display a error (fatal) error bashopts_error() { # display a error (fatal) bashopts_critical() { local l >&2 echo -n "[ERROR] " for l in "$@"; do Loading @@ -54,7 +54,16 @@ bashopts_error() { exit 1 } # display a warning (non fatal) error # display a error (non fatal) bashopts_error() { local l >&2 echo -n "[ERROR] " for l in "$@"; do >&2 echo -e "$l" done } # display a warning (non fatal) bashopts_warning() { local l >&2 echo -n "[WARNING] " Loading @@ -67,16 +76,6 @@ bashopts_regex_escape () { echo $1 | sed 's/[][()\.^$\/?*+]/\\&/g' } # check if the value is a boolean bashopts_isboolean() { [[ $1 =~ ^(true|false)$ ]] || return 1 } # check if the value is a number bashopts_isnumber() { [[ $1 =~ ^-?[0-9]+([.][0-9]+)?$ ]] || return 1 } # extract the value part of a declaration ("the value") bashopts_get_def() { declare | grep "^$1=" | sed -E 's/^[^=]+=//g' Loading @@ -92,43 +91,60 @@ bashopts_get_def_full() { } # check and format an option name value bashopts_chkvarname() { if [[ $1 =~ ^[a-zA-Z0-9_]+$ ]]; then bashopts_check_opt_name() { if [[ "$1" =~ ^[a-zA-Z0-9_]+$ ]]; then echo $1 return 0 fi bashopts_error "$1 is not a valid variable name" bashopts_error "'$1' is not a valid variable name" return 1 } # check and format a number value bashopts_chknum() { bashopts_check_number() { if [ -z "$1" ]; then echo 0 return 0 elif [[ $1 =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then elif [[ "$1" =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then echo $1 return 0 fi bashopts_error "$1 is not a valid number" bashopts_error "'$1' is not a valid number" return 1 } # check and format a boolean value bashopts_chkbool() { bashopts_check_boolean() { case "$1" in ''|f|false|F|FALSE|0) ''|f|false|F|FALSE|False|0) echo "false" return 0 ;; t|true|T|TRUE|1) t|true|T|TRUE|True|1) echo "true" return 0 ;; *) bashopts_error "$1 is not a valid boolean value" bashopts_error "'$1' is not a valid boolean value" return 1 ;; esac } # check and format a string value bashopts_check_string() { echo "$1" return 0 } # check nothing bashopts_check_nothing() { echo "$1" return 0 } # declare the options property arrays for f in name default expression short_opt long_opt description type method setting interactive req_value; do for f in name default expression short_opt long_opt description type method check setting interactive req_value; do eval declare -x -A bashopts_optprop_$f done Loading @@ -147,7 +163,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_error "Usage bashopts_setup:" \ bashopts_critical "Usage bashopts_setup:" \ " -n <val> Tool name" \ " -d <val> Tool description" \ " -u <val> Tool usage description" \ Loading @@ -155,7 +171,6 @@ bashopts_setup() { " -y Set non interactive mode as the default mode" \ " -x Disable entirely interactive mode" \ " -p Force value storage even if the value is equal to the default one" return 1 fi eval set -- "$arglist"; # Store the global bashopts properties Loading @@ -171,14 +186,14 @@ bashopts_setup() { -x) disable_interactive="true";; -p) bashopts_tool_settings_force_write="true";; --) break;; *) bashopts_error "Fatal error";; *) bashopts_critical "Fatal error";; esac done if [ -z "$bashopts_tool_name" ]; then bashopts_error "Undefined tool name" bashopts_critical "Undefined tool name" fi if [ -z "$bashopts_tool_description" ]; then bashopts_error "Undefined tool description" bashopts_critical "Undefined tool description" fi bashopts_tool_usage=${bashopts_tool_usage:-"$bashopts_tool_name [options and commands] [-- [extra args]]"} # add the default options Loading @@ -197,8 +212,8 @@ bashopts_setup() { # STEP 2: add options bashopts_declare() { local arg arglist options if ! arglist=$(getopt -o "n:v:e:o:l:d:t:m:rsi" -n "$0 " -- "$@"); then bashopts_error "Usage bashopts_declare:" \ if ! arglist=$(getopt -o "n:v:e:o:l:d:t:m:k:rsi" -n "$0 " -- "$@"); then bashopts_critical "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" \ Loading @@ -207,10 +222,10 @@ bashopts_declare() { " -d <val> Description" \ " -t <val> Value type: string (default), number, boolean" \ " -m <val> Method: set (DEFAULT: simple value), add (list with several values)" \ " -k <val> Custom check method (bash function)" \ " -r Value required" \ " -i Enable interactive edition" \ " -s Store in setting" return 1 fi eval set -- "$arglist"; declare -A options Loading @@ -219,7 +234,7 @@ bashopts_declare() { arg=$1 shift case "$arg" in -n) options[name]=$(bashopts_chkvarname $1); shift;; -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;; Loading @@ -227,18 +242,18 @@ bashopts_declare() { -d) options[description]=$1; shift;; -t) options[type]=$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_error "Fatal error";; *) bashopts_critical "Fatal error";; esac done # format the type and check/format the default value case "${options[type]}" in ''|bool|boolean) options[type]="boolean" options[default]=$(bashopts_chkbool ${options[default]}) ;; str|string) options[type]="string" Loading @@ -247,12 +262,17 @@ bashopts_declare() { num|number) options[type]="number" options[req_value]="true" options[default]=$(bashopts_chknum ${options[default]}) ;; *) bashopts_error "Invalid type ${options[type]}" bashopts_critical "Invalid type ${options[type]}" ;; esac # Setup check value method if ! [[ -v options[check] ]]; then options[check]="bashopts_check_${options[type]}" fi # Check the default value format options[default]="$(${options[check]} "${options[default]}")" || exit 1 # format the option method case "${options[method]}" in ''|s|set) Loading @@ -264,30 +284,30 @@ bashopts_declare() { options[method]="add" ;; *) bashopts_error "Invalid method ${options[method]}" bashopts_critical "Invalid method ${options[method]}" ;; esac # Check option name if [[ -v bashopts_optprop_name[${options[name]}] ]]; then bashopts_error "Dupplicate option name '${options[name]}'" bashopts_critical "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_error "Invalid short option ${options[short_opt]}" bashopts_critical "Invalid short option ${options[short_opt]}" fi if [[ -v bashopts_arg2op[-${options[short_opt]}] ]]; then bashopts_error "Dupplicate short option '${options[short_opt]}'" bashopts_critical "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_error "Invalid long option ${options[long_opt]}" bashopts_critical "Invalid long option ${options[long_opt]}" fi if [[ -v bashopts_arg2op[--${options[long_opt]}] ]]; then bashopts_error "Dupplicate long option '${options[long_opt]}'" bashopts_critical "Dupplicate long option '${options[long_opt]}'" fi bashopts_arg2op[--${options[long_opt]}]=${options[name]} fi Loading @@ -311,14 +331,14 @@ bashopts_math_min() { # join array element bashopts_join_by() { local IFS="$1" shift || bashopts_error "Usage: bashopts_join_by <character> [elt1 [elt2...]]" shift || bashopts_critical "Usage: bashopts_join_by <character> [elt1 [elt2...]]" echo "$*" } # dump an option value by its name bashopts_dump_value() { local op=$1 shift || bashopts_error "Usage: bashopts_dump_value op_name" shift || bashopts_critical "Usage: bashopts_dump_value op_name" [[ -v "$op" ]] || return 0 if [ "${bashopts_optprop_method[$op]}" == "set" ]; then if [ "${bashopts_optprop_type[$op]}" == "string" ]; then Loading Loading @@ -460,28 +480,17 @@ bashopts_parse_args() { val="$1" shift op=${bashopts_arg2op[$arg]} if [ -z "$val" ] && [ "${bashopts_optprop_type[$op]}" != "boolean" ]; then if [ -z "$val" ]; then if [ "${bashopts_optprop_type[$op]}" == "boolean" ]; then # boolean argument with no value is considered as true val="true" else # empty value tell to unset the value or clear the array unset $op continue fi case "${bashopts_optprop_type[$op]}" in boolean) if [ -z "$val" ]; then # boolean argument with no value is considered as true val="true" fi # check and format boolean value val=$(bashopts_chkbool $val) ;; string) # nothing to do ;; number) # check and format number value val=$(bashopts_chknum $val) ;; esac val="$(${bashopts_optprop_check[$op]} "$val")" || exit 1 case "${bashopts_optprop_method[$op]}" in set) # normal case: override the value Loading @@ -494,7 +503,7 @@ bashopts_parse_args() { esac ;; *) bashopts_error "Fatal error: args" bashopts_critical "Fatal error: args" ;; esac done Loading @@ -506,7 +515,7 @@ bashopts_parse_args() { # display an array: [val1, val2, ...] bashopts_dump_array() { local type=$1 shift || bashopts_error "Usage: bashopts_dump_array type elt1 [elt2...]" shift || bashopts_critical "Usage: bashopts_dump_array type elt1 [elt2...]" echo -n "[" if [ "$type" == "string" ]; then echo -n "\"${1//\"/\\\"}\"" Loading @@ -525,76 +534,117 @@ bashopts_dump_array() { echo -n "]" } # STEP 4: process arg bashopts_process_args() { local dval ival if [ "$__bashopts_display_help__" == "true" ]; then bashopts_interactive="false" 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" \ " -n <val> property name" \ " -k <val> override value check function" \ " -r At least one value required" fi for op in "${bashopts_optlist[@]}"; do unset dval eval set -- "$arglist"; # parse all the parameters while true; do arg=$1 shift case "$arg" in -n) op=$1; shift;; -k) check=$1; shift;; -r) val_req="true";; --) break;; *) bashopts_critical "Fatal error";; esac done test -n "$op" || \ bashopts_critical "bashopts_process_option: missing -n option" if [ -z "$check" ]; then check="${bashopts_optprop_check[$op]}" fi # eval or get default value if [[ -v bashopts_optprop_expression[$op] ]]; then eval "dval=${bashopts_optprop_expression[$op]}" elif [ "${bashopts_optprop_method[$op]}" == "add" ]; then dval=() else dval=${bashopts_optprop_default[$op]} fi if ! [[ -v $op ]]; then unset tval # read the value from file if ! [[ -v $op ]] && [ "${bashopts_optprop_setting[$op]}" == "true" ] \ dval="${bashopts_optprop_default[$op]}" fi # Init edit_req edit_req=${bashopts_optprop_interactive[$op]} if [[ -v $op ]]; then # Extract value from option name eval "tval=$(bashopts_get_def $op)" # Edition no more really required if already defined edit_req="false" elif [ "${bashopts_optprop_setting[$op]}" == "true" ] \ && [ -f "$(readlink -f $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 if [[ -v tval ]]; then # Check current value(s) 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" fi # (re)enable edition edit_req="true" break fi done elif [ "$val_req" == "true" ]; then bashopts_error "At least one value required" if [ "$bashopts_interactive" != "true" ]; then bashopts_critical "Non interactive mode: Exit due to one or more error" fi # (re)enable edition edit_req="true" fi if ! [[ -v tval ]] || [ "$edit_req" == "true" ]; then if [[ ! -v tval ]] && [ -n "$dval" ]; then # set default value eval "tval=$(bashopts_get_def dval)" fi if [ "${bashopts_optprop_interactive[$op]}" == "true" ] && [ "$bashopts_interactive" == "true" ]; then if [ "$edit_req" == "true" ]; then if [ "$bashopts_interactive" == "true" ]; then # interactive edition while true; do echo "* ${bashopts_optprop_description[$op]}" echo -n " $(bashopts_dump_array {bashopts_optprop_type[$op]} "${tval[@]}"): " read ival if [ -z "$ival" ]; then # keep the previous/default value break; fi read ival || return 1 if [ -n "$ival" ]; then if [ "${bashopts_optprop_method[$op]}" == "add" ]; then # array value if ! eval "tval=$ival" 2>/dev/null; then echo "'$ival' is not a valid BASH array (format: '( \"val 1\" \"val2\" \"val3...\" )')" bashopts_error "'$ival' must be written in BASH array format: '( \"val 1\" \"val2\" \"val3...\" )'" unset tval continue fi else # non array/norman value # non array/normal value tval=$ival fi # check boolean/number (array or not) case "${bashopts_optprop_type[$op]}" in boolean) for v in "${tval[@]}"; do if ! bashopts_isboolean $v; then echo "'$v' is not a valid boolean value" fi # check format if [ "${#tval[@]}" -eq 0 ] && [ "$val_req" == "true" ]; then bashopts_error "At least one value required" fi if [ "${bashopts_optprop_method[$op]}" == "add" ]; then # array value for (( i=0; i<${#tval[@]}; i++)); do if ! tval[$i]="$($check "${tval[$i]}")"; then unset tval break fi done ;; number) for v in "${tval[@]}"; do if ! bashopts_isnumber $v; then echo "'$v' is not a valid number value" else # non array/normal value if ! tval="$($check "$tval")"; then unset tval break fi done ;; esac fi if [[ -v tval ]]; then # edit OK, break break Loading @@ -602,10 +652,11 @@ bashopts_process_args() { # otherwise, loop... done fi fi fi if [[ -v tval ]]; then eval "$op=$(bashopts_get_def tval)" fi fi if [ "${bashopts_optprop_setting[$op]}" == "true" ]; then if [ -n "$bashopts_tool_settings_path" ]; then # vrite the value to the setting file Loading @@ -632,9 +683,23 @@ bashopts_process_args() { bashopts_interactive="true" fi fi } # STEP 4: process arg bashopts_process_opts() { local op if [ "$__bashopts_display_help__" == "true" ]; then bashopts_interactive="false" fi for op in "${bashopts_optlist[@]}"; do bashopts_process_option -n $op done if [ "$__bashopts_display_help__" == "true" ]; then bashopts_diplay_help exit 0 fi } bashopts_process_args() { bashopts_process_opts "$@" }
example.sh +3 −3 Original line number Diff line number Diff line Loading @@ -12,15 +12,15 @@ bashopts_setup -n "example.sh" -d "This is myapp tool description displayed on h # Declare the options bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\$first_name \$last_name" bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\"\$first_name \$last_name\"" bashopts_declare -n age -l number -d "Age" -t number bashopts_declare -n email_list -t string -m add -l email -d "Email adress" # Parse arguments bashopts_parse_args "$@" # Process argument bashopts_process_args # Process options bashopts_process_opts # Display the values echo Loading