Commit 5b714bbfe438b0450965220304cba854d0cb972a

Authored by Henry Schreiner
Committed by GitHub
2 parents c435239b 0973f348

Merge pull request #15 from CLIUtils/basic-enum

Adding enum support
CHANGELOG.md
1 ## Version 1.1 (in progress) 1 ## Version 1.1 (in progress)
  2 +
  3 +* Added simple support for enumerations, allow non-printable objects [#12](https://github.com/CLIUtils/CLI11/issues/12)
2 * Added `app.parse_order()` with original parse order ([#13](https://github.com/CLIUtils/CLI11/issues/13), [#16](https://github.com/CLIUtils/CLI11/pull/16)) 4 * Added `app.parse_order()` with original parse order ([#13](https://github.com/CLIUtils/CLI11/issues/13), [#16](https://github.com/CLIUtils/CLI11/pull/16))
3 * Added `prefix_command()`, which is like `allow_extras` but instantly stops and returns. ([#8](https://github.com/CLIUtils/CLI11/issues/8), [#17](https://github.com/CLIUtils/CLI11/pull/17)) 5 * Added `prefix_command()`, which is like `allow_extras` but instantly stops and returns. ([#8](https://github.com/CLIUtils/CLI11/issues/8), [#17](https://github.com/CLIUtils/CLI11/pull/17))
4 * Removed Windows error ([#10](https://github.com/CLIUtils/CLI11/issues/10), [#20](https://github.com/CLIUtils/CLI11/pull/20)) 6 * Removed Windows error ([#10](https://github.com/CLIUtils/CLI11/issues/10), [#20](https://github.com/CLIUtils/CLI11/pull/20))
5 * Some improvements to CMake, detect Python and no dependencies on Python 2 (like Python 3) ([#18](https://github.com/CLIUtils/CLI11/issues/18), [#21](https://github.com/CLIUtils/CLI11/pull/21)) 7 * Some improvements to CMake, detect Python and no dependencies on Python 2 (like Python 3) ([#18](https://github.com/CLIUtils/CLI11/issues/18), [#21](https://github.com/CLIUtils/CLI11/pull/21))
6 8
7 -  
8 ## Version 1.0 9 ## Version 1.0
9 * Cleanup using `clang-tidy` and `clang-format` 10 * Cleanup using `clang-tidy` and `clang-format`
10 * Small improvements to Timers, easier to subclass Error 11 * Small improvements to Timers, easier to subclass Error
examples/CMakeLists.txt
@@ -19,3 +19,4 @@ add_cli_exe(subcommands subcommands.cpp) @@ -19,3 +19,4 @@ add_cli_exe(subcommands subcommands.cpp)
19 add_cli_exe(groups groups.cpp) 19 add_cli_exe(groups groups.cpp)
20 add_cli_exe(inter_argument_order inter_argument_order.cpp) 20 add_cli_exe(inter_argument_order inter_argument_order.cpp)
21 add_cli_exe(prefix_command prefix_command.cpp) 21 add_cli_exe(prefix_command prefix_command.cpp)
  22 +add_cli_exe(enum enum.cpp)
examples/enum.cpp 0 → 100644
  1 +#include <CLI/CLI.hpp>
  2 +
  3 +enum Level : std::int32_t {
  4 + High,
  5 + Medium,
  6 + Low
  7 +};
  8 +
  9 +int main(int argc, char** argv) {
  10 + CLI::App app;
  11 +
  12 + Level level;
  13 + app.add_set("-l,--level", level, {High, Medium, Low}, "Level settings")
  14 + ->set_type_name("enum/Level in {High=0, Medium=1, Low=2}");
  15 +
  16 + try {
  17 + app.parse(argc, argv);
  18 + } catch (CLI::Error const& e) {
  19 + app.exit(e);
  20 + }
  21 + return 0;
  22 +}
  23 +
include/CLI/App.hpp
@@ -225,13 +225,14 @@ class App { @@ -225,13 +225,14 @@ class App {
225 } else 225 } else
226 throw OptionAlreadyAdded(myopt.get_name()); 226 throw OptionAlreadyAdded(myopt.get_name());
227 } 227 }
  228 +
  229 +
228 230
229 - /// Add option for non-vectors 231 + /// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`)
230 template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> 232 template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy>
231 Option *add_option(std::string name, 233 Option *add_option(std::string name,
232 T &variable, ///< The variable to set 234 T &variable, ///< The variable to set
233 - std::string description = "",  
234 - bool defaulted = false) { 235 + std::string description = "") {
235 236
236 CLI::callback_t fun = [&variable](CLI::results_t res) { 237 CLI::callback_t fun = [&variable](CLI::results_t res) {
237 if(res.size() != 1) 238 if(res.size() != 1)
@@ -239,6 +240,24 @@ class App { @@ -239,6 +240,24 @@ class App {
239 return detail::lexical_cast(res[0], variable); 240 return detail::lexical_cast(res[0], variable);
240 }; 241 };
241 242
  243 + Option *opt = add_option(name, fun, description, false);
  244 + opt->set_custom_option(detail::type_name<T>());
  245 + return opt;
  246 + }
  247 +
  248 + /// Add option for non-vectors with a default print
  249 + template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy>
  250 + Option *add_option(std::string name,
  251 + T &variable, ///< The variable to set
  252 + std::string description,
  253 + bool defaulted) {
  254 +
  255 + CLI::callback_t fun = [&variable](CLI::results_t res) {
  256 + if(res.size() != 1)
  257 + return false;
  258 + return detail::lexical_cast(res[0], variable);
  259 + };
  260 +
242 Option *opt = add_option(name, fun, description, defaulted); 261 Option *opt = add_option(name, fun, description, defaulted);
243 opt->set_custom_option(detail::type_name<T>()); 262 opt->set_custom_option(detail::type_name<T>());
244 if(defaulted) { 263 if(defaulted) {
@@ -248,13 +267,34 @@ class App { @@ -248,13 +267,34 @@ class App {
248 } 267 }
249 return opt; 268 return opt;
250 } 269 }
251 - 270 +
  271 + /// Add option for vectors (no default)
  272 + template <typename T>
  273 + Option *add_option(std::string name,
  274 + std::vector<T> &variable, ///< The variable vector to set
  275 + std::string description = "") {
  276 +
  277 + CLI::callback_t fun = [&variable](CLI::results_t res) {
  278 + bool retval = true;
  279 + variable.clear();
  280 + for(const auto &a : res) {
  281 + variable.emplace_back();
  282 + retval &= detail::lexical_cast(a, variable.back());
  283 + }
  284 + return (!variable.empty()) && retval;
  285 + };
  286 +
  287 + Option *opt = add_option(name, fun, description, false);
  288 + opt->set_custom_option(detail::type_name<T>(), -1, true);
  289 + return opt;
  290 + }
  291 +
252 /// Add option for vectors 292 /// Add option for vectors
253 template <typename T> 293 template <typename T>
254 Option *add_option(std::string name, 294 Option *add_option(std::string name,
255 std::vector<T> &variable, ///< The variable vector to set 295 std::vector<T> &variable, ///< The variable vector to set
256 - std::string description = "",  
257 - bool defaulted = false) { 296 + std::string description,
  297 + bool defaulted) {
258 298
259 CLI::callback_t fun = [&variable](CLI::results_t res) { 299 CLI::callback_t fun = [&variable](CLI::results_t res) {
260 bool retval = true; 300 bool retval = true;
@@ -323,13 +363,12 @@ class App { @@ -323,13 +363,12 @@ class App {
323 return opt; 363 return opt;
324 } 364 }
325 365
326 - /// Add set of options 366 + /// Add set of options (No default)
327 template <typename T> 367 template <typename T>
328 Option *add_set(std::string name, 368 Option *add_set(std::string name,
329 T &member, ///< The selected member of the set 369 T &member, ///< The selected member of the set
330 std::set<T> options, ///< The set of posibilities 370 std::set<T> options, ///< The set of posibilities
331 - std::string description = "",  
332 - bool defaulted = false) { 371 + std::string description = "") {
333 372
334 CLI::callback_t fun = [&member, options](CLI::results_t res) { 373 CLI::callback_t fun = [&member, options](CLI::results_t res) {
335 if(res.size() != 1) { 374 if(res.size() != 1) {
@@ -341,6 +380,31 @@ class App { @@ -341,6 +380,31 @@ class App {
341 return std::find(std::begin(options), std::end(options), member) != std::end(options); 380 return std::find(std::begin(options), std::end(options), member) != std::end(options);
342 }; 381 };
343 382
  383 + Option *opt = add_option(name, fun, description, false);
  384 + std::string typeval = detail::type_name<T>();
  385 + typeval += " in {" + detail::join(options) + "}";
  386 + opt->set_custom_option(typeval);
  387 + return opt;
  388 + }
  389 +
  390 + /// Add set of options
  391 + template <typename T>
  392 + Option *add_set(std::string name,
  393 + T &member, ///< The selected member of the set
  394 + std::set<T> options, ///< The set of posibilities
  395 + std::string description,
  396 + bool defaulted) {
  397 +
  398 + CLI::callback_t fun = [&member, options](CLI::results_t res) {
  399 + if(res.size() != 1) {
  400 + return false;
  401 + }
  402 + bool retval = detail::lexical_cast(res[0], member);
  403 + if(!retval)
  404 + return false;
  405 + return std::find(std::begin(options), std::end(options), member) != std::end(options);
  406 + };
  407 +
344 Option *opt = add_option(name, fun, description, defaulted); 408 Option *opt = add_option(name, fun, description, defaulted);
345 std::string typeval = detail::type_name<T>(); 409 std::string typeval = detail::type_name<T>();
346 typeval += " in {" + detail::join(options) + "}"; 410 typeval += " in {" + detail::join(options) + "}";
@@ -353,12 +417,11 @@ class App { @@ -353,12 +417,11 @@ class App {
353 return opt; 417 return opt;
354 } 418 }
355 419
356 - /// Add set of options, string only, ignore case 420 + /// Add set of options, string only, ignore case (no default)
357 Option *add_set_ignore_case(std::string name, 421 Option *add_set_ignore_case(std::string name,
358 std::string &member, ///< The selected member of the set 422 std::string &member, ///< The selected member of the set
359 std::set<std::string> options, ///< The set of posibilities 423 std::set<std::string> options, ///< The set of posibilities
360 - std::string description = "",  
361 - bool defaulted = false) { 424 + std::string description = "") {
362 425
363 CLI::callback_t fun = [&member, options](CLI::results_t res) { 426 CLI::callback_t fun = [&member, options](CLI::results_t res) {
364 if(res.size() != 1) { 427 if(res.size() != 1) {
@@ -376,6 +439,37 @@ class App { @@ -376,6 +439,37 @@ class App {
376 } 439 }
377 }; 440 };
378 441
  442 + Option *opt = add_option(name, fun, description, false);
  443 + std::string typeval = detail::type_name<std::string>();
  444 + typeval += " in {" + detail::join(options) + "}";
  445 + opt->set_custom_option(typeval);
  446 +
  447 + return opt;
  448 + }
  449 +
  450 + /// Add set of options, string only, ignore case
  451 + Option *add_set_ignore_case(std::string name,
  452 + std::string &member, ///< The selected member of the set
  453 + std::set<std::string> options, ///< The set of posibilities
  454 + std::string description,
  455 + bool defaulted) {
  456 +
  457 + CLI::callback_t fun = [&member, options](CLI::results_t res) {
  458 + if(res.size() != 1) {
  459 + return false;
  460 + }
  461 + member = detail::to_lower(res[0]);
  462 + auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
  463 + return detail::to_lower(val) == member;
  464 + });
  465 + if(iter == std::end(options))
  466 + return false;
  467 + else {
  468 + member = *iter;
  469 + return true;
  470 + }
  471 + };
  472 +
379 Option *opt = add_option(name, fun, description, defaulted); 473 Option *opt = add_option(name, fun, description, defaulted);
380 std::string typeval = detail::type_name<std::string>(); 474 std::string typeval = detail::type_name<std::string>();
381 typeval += " in {" + detail::join(options) + "}"; 475 typeval += " in {" + detail::join(options) + "}";
include/CLI/TypeTools.hpp
@@ -74,8 +74,10 @@ constexpr const char *type_name() { @@ -74,8 +74,10 @@ constexpr const char *type_name() {
74 74
75 // Lexical cast 75 // Lexical cast
76 76
77 -/// Integers  
78 -template <typename T, enable_if_t<std::is_integral<T>::value, detail::enabler> = detail::dummy> 77 +/// Integers / enums
  78 +template <typename T, enable_if_t<std::is_integral<T>::value
  79 + || std::is_enum<T>::value
  80 + , detail::enabler> = detail::dummy>
79 bool lexical_cast(std::string input, T &output) { 81 bool lexical_cast(std::string input, T &output) {
80 try { 82 try {
81 output = static_cast<T>(std::stoll(input)); 83 output = static_cast<T>(std::stoll(input));
@@ -103,7 +105,9 @@ bool lexical_cast(std::string input, T &amp;output) { @@ -103,7 +105,9 @@ bool lexical_cast(std::string input, T &amp;output) {
103 /// String and similar 105 /// String and similar
104 template < 106 template <
105 typename T, 107 typename T,
106 - enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value, detail::enabler> = detail::dummy> 108 + enable_if_t<!std::is_floating_point<T>::value
  109 + && !std::is_integral<T>::value
  110 + && !std::is_enum<T>::value, detail::enabler> = detail::dummy>
107 bool lexical_cast(std::string input, T &output) { 111 bool lexical_cast(std::string input, T &output) {
108 output = input; 112 output = input;
109 return true; 113 return true;
tests/AppTest.cpp
@@ -203,6 +203,35 @@ TEST_F(TApp, DefaultOpts) { @@ -203,6 +203,35 @@ TEST_F(TApp, DefaultOpts) {
203 EXPECT_EQ("9", s); 203 EXPECT_EQ("9", s);
204 } 204 }
205 205
  206 +TEST_F(TApp, EnumTest) {
  207 + enum Level : std::int32_t {
  208 + High,
  209 + Medium,
  210 + Low
  211 + };
  212 + Level level = Level::Low;
  213 + app.add_option("--level", level);
  214 +
  215 + args = {"--level", "1"};
  216 + run();
  217 + EXPECT_EQ(level, Level::Medium);
  218 +}
  219 +
  220 +TEST_F(TApp, NewEnumTest) {
  221 + enum class Level2 : std::int32_t {
  222 + High,
  223 + Medium,
  224 + Low
  225 + };
  226 + Level2 level = Level2::Low;
  227 + app.add_option("--level", level);
  228 +
  229 + args = {"--level", "1"};
  230 + run();
  231 + EXPECT_EQ(level, Level2::Medium);
  232 +}
  233 +
  234 +
206 TEST_F(TApp, RequiredFlags) { 235 TEST_F(TApp, RequiredFlags) {
207 app.add_flag("-a")->required(); 236 app.add_flag("-a")->required();
208 app.add_flag("-b")->mandatory(); // Alternate term 237 app.add_flag("-b")->mandatory(); // Alternate term
@@ -391,6 +420,47 @@ TEST_F(TApp, InSet) { @@ -391,6 +420,47 @@ TEST_F(TApp, InSet) {
391 EXPECT_THROW(run(), CLI::ConversionError); 420 EXPECT_THROW(run(), CLI::ConversionError);
392 } 421 }
393 422
  423 +TEST_F(TApp, InSetWithDefault) {
  424 +
  425 + std::string choice = "one";
  426 + app.add_set("-q,--quick", choice, {"one", "two", "three"}, "", true);
  427 +
  428 + run();
  429 + EXPECT_EQ("one", choice);
  430 + app.reset();
  431 +
  432 + args = {"--quick", "two"};
  433 +
  434 + run();
  435 + EXPECT_EQ("two", choice);
  436 +
  437 + app.reset();
  438 +
  439 + args = {"--quick", "four"};
  440 + EXPECT_THROW(run(), CLI::ConversionError);
  441 +}
  442 +
  443 +
  444 +TEST_F(TApp, InCaselessSetWithDefault) {
  445 +
  446 + std::string choice = "one";
  447 + app.add_set_ignore_case("-q,--quick", choice, {"one", "two", "three"}, "", true);
  448 +
  449 + run();
  450 + EXPECT_EQ("one", choice);
  451 + app.reset();
  452 +
  453 + args = {"--quick", "tWo"};
  454 +
  455 + run();
  456 + EXPECT_EQ("two", choice);
  457 +
  458 + app.reset();
  459 +
  460 + args = {"--quick", "four"};
  461 + EXPECT_THROW(run(), CLI::ConversionError);
  462 +}
  463 +
394 TEST_F(TApp, InIntSet) { 464 TEST_F(TApp, InIntSet) {
395 465
396 int choice; 466 int choice;
@@ -462,6 +532,20 @@ TEST_F(TApp, VectorFixedString) { @@ -462,6 +532,20 @@ TEST_F(TApp, VectorFixedString) {
462 EXPECT_EQ(answer, strvec); 532 EXPECT_EQ(answer, strvec);
463 } 533 }
464 534
  535 +
  536 +TEST_F(TApp, VectorDefaultedFixedString) {
  537 + std::vector<std::string> strvec{"one"};
  538 + std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
  539 +
  540 + CLI::Option *opt = app.add_option("-s,--string", strvec, "", true)->expected(3);
  541 + EXPECT_EQ(3, opt->get_expected());
  542 +
  543 + args = {"--string", "mystring", "mystring2", "mystring3"};
  544 + run();
  545 + EXPECT_EQ((size_t)3, app.count("--string"));
  546 + EXPECT_EQ(answer, strvec);
  547 +}
  548 +
465 TEST_F(TApp, VectorUnlimString) { 549 TEST_F(TApp, VectorUnlimString) {
466 std::vector<std::string> strvec; 550 std::vector<std::string> strvec;
467 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; 551 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
@@ -808,3 +892,43 @@ TEST_F(TApp, CheckSubcomFail) { @@ -808,3 +892,43 @@ TEST_F(TApp, CheckSubcomFail) {
808 892
809 EXPECT_THROW(CLI::detail::AppFriend::parse_subcommand(&app, args), CLI::HorribleError); 893 EXPECT_THROW(CLI::detail::AppFriend::parse_subcommand(&app, args), CLI::HorribleError);
810 } 894 }
  895 +
  896 +// Added to test defaults on dual method
  897 +TEST_F(TApp, OptionWithDefaults) {
  898 + int someint=2;
  899 + app.add_option("-a", someint, "", true);
  900 +
  901 + args = {"-a1", "-a2"};
  902 +
  903 + EXPECT_THROW(run(), CLI::ConversionError);
  904 +}
  905 +
  906 +// Added to test defaults on dual method
  907 +TEST_F(TApp, SetWithDefaults) {
  908 + int someint=2;
  909 + app.add_set("-a", someint, {1,2,3,4}, "", true);
  910 +
  911 + args = {"-a1", "-a2"};
  912 +
  913 + EXPECT_THROW(run(), CLI::ConversionError);
  914 +}
  915 +
  916 +// Added to test defaults on dual method
  917 +TEST_F(TApp, SetWithDefaultsConversion) {
  918 + int someint=2;
  919 + app.add_set("-a", someint, {1,2,3,4}, "", true);
  920 +
  921 + args = {"-a", "hi"};
  922 +
  923 + EXPECT_THROW(run(), CLI::ConversionError);
  924 +}
  925 +
  926 +// Added to test defaults on dual method
  927 +TEST_F(TApp, SetWithDefaultsIC) {
  928 + std::string someint="ho";
  929 + app.add_set_ignore_case("-a", someint, {"Hi", "Ho"}, "", true);
  930 +
  931 + args = {"-aHi", "-aHo"};
  932 +
  933 + EXPECT_THROW(run(), CLI::ConversionError);
  934 +}