diff --git a/assets/dispatch_demo.gif b/assets/dispatch_demo.gif index 652bf19..68dc5e0 100644 Binary files a/assets/dispatch_demo.gif and b/assets/dispatch_demo.gif differ diff --git a/shifu b/shifu index dd592e8..e28a8ea 100644 --- a/shifu +++ b/shifu @@ -306,6 +306,48 @@ _shifu_opt_build() { done } +_shifu_add_equals_case_arms() { + shifu_eq_target="$1"; shifu_eq_var="$2" + _shifu_set_for_looping shifu_eq_list shifu_eq_saved_args + for shifu_eq_flag in $shifu_eq_list; do + case "$shifu_eq_flag" in + --) break ;; + --*) _shifu_append_on_new_line "$shifu_eq_target" \ + " $shifu_eq_flag=*) $shifu_eq_var=\\\${1#*=}; shift; _shifu_ack_arg ;;" ;; + esac + done +} + +_shifu_add_equals_defer_case_arms() { + shifu_eq_var="$1" + _shifu_set_for_looping shifu_eq_list shifu_eq_saved_args + for shifu_eq_flag in $shifu_eq_list; do + case "$shifu_eq_flag" in + --) break ;; + --*) shifu_defer_case="$shifu_defer_case + $shifu_eq_flag=*) $shifu_eq_var=\\\${1#*=}; shift; _shifu_ack_arg ;;" ;; + esac + done +} + +_shifu_add_comp_eq_patterns() { + if [ -n "${shifu_comp_eq_patterns:-}" ]; then + shifu_case_stmt="${shifu_case_stmt}${shifu_comp_eq_patterns}" + shifu_comp_eq_patterns="" + fi +} + +_shifu_add_equals_comp_case_arms() { + shifu_eq_target="$1" + _shifu_set_for_looping shifu_eq_list shifu_eq_saved_args + for shifu_eq_flag in $shifu_eq_list; do + case "$shifu_eq_flag" in + --) break ;; + --*) _shifu_append_on_new_line "$shifu_eq_target" " $shifu_eq_flag=*) shift ;;" ;; + esac + done +} + _shifu_validate_flag() { case "$1" in -*) ;; @@ -419,59 +461,71 @@ _shifu_cmd_optd_help() { } _shifu_cmd_optd_build_now() { + shifu_eq_saved_args="$*" _shifu_opt_case_parse "$@" shift $shifu_opt_count _shifu_error_if_invalid_arg_order "$1" $shifu_parse_stage _shifu_set_variable "$1" "$2" _shifu_update_case_stmt_option_value_check shifu_case_stmt="$shifu_case_stmt $1=\$2; shift 2; _shifu_ack_arg 2 ;;" + _shifu_add_equals_case_arms shifu_case_stmt "$1" } _shifu_cmd_optd_build_defer() { + shifu_eq_saved_args="$*" _shifu_opt_defer_parse "$@" shift $shifu_opt_count _shifu_set_variable "$1" "$2" _shifu_update_case_stmt_option_value_check defer shifu_defer_case="$shifu_defer_case $1=\\\$2; shift 2; _shifu_ack_arg 2 ;;" shifu_defer_help="${shifu_defer_help:-} [$1]\n $3\n Default: $2" + _shifu_add_equals_defer_case_arms "$1" } _shifu_cmd_optv_build_comp_now() { + shifu_eq_saved_args="$*" _shifu_opt_comp_eager_parse "$@" shift $shifu_opt_count _shifu_error_if_invalid_arg_order "$1" $shifu_parse_stage shifu_case_stmt="$shifu_case_stmt) shift;" + _shifu_add_equals_comp_case_arms shifu_comp_eq_patterns } _shifu_cmd_optv_build_comp_defer() { + shifu_eq_saved_args="$*" _shifu_opt_build_comp_defer_parse "$@" shift $shifu_opt_count shifu_defer_comp_case="${shifu_defer_comp_case:-}) shift;" + _shifu_add_equals_comp_case_arms shifu_comp_eq_patterns } _shifu_cmd_optr_help() { _shifu_opt_help_parse "$@" shift $shifu_opt_count _shifu_error_if_invalid_arg_order "$1" $shifu_help_stage - shifu_help_options="$shifu_help_options [$1]\n $2\n Required" + shifu_help_options="$shifu_help_options $1\n $2\n Required" } _shifu_cmd_optr_build_now() { + shifu_eq_saved_args="$*" _shifu_opt_case_parse "$@" shift $shifu_opt_count _shifu_error_if_invalid_arg_order "$1" $shifu_parse_stage _shifu_list_append shifu_required_variables "$1" _shifu_update_case_stmt_option_value_check shifu_case_stmt="$shifu_case_stmt $1=\$2; shift 2; _shifu_ack_arg 2 ;;" + _shifu_add_equals_case_arms shifu_case_stmt "$1" } _shifu_cmd_optr_build_defer() { + shifu_eq_saved_args="$*" _shifu_opt_defer_parse "$@" shift $shifu_opt_count _shifu_list_append shifu_required_variables "$1" _shifu_update_case_stmt_option_value_check defer shifu_defer_case="$shifu_defer_case $1=\\\$2; shift 2; _shifu_ack_arg 2 ;;" - shifu_defer_help="${shifu_defer_help:-} [$1]\n $2\n Required" + shifu_defer_help="${shifu_defer_help:-} $1\n $2\n Required" + _shifu_add_equals_defer_case_arms "$1" } _shifu_cmd_optd_build_comp_now() { _shifu_cmd_optv_build_comp_now "$@"; } @@ -505,6 +559,7 @@ _shifu_cmd_argr_build_comp_now() { _shifu_opt_comp_preamble if [ $shifu_parse_stage -eq 0 ]; then shifu_parse_stage=1 + _shifu_add_comp_eq_patterns _shifu_append_on_new_line shifu_case_stmt " *)" elif [ $shifu_parse_stage -gt 1 ]; then _shifu_error "No arguments after remaining are declared: $1" @@ -526,6 +581,7 @@ _shifu_cmd_args_build_now() { _shifu_cmd_args_build_comp_now() { _shifu_opt_comp_preamble [ $shifu_parse_stage -eq 2 ] && _shifu_error "Can only declare remaining arguments once: $1" + [ $shifu_parse_stage -eq 0 ] && _shifu_add_comp_eq_patterns shifu_parse_stage=2 } @@ -705,6 +761,7 @@ _shifu_complete_func_args() { shifu_parse_stage=0 shifu_parse_eager="$1"; shifu_cmd="$2"; shift 2 _shifu_args_parsed=0 + shifu_comp_eq_patterns="" shifu_case_stmt="case \"\${1:-}\" in " if [ "$shifu_parse_eager" = false -a -n "${shifu_defer_comp_case:-}" ]; then shifu_case_stmt="${shifu_case_stmt} @@ -719,6 +776,7 @@ ${shifu_defer_comp_case:-}" if [ $shifu_parse_stage -eq 0 -a $shifu_arg_completion_set = false ]; then shifu_case_stmt="$shifu_case_stmt shift ;;" fi + _shifu_add_comp_eq_patterns if [ "$shifu_parse_eager" = true ]; then _shifu_append_on_new_line shifu_case_stmt " *) _shifu_comp_done=true ;;" elif [ $shifu_parse_stage -ne 0 ]; then diff --git a/tests/test_shifu.sh b/tests/test_shifu.sh index 4391db3..d71487a 100644 --- a/tests/test_shifu.sh +++ b/tests/test_shifu.sh @@ -276,6 +276,69 @@ test_shifu_run_option_missing_value() { -- short_option "-o" } +test_shifu_run_optd_equals_value() { + run_test() { + shifu_test_params option expected -- "$@" + shifu_run shifu_test_all_options_cmd \ + -a req_flag_value --option-req req_option_value \ + --flag-option-req req_flag_option_value \ + $option \ + positional_arg_value_one positional_arg_value_two + shifu_assert_zero exit_code $? + shifu_assert_equal option_def "$OPTION_DEF" "$expected" + } + shifu_parameterize_test run_test \ + -- value "--option-def=custom" "custom" \ + -- nested "--option-def=key=value" "key=value" \ + -- empty "--option-def=" "" +} + +test_shifu_run_optr_equals_value() { + run_test() { + shifu_test_params option expected -- "$@" + shifu_run shifu_test_all_options_cmd \ + -a req_flag_value --flag-option-req req_flag_option_value \ + $option \ + positional_arg_value_one positional_arg_value_two + shifu_assert_zero exit_code $? + shifu_assert_equal option_req "$OPTION_REQ" "$expected" + } + shifu_parameterize_test run_test \ + -- value "--option-req=custom_req" "custom_req" \ + -- nested "--option-req=key=value" "key=value" \ + -- empty "--option-req=" "" +} + +test_shifu_run_defer_and_eager_equals_value() { + run_test() { + shifu_test_params @cmd_args var expected -- "$@" + shifu_run shifu_test_root_cmd $cmd_args + shifu_assert_zero exit_code $? + eval "shifu_assert_equal $var \"\$$var\" \"$expected\"" + } + shifu_parameterize_test run_test \ + -- defer "sub-two leaf-three -g --defer-def=custom_defer one two" \ + DEFER_DEF "custom_defer" \ + -- eager "sub-two --eager-test=custom_eager leaf-three one two" \ + EAGER_TEST "custom_eager" +} + +test_shifu_run_short_flag_equals_errors() { + actual=$(shifu_run shifu_test_all_options_cmd -d=bad_value 2>&1) + shifu_assert_non_zero exit_code $? + shifu_assert_string_contains error_message "$actual" "Invalid option" +} + +test_shifu_run_optb_equals_errors() { + actual=$(shifu_run shifu_test_all_options_cmd \ + -a req_flag_value --option-req req_option_value \ + --flag-option-req req_flag_option_value \ + --option-bin=bad_value \ + positional_arg_value_one positional_arg_value_two 2>&1) + shifu_assert_non_zero exit_code $? + shifu_assert_string_contains error_message "$actual" "Invalid option" +} + test_shifu_run_bad_first_cmd() { expected="$( echo 'Unknown command: bad' @@ -540,7 +603,7 @@ Options -f binary flag help Default: 0, set: 1 - -a [FLAG_REQ] + -a FLAG_REQ required flag help Required -d [FLAG_DEF] @@ -549,7 +612,7 @@ Options --option-bin binary option help Default: 0, set: 1 - --option-req [OPTION_REQ] + --option-req OPTION_REQ required option help Required --option-def [OPTION_DEF] @@ -558,7 +621,7 @@ Options -F, --flag-option-bin binary flag/option help Default: 0, set: 1 - -A, --flag-option-req [FLAG_OPTION_REQ] + -A, --flag-option-req FLAG_OPTION_REQ required flag/option help Required -D, --flag-option-def [FLAG_OPTION_DEF] @@ -689,7 +752,13 @@ test_shifu_complete() { "defer_one defer_two defer_three" \ -- nested_eager_at_multiple_levels \ shifu_test_eager_root_cmd "cur_word -r root_one sub-multi-eager -b -d data_one" \ - "leaf-three leaf-four" + "leaf-three leaf-four" \ + -- subcmds_after_eager_equals \ + shifu_test_root_cmd "cur_word sub-two --eager-test=some_value" \ + "leaf-three leaf-four" \ + -- leaf_options_through_equals_arg \ + shifu_test_root_cmd "--f sub-two --eager-test=some_value leaf-four" \ + "--fake-arg" } test_shifu_complete_single_dash_with_config_shows_all_options() { @@ -979,10 +1048,14 @@ shifu_parameterize_test() { [ "$pt_arg" = "--" ] && break pt_count=$((pt_count + 1)) done - pt_errors_in=$errors - pt_output_buffer="" - "$pt_test_name" "$@" - if [ $errors -eq $pt_errors_in ]; then + pt_case_output=$( + errors=0 + unset pt_output_buffer + "$pt_test_name" "$@" + exit $errors + ) + pt_case_errors=$? + if [ $pt_case_errors -eq 0 ]; then pt_passed=$((pt_passed + 1)) if [ "${shifu_verbose_tests:-}" = true ]; then printf " $shifu_green%-4s$shifu_reset%s\n" "+" "$pt_case_name" @@ -990,7 +1063,7 @@ shifu_parameterize_test() { else pt_failed=$((pt_failed + 1)) printf " $shifu_red%-4s$shifu_reset%s\n" "x" "$pt_case_name" - [ -n "$pt_output_buffer" ] && echo "$pt_output_buffer" + [ -n "$pt_case_output" ] && echo "$pt_case_output" fi shift $pt_count done