Commit 4bfce437958547efe65e1cc834ff78a8d8814fc8
Committed by
Henry Schreiner
1 parent
eb6f759f
required positional arguments and a vector positionals (#306)
* add a check loop for missing required positional, when the number of arguments get small. * fix a few warnings on signed/unsigned checks * add check for a required positional vector.
Showing
2 changed files
with
102 additions
and
5 deletions
include/CLI/App.hpp
| ... | ... | @@ -2326,11 +2326,15 @@ class App { |
| 2326 | 2326 | /// Count the required remaining positional arguments |
| 2327 | 2327 | size_t _count_remaining_positionals(bool required_only = false) const { |
| 2328 | 2328 | size_t retval = 0; |
| 2329 | - for(const Option_p &opt : options_) | |
| 2330 | - if(opt->get_positional() && (!required_only || opt->get_required()) && opt->get_items_expected() > 0 && | |
| 2331 | - static_cast<int>(opt->count()) < opt->get_items_expected()) | |
| 2332 | - retval = static_cast<size_t>(opt->get_items_expected()) - opt->count(); | |
| 2333 | - | |
| 2329 | + for(const Option_p &opt : options_) { | |
| 2330 | + if(opt->get_positional() && (!required_only || opt->get_required())) { | |
| 2331 | + if(opt->get_items_expected() > 0 && static_cast<int>(opt->count()) < opt->get_items_expected()) { | |
| 2332 | + retval += static_cast<size_t>(opt->get_items_expected()) - opt->count(); | |
| 2333 | + } else if(opt->get_required() && opt->get_items_expected() < 0 && opt->count() == 0ul) { | |
| 2334 | + retval += 1; | |
| 2335 | + } | |
| 2336 | + } | |
| 2337 | + } | |
| 2334 | 2338 | return retval; |
| 2335 | 2339 | } |
| 2336 | 2340 | |
| ... | ... | @@ -2349,6 +2353,32 @@ class App { |
| 2349 | 2353 | bool _parse_positional(std::vector<std::string> &args) { |
| 2350 | 2354 | |
| 2351 | 2355 | const std::string &positional = args.back(); |
| 2356 | + | |
| 2357 | + if(positionals_at_end_) { | |
| 2358 | + // deal with the case of required arguments at the end which should take precedence over other arguments | |
| 2359 | + auto arg_rem = args.size(); | |
| 2360 | + auto remreq = _count_remaining_positionals(true); | |
| 2361 | + if(arg_rem <= remreq) { | |
| 2362 | + for(const Option_p &opt : options_) { | |
| 2363 | + if(opt->get_positional() && opt->required_) { | |
| 2364 | + if(static_cast<int>(opt->count()) < opt->get_items_expected() || | |
| 2365 | + (opt->get_items_expected() < 0 && opt->count() == 0lu)) { | |
| 2366 | + if(validate_positionals_) { | |
| 2367 | + std::string pos = positional; | |
| 2368 | + pos = opt->_validate(pos); | |
| 2369 | + if(!pos.empty()) { | |
| 2370 | + continue; | |
| 2371 | + } | |
| 2372 | + } | |
| 2373 | + opt->add_result(positional); | |
| 2374 | + parse_order_.push_back(opt.get()); | |
| 2375 | + args.pop_back(); | |
| 2376 | + return true; | |
| 2377 | + } | |
| 2378 | + } | |
| 2379 | + } | |
| 2380 | + } | |
| 2381 | + } | |
| 2352 | 2382 | for(const Option_p &opt : options_) { |
| 2353 | 2383 | // Eat options, one by one, until done |
| 2354 | 2384 | if(opt->get_positional() && | ... | ... |
tests/AppTest.cpp
| ... | ... | @@ -993,6 +993,73 @@ TEST_F(TApp, PositionalAtEnd) { |
| 993 | 993 | } |
| 994 | 994 | |
| 995 | 995 | // Tests positionals at end |
| 996 | +TEST_F(TApp, RequiredPositionals) { | |
| 997 | + std::vector<std::string> sources; | |
| 998 | + std::string dest; | |
| 999 | + app.add_option("src", sources); | |
| 1000 | + app.add_option("dest", dest)->required(); | |
| 1001 | + app.positionals_at_end(); | |
| 1002 | + | |
| 1003 | + args = {"1", "2", "3"}; | |
| 1004 | + run(); | |
| 1005 | + | |
| 1006 | + EXPECT_EQ(sources.size(), 2u); | |
| 1007 | + EXPECT_EQ(dest, "3"); | |
| 1008 | + | |
| 1009 | + args = {"a"}; | |
| 1010 | + sources.clear(); | |
| 1011 | + run(); | |
| 1012 | + | |
| 1013 | + EXPECT_EQ(sources.size(), 0u); | |
| 1014 | + EXPECT_EQ(dest, "a"); | |
| 1015 | +} | |
| 1016 | + | |
| 1017 | +TEST_F(TApp, RequiredPositionalVector) { | |
| 1018 | + std::string d1; | |
| 1019 | + std::string d2; | |
| 1020 | + std::string d3; | |
| 1021 | + std::vector<std::string> sources; | |
| 1022 | + | |
| 1023 | + app.add_option("dest1", d1); | |
| 1024 | + app.add_option("dest2", d2); | |
| 1025 | + app.add_option("dest3", d3); | |
| 1026 | + app.add_option("src", sources)->required(); | |
| 1027 | + | |
| 1028 | + app.positionals_at_end(); | |
| 1029 | + | |
| 1030 | + args = {"1", "2", "3"}; | |
| 1031 | + run(); | |
| 1032 | + | |
| 1033 | + EXPECT_EQ(sources.size(), 1u); | |
| 1034 | + EXPECT_EQ(d1, "1"); | |
| 1035 | + EXPECT_EQ(d2, "2"); | |
| 1036 | + EXPECT_TRUE(d3.empty()); | |
| 1037 | + args = {"a"}; | |
| 1038 | + sources.clear(); | |
| 1039 | + run(); | |
| 1040 | + | |
| 1041 | + EXPECT_EQ(sources.size(), 1u); | |
| 1042 | +} | |
| 1043 | + | |
| 1044 | +// Tests positionals at end | |
| 1045 | +TEST_F(TApp, RequiredPositionalValidation) { | |
| 1046 | + std::vector<std::string> sources; | |
| 1047 | + int dest; | |
| 1048 | + std::string d2; | |
| 1049 | + app.add_option("src", sources); | |
| 1050 | + app.add_option("dest", dest)->required()->check(CLI::PositiveNumber); | |
| 1051 | + app.add_option("dest2", d2)->required(); | |
| 1052 | + app.positionals_at_end()->validate_positionals(); | |
| 1053 | + | |
| 1054 | + args = {"1", "2", "string", "3"}; | |
| 1055 | + run(); | |
| 1056 | + | |
| 1057 | + EXPECT_EQ(sources.size(), 2u); | |
| 1058 | + EXPECT_EQ(dest, 3); | |
| 1059 | + EXPECT_EQ(d2, "string"); | |
| 1060 | +} | |
| 1061 | + | |
| 1062 | +// Tests positionals at end | |
| 996 | 1063 | TEST_F(TApp, PositionalValidation) { |
| 997 | 1064 | std::string options; |
| 998 | 1065 | std::string foo; | ... | ... |