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
}