Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions include/CLI/App.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -500,8 +500,9 @@ class App {
}

/// Enable or disable prefix command mode. If enabled, parsing stops at the
/// first unrecognized option and all remaining arguments are stored in
/// remaining args.
/// first positional argument (or the `--` separator) and all remaining
/// arguments are stored in remaining args. Unrecognized options do not stop
/// parsing; they are collected as extras.
App *prefix_command(bool is_prefix = true) {
prefix_command_ = is_prefix ? PrefixCommandMode::On : PrefixCommandMode::Off;
return this;
Expand Down
11 changes: 4 additions & 7 deletions include/CLI/impl/App_inl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2187,15 +2187,12 @@ App::_parse_arg(std::vector<std::string> &args, detail::Classifier current_type,
if(parent_ != nullptr && fallthrough_)
return _get_fallthrough_parent()->_parse_arg(args, current_type, false);

// Otherwise, add to missing
// Otherwise, add to missing. Note prefix_command only stops parsing at a positional or "--"
// (handled in _parse_positional/_parse_single); an unrecognized option is left as an extra
// and parsing continues so later registered options are still matched (#1374).
args.pop_back();
_move_to_missing(current_type, current);
if(get_prefix_command_mode() == PrefixCommandMode::On) {
while(!args.empty()) {
missing_.emplace_back(detail::Classifier::NONE, args.back());
args.pop_back();
}
} else if(allow_extras_ == ExtrasMode::AssumeSingleArgument) {
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();
Expand Down
23 changes: 22 additions & 1 deletion tests/AppTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2590,7 +2590,9 @@ TEST_CASE_METHOD(TApp, "PrefixCommand", "[app]") {

run();
rem = app.remaining();
CHECK(rem.size() == 5U);
// "--test4" is unrecognized but parsing continues, so "-f 27" is still recognized
CHECK(rem.size() == 3U);
CHECK(v1 == 27);

app.prefix_command(CLI::PrefixCommandMode::SeparatorOnly);
CHECK_THROWS_AS(run(), CLI::ExtrasError);
Expand All @@ -2613,6 +2615,25 @@ TEST_CASE_METHOD(TApp, "PrefixCommand", "[app]") {
CHECK(rem.size() == 4U);
}

// prefix_command(On) must not stop parsing at an unrecognized *option*; a
// registered option after one still gets recognized regardless of order (#1374)
TEST_CASE_METHOD(TApp, "PrefixCommandUnknownOptionKeepsParsing", "[app]") {
bool known{false};
app.add_flag("--known", known);
app.prefix_command(true);
app.allow_extras(true);

args = {"--known", "--unknown"}; // App::parse consumes from the back
run();
CHECK(known);

known = false;
args = {"--unknown", "--known"};
run();
CHECK(known);
CHECK(app.remaining().size() == 1U); // only "--unknown" left over
}

// makes sure the error throws on the rValue version of the parse
TEST_CASE_METHOD(TApp, "ExtrasErrorRvalueParse", "[app]") {

Expand Down
Loading