From 9094bf69437d73dea88b117b344e95fe57cb559c Mon Sep 17 00:00:00 2001 From: Philip Top Date: Mon, 29 Jul 2019 21:48:19 -0700 Subject: [PATCH 1/3] add a check loop for missing required positional, when the number of arguments get small. --- include/CLI/App.hpp | 40 +++++++++++++++++++++++++++++++++++----- tests/AppTest.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 5 deletions(-) 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..0cbf8d7a1 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -983,6 +983,46 @@ 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(), 2); + EXPECT_EQ(dest, "3"); + + args = {"a"}; + sources.clear(); + run(); + + EXPECT_EQ(sources.size(), 0); + EXPECT_EQ(dest, "a"); +} + +// 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(), 2); + EXPECT_EQ(dest, 3); + EXPECT_EQ(d2, "string"); +} + // Tests positionals at end TEST_F(TApp, PositionalValidation) { std::string options; From e365c78ab872e169d495ea21966e72db2fa31855 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Mon, 29 Jul 2019 22:07:31 -0700 Subject: [PATCH 2/3] fix a few warnings on signed/unsigned checks --- tests/AppTest.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index 0cbf8d7a1..20d477911 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -994,14 +994,14 @@ TEST_F(TApp, RequiredPositionals) { args = {"1", "2", "3"}; run(); - EXPECT_EQ(sources.size(), 2); + EXPECT_EQ(sources.size(), 2u); EXPECT_EQ(dest, "3"); args = {"a"}; sources.clear(); run(); - EXPECT_EQ(sources.size(), 0); + EXPECT_EQ(sources.size(), 0u); EXPECT_EQ(dest, "a"); } @@ -1018,7 +1018,7 @@ TEST_F(TApp, RequiredPositionalValidation) { args = {"1", "2", "string", "3"}; run(); - EXPECT_EQ(sources.size(), 2); + EXPECT_EQ(sources.size(), 2u); EXPECT_EQ(dest, 3); EXPECT_EQ(d2, "string"); } From f11a9f829669ae4e5936e5cda707eba6c7dffd90 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Mon, 29 Jul 2019 22:36:05 -0700 Subject: [PATCH 3/3] add check for a required positional vector. --- tests/AppTest.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/AppTest.cpp b/tests/AppTest.cpp index 20d477911..1df6da4af 100644 --- a/tests/AppTest.cpp +++ b/tests/AppTest.cpp @@ -1005,6 +1005,33 @@ TEST_F(TApp, RequiredPositionals) { 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;