diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index b2552e20..23e4c3c8 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -2825,10 +2825,13 @@ class App { parse_order_.push_back(op.get()); } } - - // if we only partially completed a type then add an empty string for later processing - if(min_num > 0 && op->get_type_size_max() != min_num && (collected % op->get_type_size_max()) != 0) { - op->add_result(std::string{}); + // if we only partially completed a type then add an empty string if allowed for later processing + if(min_num > 0 && (collected % op->get_type_size_max()) != 0) { + if(op->get_type_size_max() != op->get_type_size_min()) { + op->add_result(std::string{}); + } else { + throw ArgumentMismatch::PartialType(op->get_name(), op->get_type_size_min(), op->get_type_name()); + } } if(op->get_trigger_on_parse()) { op->run_callback(); diff --git a/include/CLI/Error.hpp b/include/CLI/Error.hpp index de3122bb..1d583ae9 100644 --- a/include/CLI/Error.hpp +++ b/include/CLI/Error.hpp @@ -277,6 +277,10 @@ class ArgumentMismatch : public ParseError { static ArgumentMismatch FlagOverride(std::string name) { return ArgumentMismatch(name + " was given a disallowed flag override"); } + static ArgumentMismatch PartialType(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + type + " only partially specified: " + std::to_string(num) + + " required for each element"); + } }; /// Thrown when a requires option is missing diff --git a/tests/ConfigFileTest.cpp b/tests/ConfigFileTest.cpp index 61c066ee..6db466a0 100644 --- a/tests/ConfigFileTest.cpp +++ b/tests/ConfigFileTest.cpp @@ -2103,6 +2103,19 @@ TEST_CASE_METHOD(TApp, "TomlOutputVector", "[config]") { CHECK(str == "vector=[1, 2, 3]\n"); } +TEST_CASE_METHOD(TApp, "TomlOutputTuple", "[config]") { + + std::tuple t; + app.add_option("--tuple", t); + app.config_formatter(std::make_shared()); + args = {"--tuple", "1", "2", "3", "4"}; + + run(); + + std::string str = app.config_to_str(); + CHECK(str == "tuple=[1, 2, 3, 4]\n"); +} + TEST_CASE_METHOD(TApp, "ConfigOutputVectorCustom", "[config]") { std::vector v; diff --git a/tests/OptionTypeTest.cpp b/tests/OptionTypeTest.cpp index 19b69746..13fa3a8a 100644 --- a/tests/OptionTypeTest.cpp +++ b/tests/OptionTypeTest.cpp @@ -554,6 +554,27 @@ TEST_CASE_METHOD(TApp, "vectorPairFail", "[optiontype]") { CHECK_THROWS_AS(run(), CLI::ConversionError); } +TEST_CASE_METHOD(TApp, "vectorPairFail2", "[optiontype]") { + + std::vector> custom_opt; + + auto opt = app.add_option("--pairs", custom_opt); + + args = {"--pairs", "1", "2", "3", "4"}; + + run(); + CHECK(custom_opt.size() == 2U); + + args = {"--pairs", "1", "2", "3"}; + + CHECK_THROWS_AS(run(), CLI::ArgumentMismatch); + // now change the type size to explicitly allow 1 or 2 + opt->type_size(1, 2); + + run(); + CHECK(custom_opt.size() == 2U); +} + TEST_CASE_METHOD(TApp, "vectorPairTypeRange", "[optiontype]") { std::vector> custom_opt;