diff --git a/README.md b/README.md index 42d977d..5848da5 100644 --- a/README.md +++ b/README.md @@ -83,31 +83,32 @@ Options are script parameters. Each option has at least one alias by which it ca ## Configuration -Configuration is defined by assigning values to variables before using the parser. For example: - -```bash -_accept_command=true -``` -Configuration variables must be assigned before calling `_parse`. +Configuration is done by assigning values to special keys via [`_set_config`](#_set_config) before using [`_parse`](#_parse). | Name | Default Value | Description | | --- | --- | --- | -| _accept_command | `auto` | `any` — The script accepts a command.
`none` — The script does not accept a command.
`mapped_only` — The script accepts only a mapped command.
`auto` — The script accepts a command if at least one has been mapped. | -| _accept_options | `auto` | `any` — The script accepts any options.
`none` — The script does not accept any options.
`mapped_only` — The script accepts only mapped options.
`auto` — The script accepts only mapped options if at least one has been mapped. Otherwise, it accepts any options. | -| _default_max_positional_args | | Maximum number of positional arguments. A different value can be set for each command (see [_map_command](#_map_command)). | -| _default_min_positional_args | `0` | Minimum number of positional arguments. A different value can be set for each command (see [_map_command](#_map_command)). | -| _default_positional_arg_variable || Name of the variable that will store the single positional argument. Implicitly sets `_default_max_positional_args` and `_default_min_positional_args` to 1. Command-specific values for `max_args` and `arg_variable` disables this behavior. | -| _mapping_key_value_delimiter | `=` | Key-value delimiter for options mapping (see [_map_option](#_map_option)). | -| _mapping_values_delimiter | `,` | Values delimiter for options mapping (see [_map_option](#_map_option)). | -| _option_duplicates_allowed | `false` | `true` — Multiple uses of options are allowed.
`false` — Multiple uses of options are not allowed.| -| _option_key_value_delimiter | `' '` | Option alias-args delimiter. | -| _option_values_delimiter | `' '` | Option values delimiter. | -| _options_combination_allowed | `true` | `true` — Combining short option aliases into combinations is allowed.
`false` — Combining short option aliases is not allowed. | -| _options_combination_args_allowed | `true` | `true` — Passing arguments to the last option in a combination is allowed.
`false` — Passing arguments to the last option in a combination is not allowed. | -| _positional_args_placement | `any` | `any` — Positional arguments can be placed anywhere, including mixed with options.
`before_options` — Positional arguments are placed before options.
`after_options` — Positional arguments are placed after options. | +| accept_command | `auto` | `any` — The script accepts a command.
`none` — The script does not accept a command.
`mapped_only` — The script accepts only a mapped command.
`auto` — The script accepts a command if at least one has been mapped. | +| accept_options | `auto` | `any` — The script accepts any options.
`none` — The script does not accept any options.
`mapped_only` — The script accepts only mapped options.
`auto` — The script accepts only mapped options if at least one has been mapped. Otherwise, it accepts any options. | +| default_max_positional_args | | Maximum number of positional arguments. A different value can be set for each command (see [_map_command](#_map_command)). | +| default_min_positional_args | `0` | Minimum number of positional arguments. A different value can be set for each command (see [_map_command](#_map_command)). | +| default_positional_arg_variable || Name of the variable that will store the single positional argument. Implicitly sets `_default_max_positional_args` and `_default_min_positional_args` to 1. Command-specific values for `max_args` and `arg_variable` disables this behavior. | +| mapping_key_value_delimiter | `=` | Key-value delimiter for options mapping (see [_map_option](#_map_option)). Allowed values: `=` `:`.| +| mapping_values_delimiter | `,` | Values delimiter for options mapping (see [_map_option](#_map_option)). Allowed values: `,` `;` `\|` `/`.| +| option_duplicates_allowed | `false` | `true` — Multiple uses of options are allowed.
`false` — Multiple uses of options are not allowed.| +| option_key_value_delimiter | `' '` | Option alias-args delimiter. Allowed values: `' '` `=` `:`.| +| option_values_delimiter | `' '` | Option values delimiter. Allowed values: `' '` `,` `;` `\|` `/`.| +| options_combination_allowed | `true` | `true` — Combining short option aliases into combinations is allowed.
`false` — Combining short option aliases is not allowed. | +| options_combination_args_allowed | `true` | `true` — Passing arguments to the last option in a combination is allowed.
`false` — Passing arguments to the last option in a combination is not allowed. | +| positional_args_placement | `any` | `any` — Positional arguments can be placed anywhere, including mixed with options.
`before_options` — Positional arguments are placed before options.
`after_options` — Positional arguments are placed after options. | ## Functions +#### _set_config +Sets the configuration variables. Full list of configuration variables and expected values can be found in the [Configuration](#configuration) section. +```bash +_set_config '_accept_command' 'any' +``` + #### _parse Parses the provided arguments. Typically, takes all command-line arguments `$@` as input. ```bash diff --git a/parser.sh b/parser.sh index 6dac446..9cb45fd 100755 --- a/parser.sh +++ b/parser.sh @@ -52,6 +52,10 @@ _is_int() { esac } +_is_flag() { + [ "$1" = "true" ] || [ "$1" = "false" ] +} + _assign() { if ! _is_valid_var_name "$1"; then _err "Invalid variable name: $1." @@ -154,14 +158,14 @@ _mapped_options_only() { _is_free_var_name() { if [ -n "$_default_positional_arg_variable" ] && [ "$_default_positional_arg_variable" = "$1" ]; then - echo "variable is already used as default positional arg variable: $1." + echo "Variable is already used as default positional arg variable: $1." return 1 fi _i=1 while [ "$_i" -le "$_mapped_options_count" ]; do if [ "$1" = "$(_var_value "_options_${_i}_variable")" ]; then - echo "variable is already mapped for option #${_i}: $1." + echo "Variable is already mapped for option #${_i}: $1." return 1 fi @@ -171,7 +175,7 @@ _is_free_var_name() { _i=1 while [ "$_i" -le "$_mapped_commands_count" ]; do if [ "$1" = "$(_var_value "_commands_${_i}_arg_variable")" ]; then - echo "arg variable is already mapped for command #${_i}: $1." + echo "Arg variable is already mapped for command #${_i}: $1." return 1 fi @@ -179,6 +183,45 @@ _is_free_var_name() { done } +_set_config() { + _config_key="$1" + _config_value="$2" + + if [ "$_config_key" = "accept_command" ]; then + _validate_config_accept_command "$_config_value" + elif [ "$_config_key" = "accept_options" ]; then + _validate_config_accept_options "$_config_value" + elif [ "$_config_key" = "default_max_positional_args" ]; then + _validate_config_default_max_positional_args "$_config_value" + elif [ "$_config_key" = "default_min_positional_args" ]; then + _validate_config_default_min_positional_args "$_config_value" + elif [ "$_config_key" = "default_positional_arg_variable" ]; then + _validate_config_default_positional_arg_variable "$_config_value" + _default_max_positional_args="1" + _default_min_positional_args="1" + elif [ "$_config_key" = "mapping_key_value_delimiter" ]; then + _validate_config_mapping_key_value_delimiter "$_config_value" + elif [ "$_config_key" = "mapping_values_delimiter" ]; then + _validate_config_mapping_values_delimiter "$_config_value" + elif [ "$_config_key" = "option_duplicates_allowed" ]; then + _validate_config_option_duplicates_allowed "$_config_value" + elif [ "$_config_key" = "option_key_value_delimiter" ]; then + _validate_config_option_key_value_delimiter "$_config_value" + elif [ "$_config_key" = "option_values_delimiter" ]; then + _validate_config_option_values_delimiter "$_config_value" + elif [ "$_config_key" = "options_combination_allowed" ]; then + _validate_config_options_combination_allowed "$_config_value" + elif [ "$_config_key" = "options_combination_args_allowed" ]; then + _validate_config_options_combination_args_allowed "$_config_value" + elif [ "$_config_key" = "positional_args_placement" ]; then + _validate_config_positional_args_placement "$_config_value" # TODO + else + _err "Unknown config key: $_config_key." + fi + + _assign "_$_config_key" "$_config_value" +} + _map_command() { _command_index=$(_math "$_mapped_commands_count + 1") _mapping_command_prefix="_commands_${_command_index}" @@ -306,6 +349,131 @@ _map_option_auto() { _mapped_options_count=$_option_index } +_validate_config_accept_command() { + case "$1" in + any | none | mapped_only | auto) + ;; + *) + _err "Invalid config \"_accept_command\" value: $1. Expected: any, none, mapped_only, auto." + ;; + esac +} + +_validate_config_accept_options() { + case "$1" in + any | none | mapped_only | auto) + ;; + *) + _err "Invalid config \"_accept_options\" value: $1. Expected: any, none, mapped_only, auto." + ;; + esac +} + +_validate_config_default_max_positional_args() { + if ! _is_int "$1"; then + _err "Invalid config \"_default_max_positional_args\" value: $1. Expected: a non-negative integer." + fi + + if [ -n "$_default_positional_arg_variable" ] && [ "$1" -ne 1 ]; then + _err "Config \"_default_max_positional_args\" cannot differ from 1 if \"_default_positional_arg_variable\" is set." + fi +} + +_validate_config_default_min_positional_args() { + if ! _is_int "$1"; then + _err "Invalid config \"_default_min_positional_args\" value: $1. Expected: a non-negative integer." + fi + + if [ -n "$_default_positional_arg_variable" ] && [ "$1" -ne 1 ]; then + _err "Config \"_default_min_positional_args\" cannot differ from 1 if \"_default_positional_arg_variable\" is set." + fi +} + +_validate_config_default_positional_arg_variable() { + if ! _is_valid_var_name "$1"; then + _err "Default positional arg variable is invalid: $1. Must be a valid variable name." + fi + + if ! _description=$(_is_free_var_name "$1"); then + _err "Default positional arg is already used. ${_description}" + fi +} + +_validate_config_mapping_key_value_delimiter() { + case "$1" in + "=" | ":") + ;; + *) + _err "Invalid config \"_mapping_key_value_delimiter\" value: $1. Expected: \"=\", \":\"." + ;; + esac +} + +_validate_config_mapping_values_delimiter() { + case "$1" in + "," | "|" | ";" | "/") + ;; + *) + _err "Invalid config \"_mapping_values_delimiter\" value: $1. Expected: \",\", \"|\", \";\", \"/\"." + ;; + esac +} + +_validate_config_option_duplicates_allowed() { + if ! _is_flag "$1"; then + _err "Invalid config \"_option_duplicates_allowed\" value: $1. Expected \"true\" or \"false\"." + fi +} + +_validate_config_option_key_value_delimiter() { + case "$1" in + " " | "=" | ":") + ;; + *) + _err "Invalid config \"_option_key_value_delimiter\" value: $1. Expected: \" \", \"=\", \":\"." + ;; + esac + + if [ "$1" != " " ] && [ "$_option_key_value_delimiter" = " " ]; then + _err "Non-space value of config \"_option_key_value_delimiter\" is incompatible with space valueof config \"_option_values_delimiter\" due to parsing ambiguity." + fi +} + +_validate_config_option_values_delimiter() { + case "$1" in + " " |"," | "|" | ";" | "/") + ;; + *) + _err "Invalid config \"_mapping_values_delimiter\" value: $1. Expected: \" \", \",\", \"|\", \";\", \"/\"." + ;; + esac + + if [ "$1" = " " ] && [ "$_option_key_value_delimiter" != " " ]; then + _err "Non-space value of config \"_option_key_value_delimiter\" is incompatible with space valueof config \"_option_values_delimiter\" due to parsing ambiguity." + fi +} + +_validate_config_options_combination_allowed() { + if ! _is_flag "$1"; then + _err "Invalid config \"_options_combination_allowed\" value: $1. Expected \"true\" or \"false\"." + fi +} + +_validate_config_options_combination_args_allowed() { + if ! _is_flag "$1"; then + _err "Invalid config \"_options_combination_args_allowed\" value: $1. Expected \"true\" or \"false\"." + fi +} + +_validate_config_positional_args_placement() { + case "$1" in + any | before_options | after_options) + ;; + *) + _err "Invalid config \"_positional_args_placement\" value: $1. Expected: any, before_options, after_options." + ;; + esac +} _validate_command_description() { if [ -n "$(_var_value "${_mapping_command_prefix}_description")" ]; then @@ -400,7 +568,7 @@ _validate_command_arg_variable() { fi if ! _description=$(_is_free_var_name "$1"); then - _err "Command #${_command_index} ${_description}" + _err "Command #${_command_index} arg variable is already used. ${_description}" fi } @@ -504,7 +672,7 @@ _validate_option_variable() { fi if ! _description=$(_is_free_var_name "$1"); then - _err "Option #${_option_index} ${_description}" + _err "Option #${_option_index} variable is already used. ${_description}" fi } @@ -674,9 +842,9 @@ _parse() { if [ "$_min_positional_args" -gt 0 ] && [ "$_positional_args_count" -lt "$_min_positional_args" ]; then if [ "$_used_command_index" -eq 0 ]; then - _err "At least $_min_positional_args argument(s) required." + _err "At least $_min_positional_args positional argument(s) required." else - _err "At least $_min_positional_args argument(s) required for command: $(_var_value "_commands_${_used_command_index}_name")." + _err "At least $_min_positional_args positional argument(s) required for command: $(_var_value "_commands_${_used_command_index}_name")." fi fi }