Commit f8698895 authored by Emeric Verschuur's avatar Emeric Verschuur
Browse files

add persistant settings and a lot of comments :)

parent 91872a73
Loading
Loading
Loading
Loading
Loading
+84 −19
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ trap 'bashopts_exit_handle' ERR
#  expansions and subshells
set -o errtrace

# display a error (fatal) error
bashopts_error() {
    local l
    >&2 echo -n "[ERROR] "
@@ -53,6 +54,7 @@ bashopts_error() {
    exit 1
}

# display a warning (non fatal) error
bashopts_warning() {
    local l
    >&2 echo -n "[WARNING] "
@@ -65,26 +67,31 @@ 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'
    # NOTE: alternative but not working in some case...:
    # declare -p $1 | sed -E "s/^declare\\s[^=]*=//g"
}

# extract the full declaration (name="the value")
bashopts_get_def_full() {
    declare | grep "^$1="
    # NOTE: alternative but not working in some case...:
    # declare -p $1 | sed -E "s/^declare\\s[^=]*=/$1=/g"
}

# check and format an option name value
bashopts_chkvarname() {
    if [[ $1 =~ ^[a-zA-Z0-9_]+$ ]]; then
        echo $1
@@ -93,6 +100,7 @@ bashopts_chkvarname() {
    bashopts_error "$1 is not a valid variable name"
}

# check and format a number value
bashopts_chknum() {
    if [ -z "$1" ]; then
        echo 0
@@ -104,6 +112,7 @@ bashopts_chknum() {
    bashopts_error "$1 is not a valid number"
}

# check and format a boolean value
bashopts_chkbool() {
    case "$1" in
        ''|f|false|F|FALSE|0)
@@ -118,29 +127,37 @@ bashopts_chkbool() {
    esac
}

# declare the options property arrays
for f in name default expression short_opt long_opt description type method setting interactive req_value; do
    eval declare -x -A bashopts_optprop_$f
done

declare -x -A bashopts_opt2name
# declare the associative array: arg name => option name
declare -x -A bashopts_arg2op
# option list in the declaration order
bashopts_optlist=()
# commands list (from global tool_name [args] [commands] [-- optional extra args])
bashopts_commands=()
# extra arguments list (from global tool_name [args] [commands] [-- optional extra args])
bashopts_extra_args=()
# tool name (from global tool_name [args] [commands] [-- optional extra args])
bashopts_tool_name=$0

# STEP 1: setup
bashopts_setup() {
    local arg arglist no_default_opts
    if ! arglist=$(getopt -o "n:d:u:s:f" -n "$0 " -- "$@"); then
    if ! arglist=$(getopt -o "n:d:u:s:fp" -n "$0 " -- "$@"); then
        bashopts_error "Usage bashopts_setup:" \
            "        -n <val>  Tool name" \
            "        -d <val>  Tool description" \
            "        -u <val>  Tool usage description" \
            "        -s <val>  setting file path" \
            "        -f        Force edition if ineractive options (default: only if not defined via the cmd line)"
            "        -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
    while true; do
        arg=$1
        shift
@@ -150,6 +167,7 @@ bashopts_setup() {
            -u) bashopts_tool_usage=$1;          shift;;
            -s) bashopts_tool_settings_path=$1;  shift;;
            -f) bashopts_tool_force_edit="true";;
            -p) bashopts_tool_settings_force_write="true";;
            --) break;;
            *)  bashopts_error "Fatal error";;
        esac
@@ -161,6 +179,7 @@ bashopts_setup() {
        bashopts_error "Undefined tool description"
    fi
    bashopts_tool_usage=${bashopts_tool_usage:-"$bashopts_tool_name [options and commands] [-- [extra args]]"}
    # add the default options
    if [ "$no_default_opts" != "true" ]; then
        bashopts_declare -n __bashopts_display_help__ -l help -o h -d "Display this help"
        bashopts_declare -n bashopts_non_interactive -l non-interactive -o n -d "Non interactive mode"
@@ -187,6 +206,7 @@ bashopts_declare() {
    fi
    eval set -- "$arglist";
    declare -A options
    # parse all the parameters
    while true; do
        arg=$1
        shift
@@ -206,6 +226,7 @@ bashopts_declare() {
            *)  bashopts_error "Fatal error";;
        esac
    done
    # format the type and check/format the default value
    case "${options[type]}" in
        ''|bool|boolean)
            options[type]="boolean"
@@ -224,58 +245,69 @@ bashopts_declare() {
            bashopts_error "Invalid type ${options[type]}"
            ;;
    esac
    # format the option method
    case "${options[method]}" in
        ''|s|set)
            # default: simple value - override
            options[method]="set"
            ;;
        a|add)
            # array value - add
            options[method]="add"
            ;;
        *)
            bashopts_error "Invalid method ${options[method]}"
            ;;
    esac
    # Check option name
    if [[ -v bashopts_optprop_name[${options[name]}] ]]; then
        bashopts_error "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]}"
        fi
        if [[ -v bashopts_opt2name[-${options[short_opt]}] ]]; then
        if [[ -v bashopts_arg2op[-${options[short_opt]}] ]]; then
            bashopts_error "Dupplicate short option '${options[short_opt]}'"
        fi
        bashopts_opt2name[-${options[short_opt]}]=${options[name]}
        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]}"
        fi
        if [[ -v bashopts_opt2name[--${options[long_opt]}] ]]; then
        if [[ -v bashopts_arg2op[--${options[long_opt]}] ]]; then
            bashopts_error "Dupplicate long option '${options[long_opt]}'"
        fi
        bashopts_opt2name[--${options[long_opt]}]=${options[name]}
        bashopts_arg2op[--${options[long_opt]}]=${options[name]}
    fi
    # store the option properties
    for f in ${!options[@]}; do
        eval "bashopts_optprop_$f[${options[name]}]='${options[$f]//\'/\'\\\'\'}'"
    done
    bashopts_optlist+=(${options[name]})
}

# maximum of two values
bashopts_math_max() {
    echo $(($1>$2?$1:$2))
}

# minimum of two values
bashopts_math_min() {
    echo $(($1<$2?$1:$2))
}

# join array element
bashopts_join_by() {
    local IFS="$1"
    shift
    shift || bashopts_error "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"
@@ -307,9 +339,11 @@ bashopts_dump_value() {
    echo -n "]"
}

# display the formated help
bashopts_diplay_help() {
    local elts optargs_max_len=8 val dval ncol=$COLUMNS
    declare -A optargs
    # compute the good arguments comumn size
    for op in "${bashopts_optlist[@]}"; do
        elts=()
        unset val
@@ -323,7 +357,7 @@ bashopts_diplay_help() {
    done
    ncol=${ncol:-160}
    optargs_max_len=$(bashopts_math_min $optargs_max_len $(( $ncol / 3 )) )
    set +x
    # display global info
    echo
    echo "NAME:"
    echo "    $bashopts_tool_name - $bashopts_tool_description"
@@ -336,6 +370,8 @@ bashopts_diplay_help() {
        elts=""
        dval=""
        if ! [[ $op =~ ^__.*__$ ]]; then
            # display additional information the each properties
            # discarding special options like --help
            if [[ -v bashopts_optprop_expression[$op] ]]; then
                dval="\"${bashopts_optprop_expression[$op]//\"/\\\"}\""
            fi
@@ -344,13 +380,16 @@ bashopts_diplay_help() {
            else
                dval=${dval:-${bashopts_optprop_default[$op]}}
            fi
            # add type and default value info
            elts="- [\$$op] (type:${bashopts_optprop_type[$op]}, default:$dval)"
        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
}

# display all otions values and properties
bashopts_diplay_summary() {
    local elts desc_max_len=0 val dval
    declare -A optargs
@@ -368,6 +407,7 @@ bashopts_diplay_summary() {
bashopts_parse_args() {
    local op arg val args is_arg short_opts long_opts

    # split argument into two arrays: normal and extra arguments
    is_arg=1
    args=()
    for arg in "$@"; do
@@ -379,6 +419,7 @@ bashopts_parse_args() {
        fi
    done

    # build the long and short getopt option list from the options
    short_opts=""
    long_opts=()
    for op in "${bashopts_optlist[@]}"; do
@@ -391,45 +432,55 @@ bashopts_parse_args() {
    done
    long_opts=$(bashopts_join_by , ${long_opts[@]})

    # call the getopt
    if ! args=$(getopt -o $short_opts -l "$long_opts" -n "$bashopts_tool_name" -- "${args[@]}"); then
        >&2 bashopts_diplay_help
        exit 1
    fi
    eval set -- "$args";

    # store the arguments value part
    while true; do
        arg=$1
        shift
        case $arg in
            --)
                # end of the argument part
                break
                ;;
            -*)
                val="$1"
                shift
                op=${bashopts_opt2name[$arg]}
                op=${bashopts_arg2op[$arg]}
                if [ -z "$val" ] && [ "${bashopts_optprop_type[$op]}" != "boolean" ]; then
                    # 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
                case "${bashopts_optprop_method[$op]}" in
                    set)
                        # normal case: override the value
                        eval "$op=$(declare -p val | sed -E 's/^declare\s[^=]*=//g')"
                        ;;
                    add)
                        # array case: add the value
                        eval "$op+=($(declare -p val | sed -E 's/^declare\s[^=]*=//g'))"
                        ;;
                esac
@@ -439,9 +490,12 @@ bashopts_parse_args() {
                ;;
        esac
    done

    # store the command part
    bashopts_commands=("$@")
}

# display an array: [val1, val2, ...]
bashopts_dump_array() {
    local type=$1
    shift || bashopts_error "Usage: bashopts_dump_array type elt1 [elt2...]"
@@ -492,22 +546,27 @@ bashopts_process_args() {
                eval "tval=$(bashopts_get_def dval)"
            fi
            if [ "${bashopts_optprop_interactive[$op]}" == "true" ] && [ "$bashopts_non_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
                    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...\" )')"
                            unset tval
                            continue
                        fi
                    else
                        # non array/norman value
                        tval=$ival
                    fi
                    # check boolean/number (array or not)
                    case "${bashopts_optprop_type[$op]}" in
                        boolean)
                            for v in "${tval[@]}"; do
@@ -529,8 +588,10 @@ bashopts_process_args() {
                            ;;
                    esac
                    if [[ -v tval ]]; then
                        # edit OK, break
                        break
                    fi
                    # otherwise, loop...
                done
            fi
            if [[ -v tval ]]; then
@@ -542,9 +603,13 @@ bashopts_process_args() {
                # vrite the value to the setting file
                (
                    if [ -f "$bashopts_tool_settings_path" ]; then
                        # remove old value
                        sed -i "/^$op=/d" $bashopts_tool_settings_path
                    fi
                    if [ "$(bashopts_get_def $op)" != "$(bashopts_get_def dval)" ]; then
                    if [ "$bashopts_tool_settings_force_write" == "true" ] \
                        || [ "$(bashopts_get_def $op)" != "$(bashopts_get_def dval)" ]; then
                        # append the new value to the file if the value is not the default or 
                        # force_write is true
                        echo "$(bashopts_get_def_full $op)" >> $bashopts_tool_settings_path
                    fi
                ) || bashopts_warning "Please check the settings file"
+13 −0
Original line number Diff line number Diff line
@@ -247,6 +247,19 @@ _test_case_16() {
    fi
}

_test_case_17() {
    rm -f /tmp/bashopts_testrc
    bashopts_setup -n "$0" -d "Test case $0" -s /tmp/bashopts_testrc -p

    bashopts_declare -n a_value -l value -d "A value" -s -i -t string -v "default value"

    bashopts_parse_args
    bashopts_process_args > /dev/null <<< ""

    req_test_eq "$a_value" "default value"
    req_test_eq "$(cat /tmp/bashopts_testrc)" "a_value='default value'"
}

if [ ${#} -eq 0 ]; then
    for t in $(grep -E -o '_test_case_\w+\>' $0); do
        echo "=> Test case $t"