Commit 4d5bff2393eae8348e7abdada6af3f4e5e075593
Committed by
Henry Schreiner
1 parent
afd4e328
Adding ArgumentMismatch, changable improvement
Showing
4 changed files
with
32 additions
and
35 deletions
include/CLI/App.hpp
| ... | ... | @@ -274,8 +274,6 @@ class App { |
| 274 | 274 | |
| 275 | 275 | std::string simple_name = CLI::detail::split(name, ',').at(0); |
| 276 | 276 | CLI::callback_t fun = [&variable, simple_name](CLI::results_t res) { |
| 277 | - if(res.size() != 1) | |
| 278 | - throw ConversionError("Only one " + simple_name + " allowed"); | |
| 279 | 277 | return detail::lexical_cast(res[0], variable); |
| 280 | 278 | }; |
| 281 | 279 | |
| ... | ... | @@ -293,8 +291,6 @@ class App { |
| 293 | 291 | |
| 294 | 292 | std::string simple_name = CLI::detail::split(name, ',').at(0); |
| 295 | 293 | CLI::callback_t fun = [&variable, simple_name](CLI::results_t res) { |
| 296 | - if(res.size() != 1) | |
| 297 | - throw ConversionError("Only one " + simple_name + " allowed"); | |
| 298 | 294 | return detail::lexical_cast(res[0], variable); |
| 299 | 295 | }; |
| 300 | 296 | |
| ... | ... | @@ -325,7 +321,7 @@ class App { |
| 325 | 321 | }; |
| 326 | 322 | |
| 327 | 323 | Option *opt = add_option(name, fun, description, false); |
| 328 | - opt->set_custom_option(detail::type_name<T>(), -1, true); | |
| 324 | + opt->set_custom_option(detail::type_name<T>(), -1); | |
| 329 | 325 | return opt; |
| 330 | 326 | } |
| 331 | 327 | |
| ... | ... | @@ -347,7 +343,7 @@ class App { |
| 347 | 343 | }; |
| 348 | 344 | |
| 349 | 345 | Option *opt = add_option(name, fun, description, defaulted); |
| 350 | - opt->set_custom_option(detail::type_name<T>(), -1, true); | |
| 346 | + opt->set_custom_option(detail::type_name<T>(), -1); | |
| 351 | 347 | if(defaulted) |
| 352 | 348 | opt->set_default_str("[" + detail::join(variable) + "]"); |
| 353 | 349 | return opt; |
| ... | ... | @@ -454,9 +450,6 @@ class App { |
| 454 | 450 | |
| 455 | 451 | std::string simple_name = CLI::detail::split(name, ',').at(0); |
| 456 | 452 | CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) { |
| 457 | - if(res.size() != 1) { | |
| 458 | - throw ConversionError("Only one " + simple_name + " allowed"); | |
| 459 | - } | |
| 460 | 453 | bool retval = detail::lexical_cast(res[0], member); |
| 461 | 454 | if(!retval) |
| 462 | 455 | throw ConversionError("The value " + res[0] + "is not an allowed value for " + simple_name); |
| ... | ... | @@ -480,9 +473,6 @@ class App { |
| 480 | 473 | |
| 481 | 474 | std::string simple_name = CLI::detail::split(name, ',').at(0); |
| 482 | 475 | CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) { |
| 483 | - if(res.size() != 1) { | |
| 484 | - throw ConversionError("Only one " + simple_name + " allowed"); | |
| 485 | - } | |
| 486 | 476 | bool retval = detail::lexical_cast(res[0], member); |
| 487 | 477 | if(!retval) |
| 488 | 478 | throw ConversionError("The value " + res[0] + "is not an allowed value for " + simple_name); |
| ... | ... | @@ -509,9 +499,6 @@ class App { |
| 509 | 499 | |
| 510 | 500 | std::string simple_name = CLI::detail::split(name, ',').at(0); |
| 511 | 501 | CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) { |
| 512 | - if(res.size() != 1) { | |
| 513 | - throw ConversionError("Only one " + simple_name + " allowed"); | |
| 514 | - } | |
| 515 | 502 | member = detail::to_lower(res[0]); |
| 516 | 503 | auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) { |
| 517 | 504 | return detail::to_lower(val) == member; |
| ... | ... | @@ -541,9 +528,6 @@ class App { |
| 541 | 528 | |
| 542 | 529 | std::string simple_name = CLI::detail::split(name, ',').at(0); |
| 543 | 530 | CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) { |
| 544 | - if(res.size() != 1) { | |
| 545 | - throw ConversionError("Only one " + simple_name + " allowed"); | |
| 546 | - } | |
| 547 | 531 | member = detail::to_lower(res[0]); |
| 548 | 532 | auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) { |
| 549 | 533 | return detail::to_lower(val) == member; | ... | ... |
include/CLI/Error.hpp
| ... | ... | @@ -43,6 +43,7 @@ enum class ExitCodes { |
| 43 | 43 | InvalidError, |
| 44 | 44 | HorribleError, |
| 45 | 45 | OptionNotFound, |
| 46 | + ArgumentMismatch, | |
| 46 | 47 | BaseClass = 127 |
| 47 | 48 | }; |
| 48 | 49 | |
| ... | ... | @@ -146,6 +147,17 @@ class RequiredError : public ParseError { |
| 146 | 147 | CLI11_ERROR_SIMPLE(RequiredError) |
| 147 | 148 | }; |
| 148 | 149 | |
| 150 | +/// Thrown when the wrong number of arguments has been recieved | |
| 151 | +class ArgumentMismatch : ParseError { | |
| 152 | + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) | |
| 153 | + ArgumentMismatch(std::string name, int expected, size_t recieved) | |
| 154 | + : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + | |
| 155 | + ", got " + std::to_string(recieved)) | |
| 156 | + : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + | |
| 157 | + ", got " + std::to_string(recieved)), | |
| 158 | + ExitCodes::ArgumentMismatch) {} | |
| 159 | +}; | |
| 160 | + | |
| 149 | 161 | /// Thrown when a requires option is missing |
| 150 | 162 | class RequiresError : public ParseError { |
| 151 | 163 | CLI11_ERROR_DEF(ParseError, RequiresError) | ... | ... |
include/CLI/Option.hpp
| ... | ... | @@ -207,14 +207,15 @@ class Option : public OptionBase<Option> { |
| 207 | 207 | |
| 208 | 208 | /// Set the number of expected arguments (Flags bypass this) |
| 209 | 209 | Option *expected(int value) { |
| 210 | - if(value == 0) | |
| 210 | + if(expected_ == value) | |
| 211 | + return this; | |
| 212 | + else if(value == 0) | |
| 211 | 213 | throw IncorrectConstruction("Cannot set 0 expected, use a flag instead"); |
| 212 | - else if(expected_ == 0) | |
| 213 | - throw IncorrectConstruction("Cannot make a flag take arguments!"); | |
| 214 | 214 | else if(!changeable_) |
| 215 | 215 | throw IncorrectConstruction("You can only change the expected arguments for vectors"); |
| 216 | 216 | else if(last_) |
| 217 | 217 | throw IncorrectConstruction("You can't change expected arguments after you've set take_last!"); |
| 218 | + | |
| 218 | 219 | expected_ = value; |
| 219 | 220 | return this; |
| 220 | 221 | } |
| ... | ... | @@ -447,7 +448,11 @@ class Option : public OptionBase<Option> { |
| 447 | 448 | results_t partial_result = {results_.back()}; |
| 448 | 449 | local_result = !callback_(partial_result); |
| 449 | 450 | } else { |
| 450 | - local_result = !callback_(results_); | |
| 451 | + if((expected_ > 0 && results_.size() != static_cast<size_t>(expected_)) || | |
| 452 | + (expected_ < 0 && results_.size() < static_cast<size_t>(-expected_))) | |
| 453 | + throw ArgumentMismatch(single_name(), expected_, results_.size()); | |
| 454 | + else | |
| 455 | + local_result = !callback_(results_); | |
| 451 | 456 | } |
| 452 | 457 | |
| 453 | 458 | if(local_result) |
| ... | ... | @@ -527,13 +532,13 @@ class Option : public OptionBase<Option> { |
| 527 | 532 | /// @name Custom options |
| 528 | 533 | ///@{ |
| 529 | 534 | |
| 530 | - /// Set a custom option, typestring, expected, and changeable | |
| 531 | - void set_custom_option(std::string typeval, int expected = 1, bool changeable = false) { | |
| 535 | + /// Set a custom option, typestring, expected; locks changeable unless expected is -1 | |
| 536 | + void set_custom_option(std::string typeval, int expected = 1) { | |
| 532 | 537 | typeval_ = typeval; |
| 533 | 538 | expected_ = expected; |
| 534 | 539 | if(expected == 0) |
| 535 | 540 | required_ = false; |
| 536 | - changeable_ = changeable; | |
| 541 | + changeable_ = expected < 0; | |
| 537 | 542 | } |
| 538 | 543 | |
| 539 | 544 | /// Set the default value string representation | ... | ... |
tests/AppTest.cpp
| ... | ... | @@ -119,7 +119,7 @@ TEST_F(TApp, DualOptions) { |
| 119 | 119 | EXPECT_EQ(ans, vstr); |
| 120 | 120 | |
| 121 | 121 | args = {"--string=one", "--string=two"}; |
| 122 | - EXPECT_THROW(run(), CLI::ConversionError); | |
| 122 | + EXPECT_THROW(run(), CLI::ArgumentMismatch); | |
| 123 | 123 | } |
| 124 | 124 | |
| 125 | 125 | TEST_F(TApp, LotsOfFlags) { |
| ... | ... | @@ -737,7 +737,7 @@ TEST_F(TApp, FailSet) { |
| 737 | 737 | app.add_set("-q,--quick", choice, {1, 2, 3}); |
| 738 | 738 | |
| 739 | 739 | args = {"--quick", "3", "--quick=2"}; |
| 740 | - EXPECT_THROW(run(), CLI::ConversionError); | |
| 740 | + EXPECT_THROW(run(), CLI::ArgumentMismatch); | |
| 741 | 741 | |
| 742 | 742 | app.reset(); |
| 743 | 743 | |
| ... | ... | @@ -770,7 +770,7 @@ TEST_F(TApp, InSetIgnoreCase) { |
| 770 | 770 | |
| 771 | 771 | app.reset(); |
| 772 | 772 | args = {"--quick=one", "--quick=two"}; |
| 773 | - EXPECT_THROW(run(), CLI::ConversionError); | |
| 773 | + EXPECT_THROW(run(), CLI::ArgumentMismatch); | |
| 774 | 774 | } |
| 775 | 775 | |
| 776 | 776 | TEST_F(TApp, VectorFixedString) { |
| ... | ... | @@ -1145,27 +1145,24 @@ TEST_F(TApp, CheckSubcomFail) { |
| 1145 | 1145 | EXPECT_THROW(CLI::detail::AppFriend::parse_subcommand(&app, args), CLI::HorribleError); |
| 1146 | 1146 | } |
| 1147 | 1147 | |
| 1148 | -// Added to test defaults on dual method | |
| 1149 | 1148 | TEST_F(TApp, OptionWithDefaults) { |
| 1150 | 1149 | int someint = 2; |
| 1151 | 1150 | app.add_option("-a", someint, "", true); |
| 1152 | 1151 | |
| 1153 | 1152 | args = {"-a1", "-a2"}; |
| 1154 | 1153 | |
| 1155 | - EXPECT_THROW(run(), CLI::ConversionError); | |
| 1154 | + EXPECT_THROW(run(), CLI::ArgumentMismatch); | |
| 1156 | 1155 | } |
| 1157 | 1156 | |
| 1158 | -// Added to test defaults on dual method | |
| 1159 | 1157 | TEST_F(TApp, SetWithDefaults) { |
| 1160 | 1158 | int someint = 2; |
| 1161 | 1159 | app.add_set("-a", someint, {1, 2, 3, 4}, "", true); |
| 1162 | 1160 | |
| 1163 | 1161 | args = {"-a1", "-a2"}; |
| 1164 | 1162 | |
| 1165 | - EXPECT_THROW(run(), CLI::ConversionError); | |
| 1163 | + EXPECT_THROW(run(), CLI::ArgumentMismatch); | |
| 1166 | 1164 | } |
| 1167 | 1165 | |
| 1168 | -// Added to test defaults on dual method | |
| 1169 | 1166 | TEST_F(TApp, SetWithDefaultsConversion) { |
| 1170 | 1167 | int someint = 2; |
| 1171 | 1168 | app.add_set("-a", someint, {1, 2, 3, 4}, "", true); |
| ... | ... | @@ -1175,14 +1172,13 @@ TEST_F(TApp, SetWithDefaultsConversion) { |
| 1175 | 1172 | EXPECT_THROW(run(), CLI::ConversionError); |
| 1176 | 1173 | } |
| 1177 | 1174 | |
| 1178 | -// Added to test defaults on dual method | |
| 1179 | 1175 | TEST_F(TApp, SetWithDefaultsIC) { |
| 1180 | 1176 | std::string someint = "ho"; |
| 1181 | 1177 | app.add_set_ignore_case("-a", someint, {"Hi", "Ho"}, "", true); |
| 1182 | 1178 | |
| 1183 | 1179 | args = {"-aHi", "-aHo"}; |
| 1184 | 1180 | |
| 1185 | - EXPECT_THROW(run(), CLI::ConversionError); | |
| 1181 | + EXPECT_THROW(run(), CLI::ArgumentMismatch); | |
| 1186 | 1182 | } |
| 1187 | 1183 | |
| 1188 | 1184 | // Added to test ->transform | ... | ... |