From 72028a05707724a961892f4589fce7ddf1402642 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Sun, 14 Dec 2025 13:32:36 -0800 Subject: [PATCH 1/7] start adding options for Extras to better control how those are handled. --- include/CLI/App.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 969b061fd..d202a4d7c 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -65,10 +65,13 @@ CLI11_INLINE std::string simple(const App *app, const Error &e); /// Printout the full help string on error (if this fn is set, the old default for CLI11) 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, ignore,assume_arguments,capture}; /// 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 }; +enum class ConfigExtrasMode : std::uint8_t { Error = 0, Ignore, IgnoreAll, Capture }; /// @brief enumeration of prefix command modes, separator requires that the first extra argument be a "--", other /// unrecognized arguments will cause an error. on allows the first extra to trigger prefix mode regardless of other /// recognized options @@ -116,7 +119,7 @@ class App { std::string description_{}; /// If true, allow extra arguments (ie, don't throw an error). INHERITABLE - bool allow_extras_{false}; + bool 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 From 90f9a8256020c7349f2fe58faf519431e5cc5f10 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Thu, 18 Dec 2025 07:33:56 -0800 Subject: [PATCH 2/7] fix the errors --- include/CLI/App.hpp | 37 +++++++++++++++++++++++++++--------- include/CLI/impl/App_inl.hpp | 12 ++++++------ 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index d202a4d7c..ab60d21c0 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -67,11 +67,15 @@ 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, ignore,assume_arguments,capture}; +enum class ExtrasMode:std::uint8_t{Error=0, Ignore,AssumeArguments,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 }; -enum class ConfigExtrasMode : std::uint8_t { Error = 0, Ignore, IgnoreAll, Capture }; + /// @brief enumeration of prefix command modes, separator requires that the first extra argument be a "--", other /// unrecognized arguments will cause an error. on allows the first extra to trigger prefix mode regardless of other /// recognized options @@ -119,11 +123,11 @@ class App { std::string description_{}; /// If true, allow extra arguments (ie, don't throw an error). INHERITABLE - bool allow_extras_{ExtrasMode::error}; + 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}; @@ -391,6 +395,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; } @@ -464,16 +474,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(mode); + return this; + } + + /// ignore extras in config files + App *allow_config_extras(ConfigExtrasMode mode) { allow_config_extras_ = mode; return this; } @@ -1182,7 +1198,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_extra_mode() const { return allow_extras_; } /// Get the status of required CLI11_NODISCARD bool get_required() const { return required_; } @@ -1213,7 +1232,7 @@ 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(allow_config_extras_); } /// Get a pointer to the help flag. Option *get_help_ptr() { return help_ptr_; } diff --git a/include/CLI/impl/App_inl.hpp b/include/CLI/impl/App_inl.hpp index b894c8c4a..693460d79 100644 --- a/include/CLI/impl/App_inl.hpp +++ b/include/CLI/impl/App_inl.hpp @@ -986,7 +986,7 @@ CLI11_NODISCARD CLI11_INLINE std::vector App::remaining(bool recurs } // Get from a subcommand that may allow extras if(recurse) { - if(!allow_extras_) { + if(allow_extras_!=ExtrasMode::Error) { for(const auto &sub : subcommands_) { if(sub->name_.empty() && !sub->missing_.empty()) { for(const std::pair &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 &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()); } } @@ -2343,13 +2343,13 @@ CLI11_NODISCARD CLI11_INLINE const std::string &App::_compare_subcommand_names(c } 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::Capture||allow_extras_==ExtrasMode::AssumeArguments || subcommands_.empty() || get_prefix_command()) { 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() && (subc->allow_extras_==ExtrasMode::AssumeArguments||subc->allow_extras_==ExtrasMode::Capture)) { subc->missing_.emplace_back(val_type, val); return; } From 08750001405a7a4f6447f98634ff4f3580d3dc00 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Sat, 20 Dec 2025 08:59:57 -0800 Subject: [PATCH 3/7] add extrasMode operation and tests --- include/CLI/App.hpp | 6 ++--- include/CLI/impl/App_inl.hpp | 39 ++++++++++++++++++++++----- tests/AppTest.cpp | 52 +++++++++++++++++++++++++++++++++++- tests/OptionGroupTest.cpp | 8 ++++++ 4 files changed, 94 insertions(+), 11 deletions(-) diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index ab60d21c0..e8a2257ab 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -65,9 +65,9 @@ CLI11_INLINE std::string simple(const App *app, const Error &e); /// Printout the full help string on error (if this fn is set, the old default for CLI11) 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, Ignore,AssumeArguments,Capture}; +/// enumeration of modes of how to deal with command line extras +enum class ExtrasMode:std::uint8_t{Error=0, 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}; @@ -1201,7 +1201,7 @@ class App { CLI11_NODISCARD bool get_allow_extras() const { return allow_extras_>ExtrasMode::Ignore; } /// Get the mode of allow_extras - CLI11_NODISCARD ExtrasMode get_allow_extra_mode() const { return 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_; } diff --git a/include/CLI/impl/App_inl.hpp b/include/CLI/impl/App_inl.hpp index 693460d79..78beaeb0a 100644 --- a/include/CLI/impl/App_inl.hpp +++ b/include/CLI/impl/App_inl.hpp @@ -986,7 +986,7 @@ CLI11_NODISCARD CLI11_INLINE std::vector App::remaining(bool recurs } // Get from a subcommand that may allow extras if(recurse) { - if(allow_extras_!=ExtrasMode::Error) { + 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 &miss : sub->missing_) { @@ -1730,7 +1730,8 @@ CLI11_INLINE bool App::_parse_single(std::vector &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(); @@ -2154,6 +2155,22 @@ App::_parse_arg(std::vector &args, detail::Classifier current_type, 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,20 +2359,28 @@ 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_==ExtrasMode::Capture||allow_extras_==ExtrasMode::AssumeArguments || subcommands_.empty() || get_prefix_command()) { - missing_.emplace_back(val_type, 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_==ExtrasMode::AssumeArguments||subc->allow_extras_==ExtrasMode::Capture)) { + if(subc->name_.empty() && capture_extras(subc->allow_extras_)) { subc->missing_.emplace_back(val_type, val); return; } } - // if we haven't found any place to put them yet put them in missing - missing_.emplace_back(val_type, val); + 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) { diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index 8aa40cfda..3923f5856 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -2391,6 +2391,12 @@ TEST_CASE_METHOD(TApp, "AllowExtras", "[app]") { REQUIRE_NOTHROW(run()); CHECK(val); CHECK(std::vector({"-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({"-x", "45", "-f", "27"}) == app.remaining()); @@ -2428,6 +2433,51 @@ 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, "PrefixCommand", "[app]") { int v1{0}; int v2{0}; diff --git a/tests/OptionGroupTest.cpp b/tests/OptionGroupTest.cpp index d453d53d3..18c916581 100644 --- a/tests/OptionGroupTest.cpp +++ b/tests/OptionGroupTest.cpp @@ -706,6 +706,14 @@ TEST_CASE_METHOD(ManyGroups, "ExtrasFallDown", "[optiongroup]") { std::vector 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 From c1e26bc687ca9ab4823c3e0b197a5d29f3c320f2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 20 Dec 2025 17:04:18 +0000 Subject: [PATCH 4/7] style: pre-commit.ci fixes --- include/CLI/App.hpp | 13 +++++++------ include/CLI/impl/App_inl.hpp | 36 +++++++++++++++--------------------- tests/AppTest.cpp | 24 ++++++++++-------------- 3 files changed, 32 insertions(+), 41 deletions(-) diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index e8a2257ab..be1e85359 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -67,15 +67,14 @@ 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, Ignore,AssumeSingleArgument,AssumeMultipleArguments,Capture}; +enum class ExtrasMode : std::uint8_t { Error = 0, 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}; +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 /// unrecognized arguments will cause an error. on allows the first extra to trigger prefix mode regardless of other /// recognized options @@ -395,7 +394,7 @@ 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; + allow_extras_ = allow ? ExtrasMode::Capture : ExtrasMode::Error; return this; } @@ -1198,7 +1197,7 @@ 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_>ExtrasMode::Ignore; } + 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_; } @@ -1232,7 +1231,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 static_cast(allow_config_extras_); } + CLI11_NODISCARD config_extras_mode get_allow_config_extras() const { + return static_cast(allow_config_extras_); + } /// Get a pointer to the help flag. Option *get_help_ptr() { return help_ptr_; } diff --git a/include/CLI/impl/App_inl.hpp b/include/CLI/impl/App_inl.hpp index 78beaeb0a..55e8225c0 100644 --- a/include/CLI/impl/App_inl.hpp +++ b/include/CLI/impl/App_inl.hpp @@ -986,7 +986,7 @@ CLI11_NODISCARD CLI11_INLINE std::vector App::remaining(bool recurs } // Get from a subcommand that may allow extras if(recurse) { - if(allow_extras_==ExtrasMode::Error||allow_extras_==ExtrasMode::Ignore) { + 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 &miss : sub->missing_) { @@ -1461,13 +1461,13 @@ CLI11_INLINE void App::_process() { } CLI11_INLINE void App::_process_extras() { - if(allow_extras_==ExtrasMode::Error && 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_==ExtrasMode::Error && 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() != "--") { @@ -1730,7 +1730,7 @@ CLI11_INLINE bool App::_parse_single(std::vector &args, bool &posit args.pop_back(); positional_only = true; if(get_prefix_command()) { - //don't care about extras mode here + // don't care about extras mode here missing_.emplace_back(classifier, "--"); while(!args.empty()) { missing_.emplace_back(detail::Classifier::NONE, args.back()); @@ -2154,20 +2154,14 @@ App::_parse_arg(std::vector &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()); + } 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()); + } 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(); } } @@ -2360,11 +2354,12 @@ CLI11_NODISCARD CLI11_INLINE const std::string &App::_compare_subcommand_names(c } inline bool capture_extras(ExtrasMode mode) { - return mode == ExtrasMode::Capture || mode == ExtrasMode::AssumeSingleArgument||mode==ExtrasMode::AssumeMultipleArguments; + 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 (capture_extras(allow_extras_) || subcommands_.empty() || get_prefix_command()) { - if (allow_extras_ != ExtrasMode::Ignore) { + if(capture_extras(allow_extras_) || subcommands_.empty() || get_prefix_command()) { + if(allow_extras_ != ExtrasMode::Ignore) { missing_.emplace_back(val_type, val); } return; @@ -2376,11 +2371,10 @@ CLI11_INLINE void App::_move_to_missing(detail::Classifier val_type, const std:: return; } } - if (allow_extras_ != ExtrasMode::Ignore) { + 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) { diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index 3923f5856..61080adcc 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -2444,38 +2444,34 @@ TEST_CASE_METHOD(TApp, "AllowExtrasAssumptions", "[app]") { args = {"--one", "45", "--three", "27", "this"}; REQUIRE_NOTHROW(run()); - CHECK(one=="45"); + CHECK(one == "45"); CHECK(two == "this"); - CHECK(app.remaining().size()==2U); - - - + CHECK(app.remaining().size() == 2U); two.clear(); app.allow_extras(CLI::ExtrasMode::AssumeMultipleArguments); run(); - CHECK(one=="45"); + CHECK(one == "45"); CHECK(two.empty()); - CHECK(app.remaining().size()==3U); + 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"}; + args = {"--three", "27", "--one", "45", "this"}; run(); - CHECK(one=="45"); + CHECK(one == "45"); CHECK(two == "this"); - CHECK(app.remaining().size()==2U); + 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"}; + args = {"--three", "27", "extra", "--one", "45", "this"}; one.clear(); two.clear(); run(); - CHECK(one=="45"); + CHECK(one == "45"); CHECK(two == "this"); - CHECK(app.remaining().size()==3U); - + CHECK(app.remaining().size() == 3U); } TEST_CASE_METHOD(TApp, "PrefixCommand", "[app]") { From 96cecb73169341f2f74186bd632b327a742afffd Mon Sep 17 00:00:00 2001 From: Philip Top Date: Mon, 22 Dec 2025 06:31:21 -0800 Subject: [PATCH 5/7] Add ErrorImmediately option for extras --- README.md | 7 +++++++ include/CLI/App.hpp | 2 +- include/CLI/impl/App_inl.hpp | 8 ++++++-- tests/AppTest.cpp | 26 ++++++++++++++++++++++++-- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 17849c13c..7cf0ec440 100644 --- a/README.md +++ b/README.md @@ -1123,6 +1123,13 @@ 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 arugment 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 arugments 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 diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index be1e85359..c1ee7c389 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -67,7 +67,7 @@ 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, Ignore, AssumeSingleArgument, AssumeMultipleArguments, Capture }; +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 }; diff --git a/include/CLI/impl/App_inl.hpp b/include/CLI/impl/App_inl.hpp index 55e8225c0..1179abedb 100644 --- a/include/CLI/impl/App_inl.hpp +++ b/include/CLI/impl/App_inl.hpp @@ -2358,8 +2358,12 @@ inline bool capture_extras(ExtrasMode mode) { mode == ExtrasMode::AssumeMultipleArguments; } CLI11_INLINE void App::_move_to_missing(detail::Classifier val_type, const std::string &val) { - if(capture_extras(allow_extras_) || subcommands_.empty() || get_prefix_command()) { - if(allow_extras_ != ExtrasMode::Ignore) { + if (allow_extras_ == ExtrasMode::ErrorImmediately) + { + throw ExtrasError(name_, std::vector{val}); + } + if (capture_extras(allow_extras_) || subcommands_.empty() || get_prefix_command()) { + if (allow_extras_ != ExtrasMode::Ignore) { missing_.emplace_back(val_type, val); } return; diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index 61080adcc..e7fc600b4 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -2474,6 +2474,30 @@ TEST_CASE_METHOD(TApp, "AllowExtrasAssumptions", "[app]") { 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().size()==0U); + 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().size()==0U); +} + TEST_CASE_METHOD(TApp, "PrefixCommand", "[app]") { int v1{0}; int v2{0}; @@ -2527,8 +2551,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({"-x", "45", "-f", "27"})), CLI::ExtrasError); } From 1ba68bb74a2978f8e1d36fa94ec9f7e119a01e8a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 14:37:46 +0000 Subject: [PATCH 6/7] style: pre-commit.ci fixes --- README.md | 25 ++++++++++++++++++------- include/CLI/App.hpp | 9 ++++++++- include/CLI/impl/App_inl.hpp | 7 +++---- tests/AppTest.cpp | 27 +++++++++++++-------------- 4 files changed, 42 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 7cf0ec440..4f5b55032 100644 --- a/README.md +++ b/README.md @@ -1123,13 +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 arugment 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 arugments 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`: +- `.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 arugment 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 arugments 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 diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index c1ee7c389..4db2e2baa 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -67,7 +67,14 @@ 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}; +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 }; diff --git a/include/CLI/impl/App_inl.hpp b/include/CLI/impl/App_inl.hpp index 1179abedb..0785e5dd8 100644 --- a/include/CLI/impl/App_inl.hpp +++ b/include/CLI/impl/App_inl.hpp @@ -2358,12 +2358,11 @@ inline bool capture_extras(ExtrasMode mode) { mode == ExtrasMode::AssumeMultipleArguments; } CLI11_INLINE void App::_move_to_missing(detail::Classifier val_type, const std::string &val) { - if (allow_extras_ == ExtrasMode::ErrorImmediately) - { + if(allow_extras_ == ExtrasMode::ErrorImmediately) { throw ExtrasError(name_, std::vector{val}); } - if (capture_extras(allow_extras_) || subcommands_.empty() || get_prefix_command()) { - if (allow_extras_ != ExtrasMode::Ignore) { + if(capture_extras(allow_extras_) || subcommands_.empty() || get_prefix_command()) { + if(allow_extras_ != ExtrasMode::Ignore) { missing_.emplace_back(val_type, val); } return; diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index e7fc600b4..a7e6735cc 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -2474,28 +2474,27 @@ TEST_CASE_METHOD(TApp, "AllowExtrasAssumptions", "[app]") { CHECK(app.remaining().size() == 3U); } - TEST_CASE_METHOD(TApp, "AllowExtrasImmediateError", "[app]") { - int v1{ 0 }; - int v2{ 0 }; + 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" }; + 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" }; + 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().size()==0U); - args = { "-x", "27","-g", "29", "-f", "31" }; + CHECK(v1 == 23); //-f still triggers + CHECK(v2 == 15); + CHECK(app.remaining().size() == 0U); + 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().size()==0U); + CHECK(v1 == 23); // -f did not trigger + CHECK(v2 == 15); + CHECK(app.remaining().size() == 0U); } TEST_CASE_METHOD(TApp, "PrefixCommand", "[app]") { From a57628d3e9972c29ad759969f4c1d571d462910e Mon Sep 17 00:00:00 2001 From: Philip Top Date: Mon, 22 Dec 2025 11:05:57 -0800 Subject: [PATCH 7/7] fix some warnings --- README.md | 4 ++-- tests/AppTest.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4f5b55032..93ad87612 100644 --- a/README.md +++ b/README.md @@ -1132,11 +1132,11 @@ option_groups. These are: - `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 arugment is not a flag or option argument + 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 arugments are not a flag or option + 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 diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index a7e6735cc..2026b35ca 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -2487,14 +2487,14 @@ TEST_CASE_METHOD(TApp, "AllowExtrasImmediateError", "[app]") { 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(v1 == 23); // -f still triggers CHECK(v2 == 15); - CHECK(app.remaining().size() == 0U); + 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().size() == 0U); + CHECK(app.remaining().empty()); } TEST_CASE_METHOD(TApp, "PrefixCommand", "[app]") {