diff --git a/lib/bash/cli/lib_cli.sh b/lib/bash/cli/lib_cli.sh index 2b0c303..0963e03 100644 --- a/lib/bash/cli/lib_cli.sh +++ b/lib/bash/cli/lib_cli.sh @@ -953,6 +953,7 @@ base_cli_option() { # Usage: base_cli_positional model command_path name [required=true] [repeatable=true] ... base_cli_positional() { local model="${1-}" path="${2-}" name="${3-}" key names + local previous previous_required local -a __base_bash_libs_cli_previous_positionals=() if (($# < 3)); then @@ -991,12 +992,23 @@ base_cli_positional() { fi if [[ -n "$names" ]]; then IFS=, read -r -a __base_bash_libs_cli_previous_positionals <<< "$names" - local previous="${__base_bash_libs_cli_previous_positionals[${#__base_bash_libs_cli_previous_positionals[@]} - 1]}" + previous="${__base_bash_libs_cli_previous_positionals[${#__base_bash_libs_cli_previous_positionals[@]} - 1]}" if [[ "$(__base_bash_libs_cli_positional_meta__ "$model" "$path" "$previous" repeatable)" =~ ^(1|true|yes)$ ]]; then __base_bash_libs_cli_declaration_usage__ "base_cli_positional: cannot declare '$name' after repeatable positional '$previous'." return 2 fi fi + if [[ "${__base_bash_libs_cli_attrs[required]-}" =~ ^(1|true|yes)$ && -n "$names" ]]; then + for previous in "${__base_bash_libs_cli_previous_positionals[@]}"; do + previous_required="$(__base_bash_libs_cli_positional_meta__ "$model" "$path" "$previous" required)" + if [[ ! "$previous_required" =~ ^(1|true|yes)$ || + -n "${__base_bash_libs_cli_models["$model|positional|$path|meta|$previous|default"]+set}" ]]; then + __base_bash_libs_cli_declaration_usage__ \ + "base_cli_positional: cannot declare required positional '$name' after optional positional '$previous'." + return 2 + fi + done + fi if [[ -n "$names" ]]; then names="$names,$name"; else names="$name"; fi __base_bash_libs_cli_models["$model|command|positionals|$path"]="$names" for key in help metavar default required enum validator repeatable; do diff --git a/lib/bash/cli/tests/lib_cli.bats b/lib/bash/cli/tests/lib_cli.bats index dc3339e..68ee84a 100644 --- a/lib/bash/cli/tests/lib_cli.bats +++ b/lib/bash/cli/tests/lib_cli.bats @@ -296,6 +296,33 @@ EOF base_cli_parse optional_repeat -- run target-only } +@test "required positionals cannot follow optional or defaulted positionals" { + base_cli_model_init ordering name=ordering + base_cli_command ordering run "Run" + base_cli_positional ordering run context + + bats_run base_cli_positional ordering run target required=true + [ "$status" -eq 2 ] + [[ "$output" == *"cannot declare required positional 'target' after optional positional 'context'"* ]] + [ "${__base_bash_libs_cli_models[ordering\|command\|positionals\|run]}" = context ] + + base_cli_model_init defaulted_order name=defaulted-order + base_cli_command defaulted_order run "Run" + base_cli_positional defaulted_order run context default=working + + bats_run base_cli_positional defaulted_order run target required=true + [ "$status" -eq 2 ] + [[ "$output" == *"cannot declare required positional 'target' after optional positional 'context'"* ]] + + base_cli_model_init valid_order name=valid-order + base_cli_command valid_order run "Run" + base_cli_positional valid_order run target required=true + base_cli_positional valid_order run context default=working + base_cli_parse valid_order -- run target-value + [ "${BASE_BASH_LIBS_CLI_RESULT_POSITIONALS[0]}" = target-value ] + [ "${BASE_BASH_LIBS_CLI_RESULT_POSITIONALS[1]}" = working ] +} + @test "quick declarations enforce required repeatable positional tails" { base_cli_declare table_repeat \ 'model|name=table-repeat' \