diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index c1e06b345..00580b45a 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -2326,11 +2326,15 @@ class App { /// Count the required remaining positional arguments size_t _count_remaining_positionals(bool required_only = false) const { size_t retval = 0; - for(const Option_p &opt : options_) - if(opt->get_positional() && (!required_only || opt->get_required()) && opt->get_items_expected() > 0 && - static_cast(opt->count()) < opt->get_items_expected()) - retval = static_cast(opt->get_items_expected()) - opt->count(); - + for(const Option_p &opt : options_) { + if(opt->get_positional() && (!required_only || opt->get_required())) { + if(opt->get_items_expected() > 0 && static_cast(opt->count()) < opt->get_items_expected()) { + retval += static_cast(opt->get_items_expected()) - opt->count(); + } else if(opt->get_required() && opt->get_items_expected() < 0 && opt->count() == 0ul) { + retval += 1; + } + } + } return retval; } @@ -2349,6 +2353,32 @@ class App { bool _parse_positional(std::vector &args) { const std::string &positional = args.back(); + + if(positionals_at_end_) { + // deal with the case of required arguments at the end which should take precedence over other arguments + auto arg_rem = args.size(); + auto remreq = _count_remaining_positionals(true); + if(arg_rem <= remreq) { + for(const Option_p &opt : options_) { + if(opt->get_positional() && opt->required_) { + if(static_cast(opt->count()) < opt->get_items_expected() || + (opt->get_items_expected() < 0 && opt->count() == 0lu)) { + if(validate_positionals_) { + std::string pos = positional; + pos = opt->_validate(pos); + if(!pos.empty()) { + continue; + } + } + opt->add_result(positional); + parse_order_.push_back(opt.get()); + args.pop_back(); + return true; + } + } + } + } + } for(const Option_p &opt : options_) { // Eat options, one by one, until done if(opt->get_positional() && diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index 4742dfe1a..1df6da4af 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -983,6 +983,73 @@ TEST_F(TApp, PositionalAtEnd) { EXPECT_THROW(run(), CLI::ExtrasError); } +// Tests positionals at end +TEST_F(TApp, RequiredPositionals) { + std::vector sources; + std::string dest; + app.add_option("src", sources); + app.add_option("dest", dest)->required(); + app.positionals_at_end(); + + args = {"1", "2", "3"}; + run(); + + EXPECT_EQ(sources.size(), 2u); + EXPECT_EQ(dest, "3"); + + args = {"a"}; + sources.clear(); + run(); + + EXPECT_EQ(sources.size(), 0u); + EXPECT_EQ(dest, "a"); +} + +TEST_F(TApp, RequiredPositionalVector) { + std::string d1; + std::string d2; + std::string d3; + std::vector sources; + + app.add_option("dest1", d1); + app.add_option("dest2", d2); + app.add_option("dest3", d3); + app.add_option("src", sources)->required(); + + app.positionals_at_end(); + + args = {"1", "2", "3"}; + run(); + + EXPECT_EQ(sources.size(), 1u); + EXPECT_EQ(d1, "1"); + EXPECT_EQ(d2, "2"); + EXPECT_TRUE(d3.empty()); + args = {"a"}; + sources.clear(); + run(); + + EXPECT_EQ(sources.size(), 1u); +} + +// Tests positionals at end +TEST_F(TApp, RequiredPositionalValidation) { + std::vector sources; + int dest; + std::string d2; + app.add_option("src", sources); + app.add_option("dest", dest)->required()->check(CLI::PositiveNumber); + app.add_option("dest2", d2)->required(); + app.positionals_at_end()->validate_positionals(); + + args = {"1", "2", "string", "3"}; + run(); + + EXPECT_EQ(sources.size(), 2u); + EXPECT_EQ(dest, 3); + EXPECT_EQ(d2, "string"); +} + // Tests positionals at end TEST_F(TApp, PositionalValidation) { std::string options;