diff --git a/README.md b/README.md index 36eb4fff..7413d127 100644 --- a/README.md +++ b/README.md @@ -324,6 +324,7 @@ On the command line, options can be given as: Extra positional arguments will cause the program to exit, so at least one positional option with a vector is recommended if you want to allow extraneous arguments. If you set `.allow_extras()` on the main `App`, you will not get an error. You can access the missing options using `remaining` (if you have subcommands, `app.remaining(true)` will get all remaining options, subcommands included). +If the remaining arguments are to processed by another `App` then the function `remaining_for_passthrough()`🚧 can be used to get the remaining arguments in reverse order such that `app.parse(vector)` works directly and could even be used inside a subcommand callback. You can access a vector of pointers to the parsed options in the original order using `parse_order()`. If `--` is present in the command line that does not end an unlimited option, then diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c1731f67..16cea0c7 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -189,6 +189,14 @@ set_property(TEST prefix_command PROPERTY PASS_REGULAR_EXPRESSION "Prefix: 3 : 2 : 1" "Remaining commands: other one two 3") +add_cli_exe(callback_passthrough callback_passthrough.cpp) +add_test(NAME callback_passthrough1 COMMAND callback_passthrough --argname t2 --t2 test) +set_property(TEST callback_passthrough1 PROPERTY PASS_REGULAR_EXPRESSION + "the value is now test") +add_test(NAME callback_passthrough2 COMMAND callback_passthrough --arg EEEK --argname arg) +set_property(TEST callback_passthrough2 PROPERTY PASS_REGULAR_EXPRESSION + "the value is now EEEK") + add_cli_exe(enum enum.cpp) add_test(NAME enum_pass COMMAND enum -l 1) add_test(NAME enum_fail COMMAND enum -l 4) diff --git a/examples/callback_passthrough.cpp b/examples/callback_passthrough.cpp new file mode 100644 index 00000000..8d0203b1 --- /dev/null +++ b/examples/callback_passthrough.cpp @@ -0,0 +1,20 @@ +#include "CLI/CLI.hpp" + +int main(int argc, char **argv) { + + CLI::App app("callback_passthrough"); + app.allow_extras(); + std::string argName; + std::string val; + app.add_option("--argname", argName, "the name of the custom command line argument"); + app.callback([&app, &val, &argName]() { + if(!argName.empty()) { + CLI::App subApp; + subApp.add_option("--" + argName, val, "custom argument option"); + subApp.parse(app.remaining_for_passthrough()); + } + }); + + CLI11_PARSE(app, argc, argv); + std::cout << "the value is now " << val << '\n'; +} diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 8443711d..c08018d7 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -1295,7 +1295,7 @@ class App { std::vector args; for(int i = argc - 1; i > 0; i--) args.emplace_back(argv[i]); - parse(args); + parse(std::move(args)); } /// Parse a single string as if it contained command line arguments. @@ -1325,7 +1325,7 @@ class App { args.erase(std::remove(args.begin(), args.end(), std::string{}), args.end()); std::reverse(args.begin(), args.end()); - parse(args); + parse(std::move(args)); } /// The real work is done here. Expects a reversed vector. @@ -1349,6 +1349,26 @@ class App { run_callback(); } + /// The real work is done here. Expects a reversed vector. + void parse(std::vector &&args) { + // Clear if parsed + if(parsed_ > 0) + clear(); + + // parsed_ is incremented in commands/subcommands, + // but placed here to make sure this is cleared when + // running parse after an error is thrown, even by _validate or _configure. + parsed_ = 1; + _validate(); + _configure(); + // set the parent as nullptr as this object should be the top now + parent_ = nullptr; + parsed_ = 0; + + _parse(std::move(args)); + run_callback(); + } + /// Provide a function to print a help message. The function gets access to the App pointer and error. void failure_message(std::function function) { failure_message_ = function; @@ -1755,6 +1775,13 @@ class App { return miss_list; } + /// This returns the missing options in a form ready for processing by another command line program + std::vector remaining_for_passthrough(bool recurse = false) const { + std::vector miss_list = remaining(recurse); + std::reverse(std::begin(miss_list), std::end(miss_list)); + return miss_list; + } + /// This returns the number of remaining options, minus the -- separator size_t remaining_size(bool recurse = false) const { auto remaining_options = static_cast(std::count_if( @@ -1878,7 +1905,7 @@ class App { return detail::Classifier::SHORT; if((allow_windows_style_options_) && (detail::split_windows_style(current, dummy1, dummy2))) return detail::Classifier::WINDOWS; - if((current == "++") && !name_.empty()) + if((current == "++") && !name_.empty() && parent_ != nullptr) return detail::Classifier::SUBCOMMAND_TERMINATOR; return detail::Classifier::NONE; } @@ -2106,6 +2133,21 @@ class App { _process_requirements(); } + /// Throw an error if anything is left over and should not be. + void _process_extras() { + if(!(allow_extras_ || prefix_command_)) { + size_t num_left_over = remaining_size(); + if(num_left_over > 0) { + throw ExtrasError(remaining(false)); + } + } + + for(App_p &sub : subcommands_) { + if(sub->count() > 0) + sub->_process_extras(); + } + } + /// Throw an error if anything is left over and should not be. /// Modifies the args to fill in the missing items before throwing. void _process_extras(std::vector &args) { @@ -2149,8 +2191,8 @@ class App { // Throw error if any items are left over (depending on settings) _process_extras(args); - // Convert missing (pairs) to extras (string only) - args = remaining(false); + // Convert missing (pairs) to extras (string only) ready for processing in another app + args = remaining_for_passthrough(false); } else if(immediate_callback_) { _process_env(); _process_callbacks(); @@ -2160,6 +2202,23 @@ class App { } } + /// Internal parse function + void _parse(std::vector &&args) { + // this can only be called by the top level in which case parent == nullptr by definition + // operation is simplified + increment_parsed(); + _trigger_pre_parse(args.size()); + bool positional_only = false; + + while(!args.empty()) { + _parse_single(args, positional_only); + } + _process(); + + // Throw error if any items are left over (depending on settings) + _process_extras(); + } + /// Parse one config param, return false if not found in any subcommand, remove if it is /// /// If this has more than one dot.separated.name, go into the subcommand matching it @@ -2227,6 +2286,7 @@ class App { } break; case detail::Classifier::SUBCOMMAND_TERMINATOR: + // treat this like a positional mark if in the parent app args.pop_back(); retval = false; break; diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index 54cf8501..12ec19fe 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -1815,7 +1815,74 @@ TEST_F(TApp, AllowExtrasOrder) { std::vector left_over = app.remaining(); app.parse(left_over); - EXPECT_EQ(app.remaining(), left_over); + EXPECT_EQ(app.remaining(), std::vector({"-f", "-x"})); + EXPECT_EQ(app.remaining_for_passthrough(), left_over); +} + +TEST_F(TApp, AllowExtrasCascade) { + + app.allow_extras(); + + args = {"-x", "45", "-f", "27"}; + ASSERT_NO_THROW(run()); + EXPECT_EQ(app.remaining(), std::vector({"-x", "45", "-f", "27"})); + + std::vector left_over = app.remaining_for_passthrough(); + + CLI::App capp{"cascade_program"}; + int v1 = 0; + int v2 = 0; + capp.add_option("-x", v1); + capp.add_option("-f", v2); + + capp.parse(left_over); + EXPECT_EQ(v1, 45); + EXPECT_EQ(v2, 27); +} +// makes sure the error throws on the rValue version of the parse +TEST_F(TApp, ExtrasErrorRvalueParse) { + + args = {"-x", "45", "-f", "27"}; + EXPECT_THROW(app.parse(std::vector({"-x", "45", "-f", "27"})), CLI::ExtrasError); +} + +TEST_F(TApp, AllowExtrasCascadeDirect) { + + app.allow_extras(); + + args = {"-x", "45", "-f", "27"}; + ASSERT_NO_THROW(run()); + EXPECT_EQ(app.remaining(), std::vector({"-x", "45", "-f", "27"})); + + CLI::App capp{"cascade_program"}; + int v1 = 0; + int v2 = 0; + capp.add_option("-x", v1); + capp.add_option("-f", v2); + + capp.parse(app.remaining_for_passthrough()); + EXPECT_EQ(v1, 45); + EXPECT_EQ(v2, 27); +} + +TEST_F(TApp, AllowExtrasArgModify) { + + int v1 = 0; + int v2 = 0; + app.allow_extras(); + app.add_option("-f", v2); + args = {"27", "-f", "45", "-x"}; + auto cargs = args; + app.parse(args); + EXPECT_EQ(args, std::vector({"45", "-x"})); + + CLI::App capp{"cascade_program"}; + + capp.add_option("-x", v1); + + capp.parse(args); + EXPECT_EQ(v1, 45); + EXPECT_EQ(v2, 27); } // Test horrible error