Commit f059528559b7ee890bac0b76b977310374a8c675
Committed by
Henry Schreiner
1 parent
447bda04
Support for multi-size MultiOptionPolicies
Showing
3 changed files
with
63 additions
and
11 deletions
include/CLI/Error.hpp
| ... | ... | @@ -105,7 +105,7 @@ class IncorrectConstruction : public ConstructionError { |
| 105 | 105 | return IncorrectConstruction("Option " + name + " is not defined"); |
| 106 | 106 | } |
| 107 | 107 | static IncorrectConstruction MultiOptionPolicy(std::string name) { |
| 108 | - return IncorrectConstruction(name + ": multi_option_policy only works for flags and single value options"); | |
| 108 | + return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); | |
| 109 | 109 | } |
| 110 | 110 | }; |
| 111 | 111 | ... | ... |
include/CLI/Option.hpp
| ... | ... | @@ -383,8 +383,8 @@ class Option : public OptionBase<Option> { |
| 383 | 383 | |
| 384 | 384 | /// Take the last argument if given multiple times (or another policy) |
| 385 | 385 | Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) { |
| 386 | - // TODO: This can support multiple options | |
| 387 | - if(get_type_size() != 0 && get_expected() != 1) | |
| 386 | + | |
| 387 | + if(get_items_expected() < 0) | |
| 388 | 388 | throw IncorrectConstruction::MultiOptionPolicy(single_name()); |
| 389 | 389 | multi_option_policy_ = value; |
| 390 | 390 | return this; |
| ... | ... | @@ -400,12 +400,25 @@ class Option : public OptionBase<Option> { |
| 400 | 400 | /// The number of times the option expects to be included |
| 401 | 401 | int get_expected() const { return expected_; } |
| 402 | 402 | |
| 403 | - /// The total number of expected values (including the type) | |
| 403 | + /// \breif The total number of expected values (including the type) | |
| 404 | + /// This is positive if exactly this number is expected, and negitive for at least N values | |
| 405 | + /// | |
| 406 | + /// v = fabs(size_type*expected) | |
| 407 | + /// !MultiOptionPolicy::Throw | |
| 408 | + /// | Expected < 0 | Expected == 0 | Expected > 0 | |
| 409 | + /// Size < 0 | -v | 0 | -v | |
| 410 | + /// Size == 0 | 0 | 0 | 0 | |
| 411 | + /// Size > 0 | -v | 0 | -v // Expected must be 1 | |
| 412 | + /// | |
| 413 | + /// MultiOptionPolicy::Throw | |
| 414 | + /// | Expected < 0 | Expected == 0 | Expected > 0 | |
| 415 | + /// Size < 0 | -v | 0 | v | |
| 416 | + /// Size == 0 | 0 | 0 | 0 | |
| 417 | + /// Size > 0 | v | 0 | v // Expected must be 1 | |
| 418 | + /// | |
| 404 | 419 | 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_; | |
| 420 | + return std::abs(type_size_ * expected_) * | |
| 421 | + ((multi_option_policy_ != MultiOptionPolicy::Throw || (expected_ < 0 && type_size_ < 0) ? -1 : 1)); | |
| 409 | 422 | } |
| 410 | 423 | |
| 411 | 424 | /// True if this has a default value |
| ... | ... | @@ -525,14 +538,18 @@ class Option : public OptionBase<Option> { |
| 525 | 538 | |
| 526 | 539 | bool local_result; |
| 527 | 540 | |
| 541 | + // Num items expected or length of vector, always at least 1 | |
| 542 | + // Only valid for a trimming policy | |
| 543 | + int trim_size = std::min(std::max(std::abs(get_items_expected()), 1), static_cast<int>(results_.size())); | |
| 544 | + | |
| 528 | 545 | // Operation depends on the policy setting |
| 529 | 546 | if(multi_option_policy_ == MultiOptionPolicy::TakeLast) { |
| 530 | - // TODO: add non-1 size arguments here | |
| 531 | - results_t partial_result = {results_.back()}; | |
| 547 | + // Allow multi-option sizes (including 0) | |
| 548 | + results_t partial_result{results_.end() - trim_size, results_.end()}; | |
| 532 | 549 | local_result = !callback_(partial_result); |
| 533 | 550 | |
| 534 | 551 | } else if(multi_option_policy_ == MultiOptionPolicy::TakeFirst) { |
| 535 | - results_t partial_result = {results_.at(0)}; | |
| 552 | + results_t partial_result{results_.begin(), results_.begin() + trim_size}; | |
| 536 | 553 | local_result = !callback_(partial_result); |
| 537 | 554 | |
| 538 | 555 | } else if(multi_option_policy_ == MultiOptionPolicy::Join) { | ... | ... |
tests/AppTest.cpp
| 1 | 1 | #include "app_helper.hpp" |
| 2 | 2 | #include <cstdlib> |
| 3 | +#include <complex> | |
| 3 | 4 | |
| 4 | 5 | TEST_F(TApp, OneFlagShort) { |
| 5 | 6 | app.add_flag("-c,--count"); |
| ... | ... | @@ -290,6 +291,40 @@ TEST_F(TApp, JoinOpt2) { |
| 290 | 291 | EXPECT_EQ(str, "one\ntwo"); |
| 291 | 292 | } |
| 292 | 293 | |
| 294 | +TEST_F(TApp, TakeLastOptMulti) { | |
| 295 | + std::vector<int> vals; | |
| 296 | + app.add_option("--long", vals)->expected(2)->take_last(); | |
| 297 | + | |
| 298 | + args = {"--long", "1", "2", "3"}; | |
| 299 | + | |
| 300 | + run(); | |
| 301 | + | |
| 302 | + EXPECT_EQ(vals, std::vector<int>({2, 3})); | |
| 303 | +} | |
| 304 | + | |
| 305 | +TEST_F(TApp, TakeFirstOptMulti) { | |
| 306 | + std::vector<int> vals; | |
| 307 | + app.add_option("--long", vals)->expected(2)->take_first(); | |
| 308 | + | |
| 309 | + args = {"--long", "1", "2", "3"}; | |
| 310 | + | |
| 311 | + run(); | |
| 312 | + | |
| 313 | + EXPECT_EQ(vals, std::vector<int>({1, 2})); | |
| 314 | +} | |
| 315 | + | |
| 316 | +TEST_F(TApp, ComplexOptMulti) { | |
| 317 | + std::complex<double> val; | |
| 318 | + app.add_complex("--long", val)->take_first(); | |
| 319 | + | |
| 320 | + args = {"--long", "1", "2", "3", "4"}; | |
| 321 | + | |
| 322 | + run(); | |
| 323 | + | |
| 324 | + EXPECT_FLOAT_EQ(val.real(), 1); | |
| 325 | + EXPECT_FLOAT_EQ(val.imag(), 2); | |
| 326 | +} | |
| 327 | + | |
| 293 | 328 | TEST_F(TApp, MissingValueNonRequiredOpt) { |
| 294 | 329 | int count; |
| 295 | 330 | app.add_option("-c,--count", count); | ... | ... |