1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 12:13:52 +00:00

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.
This commit is contained in:
Philip Top 2019-08-07 07:52:58 -07:00 committed by Henry Schreiner
parent eb6f759f51
commit 4bfce43795
2 changed files with 102 additions and 5 deletions

View File

@ -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<int>(opt->count()) < opt->get_items_expected())
retval = static_cast<size_t>(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<int>(opt->count()) < opt->get_items_expected()) {
retval += static_cast<size_t>(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<std::string> &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<int>(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() &&

View File

@ -992,6 +992,73 @@ TEST_F(TApp, PositionalAtEnd) {
EXPECT_THROW(run(), CLI::ExtrasError);
}
// Tests positionals at end
TEST_F(TApp, RequiredPositionals) {
std::vector<std::string> 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<std::string> 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<std::string> 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;