Commit 447bda047ff97b15feb9f3e4af4edbf526fdf05b
Committed by
Henry Schreiner
1 parent
de06d50f
Adding size instead of expected + unchangable
Showing
4 changed files
with
66 additions
and
32 deletions
include/CLI/App.hpp
| ... | ... | @@ -835,7 +835,7 @@ class App { |
| 835 | 835 | std::string value; |
| 836 | 836 | |
| 837 | 837 | // Non-flags |
| 838 | - if(opt->get_expected() != 0) { | |
| 838 | + if(opt->get_type_size() != 0) { | |
| 839 | 839 | |
| 840 | 840 | // If the option was found on command line |
| 841 | 841 | if(opt->count() > 0) |
| ... | ... | @@ -1069,7 +1069,7 @@ class App { |
| 1069 | 1069 | /// Currently checks to see if multiple positionals exist with -1 args |
| 1070 | 1070 | void _validate() const { |
| 1071 | 1071 | auto count = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) { |
| 1072 | - return opt->get_expected() == -1 && opt->get_positional(); | |
| 1072 | + return opt->get_items_expected() < 0 && opt->get_positional(); | |
| 1073 | 1073 | }); |
| 1074 | 1074 | if(count > 1) |
| 1075 | 1075 | throw InvalidError(name_); |
| ... | ... | @@ -1189,8 +1189,8 @@ class App { |
| 1189 | 1189 | // Required or partially filled |
| 1190 | 1190 | if(opt->get_required() || opt->count() != 0) { |
| 1191 | 1191 | // Make sure enough -N arguments parsed (+N is already handled in parsing function) |
| 1192 | - if(opt->get_expected() < 0 && opt->count() < static_cast<size_t>(-opt->get_expected())) | |
| 1193 | - throw ArgumentMismatch::AtLeast(opt->single_name(), -opt->get_expected()); | |
| 1192 | + if(opt->get_items_expected() < 0 && opt->count() < static_cast<size_t>(-opt->get_items_expected())) | |
| 1193 | + throw ArgumentMismatch::AtLeast(opt->single_name(), -opt->get_items_expected()); | |
| 1194 | 1194 | |
| 1195 | 1195 | // Required but empty |
| 1196 | 1196 | if(opt->get_required() && opt->count() == 0) |
| ... | ... | @@ -1260,7 +1260,7 @@ class App { |
| 1260 | 1260 | |
| 1261 | 1261 | if(op->results_.empty()) { |
| 1262 | 1262 | // Flag parsing |
| 1263 | - if(op->get_expected() == 0) { | |
| 1263 | + if(op->get_type_size() == 0) { | |
| 1264 | 1264 | if(current.inputs.size() == 1) { |
| 1265 | 1265 | std::string val = current.inputs.at(0); |
| 1266 | 1266 | val = detail::to_lower(val); |
| ... | ... | @@ -1320,9 +1320,9 @@ class App { |
| 1320 | 1320 | size_t _count_remaining_positionals(bool required = false) const { |
| 1321 | 1321 | size_t retval = 0; |
| 1322 | 1322 | for(const Option_p &opt : options_) |
| 1323 | - if(opt->get_positional() && (!required || opt->get_required()) && opt->get_expected() > 0 && | |
| 1324 | - static_cast<int>(opt->count()) < opt->get_expected()) | |
| 1325 | - retval = static_cast<size_t>(opt->get_expected()) - opt->count(); | |
| 1323 | + if(opt->get_positional() && (!required || opt->get_required()) && opt->get_items_expected() > 0 && | |
| 1324 | + static_cast<int>(opt->count()) < opt->get_items_expected()) | |
| 1325 | + retval = static_cast<size_t>(opt->get_items_expected()) - opt->count(); | |
| 1326 | 1326 | |
| 1327 | 1327 | return retval; |
| 1328 | 1328 | } |
| ... | ... | @@ -1334,7 +1334,7 @@ class App { |
| 1334 | 1334 | for(const Option_p &opt : options_) { |
| 1335 | 1335 | // Eat options, one by one, until done |
| 1336 | 1336 | if(opt->get_positional() && |
| 1337 | - (static_cast<int>(opt->count()) < opt->get_expected() || opt->get_expected() < 0)) { | |
| 1337 | + (static_cast<int>(opt->count()) < opt->get_items_expected() || opt->get_items_expected() < 0)) { | |
| 1338 | 1338 | |
| 1339 | 1339 | opt->add_result(positional); |
| 1340 | 1340 | parse_order_.push_back(opt.get()); |
| ... | ... | @@ -1421,7 +1421,7 @@ class App { |
| 1421 | 1421 | // Get a reference to the pointer to make syntax bearable |
| 1422 | 1422 | Option_p &op = *op_ptr; |
| 1423 | 1423 | |
| 1424 | - int num = op->get_expected(); | |
| 1424 | + int num = op->get_items_expected(); | |
| 1425 | 1425 | |
| 1426 | 1426 | // Make sure we always eat the minimum for unlimited vectors |
| 1427 | 1427 | int collected = 0; |
| ... | ... | @@ -1459,7 +1459,7 @@ class App { |
| 1459 | 1459 | |
| 1460 | 1460 | // If there are any unlimited positionals, those also take priority |
| 1461 | 1461 | if(std::any_of(std::begin(options_), std::end(options_), [](const Option_p &opt) { |
| 1462 | - return opt->get_positional() && opt->get_expected() < 0; | |
| 1462 | + return opt->get_positional() && opt->get_items_expected() < 0; | |
| 1463 | 1463 | })) |
| 1464 | 1464 | break; |
| 1465 | 1465 | } | ... | ... |
include/CLI/Error.hpp
| ... | ... | @@ -91,6 +91,9 @@ class IncorrectConstruction : public ConstructionError { |
| 91 | 91 | static IncorrectConstruction Set0Opt(std::string name) { |
| 92 | 92 | return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); |
| 93 | 93 | } |
| 94 | + static IncorrectConstruction SetFlag(std::string name) { | |
| 95 | + return IncorrectConstruction(name + ": Cannot set an expected number for flags"); | |
| 96 | + } | |
| 94 | 97 | static IncorrectConstruction ChangeNotVector(std::string name) { |
| 95 | 98 | return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); |
| 96 | 99 | } | ... | ... |
include/CLI/Option.hpp
| ... | ... | @@ -180,11 +180,13 @@ class Option : public OptionBase<Option> { |
| 180 | 180 | /// @name Configuration |
| 181 | 181 | ///@{ |
| 182 | 182 | |
| 183 | - /// The number of expected values, 0 for flag, -1 for unlimited vector | |
| 184 | - int expected_{1}; | |
| 183 | + /// The number of arguments that make up one option. -1=unlimited (vector-like), 0=flag, 1=normal option, | |
| 184 | + /// 2=complex/pair, etc. Set only when the option is created; this is intrinsic to the type. Eventually, -2 may mean | |
| 185 | + /// vector of pairs. | |
| 186 | + int type_size_{1}; | |
| 185 | 187 | |
| 186 | - /// A private setting to allow args to not be able to accept incorrect expected values | |
| 187 | - bool changeable_{false}; | |
| 188 | + /// The number of expected values, type_size_ must be < 0. Ignored for flag. N < 0 means at least -N values. | |
| 189 | + int expected_{1}; | |
| 188 | 190 | |
| 189 | 191 | /// A list of validators to run on each value parsed |
| 190 | 192 | std::vector<std::function<std::string(std::string &)>> validators_; |
| ... | ... | @@ -244,14 +246,25 @@ class Option : public OptionBase<Option> { |
| 244 | 246 | /// @name Setting options |
| 245 | 247 | ///@{ |
| 246 | 248 | |
| 247 | - /// Set the number of expected arguments (Flags bypass this) | |
| 249 | + /// Set the number of expected arguments (Flags don't use this) | |
| 248 | 250 | Option *expected(int value) { |
| 249 | - if(expected_ == value) | |
| 250 | - return this; | |
| 251 | + // Break if this is a flag | |
| 252 | + if(type_size_ == 0) | |
| 253 | + throw IncorrectConstruction::SetFlag(single_name()); | |
| 254 | + | |
| 255 | + // Setting 0 is not allowed | |
| 251 | 256 | else if(value == 0) |
| 252 | 257 | throw IncorrectConstruction::Set0Opt(single_name()); |
| 253 | - else if(!changeable_) | |
| 258 | + | |
| 259 | + // No change is okay, quit now | |
| 260 | + else if(expected_ == value) | |
| 261 | + return this; | |
| 262 | + | |
| 263 | + // Type must be a vector | |
| 264 | + else if(type_size_ >= 0) | |
| 254 | 265 | throw IncorrectConstruction::ChangeNotVector(single_name()); |
| 266 | + | |
| 267 | + // TODO: Can support multioption for non-1 values (except for join) | |
| 255 | 268 | else if(value != 1 && multi_option_policy_ != MultiOptionPolicy::Throw) |
| 256 | 269 | throw IncorrectConstruction::AfterMultiOpt(single_name()); |
| 257 | 270 | |
| ... | ... | @@ -368,9 +381,10 @@ class Option : public OptionBase<Option> { |
| 368 | 381 | return this; |
| 369 | 382 | } |
| 370 | 383 | |
| 371 | - /// Take the last argument if given multiple times | |
| 384 | + /// Take the last argument if given multiple times (or another policy) | |
| 372 | 385 | Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) { |
| 373 | - if(get_expected() != 0 && get_expected() != 1) | |
| 386 | + // TODO: This can support multiple options | |
| 387 | + if(get_type_size() != 0 && get_expected() != 1) | |
| 374 | 388 | throw IncorrectConstruction::MultiOptionPolicy(single_name()); |
| 375 | 389 | multi_option_policy_ = value; |
| 376 | 390 | return this; |
| ... | ... | @@ -381,8 +395,19 @@ class Option : public OptionBase<Option> { |
| 381 | 395 | ///@{ |
| 382 | 396 | |
| 383 | 397 | /// The number of arguments the option expects |
| 398 | + int get_type_size() const { return type_size_; } | |
| 399 | + | |
| 400 | + /// The number of times the option expects to be included | |
| 384 | 401 | int get_expected() const { return expected_; } |
| 385 | 402 | |
| 403 | + /// The total number of expected values (including the type) | |
| 404 | + int get_items_expected() const { | |
| 405 | + // type_size == 0, return 0 | |
| 406 | + // type_size > 1, return type_size_ | |
| 407 | + // type_size < 0, return -type_size * expected; | |
| 408 | + return type_size_ < 0 ? -1 * type_size_ * expected_ : type_size_; | |
| 409 | + } | |
| 410 | + | |
| 386 | 411 | /// True if this has a default value |
| 387 | 412 | int get_default() const { return default_; } |
| 388 | 413 | |
| ... | ... | @@ -428,7 +453,7 @@ class Option : public OptionBase<Option> { |
| 428 | 453 | return out; |
| 429 | 454 | } |
| 430 | 455 | |
| 431 | - /// The most discriptive name available | |
| 456 | + /// The most descriptive name available | |
| 432 | 457 | std::string single_name() const { |
| 433 | 458 | if(!lnames_.empty()) |
| 434 | 459 | return std::string("--") + lnames_[0]; |
| ... | ... | @@ -456,7 +481,7 @@ class Option : public OptionBase<Option> { |
| 456 | 481 | std::string help_aftername() const { |
| 457 | 482 | std::stringstream out; |
| 458 | 483 | |
| 459 | - if(get_expected() != 0) { | |
| 484 | + if(get_type_size() != 0) { | |
| 460 | 485 | if(!typeval_.empty()) |
| 461 | 486 | out << " " << typeval_; |
| 462 | 487 | if(!defaultval_.empty()) |
| ... | ... | @@ -502,18 +527,23 @@ class Option : public OptionBase<Option> { |
| 502 | 527 | |
| 503 | 528 | // Operation depends on the policy setting |
| 504 | 529 | if(multi_option_policy_ == MultiOptionPolicy::TakeLast) { |
| 530 | + // TODO: add non-1 size arguments here | |
| 505 | 531 | results_t partial_result = {results_.back()}; |
| 506 | 532 | local_result = !callback_(partial_result); |
| 533 | + | |
| 507 | 534 | } else if(multi_option_policy_ == MultiOptionPolicy::TakeFirst) { |
| 508 | 535 | results_t partial_result = {results_.at(0)}; |
| 509 | 536 | local_result = !callback_(partial_result); |
| 537 | + | |
| 510 | 538 | } else if(multi_option_policy_ == MultiOptionPolicy::Join) { |
| 511 | 539 | results_t partial_result = {detail::join(results_, "\n")}; |
| 512 | 540 | local_result = !callback_(partial_result); |
| 541 | + | |
| 513 | 542 | } else { |
| 514 | - if((expected_ > 0 && results_.size() != static_cast<size_t>(expected_)) || | |
| 515 | - (expected_ < 0 && results_.size() < static_cast<size_t>(-expected_))) | |
| 516 | - throw ArgumentMismatch(single_name(), expected_, results_.size()); | |
| 543 | + // For now, vector of non size 1 types are not supported but possibility included here | |
| 544 | + if((get_items_expected() > 0 && results_.size() != static_cast<size_t>(get_items_expected())) || | |
| 545 | + (get_items_expected() < 0 && results_.size() < static_cast<size_t>(-get_items_expected()))) | |
| 546 | + throw ArgumentMismatch(single_name(), get_items_expected(), results_.size()); | |
| 517 | 547 | else |
| 518 | 548 | local_result = !callback_(results_); |
| 519 | 549 | } |
| ... | ... | @@ -595,13 +625,14 @@ class Option : public OptionBase<Option> { |
| 595 | 625 | /// @name Custom options |
| 596 | 626 | ///@{ |
| 597 | 627 | |
| 598 | - /// Set a custom option, typestring, expected; locks changeable unless expected is -1 | |
| 599 | - void set_custom_option(std::string typeval, int expected = 1) { | |
| 628 | + /// Set a custom option, typestring, type_size | |
| 629 | + void set_custom_option(std::string typeval, int type_size = 1) { | |
| 600 | 630 | typeval_ = typeval; |
| 601 | - expected_ = expected; | |
| 602 | - if(expected == 0) | |
| 631 | + type_size_ = type_size; | |
| 632 | + if(type_size_ == 0) | |
| 603 | 633 | required_ = false; |
| 604 | - changeable_ = expected < 0; | |
| 634 | + if(type_size < 0) | |
| 635 | + expected_ = -1; | |
| 605 | 636 | } |
| 606 | 637 | |
| 607 | 638 | /// Set the default value string representation | ... | ... |
tests/CreationTest.cpp
| ... | ... | @@ -125,7 +125,7 @@ TEST_F(TApp, IncorrectConstructionFlagPositional3) { |
| 125 | 125 | |
| 126 | 126 | TEST_F(TApp, IncorrectConstructionFlagExpected) { |
| 127 | 127 | auto cat = app.add_flag("--cat"); |
| 128 | - EXPECT_NO_THROW(cat->expected(0)); | |
| 128 | + EXPECT_THROW(cat->expected(0), CLI::IncorrectConstruction); | |
| 129 | 129 | EXPECT_THROW(cat->expected(1), CLI::IncorrectConstruction); |
| 130 | 130 | } |
| 131 | 131 | ... | ... |