Commit 5b714bbfe438b0450965220304cba854d0cb972a
Committed by
GitHub
Merge pull request #15 from CLIUtils/basic-enum
Adding enum support
Showing
6 changed files
with
263 additions
and
16 deletions
CHANGELOG.md
| 1 | 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 | 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 | 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 | 6 | * Removed Windows error ([#10](https://github.com/CLIUtils/CLI11/issues/10), [#20](https://github.com/CLIUtils/CLI11/pull/20)) |
| 5 | 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 | 9 | ## Version 1.0 |
| 9 | 10 | * Cleanup using `clang-tidy` and `clang-format` |
| 10 | 11 | * Small improvements to Timers, easier to subclass Error | ... | ... |
examples/CMakeLists.txt
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 | 225 | } else |
| 226 | 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 | 232 | template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> |
| 231 | 233 | Option *add_option(std::string name, |
| 232 | 234 | T &variable, ///< The variable to set |
| 233 | - std::string description = "", | |
| 234 | - bool defaulted = false) { | |
| 235 | + std::string description = "") { | |
| 235 | 236 | |
| 236 | 237 | CLI::callback_t fun = [&variable](CLI::results_t res) { |
| 237 | 238 | if(res.size() != 1) |
| ... | ... | @@ -239,6 +240,24 @@ class App { |
| 239 | 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 | 261 | Option *opt = add_option(name, fun, description, defaulted); |
| 243 | 262 | opt->set_custom_option(detail::type_name<T>()); |
| 244 | 263 | if(defaulted) { |
| ... | ... | @@ -248,13 +267,34 @@ class App { |
| 248 | 267 | } |
| 249 | 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 | 292 | /// Add option for vectors |
| 253 | 293 | template <typename T> |
| 254 | 294 | Option *add_option(std::string name, |
| 255 | 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 | 299 | CLI::callback_t fun = [&variable](CLI::results_t res) { |
| 260 | 300 | bool retval = true; |
| ... | ... | @@ -323,13 +363,12 @@ class App { |
| 323 | 363 | return opt; |
| 324 | 364 | } |
| 325 | 365 | |
| 326 | - /// Add set of options | |
| 366 | + /// Add set of options (No default) | |
| 327 | 367 | template <typename T> |
| 328 | 368 | Option *add_set(std::string name, |
| 329 | 369 | T &member, ///< The selected member of the set |
| 330 | 370 | std::set<T> options, ///< The set of posibilities |
| 331 | - std::string description = "", | |
| 332 | - bool defaulted = false) { | |
| 371 | + std::string description = "") { | |
| 333 | 372 | |
| 334 | 373 | CLI::callback_t fun = [&member, options](CLI::results_t res) { |
| 335 | 374 | if(res.size() != 1) { |
| ... | ... | @@ -341,6 +380,31 @@ class App { |
| 341 | 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 | 408 | Option *opt = add_option(name, fun, description, defaulted); |
| 345 | 409 | std::string typeval = detail::type_name<T>(); |
| 346 | 410 | typeval += " in {" + detail::join(options) + "}"; |
| ... | ... | @@ -353,12 +417,11 @@ class App { |
| 353 | 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 | 421 | Option *add_set_ignore_case(std::string name, |
| 358 | 422 | std::string &member, ///< The selected member of the set |
| 359 | 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 | 426 | CLI::callback_t fun = [&member, options](CLI::results_t res) { |
| 364 | 427 | if(res.size() != 1) { |
| ... | ... | @@ -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 | 473 | Option *opt = add_option(name, fun, description, defaulted); |
| 380 | 474 | std::string typeval = detail::type_name<std::string>(); |
| 381 | 475 | typeval += " in {" + detail::join(options) + "}"; | ... | ... |
include/CLI/TypeTools.hpp
| ... | ... | @@ -74,8 +74,10 @@ constexpr const char *type_name() { |
| 74 | 74 | |
| 75 | 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 | 81 | bool lexical_cast(std::string input, T &output) { |
| 80 | 82 | try { |
| 81 | 83 | output = static_cast<T>(std::stoll(input)); |
| ... | ... | @@ -103,7 +105,9 @@ bool lexical_cast(std::string input, T &output) { |
| 103 | 105 | /// String and similar |
| 104 | 106 | template < |
| 105 | 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 | 111 | bool lexical_cast(std::string input, T &output) { |
| 108 | 112 | output = input; |
| 109 | 113 | return true; | ... | ... |
tests/AppTest.cpp
| ... | ... | @@ -203,6 +203,35 @@ TEST_F(TApp, DefaultOpts) { |
| 203 | 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 | 235 | TEST_F(TApp, RequiredFlags) { |
| 207 | 236 | app.add_flag("-a")->required(); |
| 208 | 237 | app.add_flag("-b")->mandatory(); // Alternate term |
| ... | ... | @@ -391,6 +420,47 @@ TEST_F(TApp, InSet) { |
| 391 | 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 | 464 | TEST_F(TApp, InIntSet) { |
| 395 | 465 | |
| 396 | 466 | int choice; |
| ... | ... | @@ -462,6 +532,20 @@ TEST_F(TApp, VectorFixedString) { |
| 462 | 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 | 549 | TEST_F(TApp, VectorUnlimString) { |
| 466 | 550 | std::vector<std::string> strvec; |
| 467 | 551 | std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; |
| ... | ... | @@ -808,3 +892,43 @@ TEST_F(TApp, CheckSubcomFail) { |
| 808 | 892 | |
| 809 | 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 | +} | ... | ... |