Commit 3917b1ab5923bd49491a1b9c137bbc4df1dd098d

Authored by Henry Schreiner
Committed by GitHub
1 parent af2ed66d

Sets by reference (#114)

* Adding const & access to sets

* Adding set reference option

* One missing line in coverage
CHANGELOG.md
1 1 ### Version 1.6: Formatting
2 2  
3   -Added a new formatting system. You can now set the formatter on Apps.
  3 +Added a new formatting system. [#109] You can now set the formatter on Apps.
4 4  
5 5 * Added `CLI::Formatter` and `formatter` slot for apps, inherited.
6 6 * Added `help_all` support (not added by default)
... ... @@ -18,11 +18,18 @@ Changes to the help system (most normal users will not notice this):
18 18 * `format_help` can now be chained
19 19  
20 20  
21   -Other small changes:
  21 +Other changes:
22 22  
23   -* Testing (only) now uses submodules.
24   -* Removed `requires` in favor of `needs` (deprecated in last version)
25   -* Better CMake policy handling
  23 +* Using `add_set` will now capture L-values for sets, allowing further modification [#113]
  24 +* Testing (only) now uses submodules. [#111]
  25 +* Removed `requires` in favor of `needs` (deprecated in last version) [#112]
  26 +* Better CMake policy handling [#110]
  27 +
  28 +[#109]: https://github.com/CLIUtils/CLI11/pull/109
  29 +[#110]: https://github.com/CLIUtils/CLI11/pull/110
  30 +[#111]: https://github.com/CLIUtils/CLI11/pull/111
  31 +[#112]: https://github.com/CLIUtils/CLI11/pull/112
  32 +[#113]: https://github.com/CLIUtils/CLI11/issues/113
26 33  
27 34 ### Version 1.5.3: Compiler compatibility
28 35 This version fixes older AppleClang compilers by removing the optimization for casting. The minimum version of Boost Optional supported has been clarified to be 1.58. CUDA 7.0 NVCC is now supported.
... ... @@ -45,7 +52,7 @@ This patch release adds better access to the App progromatically, to assist with
45 52  
46 53 [#102]: https://github.com/CLIUtils/CLI11/issues/102
47 54 [#104]: https://github.com/CLIUtils/CLI11/pull/104
48   -[#105]: https://github.com/CLIUtils/CLI11/issues/105
  55 +[#105]: https://github.com/CLIUtils/CLI11/pull/105
49 56 [#106]: https://github.com/CLIUtils/CLI11/pull/106
50 57  
51 58  
... ...
README.md
... ... @@ -163,7 +163,7 @@ app.add_set_ignore_case(... // String only
163 163 App* subcom = app.add_subcommand(name, discription);
164 164 ```
165 165  
166   -An option name must start with a alphabetic character or underscore. For long options, anything but an equals sign or a comma is valid after that. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on help line for its positional form. If you want the default value to print in the help description, pass in `true` for the final parameter for `add_option` or `add_set`. The set options allow your users to pick from a set of predefined options.
  166 +An option name must start with a alphabetic character or underscore. For long options, anything but an equals sign or a comma is valid after that. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on help line for its positional form. If you want the default value to print in the help description, pass in `true` for the final parameter for `add_option` or `add_set`. The set options allow your users to pick from a set of predefined options; you can add an existing set if you need to modify the set later, or you can use an initializer list.
167 167  
168 168 On a C++14 compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant `CLI::ParseError` to signal a failure.
169 169  
... ...
include/CLI/App.hpp
... ... @@ -500,11 +500,11 @@ class App {
500 500 }
501 501 #endif
502 502  
503   - /// Add set of options (No default)
  503 + /// Add set of options (No default, temp refernce, such as an inline set)
504 504 template <typename T>
505 505 Option *add_set(std::string name,
506   - T &member, ///< The selected member of the set
507   - std::set<T> options, ///< The set of possibilities
  506 + T &member, ///< The selected member of the set
  507 + const std::set<T> &&options, ///< The set of possibilities
508 508 std::string description = "") {
509 509  
510 510 std::string simple_name = CLI::detail::split(name, ',').at(0);
... ... @@ -522,11 +522,33 @@ class App {
522 522 return opt;
523 523 }
524 524  
525   - /// Add set of options
  525 + /// Add set of options (No default, non-temp refernce, such as an existing set)
526 526 template <typename T>
527 527 Option *add_set(std::string name,
528   - T &member, ///< The selected member of the set
529   - std::set<T> options, ///< The set of posibilities
  528 + T &member, ///< The selected member of the set
  529 + const std::set<T> &options, ///< The set of possibilities
  530 + std::string description = "") {
  531 +
  532 + std::string simple_name = CLI::detail::split(name, ',').at(0);
  533 + CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
  534 + bool retval = detail::lexical_cast(res[0], member);
  535 + if(!retval)
  536 + throw ConversionError(res[0], simple_name);
  537 + return std::find(std::begin(options), std::end(options), member) != std::end(options);
  538 + };
  539 +
  540 + Option *opt = add_option(name, fun, description, false);
  541 + std::string typeval = detail::type_name<T>();
  542 + typeval += " in {" + detail::join(options) + "}";
  543 + opt->set_custom_option(typeval);
  544 + return opt;
  545 + }
  546 +
  547 + /// Add set of options (with default, R value, such as an inline set)
  548 + template <typename T>
  549 + Option *add_set(std::string name,
  550 + T &member, ///< The selected member of the set
  551 + const std::set<T> &&options, ///< The set of posibilities
530 552 std::string description,
531 553 bool defaulted) {
532 554  
... ... @@ -550,10 +572,38 @@ class App {
550 572 return opt;
551 573 }
552 574  
553   - /// Add set of options, string only, ignore case (no default)
  575 + /// Add set of options (with default, L value refernce, such as an existing set)
  576 + template <typename T>
  577 + Option *add_set(std::string name,
  578 + T &member, ///< The selected member of the set
  579 + const std::set<T> &options, ///< The set of posibilities
  580 + std::string description,
  581 + bool defaulted) {
  582 +
  583 + std::string simple_name = CLI::detail::split(name, ',').at(0);
  584 + CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
  585 + bool retval = detail::lexical_cast(res[0], member);
  586 + if(!retval)
  587 + throw ConversionError(res[0], simple_name);
  588 + return std::find(std::begin(options), std::end(options), member) != std::end(options);
  589 + };
  590 +
  591 + Option *opt = add_option(name, fun, description, defaulted);
  592 + std::string typeval = detail::type_name<T>();
  593 + typeval += " in {" + detail::join(options) + "}";
  594 + opt->set_custom_option(typeval);
  595 + if(defaulted) {
  596 + std::stringstream out;
  597 + out << member;
  598 + opt->set_default_str(out.str());
  599 + }
  600 + return opt;
  601 + }
  602 +
  603 + /// Add set of options, string only, ignore case (no default, R value)
554 604 Option *add_set_ignore_case(std::string name,
555   - std::string &member, ///< The selected member of the set
556   - std::set<std::string> options, ///< The set of possibilities
  605 + std::string &member, ///< The selected member of the set
  606 + const std::set<std::string> &&options, ///< The set of possibilities
557 607 std::string description = "") {
558 608  
559 609 std::string simple_name = CLI::detail::split(name, ',').at(0);
... ... @@ -578,10 +628,38 @@ class App {
578 628 return opt;
579 629 }
580 630  
581   - /// Add set of options, string only, ignore case
  631 + /// Add set of options, string only, ignore case (no default, L value)
582 632 Option *add_set_ignore_case(std::string name,
583   - std::string &member, ///< The selected member of the set
584   - std::set<std::string> options, ///< The set of posibilities
  633 + std::string &member, ///< The selected member of the set
  634 + const std::set<std::string> &options, ///< The set of possibilities
  635 + std::string description = "") {
  636 +
  637 + std::string simple_name = CLI::detail::split(name, ',').at(0);
  638 + CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
  639 + member = detail::to_lower(res[0]);
  640 + auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
  641 + return detail::to_lower(val) == member;
  642 + });
  643 + if(iter == std::end(options))
  644 + throw ConversionError(member, simple_name);
  645 + else {
  646 + member = *iter;
  647 + return true;
  648 + }
  649 + };
  650 +
  651 + Option *opt = add_option(name, fun, description, false);
  652 + std::string typeval = detail::type_name<std::string>();
  653 + typeval += " in {" + detail::join(options) + "}";
  654 + opt->set_custom_option(typeval);
  655 +
  656 + return opt;
  657 + }
  658 +
  659 + /// Add set of options, string only, ignore case (default, R value)
  660 + Option *add_set_ignore_case(std::string name,
  661 + std::string &member, ///< The selected member of the set
  662 + const std::set<std::string> &&options, ///< The set of posibilities
585 663 std::string description,
586 664 bool defaulted) {
587 665  
... ... @@ -609,6 +687,37 @@ class App {
609 687 return opt;
610 688 }
611 689  
  690 + /// Add set of options, string only, ignore case (default, L value)
  691 + Option *add_set_ignore_case(std::string name,
  692 + std::string &member, ///< The selected member of the set
  693 + const std::set<std::string> &options, ///< The set of posibilities
  694 + std::string description,
  695 + bool defaulted) {
  696 +
  697 + std::string simple_name = CLI::detail::split(name, ',').at(0);
  698 + CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) {
  699 + member = detail::to_lower(res[0]);
  700 + auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) {
  701 + return detail::to_lower(val) == member;
  702 + });
  703 + if(iter == std::end(options))
  704 + throw ConversionError(member, simple_name);
  705 + else {
  706 + member = *iter;
  707 + return true;
  708 + }
  709 + };
  710 +
  711 + Option *opt = add_option(name, fun, description, defaulted);
  712 + std::string typeval = detail::type_name<std::string>();
  713 + typeval += " in {" + detail::join(options) + "}";
  714 + opt->set_custom_option(typeval);
  715 + if(defaulted) {
  716 + opt->set_default_str(member);
  717 + }
  718 + return opt;
  719 + }
  720 +
612 721 /// Add a complex number
613 722 template <typename T>
614 723 Option *add_complex(std::string name,
... ...
tests/AppTest.cpp
... ... @@ -992,6 +992,21 @@ TEST_F(TApp, FailSet) {
992 992 EXPECT_THROW(run(), CLI::ConversionError);
993 993 }
994 994  
  995 +TEST_F(TApp, FailLValueSet) {
  996 +
  997 + int choice;
  998 + std::set<int> vals{1, 2, 3};
  999 + app.add_set("-q,--quick", choice, vals);
  1000 + app.add_set("-s,--slow", choice, vals, "", true);
  1001 +
  1002 + args = {"--quick=hello"};
  1003 + EXPECT_THROW(run(), CLI::ConversionError);
  1004 +
  1005 + app.reset();
  1006 + args = {"--slow=hello"};
  1007 + EXPECT_THROW(run(), CLI::ConversionError);
  1008 +}
  1009 +
995 1010 TEST_F(TApp, InSetIgnoreCase) {
996 1011  
997 1012 std::string choice;
... ... @@ -1480,3 +1495,72 @@ TEST_F(TApp, CustomDoubleOption) {
1480 1495 EXPECT_EQ(custom_opt.first, 12);
1481 1496 EXPECT_DOUBLE_EQ(custom_opt.second, 1.5);
1482 1497 }
  1498 +
  1499 +// #113
  1500 +TEST_F(TApp, AddRemoveSetItems) {
  1501 + std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"};
  1502 +
  1503 + std::string type1, type2;
  1504 + app.add_set("--type1", type1, items);
  1505 + app.add_set("--type2", type2, items, "", true);
  1506 +
  1507 + args = {"--type1", "TYPE1", "--type2", "TYPE2"};
  1508 +
  1509 + run();
  1510 + EXPECT_EQ(type1, "TYPE1");
  1511 + EXPECT_EQ(type2, "TYPE2");
  1512 +
  1513 + items.insert("TYPE6");
  1514 + items.insert("TYPE7");
  1515 +
  1516 + items.erase("TYPE1");
  1517 + items.erase("TYPE2");
  1518 +
  1519 + app.reset();
  1520 + args = {"--type1", "TYPE6", "--type2", "TYPE7"};
  1521 + run();
  1522 + EXPECT_EQ(type1, "TYPE6");
  1523 + EXPECT_EQ(type2, "TYPE7");
  1524 +
  1525 + app.reset();
  1526 + args = {"--type1", "TYPE1"};
  1527 + EXPECT_THROW(run(), CLI::ConversionError);
  1528 +
  1529 + app.reset();
  1530 + args = {"--type2", "TYPE2"};
  1531 + EXPECT_THROW(run(), CLI::ConversionError);
  1532 +}
  1533 +
  1534 +TEST_F(TApp, AddRemoveSetItemsNoCase) {
  1535 + std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"};
  1536 +
  1537 + std::string type1, type2;
  1538 + app.add_set_ignore_case("--type1", type1, items);
  1539 + app.add_set_ignore_case("--type2", type2, items, "", true);
  1540 +
  1541 + args = {"--type1", "TYPe1", "--type2", "TyPE2"};
  1542 +
  1543 + run();
  1544 + EXPECT_EQ(type1, "TYPE1");
  1545 + EXPECT_EQ(type2, "TYPE2");
  1546 +
  1547 + items.insert("TYPE6");
  1548 + items.insert("TYPE7");
  1549 +
  1550 + items.erase("TYPE1");
  1551 + items.erase("TYPE2");
  1552 +
  1553 + app.reset();
  1554 + args = {"--type1", "TyPE6", "--type2", "tYPE7"};
  1555 + run();
  1556 + EXPECT_EQ(type1, "TYPE6");
  1557 + EXPECT_EQ(type2, "TYPE7");
  1558 +
  1559 + app.reset();
  1560 + args = {"--type1", "TYPe1"};
  1561 + EXPECT_THROW(run(), CLI::ConversionError);
  1562 +
  1563 + app.reset();
  1564 + args = {"--type2", "TYpE2"};
  1565 + EXPECT_THROW(run(), CLI::ConversionError);
  1566 +}
... ...