mirror of
https://github.com/CLIUtils/CLI11.git
synced 2026-01-19 04:52:08 +00:00
Extras configuration (#1270)
Allow more control over how extras are interpreted. Specifically allowing some assumptions to be made about arguments to unrecognized options. With `CLI::ExtrasMode::AssumeMultipleArguments` all positional arguments following an unrecognized option will be considered extra as well even if there are open positional arguments. With `CLI::ExtrasMode::AssumeSingleArgument` a positional argument following an unrecognized option will also be considered unrecognized and go into the `remaining()` bin. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
18
README.md
18
README.md
@@ -1123,6 +1123,24 @@ option_groups. These are:
|
||||
executes after the first argument of an application is processed. See
|
||||
[Subcommand callbacks](#callbacks) for some additional details.
|
||||
- `.allow_extras()`: Do not throw an error if extra arguments are left over.
|
||||
- `.allow_extras(CLI::ExtrasMode)`: Specify the method of handling unrecognized
|
||||
arguments.
|
||||
- `CLI::ExtrasMode::Error`: generate an error on unrecognized argument. Same
|
||||
as `.allow_extras(false)`.
|
||||
- `CLI::ExtrasMode::ErrorImmediately`: generate an error immediately on
|
||||
parsing an unrecognized option`.
|
||||
- `CLI::ExtrasMode::Ignore`: ignore any unrecognized argument, do not generate
|
||||
an error.
|
||||
- `CLI::ExtrasMode::AssumeSingleArgument`: After an unrecognized flag or
|
||||
option argument, if the following argument is not a flag or option argument
|
||||
assume it an argument and treat it as also unrecognized even if it would
|
||||
otherwise go to a positional argument
|
||||
- `CLI::ExtrasMode::AssumeMultipleArguments`:After an unrecognized flag or
|
||||
option argument, if the following arguments are not a flag or option
|
||||
argument assume they are arguments and treat them as also unrecognized even
|
||||
if it would otherwise go to a positional argument
|
||||
- `CLI::ExtrasMode::Capture`: capture all unrecognized arguments, same as
|
||||
`true` for `.allow_extras`:
|
||||
- `.positionals_at_end()`: Specify that positional arguments occur as the last
|
||||
arguments and throw an error if an unexpected positional is encountered.
|
||||
- `.prefix_command()`: Like `allow_extras`, but stop processing immediately on
|
||||
|
||||
@@ -66,7 +66,20 @@ CLI11_INLINE std::string simple(const App *app, const Error &e);
|
||||
CLI11_INLINE std::string help(const App *app, const Error &e);
|
||||
} // namespace FailureMessage
|
||||
|
||||
/// enumeration of modes of how to deal with command line extras
|
||||
enum class ExtrasMode : std::uint8_t {
|
||||
Error = 0,
|
||||
ErrorImmediately,
|
||||
Ignore,
|
||||
AssumeSingleArgument,
|
||||
AssumeMultipleArguments,
|
||||
Capture
|
||||
};
|
||||
|
||||
/// enumeration of modes of how to deal with extras in config files
|
||||
enum class ConfigExtrasMode : std::uint8_t { Error = 0, Ignore, IgnoreAll, Capture };
|
||||
|
||||
/// @brief enumeration of modes of how to deal with extras in config files
|
||||
enum class config_extras_mode : std::uint8_t { error = 0, ignore, ignore_all, capture };
|
||||
|
||||
/// @brief enumeration of prefix command modes, separator requires that the first extra argument be a "--", other
|
||||
@@ -116,11 +129,11 @@ class App {
|
||||
std::string description_{};
|
||||
|
||||
/// If true, allow extra arguments (ie, don't throw an error). INHERITABLE
|
||||
bool allow_extras_{false};
|
||||
ExtrasMode allow_extras_{ExtrasMode::Error};
|
||||
|
||||
/// If ignore, allow extra arguments in the ini file (ie, don't throw an error). INHERITABLE
|
||||
/// if error, error on an extra argument, and if capture feed it to the app
|
||||
config_extras_mode allow_config_extras_{config_extras_mode::ignore};
|
||||
ConfigExtrasMode allow_config_extras_{ConfigExtrasMode::Ignore};
|
||||
|
||||
/// If true, cease processing on an unrecognized option (implies allow_extras) INHERITABLE
|
||||
PrefixCommandMode prefix_command_{PrefixCommandMode::Off};
|
||||
@@ -388,6 +401,12 @@ class App {
|
||||
|
||||
/// Remove the error when extras are left over on the command line.
|
||||
App *allow_extras(bool allow = true) {
|
||||
allow_extras_ = allow ? ExtrasMode::Capture : ExtrasMode::Error;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Remove the error when extras are left over on the command line.
|
||||
App *allow_extras(ExtrasMode allow) {
|
||||
allow_extras_ = allow;
|
||||
return this;
|
||||
}
|
||||
@@ -461,16 +480,22 @@ class App {
|
||||
/// ignore extras in config files
|
||||
App *allow_config_extras(bool allow = true) {
|
||||
if(allow) {
|
||||
allow_config_extras_ = config_extras_mode::capture;
|
||||
allow_extras_ = true;
|
||||
allow_config_extras_ = ConfigExtrasMode::Capture;
|
||||
allow_extras_ = ExtrasMode::Capture;
|
||||
} else {
|
||||
allow_config_extras_ = config_extras_mode::error;
|
||||
allow_config_extras_ = ConfigExtrasMode::Error;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// ignore extras in config files
|
||||
App *allow_config_extras(config_extras_mode mode) {
|
||||
allow_config_extras_ = static_cast<ConfigExtrasMode>(mode);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// ignore extras in config files
|
||||
App *allow_config_extras(ConfigExtrasMode mode) {
|
||||
allow_config_extras_ = mode;
|
||||
return this;
|
||||
}
|
||||
@@ -1179,7 +1204,10 @@ class App {
|
||||
CLI11_NODISCARD PrefixCommandMode get_prefix_command_mode() const { return prefix_command_; }
|
||||
|
||||
/// Get the status of allow extras
|
||||
CLI11_NODISCARD bool get_allow_extras() const { return allow_extras_; }
|
||||
CLI11_NODISCARD bool get_allow_extras() const { return allow_extras_ > ExtrasMode::Ignore; }
|
||||
|
||||
/// Get the mode of allow_extras
|
||||
CLI11_NODISCARD ExtrasMode get_allow_extras_mode() const { return allow_extras_; }
|
||||
|
||||
/// Get the status of required
|
||||
CLI11_NODISCARD bool get_required() const { return required_; }
|
||||
@@ -1210,7 +1238,9 @@ class App {
|
||||
CLI11_NODISCARD bool get_validate_optional_arguments() const { return validate_optional_arguments_; }
|
||||
|
||||
/// Get the status of allow extras
|
||||
CLI11_NODISCARD config_extras_mode get_allow_config_extras() const { return allow_config_extras_; }
|
||||
CLI11_NODISCARD config_extras_mode get_allow_config_extras() const {
|
||||
return static_cast<config_extras_mode>(allow_config_extras_);
|
||||
}
|
||||
|
||||
/// Get a pointer to the help flag.
|
||||
Option *get_help_ptr() { return help_ptr_; }
|
||||
|
||||
@@ -986,7 +986,7 @@ CLI11_NODISCARD CLI11_INLINE std::vector<std::string> App::remaining(bool recurs
|
||||
}
|
||||
// Get from a subcommand that may allow extras
|
||||
if(recurse) {
|
||||
if(!allow_extras_) {
|
||||
if(allow_extras_ == ExtrasMode::Error || allow_extras_ == ExtrasMode::Ignore) {
|
||||
for(const auto &sub : subcommands_) {
|
||||
if(sub->name_.empty() && !sub->missing_.empty()) {
|
||||
for(const std::pair<detail::Classifier, std::string> &miss : sub->missing_) {
|
||||
@@ -1461,13 +1461,13 @@ CLI11_INLINE void App::_process() {
|
||||
}
|
||||
|
||||
CLI11_INLINE void App::_process_extras() {
|
||||
if(!allow_extras_ && prefix_command_ == PrefixCommandMode::Off) {
|
||||
if(allow_extras_ == ExtrasMode::Error && prefix_command_ == PrefixCommandMode::Off) {
|
||||
std::size_t num_left_over = remaining_size();
|
||||
if(num_left_over > 0) {
|
||||
throw ExtrasError(name_, remaining(false));
|
||||
}
|
||||
}
|
||||
if(!allow_extras_ && prefix_command_ == PrefixCommandMode::SeparatorOnly) {
|
||||
if(allow_extras_ == ExtrasMode::Error && prefix_command_ == PrefixCommandMode::SeparatorOnly) {
|
||||
std::size_t num_left_over = remaining_size();
|
||||
if(num_left_over > 0) {
|
||||
if(remaining(false).front() != "--") {
|
||||
@@ -1555,7 +1555,7 @@ CLI11_INLINE void App::_parse_stream(std::istream &input) {
|
||||
|
||||
CLI11_INLINE void App::_parse_config(const std::vector<ConfigItem> &args) {
|
||||
for(const ConfigItem &item : args) {
|
||||
if(!_parse_single_config(item) && allow_config_extras_ == config_extras_mode::error)
|
||||
if(!_parse_single_config(item) && allow_config_extras_ == ConfigExtrasMode::Error)
|
||||
throw ConfigError::Extras(item.fullname());
|
||||
}
|
||||
}
|
||||
@@ -1730,7 +1730,8 @@ CLI11_INLINE bool App::_parse_single(std::vector<std::string> &args, bool &posit
|
||||
args.pop_back();
|
||||
positional_only = true;
|
||||
if(get_prefix_command()) {
|
||||
_move_to_missing(classifier, "--");
|
||||
// don't care about extras mode here
|
||||
missing_.emplace_back(classifier, "--");
|
||||
while(!args.empty()) {
|
||||
missing_.emplace_back(detail::Classifier::NONE, args.back());
|
||||
args.pop_back();
|
||||
@@ -2153,6 +2154,16 @@ App::_parse_arg(std::vector<std::string> &args, detail::Classifier current_type,
|
||||
missing_.emplace_back(detail::Classifier::NONE, args.back());
|
||||
args.pop_back();
|
||||
}
|
||||
} else if(allow_extras_ == ExtrasMode::AssumeSingleArgument) {
|
||||
if(!args.empty() && _recognize(args.back(), false) == detail::Classifier::NONE) {
|
||||
_move_to_missing(detail::Classifier::NONE, args.back());
|
||||
args.pop_back();
|
||||
}
|
||||
} else if(allow_extras_ == ExtrasMode::AssumeMultipleArguments) {
|
||||
while(!args.empty() && _recognize(args.back(), false) == detail::Classifier::NONE) {
|
||||
_move_to_missing(detail::Classifier::NONE, args.back());
|
||||
args.pop_back();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -2342,21 +2353,32 @@ CLI11_NODISCARD CLI11_INLINE const std::string &App::_compare_subcommand_names(c
|
||||
return estring;
|
||||
}
|
||||
|
||||
inline bool capture_extras(ExtrasMode mode) {
|
||||
return mode == ExtrasMode::Capture || mode == ExtrasMode::AssumeSingleArgument ||
|
||||
mode == ExtrasMode::AssumeMultipleArguments;
|
||||
}
|
||||
CLI11_INLINE void App::_move_to_missing(detail::Classifier val_type, const std::string &val) {
|
||||
if(allow_extras_ || subcommands_.empty() || get_prefix_command()) {
|
||||
if(allow_extras_ == ExtrasMode::ErrorImmediately) {
|
||||
throw ExtrasError(name_, std::vector<std::string>{val});
|
||||
}
|
||||
if(capture_extras(allow_extras_) || subcommands_.empty() || get_prefix_command()) {
|
||||
if(allow_extras_ != ExtrasMode::Ignore) {
|
||||
missing_.emplace_back(val_type, val);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// allow extra arguments to be placed in an option group if it is allowed there
|
||||
for(auto &subc : subcommands_) {
|
||||
if(subc->name_.empty() && subc->allow_extras_) {
|
||||
if(subc->name_.empty() && capture_extras(subc->allow_extras_)) {
|
||||
subc->missing_.emplace_back(val_type, val);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(allow_extras_ != ExtrasMode::Ignore) {
|
||||
// if we haven't found any place to put them yet put them in missing
|
||||
missing_.emplace_back(val_type, val);
|
||||
}
|
||||
}
|
||||
|
||||
CLI11_INLINE void App::_move_option(Option *opt, App *app) {
|
||||
if(opt == nullptr) {
|
||||
|
||||
@@ -2391,6 +2391,12 @@ TEST_CASE_METHOD(TApp, "AllowExtras", "[app]") {
|
||||
REQUIRE_NOTHROW(run());
|
||||
CHECK(val);
|
||||
CHECK(std::vector<std::string>({"-x"}) == app.remaining());
|
||||
|
||||
app.allow_extras(CLI::ExtrasMode::Ignore);
|
||||
val = false;
|
||||
REQUIRE_NOTHROW(run());
|
||||
CHECK(val);
|
||||
CHECK(app.remaining().empty());
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "AllowExtrasOrder", "[app]") {
|
||||
@@ -2410,7 +2416,6 @@ TEST_CASE_METHOD(TApp, "AllowExtrasOrder", "[app]") {
|
||||
TEST_CASE_METHOD(TApp, "AllowExtrasCascade", "[app]") {
|
||||
|
||||
app.allow_extras();
|
||||
|
||||
args = {"-x", "45", "-f", "27"};
|
||||
REQUIRE_NOTHROW(run());
|
||||
CHECK(std::vector<std::string>({"-x", "45", "-f", "27"}) == app.remaining());
|
||||
@@ -2428,6 +2433,70 @@ TEST_CASE_METHOD(TApp, "AllowExtrasCascade", "[app]") {
|
||||
CHECK(27 == v2);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "AllowExtrasAssumptions", "[app]") {
|
||||
|
||||
app.allow_extras(CLI::ExtrasMode::AssumeSingleArgument);
|
||||
|
||||
std::string one;
|
||||
std::string two;
|
||||
app.add_option("--one", one);
|
||||
app.add_option("two", two);
|
||||
args = {"--one", "45", "--three", "27", "this"};
|
||||
|
||||
REQUIRE_NOTHROW(run());
|
||||
CHECK(one == "45");
|
||||
CHECK(two == "this");
|
||||
CHECK(app.remaining().size() == 2U);
|
||||
|
||||
two.clear();
|
||||
app.allow_extras(CLI::ExtrasMode::AssumeMultipleArguments);
|
||||
|
||||
run();
|
||||
CHECK(one == "45");
|
||||
CHECK(two.empty());
|
||||
CHECK(app.remaining().size() == 3U);
|
||||
app.allow_extras(CLI::ExtrasMode::AssumeSingleArgument);
|
||||
CHECK(app.get_allow_extras_mode() == CLI::ExtrasMode::AssumeSingleArgument);
|
||||
args = {"--three", "27", "--one", "45", "this"};
|
||||
run();
|
||||
CHECK(one == "45");
|
||||
CHECK(two == "this");
|
||||
CHECK(app.remaining().size() == 2U);
|
||||
|
||||
app.allow_extras(CLI::ExtrasMode::AssumeMultipleArguments);
|
||||
CHECK(app.get_allow_extras_mode() == CLI::ExtrasMode::AssumeMultipleArguments);
|
||||
args = {"--three", "27", "extra", "--one", "45", "this"};
|
||||
one.clear();
|
||||
two.clear();
|
||||
run();
|
||||
CHECK(one == "45");
|
||||
CHECK(two == "this");
|
||||
CHECK(app.remaining().size() == 3U);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "AllowExtrasImmediateError", "[app]") {
|
||||
|
||||
int v1{0};
|
||||
int v2{0};
|
||||
app.add_option("-f", v1)->trigger_on_parse();
|
||||
app.add_option("-x", v2);
|
||||
args = {"-x", "15", "-f", "17", "-g", "19"};
|
||||
CHECK_THROWS_AS(run(), CLI::ExtrasError);
|
||||
CHECK(v1 == 17);
|
||||
CHECK(app.remaining().size() == 2U);
|
||||
args = {"-x", "21", "-f", "23", "-g", "25"};
|
||||
app.allow_extras(CLI::ExtrasMode::ErrorImmediately);
|
||||
CHECK_THROWS_AS(run(), CLI::ExtrasError);
|
||||
CHECK(v1 == 23); // -f still triggers
|
||||
CHECK(v2 == 15);
|
||||
CHECK(app.remaining().empty());
|
||||
args = {"-x", "27", "-g", "29", "-f", "31"};
|
||||
CHECK_THROWS_AS(run(), CLI::ExtrasError);
|
||||
CHECK(v1 == 23); // -f did not trigger
|
||||
CHECK(v2 == 15);
|
||||
CHECK(app.remaining().empty());
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(TApp, "PrefixCommand", "[app]") {
|
||||
int v1{0};
|
||||
int v2{0};
|
||||
@@ -2481,8 +2550,6 @@ TEST_CASE_METHOD(TApp, "PrefixCommand", "[app]") {
|
||||
// makes sure the error throws on the rValue version of the parse
|
||||
TEST_CASE_METHOD(TApp, "ExtrasErrorRvalueParse", "[app]") {
|
||||
|
||||
args = {"-x", "45", "-f", "27"};
|
||||
|
||||
CHECK_THROWS_AS(app.parse(std::vector<std::string>({"-x", "45", "-f", "27"})), CLI::ExtrasError);
|
||||
}
|
||||
|
||||
|
||||
@@ -706,6 +706,14 @@ TEST_CASE_METHOD(ManyGroups, "ExtrasFallDown", "[optiongroup]") {
|
||||
std::vector<std::string> extras{"--test1", "--flag", "extra"};
|
||||
CHECK(extras == app.remaining(true));
|
||||
CHECK(extras == main->remaining());
|
||||
app.allow_extras(CLI::ExtrasMode::Ignore);
|
||||
|
||||
CHECK_NOTHROW(run());
|
||||
|
||||
CHECK(0u == app.remaining_size(false));
|
||||
|
||||
CHECK(extras == app.remaining(true));
|
||||
CHECK(extras == main->remaining());
|
||||
}
|
||||
|
||||
// Test the option Inheritance
|
||||
|
||||
Reference in New Issue
Block a user