matejak / argbash

Bash argument parsing code generator
Other
1.39k stars 63 forks source link

feature request: add possibility to add code before the argbash definitions #164

Closed nicola-lunghi closed 1 year ago

nicola-lunghi commented 1 year ago

Hi, I want to add some code before the argbash functions, like

# Treat expansion of unset variables as an error and exit immediately
# if a command pipeline exits with a non-zero status
set -o nounset -o errexit

## useful variables
script_name="$(basename "${BASH_SOURCE[0]}")"
script_version="1.0"
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

It's possible to have a upper delimiter for argbash code? like

#!/bin/bash

# this is a comment
myvar=1

dosomething() {
    :
}

# ARGBASH_START()
# ARG_OPTIONAL_SINGLE([config],[c],[specify a config file],[curdir/.lab_config.conf])
# ARG_POSITIONAL_SINGLE([command],[specify a command to execute],[help])
# ARGBASH_SET_DELIM([ =])
# ARG_OPTION_STACKING([getopt])
# ARG_RESTRICT_VALUES([no-local-options])
# ARG_DEFAULTS_POS([])
# ARGBASH_PREPARE()
# ARGBASH_END
nicola-lunghi commented 1 year ago

Ok found a solution is this valid?

#!/bin/bash
# m4_ignore(
echo "This is just a script template, not the script (yet) - pass it to 'argbash' to fix this." >&2
exit 11  #)[ <-- needed because of Argbash

###############################################################################
# PREAMBLE
###############################################################################
# Treat expansion of unset variables as an error and exit immediately
# if a command pipeline exits with a non-zero status
set -o nounset -o errexit

## useful variables
script_name="$(basename "${BASH_SOURCE[0]}")"
script_version="1.0"
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

_arg_command=""
_arg_config=""
_arg_help=""
_positionals=()

###############################################################################
# START ARGBASH
###############################################################################
# ] <-- needed because of Argbash
# ARG_OPTIONAL_SINGLE([config],[c],[specify a config file],[curdir/.lab_config.conf])
# ARG_POSITIONAL_SINGLE([command],[specify a command to execute],[help])
# ARGBASH_SET_DELIM([ =])
# ARG_OPTION_STACKING([getopt])
# ARG_RESTRICT_VALUES([no-local-options])
# ARG_DEFAULTS_POS([])
# ARG_HELP([Print help function])
# ARG_VERSION([echo "$script_name $script_version"][][])
# ARGBASH_SET_INDENT([    ])
# ARG_POSITIONAL_DOUBLEDASH([])
# ARGBASH_PREPARE()
# [ <-- needed because of Argbash

###############################################################################
# MAIN
###############################################################################

# argparse stuff
parse_commandline "$@"
handle_passed_args_count
assign_positional_args 1 "${_positionals[@]}"

# printf 'Value of --%s: %s\n' 'help' "$_arg_help"
printf "Value of '%s': %s\\n" 'command' "${_arg_command}"
printf "Value of '%s': %s\\n" 'config' "${_arg_config}"
printf "Value of '%s': %s\\n" 'positionals' "${_positionals[*]}"

#### MAIN END

# ] <-- needed because of Argbash

generates

#!/bin/bash

###############################################################################
# PREAMBLE
###############################################################################
# Treat expansion of unset variables as an error and exit immediately
# if a command pipeline exits with a non-zero status
set -o nounset -o errexit

## useful variables
script_name="$(basename "${BASH_SOURCE[0]}")"
script_version="1.0"
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

_arg_command=""
_arg_config=""
_arg_help=""
_positionals=()

###############################################################################
# START ARGBASH
###############################################################################
# ARG_OPTIONAL_SINGLE([config],[c],[specify a config file],[curdir/.lab_config.conf])
# ARG_POSITIONAL_SINGLE([command],[specify a command to execute],[help])
# ARGBASH_SET_DELIM([ =])
# ARG_OPTION_STACKING([getopt])
# ARG_RESTRICT_VALUES([no-local-options])
# ARG_DEFAULTS_POS([])
# ARG_HELP([Print help function])
# ARG_VERSION([echo "$script_name $script_version"])
# ARGBASH_SET_INDENT([    ])
# ARG_POSITIONAL_DOUBLEDASH([])
# ARGBASH_PREPARE()
# needed because of Argbash --> m4_ignore([
### START OF CODE GENERATED BY Argbash v2.10.0 one line above ###
# Argbash is a bash code generator used to get arguments parsing right.
# Argbash is FREE SOFTWARE, see https://argbash.io for more info

# # When called, the process ends.
# Args:
#     $1: The exit message (print to stderr)
#     $2: The exit code (default is 1)
# if env var _PRINT_HELP is set to 'yes', the usage is print to stderr (prior to $1)
# Example:
#     test -f "$_arg_infile" || _PRINT_HELP=yes die "Can't continue, have to supply file as an argument, got '$_arg_infile'" 4
die()
{
    local _ret="${2:-1}"
    test "${_PRINT_HELP:-no}" = yes && print_help >&2
    echo "$1" >&2
    exit "${_ret}"
}

# Function that evaluates whether a value passed to an argument
# does not violate the global rule imposed by the ARG_RESTRICT_VALUES macro:
# The value must not match any long or short option this script uses
# Args:
#     $1: The name of the option
#     $2: The passed value
evaluate_strictness()
{
    [[ "$2" =~ ^-(-(config|command|help|version)$|[chv]) ]] && die "You have passed '$2' as a value of argument '$1', which makes it look like that you have omitted the actual value, since '$2' is an option accepted by this script. This is considered a fatal error."
}

# Function that evaluates whether a value passed to it begins by a character
# that is a short option of an argument the script knows about.
# This is required in order to support getopts-like short options grouping.
begins_with_short_option()
{
    local first_option all_short_options='chv'
    first_option="${1:0:1}"
    test "$all_short_options" = "${all_short_options/$first_option/}" && return 1 || return 0
}

# THE DEFAULTS INITIALIZATION - POSITIONALS
# The positional args array has to be reset before the parsing, because it may already be defined
# - for example if this script is sourced by an argbash-powered script.
_positionals=()
_arg_command="help"
# THE DEFAULTS INITIALIZATION - OPTIONALS
_arg_config="curdir/.lab_config.conf"

# Function that prints general usage of the script.
# This is useful if users asks for it, or if there is an argument parsing error (unexpected / spurious arguments)
# and it makes sense to remind the user how the script is supposed to be called.
print_help()
{
    printf '%s\n' "Print help function"
    printf 'Usage: %s [-c|--config <arg>] [-h|--help] [-v|--version] [--] [<command>]\n' "$0"
    printf '\t%s\n' "<command>: specify a command to execute (default: 'help')"
    printf '\t%s\n' "-c, --config: specify a config file (default: 'curdir/.lab_config.conf')"
    printf '\t%s\n' "-h, --help: Prints help"
    printf '\t%s\n' "-v, --version: Prints version"
}

# The parsing of the command-line
parse_commandline()
{
    _positionals_count=0
    while test $# -gt 0
    do
        _key="$1"
        # If two dashes (i.e. '--') were passed on the command-line,
        # assign the rest of arguments as positional arguments and bail out.
        if test "$_key" = '--'
        then
            shift
            # Handle the case when the double dash is the last argument.
            test $# -gt 0 || break
            _positionals+=("$@")
            _positionals_count=$((_positionals_count + $#))
            shift $(($# - 1))
            _last_positional="$1"
            break
        fi
        case "$_key" in
            # We support whitespace as a delimiter between option argument and its value.
            # Therefore, we expect the --config or -c value.
            # so we watch for --config and -c.
            # Since we know that we got the long or short option,
            # we just reach out for the next argument to get the value.
            -c|--config)
                test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1
                _arg_config="$2"
                shift
                evaluate_strictness "$_key" "$_arg_config"
                ;;
            # We support the = as a delimiter between option argument and its value.
            # Therefore, we expect --config=value, so we watch for --config=*
            # For whatever we get, we strip '--config=' using the ${var##--config=} notation
            # to get the argument value
            --config=*)
                _arg_config="${_key##--config=}"
                evaluate_strictness "$_key" "$_arg_config"
                ;;
            # We support getopts-style short arguments grouping,
            # so as -c accepts value, we allow it to be appended to it, so we watch for -c*
            # and we strip the leading -c from the argument string using the ${var##-c} notation.
            -c*)
                _arg_config="${_key##-c}"
                evaluate_strictness "$_key" "$_arg_config"
                ;;
            # The help argurment doesn't accept a value,
            # we expect the --help or -h, so we watch for them.
            -h|--help)
                print_help
                exit 0
                ;;
            # We support getopts-style short arguments clustering,
            # so as -h doesn't accept value, other short options may be appended to it, so we watch for -h*.
            # After stripping the leading -h from the argument, we have to make sure
            # that the first character that follows coresponds to a short option.
            -h*)
                print_help
                exit 0
                ;;
            # See the comment of option '--help' to see what's going on here - principle is the same.
            -v|--version)
                echo "$script_name $script_version"
                exit 0
                ;;
            # See the comment of option '-h' to see what's going on here - principle is the same.
            -v*)
                echo "$script_name $script_version"
                exit 0
                ;;
            *)
                _last_positional="$1"
                _positionals+=("$_last_positional")
                _positionals_count=$((_positionals_count + 1))
                ;;
        esac
        shift
    done
}

# Check that we receive expected amount positional arguments.
# Return 0 if everything is OK, 1 if we have too little arguments
# and 2 if we have too much arguments
handle_passed_args_count()
{
    test "${_positionals_count}" -le 1 || _PRINT_HELP=yes die "FATAL ERROR: There were spurious positional arguments --- we expect between 0 and 1, but got ${_positionals_count} (the last one was: '${_last_positional}')." 1
}

# Take arguments that we have received, and save them in variables of given names.
# The 'eval' command is needed as the name of target variable is saved into another variable.
assign_positional_args()
{
    local _positional_name _shift_for=$1
    # We have an array of variables to which we want to save positional args values.
    # This array is able to hold array elements as targets.
    # As variables don't contain spaces, they may be held in space-separated string.
    _positional_names="_arg_command "

    shift "$_shift_for"
    for _positional_name in ${_positional_names}
    do
        test $# -gt 0 || break
        eval "$_positional_name=\${1}" || die "Error during argument parsing, possibly an Argbash bug." 1
        shift
    done
}

# Call the function that assigns passed optional arguments to variables:
#  parse_commandline "$@"
# Then, call the function that checks that the amount of passed arguments is correct
# followed by the function that assigns passed positional arguments to variables:
#  handle_passed_args_count
#  assign_positional_args 1 "${_positionals[@]}"

# OTHER STUFF GENERATED BY Argbash

### END OF CODE GENERATED BY Argbash (sortof) ### ])
# [ <-- needed because of Argbash

###############################################################################
# MAIN
###############################################################################

# argparse stuff
parse_commandline "$@"
handle_passed_args_count
assign_positional_args 1 "${_positionals[@]}"

# printf 'Value of --%s: %s\n' 'help' "$_arg_help"
printf "Value of '%s': %s\\n" 'command' "${_arg_command}"
printf "Value of '%s': %s\\n" 'config' "${_arg_config}"
printf "Value of '%s': %s\\n" 'positionals' "${_positionals[*]}"

#### MAIN END

# ] <-- needed because of Argbash
matejak commented 1 year ago

You can add your code above the Argbash-generated header, just be warned that it will be processed by the m4sugar preprocessor. TLDR - you are probably fine if you don't use square brackets in that code.