Compare commits

...

2 Commits

Author SHA1 Message Date
4e979574d8 Fix README formatting 2024-09-07 16:06:37 +00:00
5f3b3c693f Add _set_config function 2024-09-07 16:04:39 +00:00
2 changed files with 196 additions and 27 deletions

View File

@@ -71,7 +71,7 @@ A command typically represents the core action performed by the script: `run`, `
- Positional arguments are placed after the command, if present. - Positional arguments are placed after the command, if present.
- The presence and number of positional arguments are determined by the script's logic. - The presence and number of positional arguments are determined by the script's logic.
- After the special argument --, all arguments are considered positional. This allows passing arguments that start with a dash and might be confused with options. - After the special argument `--`, all arguments are considered positional. This allows passing arguments that start with a dash and might be confused with options.
### Options ### Options
Options are script parameters. Each option has at least one alias by which it can be specified in the command line. Options often have both short and long aliases, such as `-h` and `--help`. Options are script parameters. Each option has at least one alias by which it can be specified in the command line. Options often have both short and long aliases, such as `-h` and `--help`.
@@ -83,31 +83,32 @@ Options are script parameters. Each option has at least one alias by which it ca
## Configuration ## Configuration
Configuration is defined by assigning values to variables before using the parser. For example: Configuration is done by assigning values to special keys via [`_set_config`](#_set_config) before using [`_parse`](#_parse).
```bash
_accept_command=true
```
Configuration variables must be assigned before calling `_parse`.
| Name | Default Value | Description | | Name | Default Value | Description |
| --- | --- | --- | | --- | --- | --- |
| _accept_command | `auto` | `any` — The script accepts a command.<br>`none` — The script does not accept a command.<br>`mapped_only` — The script accepts only a mapped command.<br>`auto` — The script accepts a command if at least one has been mapped. | | accept_command | `auto` | `any` — The script accepts a command.<br>`none` — The script does not accept a command.<br>`mapped_only` — The script accepts only a mapped command.<br>`auto` — The script accepts a command if at least one has been mapped. |
| _accept_options | `auto` | `any` — The script accepts any options.<br>`none` — The script does not accept any options.<br>`mapped_only` — The script accepts only mapped options.<br>`auto` — The script accepts only mapped options if at least one has been mapped. Otherwise, it accepts any options. | | accept_options | `auto` | `any` — The script accepts any options.<br>`none` — The script does not accept any options.<br>`mapped_only` — The script accepts only mapped options.<br>`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_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_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. | | 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_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)). | | 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.<br>`false` — Multiple uses of options are not allowed.| | option_duplicates_allowed | `false` | `true` — Multiple uses of options are allowed.<br>`false` — Multiple uses of options are not allowed.|
| _option_key_value_delimiter | `' '` | Option alias-args delimiter. | | option_key_value_delimiter | `' '` | Option alias-args delimiter. Allowed values: `' '` `=` `:`.|
| _option_values_delimiter | `' '` | Option values delimiter. | | option_values_delimiter | `' '` | Option values delimiter. Allowed values: `' '` `,` `;` `\|` `/`.|
| _options_combination_allowed | `true` | `true` — Combining short option aliases into combinations is allowed.<br>`false` — Combining short option aliases is not allowed. | | options_combination_allowed | `true` | `true` — Combining short option aliases into combinations is allowed.<br>`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.<br>`false` — Passing arguments to the last option in a combination is not allowed. | | options_combination_args_allowed | `true` | `true` — Passing arguments to the last option in a combination is allowed.<br>`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.<br>`before_options` — Positional arguments are placed before options.<br>`after_options` — Positional arguments are placed after options. | | positional_args_placement | `any` | `any` — Positional arguments can be placed anywhere, including mixed with options.<br>`before_options` — Positional arguments are placed before options.<br>`after_options` — Positional arguments are placed after options. |
## Functions ## 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 #### _parse
Parses the provided arguments. Typically, takes all command-line arguments `$@` as input. Parses the provided arguments. Typically, takes all command-line arguments `$@` as input.
```bash ```bash

182
parser.sh
View File

@@ -52,6 +52,10 @@ _is_int() {
esac esac
} }
_is_flag() {
[ "$1" = "true" ] || [ "$1" = "false" ]
}
_assign() { _assign() {
if ! _is_valid_var_name "$1"; then if ! _is_valid_var_name "$1"; then
_err "Invalid variable name: $1." _err "Invalid variable name: $1."
@@ -154,14 +158,14 @@ _mapped_options_only() {
_is_free_var_name() { _is_free_var_name() {
if [ -n "$_default_positional_arg_variable" ] && [ "$_default_positional_arg_variable" = "$1" ]; then 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 return 1
fi fi
_i=1 _i=1
while [ "$_i" -le "$_mapped_options_count" ]; do while [ "$_i" -le "$_mapped_options_count" ]; do
if [ "$1" = "$(_var_value "_options_${_i}_variable")" ]; then 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 return 1
fi fi
@@ -171,7 +175,7 @@ _is_free_var_name() {
_i=1 _i=1
while [ "$_i" -le "$_mapped_commands_count" ]; do while [ "$_i" -le "$_mapped_commands_count" ]; do
if [ "$1" = "$(_var_value "_commands_${_i}_arg_variable")" ]; then 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 return 1
fi fi
@@ -179,6 +183,45 @@ _is_free_var_name() {
done 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() { _map_command() {
_command_index=$(_math "$_mapped_commands_count + 1") _command_index=$(_math "$_mapped_commands_count + 1")
_mapping_command_prefix="_commands_${_command_index}" _mapping_command_prefix="_commands_${_command_index}"
@@ -306,6 +349,131 @@ _map_option_auto() {
_mapped_options_count=$_option_index _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() { _validate_command_description() {
if [ -n "$(_var_value "${_mapping_command_prefix}_description")" ]; then if [ -n "$(_var_value "${_mapping_command_prefix}_description")" ]; then
@@ -400,7 +568,7 @@ _validate_command_arg_variable() {
fi fi
if ! _description=$(_is_free_var_name "$1"); then if ! _description=$(_is_free_var_name "$1"); then
_err "Command #${_command_index} ${_description}" _err "Command #${_command_index} arg variable is already used. ${_description}"
fi fi
} }
@@ -504,7 +672,7 @@ _validate_option_variable() {
fi fi
if ! _description=$(_is_free_var_name "$1"); then if ! _description=$(_is_free_var_name "$1"); then
_err "Option #${_option_index} ${_description}" _err "Option #${_option_index} variable is already used. ${_description}"
fi fi
} }
@@ -674,9 +842,9 @@ _parse() {
if [ "$_min_positional_args" -gt 0 ] && [ "$_positional_args_count" -lt "$_min_positional_args" ]; then if [ "$_min_positional_args" -gt 0 ] && [ "$_positional_args_count" -lt "$_min_positional_args" ]; then
if [ "$_used_command_index" -eq 0 ]; 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 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
fi fi
} }