diff --git a/README.md b/README.md index d6629d0..97841c6 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,11 @@ * subcommand dispatch * help string formatting * tab completion for interactive shells -* a single, dependency-free, pure POSIX shell file * compatibility with POSIX-based shells; tested with: * ash, bash, dash, ksh, zsh +all in a single POSIX shell file with no dependencies. + Shell scripts are great for gluing terminal programs together. But adding subcommands, scoped options, help strings, and tab completion means a lot of boilerplate that's hard to understand and maintain. Shifu offers an API to describe CLI structure, letting you focus on real functionality. ## Table of contents @@ -167,7 +168,7 @@ dispatch_cmd() { * argument parsing * scoped help generation" # Add deferred binary option, inherited by subcommands - cmd_optb :defer: -g --global -- GLOBAL false true "Global binary option" + cmd_optb :defer: -D --deferred -- DEFERRED false true "Deferred binary option" } # Write first subcommand, referenced in `cmd_subs` above @@ -184,7 +185,7 @@ hello_cmd() { # Write first subcommand target function dispatch_hello() { - [ "$GLOBAL" = true ] && message="🌐 " || message="" + [ "$DEFERRED" = true ] && message="☝ " || message="" echo "${message}Hello, $NAME!" } @@ -204,37 +205,37 @@ echo_cmd() { # Write second subcommand target function dispatch_echo() { # Use variables populated by option/argument functions - echo "Global binary option: $GLOBAL" - echo "Required option: $REQUIRED" - echo "Option w/ default: $DEFAULT" - echo "Positional argument: $POSITIONAL" + echo "Deferred binary option: $DEFERRED" + echo "Required option: $REQUIRED" + echo "Option w/ default: $DEFAULT" + echo "Positional argument: $POSITIONAL" } # Run root command passing all script arguments shifu_run dispatch_cmd "$@" ``` -The diagram below shows how shifu is connecting together this CLI script to print the value `🌐 Hello, World!` in `dispatch_hello`. +The diagram below shows how shifu is connecting together this CLI script to print the value `☝ Hello, World!` in `dispatch_hello`. ``` ┌───────────── sets to ─────────────┐ │ ┌──────────── true ──────────────┐│ │ │ ▼│ -│ │ examples/dispatch hello -g --name World ─────────────────────┐ -│ │ ▲ ▲ ▲ │ -│ │ │ │ └────────────────────────────┐ │ -│ │ │ └───────────────────────────────┐ │ │ -│ │ dispatch_cmd() { │ ┌─► hello_cmd() { │ │ │ -│ │ cmd_name dispatch ┘ │ cmd_name hello ───┘ │ │ -│ │ cmd_subs hello_cmd echo_cmd ─────┘ ┌── cmd_func dispatch_hello │ │ -│ └── cmd_optb :defer: -g --global \ ┌───┘ cmd_optd -n --name \ ────┘ │ -└────► -- GLOBAL false true \ │ ┌──► -- NAME "mysterious \ │ - "Global binary option" │ │ user" "Name to greet" │ - } │ │ } │ - ┌──────────────────────────────┘ └────────────────────────────────┘ +│ │ examples/dispatch hello -D --name World ──────────────────────┐ +│ │ ▲ ▲ ▲ │ +│ │ │ │ └─────────────────────────────┐ │ +│ │ │ └────────────────────────────────┐ │ │ +│ │ dispatch_cmd() { │ ┌──► hello_cmd() { │ │ │ +│ │ cmd_name dispatch ┘ │ cmd_name hello ───┘ │ │ +│ │ cmd_subs hello_cmd echo_cmd ─────┘ ┌── cmd_func dispatch_hello │ │ +│ └── cmd_optb :defer: -D --deferred \ ┌──┘ cmd_optd -n --name \ ──────┘ │ +└────► -- DEFERRED false true \ │ ┌──► -- NAME "mysterious \ │ + "Deferred binary option" │ │ user" "Name to greet" │ + } │ │ } │ + ┌────────────────────────────────┘ └──────────────────────────────┘ │ └─► dispatch_hello() { - [ "$GLOBAL" = true ] && message="🌐 " || message="" + [ "$DEFERRED" = true ] && message="☝ " || message="" echo "${message}Hello, $NAME!" } ``` @@ -563,7 +564,7 @@ The option and argument declaration order in a command function matters: #### `shifu_cmd_cptf` * Function completion * Function to dynamically generate tab completions for the preceding option or argument -* The function should call `shifu_add_cpts` to register completions +* The function should call [`shifu_add_cpts`](#shifu_add_cpts) to register completions * Example ```sh shifu_cmd_cptf file_ext_completions @@ -624,6 +625,16 @@ Shifu has a few variables that can be set after sourcing to change default behav ### Miscellaneous +#### `shifu_add_cpts` +* Registers one or more strings to add as completions +* Must only be called within functions passed to `shifu_cmd_cptf` +* Example + ```sh + dynamic_completions() { + shifu_add_cpts "$(func_to_get_completions)" + } + ``` + #### `shifu_less` * Creates shorthand aliases for all `shifu_cmd_*` functions without the `shifu_` prefix (aka `cmd_name` instead of `shifu_cmd_name`) * Called after sourcing shifu, typically on the same line diff --git a/assets/dispatch_demo.gif b/assets/dispatch_demo.gif index df5ac56..652bf19 100644 Binary files a/assets/dispatch_demo.gif and b/assets/dispatch_demo.gif differ diff --git a/assets/dispatch_demo.tape b/assets/dispatch_demo.tape index f4123d2..2aa0788 100644 --- a/assets/dispatch_demo.tape +++ b/assets/dispatch_demo.tape @@ -32,7 +32,7 @@ Sleep 200ms Enter Sleep 500ms -Type "examples/dispatch hello -g -n World" +Type "examples/dispatch hello -D -n World" Sleep 200ms Enter Sleep 3000ms @@ -49,7 +49,7 @@ Sleep 200ms Enter Sleep 500ms -Type "examples/dispatch echo -g --required 'provided' \" +Type "examples/dispatch echo -D --required 'provided' \" Enter Type "-d 'not default' example" Sleep 200ms diff --git a/examples/dispatch b/examples/dispatch index dd38963..93c7c4a 100755 --- a/examples/dispatch +++ b/examples/dispatch @@ -11,7 +11,7 @@ dispatch_cmd() { * argument parsing * scoped help generation" - cmd_optb :defer: -g --global -- GLOBAL false true "Global binary option" + cmd_optb :defer: -D --deferred -- DEFERRED false true "Deferred binary option" } hello_cmd() { @@ -24,7 +24,7 @@ hello_cmd() { } dispatch_hello() { - [ "$GLOBAL" = true ] && message="🌐 " || message="" + [ "$DEFERRED" = true ] && message="☝ " || message="" echo "${message}Hello, $NAME!" } @@ -40,10 +40,10 @@ echo_cmd() { } dispatch_echo() { - echo "Global binary option: $GLOBAL" - echo "Required option: $REQUIRED" - echo "Option w/ default: $DEFAULT" - echo "Positional argument: $POSITIONAL" + echo "Deferred binary option: $DEFERRED" + echo "Required option: $REQUIRED" + echo "Option w/ default: $DEFAULT" + echo "Positional argument: $POSITIONAL" } shifu_run dispatch_cmd "$@" diff --git a/shifu b/shifu index a74eb79..dd592e8 100644 --- a/shifu +++ b/shifu @@ -627,7 +627,9 @@ _shifu_complete() { "$shifu_parent" _shifu_set_for_looping shifu_cmds shifu_cmd_subs while [ $# -ne 0 -a -n "${shifu_cmd_subs:-}" ]; do - _shifu_complete_func_args true "$shifu_parent" "$@" + _shifu_complete_func_args true "$shifu_parent" "$@" || _shifu_args_parsed=0 + shift $_shifu_args_parsed + [ $# -eq 0 ] && break shifu_build_comp_defer_option_names="${shifu_comp_option_names:-}" shifu_mode=$shifu_mode_cmds_comp shifu_arg_matched='' @@ -647,6 +649,7 @@ _shifu_complete() { done shifu_comp_option_names="${shifu_build_comp_defer_option_names:-}" _shifu_set_completing_option + [ "$shifu_completing_option" = true ] && shifu_completions="" if [ "$shifu_completing_option" = true -a -n "${shifu_cmd_func:-}" ]; then _shifu_complete_option_names elif [ "$shifu_completing_option" = true -a -n "${shifu_cmd_subs:-}" ]; then @@ -654,7 +657,7 @@ _shifu_complete() { _shifu_complete_option_names elif [ "$shifu_completing_option" = false -a -n "${shifu_cmd_func:-}" ]; then _shifu_complete_func_args false "$shifu_cmd" "$@" - elif [ "$shifu_completing_option" = false -a -n "${shifu_cmd_subs:-}" -a $# -eq 0 ]; then + elif [ "$shifu_completing_option" = false -a -n "${shifu_cmd_subs:-}" -a $# -eq 0 -a -z "${shifu_completions:-}" ]; then _shifu_complete_subcmd_names fi [ -n "${shifu_completions:-}" ] && echo "$shifu_completions" @@ -701,6 +704,7 @@ _shifu_complete_func_args() { shifu_mode=$shifu_mode_args_comp shifu_parse_stage=0 shifu_parse_eager="$1"; shifu_cmd="$2"; shift 2 + _shifu_args_parsed=0 shifu_case_stmt="case \"\${1:-}\" in " if [ "$shifu_parse_eager" = false -a -n "${shifu_defer_comp_case:-}" ]; then shifu_case_stmt="${shifu_case_stmt} @@ -710,27 +714,30 @@ ${shifu_defer_comp_case:-}" shifu_last_arg_is_eager=true "$shifu_cmd" case "${shifu_current_word:-}" in - -*) return 0 ;; + -*) [ "$shifu_parse_eager" = false ] && return 0 ;; esac if [ $shifu_parse_stage -eq 0 -a $shifu_arg_completion_set = false ]; then shifu_case_stmt="$shifu_case_stmt shift ;;" fi if [ "$shifu_parse_eager" = true ]; then - _shifu_append_on_new_line shifu_case_stmt " *) break ;;" + _shifu_append_on_new_line shifu_case_stmt " *) _shifu_comp_done=true ;;" elif [ $shifu_parse_stage -ne 0 ]; then - _shifu_append_on_new_line shifu_case_stmt " break ;;" + _shifu_append_on_new_line shifu_case_stmt " _shifu_comp_done=true ;;" elif [ $shifu_arg_completion_set = true ]; then - _shifu_append_on_new_line shifu_case_stmt " *) break ;;" + _shifu_append_on_new_line shifu_case_stmt " *) _shifu_comp_done=true ;;" else - _shifu_append_on_new_line shifu_case_stmt " break ;;" + _shifu_append_on_new_line shifu_case_stmt " _shifu_comp_done=true ;;" fi _shifu_append_on_new_line shifu_case_stmt "esac" + _shifu_args_parsed=$# _shifu_n_args=$# - while true; do - eval "$shifu_case_stmt" 2>/dev/null || exit 1 - [ $# -eq $_shifu_n_args ] && exit 1 + _shifu_comp_done=false + while [ "$_shifu_comp_done" = false ]; do + eval "$shifu_case_stmt" 2>/dev/null || return 1 + [ $# -eq $_shifu_n_args ] && _shifu_comp_done=true _shifu_n_args=$# done + _shifu_args_parsed=$((_shifu_args_parsed - $#)) } _shifu_filter_matching_options() { diff --git a/tests/test_shifu.sh b/tests/test_shifu.sh index 31a8000..408a4a8 100644 --- a/tests/test_shifu.sh +++ b/tests/test_shifu.sh @@ -15,9 +15,9 @@ shifu_test_root_cmd() { shifu_cmd_help "Test root cmd help" shifu_cmd_subs shifu_test_sub_one_cmd shifu_test_sub_two_cmd - shifu_cmd_optb :defer: -g --global-bin -- GLOBAL_BIN false true "A test global bin cmd arg" - shifu_cmd_optd :defer: -G --global-def -- GLOBAL_DEF global_def "A test global def cmd arg" - shifu_cmd_cpte global_one global_two global_three + shifu_cmd_optb :defer: -g --defer-bin -- DEFER_BIN false true "A test deferred binary arg" + shifu_cmd_optd :defer: -G --defer-def -- DEFER_DEF defer_def "A test deferred default arg" + shifu_cmd_cpte defer_one defer_two defer_three } shifu_test_sub_one_cmd() { @@ -25,8 +25,8 @@ shifu_test_sub_one_cmd() { shifu_cmd_help "Test sub one cmd help" shifu_cmd_subs shifu_test_leaf_one_cmd shifu_test_leaf_two_cmd - shifu_cmd_optd :defer: -S --sub-global -- SUB_GLOBAL sub_global "A test sub-one global arg" - shifu_cmd_cptf make_fake_sub_global_completions + shifu_cmd_optd :defer: -S --sub-defer -- SUB_DEFER sub_defer "A test sub-one deferred arg" + shifu_cmd_cptf make_fake_sub_defer_threeompletions } shifu_test_sub_two_cmd() { @@ -34,8 +34,8 @@ shifu_test_sub_two_cmd() { shifu_cmd_help "Test sub two cmd help" shifu_cmd_subs shifu_test_leaf_three_cmd shifu_test_leaf_four_cmd - shifu_cmd_optd :eager: -l --local-test -- LOCAL_TEST local-test "A test local cmd arg" - shifu_cmd_cpte local option test + shifu_cmd_optd :eager: -e --eager-test -- EAGER_TEST eager-test "A test eager arg" + shifu_cmd_cpte eager option test } shifu_test_leaf_one_cmd() { @@ -123,8 +123,8 @@ make_fake_remaining_completions() { shifu_add_cpts remaining args } -make_fake_sub_global_completions() { - shifu_add_cpts sub_global_a sub_global_b sub_global_c +make_fake_sub_defer_threeompletions() { + shifu_add_cpts sub_defer_one sub_defer_two sub_defer_three } test_shifu_run_zero_args() { @@ -148,20 +148,20 @@ test_shifu_run_good() { shifu_assert_strings_equal output "$expected" "$actual" } -test_shifu_run_good_cmd_global_arg() { - shifu_run shifu_test_root_cmd sub-two leaf-three -g -G global_val one two +test_shifu_run_good_cmd_defer_arg() { + shifu_run shifu_test_root_cmd sub-two leaf-three -g -G defer_val one two shifu_assert_zero exit_code $? - shifu_assert_equal global_bin "$GLOBAL_BIN" true - shifu_assert_equal global_def "$GLOBAL_DEF" global_val + shifu_assert_equal defer_bin "$DEFER_BIN" true + shifu_assert_equal defer_def "$DEFER_DEF" defer_val shifu_assert_equal leaf_three_args "$leaf_three_args" "one two" } -test_shifu_run_good_cmd_global_and_local_arg() { - shifu_run shifu_test_root_cmd sub-two -l local-val leaf-three -g -G global_val one two +test_shifu_run_good_cmd_defer_and_eager_arg() { + shifu_run shifu_test_root_cmd sub-two -e eager-val leaf-three -g -G defer_val one two shifu_assert_zero exit_code $? - shifu_assert_equal local_test "$LOCAL_TEST" "local-val" - shifu_assert_equal global_bin "$GLOBAL_BIN" true - shifu_assert_equal global_def "$GLOBAL_DEF" global_val + shifu_assert_equal eager_test "$EAGER_TEST" "eager-val" + shifu_assert_equal defer_bin "$DEFER_BIN" true + shifu_assert_equal defer_def "$DEFER_DEF" defer_val shifu_assert_equal leaf_three_args "$leaf_three_args" "one two" } @@ -237,11 +237,11 @@ shifu_test_required_options_cmd() { shifu_cmd_name required-options shifu_cmd_subs shifu_test_leaf_three_cmd - shifu_cmd_optr :eager: -l --local -- LOCAL_TEST "A test required local cmd arg" - shifu_cmd_optr :defer: -g --global -- GLOBAL_TEST "A test required global cmd arg" + shifu_cmd_optr :eager: -e --eager -- EAGER_TEST "A test required eager arg" + shifu_cmd_optr :defer: -g --defer -- DEFER_TEST "A test required deferred arg" } -test_shifu_run_required_local_and_global_options() { +test_shifu_run_required_eager_and_defer_options() { run_test() { test_cmd_args="$1" _shifu_set_for_looping test_cmd_args test_cmd_args @@ -251,9 +251,9 @@ test_shifu_run_required_local_and_global_options() { } shifu_parameterize_test \ run_test 3 \ - both_set "-l local leaf-three -g global" 0 "" \ - local_set "-l local leaf-three" 1 "Required variable, GLOBAL_TEST, is not set" \ - none_set "leaf-three" 1 "Required variable, LOCAL_TEST, is not set" + both_set "-e eager leaf-three -g defer" 0 "" \ + eager_set "-e eager leaf-three" 1 "Required variable, DEFER_TEST, is not set" \ + none_set "leaf-three" 1 "Required variable, EAGER_TEST, is not set" } shifu_test_option_missing_value_cmd() { @@ -292,16 +292,16 @@ Subcommands Test sub two cmd help Options - -g, --global-bin - A test global bin cmd arg + -g, --defer-bin + A test deferred binary arg Default: false, set: true - -G, --global-def [GLOBAL_DEF] - A test global def cmd arg - Default: global_def + -G, --defer-def [DEFER_DEF] + A test deferred default arg + Default: defer_def -h, --help Show this help' )" - # TODO: I don't think the global option should show up in this help string + # TODO: I don't think the defer option should show up in this help string actual=$(shifu_run shifu_test_root_cmd bad sub-one leaf-two one two 2>&1) shifu_assert_non_zero exit_code $? shifu_assert_strings_equal error_message "$expected" "$actual" @@ -319,9 +319,9 @@ Subcommands Test leaf two cmd help Options - -S, --sub-global [SUB_GLOBAL] - A test sub-one global arg - Default: sub_global + -S, --sub-defer [SUB_DEFER] + A test sub-one deferred arg + Default: sub_defer -h, --help Show this help' )" @@ -342,9 +342,9 @@ Subcommands Test leaf four cmd help Options - -l, --local-test [LOCAL_TEST] - A test local cmd arg - Default: local-test + -e, --eager-test [EAGER_TEST] + A test eager arg + Default: eager-test -h, --help Show this help' )" @@ -353,7 +353,7 @@ Options shifu_assert_strings_equal error_message "$expected" "$actual" } -test_shifu_run_bad_cmd_global_and_local_arg() { +test_shifu_run_bad_cmd_defer_and_eager_arg() { expected="$( echo 'Invalid option: -g' printf 'Test sub two cmd help @@ -365,13 +365,13 @@ Subcommands Test leaf four cmd help Options - -l, --local-test [LOCAL_TEST] - A test local cmd arg - Default: local-test + -e, --eager-test [EAGER_TEST] + A test eager arg + Default: eager-test -h, --help Show this help' )" - actual=$(shifu_run shifu_test_root_cmd sub-two -l local-test -g leaf-three one two) + actual=$(shifu_run shifu_test_root_cmd sub-two -e eager-test -g leaf-three one two) shifu_assert_non_zero exit_code $? shifu_assert_strings_equal error_message "$expected" "$actual" } @@ -454,12 +454,12 @@ Subcommands Test sub two cmd help Options - -g, --global-bin - A test global bin cmd arg + -g, --defer-bin + A test deferred binary arg Default: false, set: true - -G, --global-def [GLOBAL_DEF] - A test global def cmd arg - Default: global_def + -G, --defer-def [DEFER_DEF] + A test deferred default arg + Default: defer_def -h, --help Show this help' ) @@ -467,30 +467,30 @@ Options shifu_assert_strings_equal error_message "$expected" "$actual" } -shifu_test_bad_positional_global_arg_cmd() { - shifu_cmd_name bad-global +shifu_test_bad_positional_defer_arg_cmd() { + shifu_cmd_name bad-defer shifu_cmd_subs does not matter shifu_cmd_argr bad_positional "Bad help" } -test_shifu_run_bad_positional_global_arg_cmd() { +test_shifu_run_bad_positional_defer_arg_cmd() { expected="Positional arguments can only be used in leaf commands" - actual=$(shifu_run shifu_test_bad_positional_global_arg_cmd does not matter 2>&1) + actual=$(shifu_run shifu_test_bad_positional_defer_arg_cmd does not matter 2>&1) shifu_assert_non_zero exit_code $? shifu_assert_strings_equal error_message "$expected" "$actual" } -shifu_test_bad_positional_local_arg_cmd() { - shifu_cmd_name bad-local +shifu_test_bad_positional_eager_arg_cmd() { + shifu_cmd_name bad-eager shifu_cmd_subs does not matter shifu_cmd_argr bad_positional "Bad help" } -test_shifu_run_bad_positional_local_arg_cmd() { +test_shifu_run_bad_positional_eager_arg_cmd() { expected="Positional arguments can only be used in leaf commands" - actual=$(shifu_run shifu_test_bad_positional_local_arg_cmd does not matter 2>&1) + actual=$(shifu_run shifu_test_bad_positional_eager_arg_cmd does not matter 2>&1) shifu_assert_non_zero exit_code $? shifu_assert_strings_equal error_message "$expected" "$actual" } @@ -585,28 +585,28 @@ Subcommands Test sub two cmd help Options - -g, --global-bin - A test global bin cmd arg + -g, --defer-bin + A test deferred binary arg Default: false, set: true - -G, --global-def [GLOBAL_DEF] - A test global def cmd arg - Default: global_def + -G, --defer-def [DEFER_DEF] + A test deferred default arg + Default: defer_def -h, --help Show this help' actual=$(shifu_run shifu_test_root_cmd -h) shifu_assert_strings_equal help_message "$expected" "$actual" } -test_shifu_help_global() { +test_shifu_help_defer() { expected='Test leaf three cmd help Options - -g, --global-bin - A test global bin cmd arg + -g, --defer-bin + A test deferred binary arg Default: false, set: true - -G, --global-def [GLOBAL_DEF] - A test global def cmd arg - Default: global_def + -G, --defer-def [DEFER_DEF] + A test deferred default arg + Default: defer_def -h, --help Show this help' actual=$(shifu_run shifu_test_root_cmd sub-two leaf-three -h 2>&1) @@ -655,15 +655,15 @@ test_shifu_complete_func_args_remaining_func() { shifu_assert_strings_equal completion "$expected" "$actual" } -test_shifu_complete_func_args_local_func() { - expected="local option test" - actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete cur_word sub-two -l) +test_shifu_complete_func_args_eager_func() { + expected="eager option test" + actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete cur_word sub-two -e) shifu_assert_strings_equal completion "$expected" "$actual" } -test_shifu_complete_func_args_local_func_bad() { +test_shifu_complete_func_args_eager_func_bad() { expected="" - actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete cur_word -l) + actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete cur_word -e) shifu_assert_strings_equal completion "$expected" "$actual" } @@ -704,49 +704,49 @@ test_shifu_complete_options_only_when_word_starts_with_dash() { shifu_assert_strings_equal completion "$expected" "$actual" } -test_shifu_complete_global_option_names() { - expected="--global-bin --global-def" - actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete --global sub-one leaf-one) +test_shifu_complete_defer_option_names() { + expected="--defer-bin --defer-def" + actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete --defer sub-one leaf-one) shifu_assert_strings_equal completion "$expected" "$actual" } -test_shifu_complete_global_option_names_no_func() { +test_shifu_complete_defer_option_names_no_func() { expected="" - actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete --global sub-one) + actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete --defer sub-one) shifu_assert_strings_equal completion "$expected" "$actual" } -test_shifu_complete_local_option_names_on_subcommand() { - expected="--local-test" - actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete --local sub-two) +test_shifu_complete_eager_option_names_on_subcommand() { + expected="--eager-test" + actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete --eager sub-two) shifu_assert_strings_equal completion "$expected" "$actual" } -test_shifu_complete_global_option_values() { - expected="global_one global_two global_three" +test_shifu_complete_defer_option_values() { + expected="defer_one defer_two defer_three" actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete "" sub-one leaf-one -G) shifu_assert_strings_equal completion "$expected" "$actual" } -test_shifu_complete_global_option_values_no_func() { +test_shifu_complete_defer_option_values_no_func() { expected="" actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete "" sub-one -G) shifu_assert_strings_equal completion "$expected" "$actual" } -test_shifu_complete_global_option_values_at_root() { +test_shifu_complete_defer_option_values_at_root() { expected="" actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete "" -G) shifu_assert_strings_equal completion "$expected" "$actual" } -test_shifu_complete_global_option_func_values() { - expected="sub_global_a sub_global_b sub_global_c" +test_shifu_complete_defer_option_func_values() { + expected="sub_defer_one sub_defer_two sub_defer_three" actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete "" sub-one leaf-one -S) shifu_assert_strings_equal completion "$expected" "$actual" } -test_shifu_complete_global_option_func_values_no_func() { +test_shifu_complete_defer_option_func_values_no_func() { expected="" actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete "" sub-one -S) shifu_assert_strings_equal completion "$expected" "$actual" @@ -782,34 +782,103 @@ shifu_test_path_glob_no_pattern_cmd() { shifu_cmd_cptp :glob: } -shifu_test_global_path_files_cmd() { - shifu_cmd_name global-path-files +shifu_test_defer_path_files_cmd() { + shifu_cmd_name defer-path-files shifu_cmd_subs shifu_test_leaf_one_cmd shifu_cmd_optd :defer: -c --config -- CONFIG config_default "Config file" shifu_cmd_cptp :files: } -shifu_test_global_path_dirs_cmd() { - shifu_cmd_name global-path-dirs +shifu_test_defer_path_dirs_cmd() { + shifu_cmd_name defer-path-dirs shifu_cmd_subs shifu_test_leaf_one_cmd shifu_cmd_optd :defer: -d --dir -- DIR_ARG dir_default "Directory argument" shifu_cmd_cptp :dirs: } -shifu_test_global_path_glob_cmd() { - shifu_cmd_name global-path-glob +shifu_test_defer_path_glob_cmd() { + shifu_cmd_name defer-path-glob shifu_cmd_subs shifu_test_leaf_one_cmd shifu_cmd_optd :defer: -g --glob -- GLOB_ARG glob_default "Glob argument" shifu_cmd_cptp :glob: "*.txt" } -shifu_test_global_path_glob_no_pattern_cmd() { - shifu_cmd_name global-path-glob-no-pattern +shifu_test_defer_path_glob_no_pattern_cmd() { + shifu_cmd_name defer-path-glob-no-pattern shifu_cmd_subs shifu_test_leaf_one_cmd shifu_cmd_optd :defer: -g --glob -- GLOB_ARG glob_default "Glob argument" shifu_cmd_cptp :glob: } +shifu_test_eager_bin_cmd() { + shifu_cmd_name eager-bin + shifu_cmd_help "Test cmd with eager bin" + shifu_cmd_subs shifu_test_leaf_three_cmd shifu_test_leaf_four_cmd + + shifu_cmd_optb :eager: -b --bin-flag -- BIN_FLAG false true "A test eager bin flag" +} + +shifu_test_eager_root_cmd() { + shifu_cmd_name eager-root + shifu_cmd_help "Test root with eager option" + shifu_cmd_subs shifu_test_sub_multi_eager_cmd shifu_test_leaf_three_cmd + + shifu_cmd_optd :eager: -r --eager-root -- EAGER_ROOT eager_root "A test eager root option" + shifu_cmd_cpte root_one root_two root_three +} + +shifu_test_sub_multi_eager_cmd() { + shifu_cmd_name sub-multi-eager + shifu_cmd_help "Test sub with multiple eager options" + shifu_cmd_subs shifu_test_leaf_three_cmd shifu_test_leaf_four_cmd + + shifu_cmd_optb :eager: -b --bin-opt -- BIN_OPT false true "A test eager binary option" + shifu_cmd_optd :eager: -d --def-opt -- DEF_OPT default "A test eager default option" + shifu_cmd_cpte data_one data_two data_three +} + +test_shifu_complete_subcmds_after_eager_optb() { + expected="leaf-three leaf-four" + actual=$(_shifu_complete shifu_test_eager_bin_cmd --shifu-complete "" -b) + shifu_assert_strings_equal output "$expected" "$actual" +} + +test_shifu_complete_subcmds_after_eager_optd() { + expected="leaf-three leaf-four" + actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete "" sub-two -e some_value) + shifu_assert_strings_equal output "$expected" "$actual" +} + +test_shifu_complete_subcmds_after_eager_optr() { + expected="leaf-three" + actual=$(_shifu_complete shifu_test_required_options_cmd --shifu-complete "" -e some_value) + shifu_assert_strings_equal output "$expected" "$actual" +} + +test_shifu_complete_subcmds_after_multi_eager() { + expected="leaf-three leaf-four" + actual=$(_shifu_complete shifu_test_sub_multi_eager_cmd --shifu-complete "" -b -d some_value) + shifu_assert_strings_equal output "$expected" "$actual" +} + +test_shifu_complete_leaf_options_through_eager_parent() { + expected="--fake-arg" + actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete --f sub-two -e some_value leaf-four) + shifu_assert_strings_equal output "$expected" "$actual" +} + +test_shifu_complete_defer_option_values_through_eager_parent() { + expected="defer_one defer_two defer_three" + actual=$(_shifu_complete shifu_test_root_cmd --shifu-complete "" sub-two -e some_value leaf-four -G) + shifu_assert_strings_equal output "$expected" "$actual" +} + +test_shifu_complete_nested_eager_at_multiple_levels() { + expected="leaf-three leaf-four" + actual=$(_shifu_complete shifu_test_eager_root_cmd --shifu-complete "" -r root_one sub-multi-eager -b -d data_one) + shifu_assert_strings_equal output "$expected" "$actual" +} + test_shifu_complete_path() { run_test() { cmd=$1; complete_args="$2"; expected=$3 @@ -819,18 +888,18 @@ test_shifu_complete_path() { } shifu_parameterize_test \ run_test 3 \ - local_files_option shifu_test_path_files_cmd "-f" "SHIFU_COMP_PATH_FILES" \ - local_files_positional shifu_test_path_files_cmd "-f filled" "SHIFU_COMP_PATH_FILES" \ - local_dirs shifu_test_path_dirs_cmd "-d" "SHIFU_COMP_PATH_DIRS" \ - local_glob shifu_test_path_glob_cmd "-g" "SHIFU_COMP_PATH_GLOB:*.txt" \ - local_glob_no_pattern shifu_test_path_glob_no_pattern_cmd "-g" "" \ - global_files shifu_test_global_path_files_cmd "leaf-one -c" "SHIFU_COMP_PATH_FILES" \ - global_files_no_sub shifu_test_global_path_files_cmd "-c" "" \ - global_dirs shifu_test_global_path_dirs_cmd "leaf-one -d" "SHIFU_COMP_PATH_DIRS" \ - global_dirs_no_sub shifu_test_global_path_dirs_cmd "-d" "" \ - global_glob shifu_test_global_path_glob_cmd "leaf-one -g" "SHIFU_COMP_PATH_GLOB:*.txt" \ - global_glob_no_sub shifu_test_global_path_glob_cmd "-g" "" \ - global_glob_no_pattern shifu_test_global_path_glob_no_pattern_cmd "leaf-one -g" "" + eager_files_option shifu_test_path_files_cmd "-f" "SHIFU_COMP_PATH_FILES" \ + eager_files_positional shifu_test_path_files_cmd "-f filled" "SHIFU_COMP_PATH_FILES" \ + eager_dirs shifu_test_path_dirs_cmd "-d" "SHIFU_COMP_PATH_DIRS" \ + eager_glob shifu_test_path_glob_cmd "-g" "SHIFU_COMP_PATH_GLOB:*.txt" \ + eager_glob_no_pattern shifu_test_path_glob_no_pattern_cmd "-g" "" \ + defer_files shifu_test_defer_path_files_cmd "leaf-one -c" "SHIFU_COMP_PATH_FILES" \ + defer_files_no_sub shifu_test_defer_path_files_cmd "-c" "" \ + defer_dirs shifu_test_defer_path_dirs_cmd "leaf-one -d" "SHIFU_COMP_PATH_DIRS" \ + defer_dirs_no_sub shifu_test_defer_path_dirs_cmd "-d" "" \ + defer_glob shifu_test_defer_path_glob_cmd "leaf-one -g" "SHIFU_COMP_PATH_GLOB:*.txt" \ + defer_glob_no_sub shifu_test_defer_path_glob_cmd "-g" "" \ + defer_glob_no_pattern shifu_test_defer_path_glob_no_pattern_cmd "leaf-one -g" "" } shifu_test_bad_multiple_completions_single_arg_cmd() {