1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 20:23:55 +00:00

add a function to get the remaining arguments in a valid order (#265)

* add a function to get the remaining arguments in a valid order for parse.  and add rvalue reference overloads for parse and _parse so args is not refilled if not needed.

* check a few more tests and verify ExtrasError works on parse(rValue vector)

remove impossible to reach branches in _parse function

* add callback_passthrough example and tests
This commit is contained in:
Philip Top 2019-04-28 07:20:52 -07:00 committed by Henry Schreiner
parent 734af661c6
commit b1036a1ad0
5 changed files with 162 additions and 6 deletions

View File

@ -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. 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 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()`. 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 If `--` is present in the command line that does not end an unlimited option, then

View File

@ -189,6 +189,14 @@ set_property(TEST prefix_command PROPERTY PASS_REGULAR_EXPRESSION
"Prefix: 3 : 2 : 1" "Prefix: 3 : 2 : 1"
"Remaining commands: other one two 3") "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_cli_exe(enum enum.cpp)
add_test(NAME enum_pass COMMAND enum -l 1) add_test(NAME enum_pass COMMAND enum -l 1)
add_test(NAME enum_fail COMMAND enum -l 4) add_test(NAME enum_fail COMMAND enum -l 4)

View File

@ -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';
}

View File

@ -1295,7 +1295,7 @@ class App {
std::vector<std::string> args; std::vector<std::string> args;
for(int i = argc - 1; i > 0; i--) for(int i = argc - 1; i > 0; i--)
args.emplace_back(argv[i]); args.emplace_back(argv[i]);
parse(args); parse(std::move(args));
} }
/// Parse a single string as if it contained command line arguments. /// 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()); args.erase(std::remove(args.begin(), args.end(), std::string{}), args.end());
std::reverse(args.begin(), args.end()); std::reverse(args.begin(), args.end());
parse(args); parse(std::move(args));
} }
/// The real work is done here. Expects a reversed vector. /// The real work is done here. Expects a reversed vector.
@ -1349,6 +1349,26 @@ class App {
run_callback(); run_callback();
} }
/// The real work is done here. Expects a reversed vector.
void parse(std::vector<std::string> &&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. /// Provide a function to print a help message. The function gets access to the App pointer and error.
void failure_message(std::function<std::string(const App *, const Error &e)> function) { void failure_message(std::function<std::string(const App *, const Error &e)> function) {
failure_message_ = function; failure_message_ = function;
@ -1755,6 +1775,13 @@ class App {
return miss_list; return miss_list;
} }
/// This returns the missing options in a form ready for processing by another command line program
std::vector<std::string> remaining_for_passthrough(bool recurse = false) const {
std::vector<std::string> 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 /// This returns the number of remaining options, minus the -- separator
size_t remaining_size(bool recurse = false) const { size_t remaining_size(bool recurse = false) const {
auto remaining_options = static_cast<size_t>(std::count_if( auto remaining_options = static_cast<size_t>(std::count_if(
@ -1878,7 +1905,7 @@ class App {
return detail::Classifier::SHORT; return detail::Classifier::SHORT;
if((allow_windows_style_options_) && (detail::split_windows_style(current, dummy1, dummy2))) if((allow_windows_style_options_) && (detail::split_windows_style(current, dummy1, dummy2)))
return detail::Classifier::WINDOWS; return detail::Classifier::WINDOWS;
if((current == "++") && !name_.empty()) if((current == "++") && !name_.empty() && parent_ != nullptr)
return detail::Classifier::SUBCOMMAND_TERMINATOR; return detail::Classifier::SUBCOMMAND_TERMINATOR;
return detail::Classifier::NONE; return detail::Classifier::NONE;
} }
@ -2106,6 +2133,21 @@ class App {
_process_requirements(); _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. /// Throw an error if anything is left over and should not be.
/// Modifies the args to fill in the missing items before throwing. /// Modifies the args to fill in the missing items before throwing.
void _process_extras(std::vector<std::string> &args) { void _process_extras(std::vector<std::string> &args) {
@ -2149,8 +2191,8 @@ class App {
// Throw error if any items are left over (depending on settings) // Throw error if any items are left over (depending on settings)
_process_extras(args); _process_extras(args);
// Convert missing (pairs) to extras (string only) // Convert missing (pairs) to extras (string only) ready for processing in another app
args = remaining(false); args = remaining_for_passthrough(false);
} else if(immediate_callback_) { } else if(immediate_callback_) {
_process_env(); _process_env();
_process_callbacks(); _process_callbacks();
@ -2160,6 +2202,23 @@ class App {
} }
} }
/// Internal parse function
void _parse(std::vector<std::string> &&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 /// 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 /// If this has more than one dot.separated.name, go into the subcommand matching it
@ -2227,6 +2286,7 @@ class App {
} }
break; break;
case detail::Classifier::SUBCOMMAND_TERMINATOR: case detail::Classifier::SUBCOMMAND_TERMINATOR:
// treat this like a positional mark if in the parent app
args.pop_back(); args.pop_back();
retval = false; retval = false;
break; break;

View File

@ -1815,7 +1815,74 @@ TEST_F(TApp, AllowExtrasOrder) {
std::vector<std::string> left_over = app.remaining(); std::vector<std::string> left_over = app.remaining();
app.parse(left_over); app.parse(left_over);
EXPECT_EQ(app.remaining(), left_over); EXPECT_EQ(app.remaining(), std::vector<std::string>({"-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<std::string>({"-x", "45", "-f", "27"}));
std::vector<std::string> 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<std::string>({"-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<std::string>({"-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<std::string>({"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 // Test horrible error