Commit 4bfce437958547efe65e1cc834ff78a8d8814fc8

Authored by Philip Top
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.
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;
... ...