Commit 17d05b000c773d6ebbf81bbe059c7e1f1a890e9c
Committed by
GitHub
1 parent
5e0bb1c8
Adding map support to IsMember (#228)
* Adding first draft of mapping * IsMember now supports maps * Adding example, better Val combs, and cleanup * Reversing order of map, adding pair support * Check/Transform suppport for Validators * Adding enum tools from @phlptp, more tests
Showing
10 changed files
with
290 additions
and
80 deletions
README.md
| ... | ... | @@ -261,6 +261,7 @@ Before parsing, you can set the following options: |
| 261 | 261 | - `->description(str)`: Set/change the description. |
| 262 | 262 | - `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which do not inherit their default but always start with a specific policy). |
| 263 | 263 | - `->check(CLI::IsMember(...))`: Require an option be a member of a given set. See below for options. |
| 264 | +- `->transform(CLI::IsMember(...))`: Require an option be a member of a given set or map. Can change the parse. See below for options. | |
| 264 | 265 | - `->check(CLI::ExistingFile)`: Requires that the file exists if given. |
| 265 | 266 | - `->check(CLI::ExistingDirectory)`: Requires that the directory exists. |
| 266 | 267 | - `->check(CLI::ExistingPath)`: Requires that the path (file or directory) exists. |
| ... | ... | @@ -283,7 +284,9 @@ of `IsMember`: |
| 283 | 284 | - `CLI::IsMember({"choice1", "choice2"})`: Select from exact match to choices. |
| 284 | 285 | - `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`: Match things like `Choice_1`, too. |
| 285 | 286 | - `CLI::IsMember(std::set<int>({2,3,4}))`: Most containers and types work; you just need `std::begin`, `std::end`, and `::value_type`. |
| 287 | +- `CLI::IsMember(std::map<std::string, int>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched value with the key. | |
| 286 | 288 | - `auto p = std::make_shared<std::vector<std::string>>(std::initializer_list<std::string>("one", "two")); CLI::IsMember(p)`: You can modify `p` later. |
| 289 | +- Note that you can combine validators with `|`, and only the matched validation will modify the output in transform. The first `IsMember` is the only one that will print in help, though the error message will include all Validators. | |
| 287 | 290 | |
| 288 | 291 | On the command line, options can be given as: |
| 289 | 292 | ... | ... |
examples/CMakeLists.txt
| ... | ... | @@ -130,7 +130,7 @@ add_cli_exe(enum enum.cpp) |
| 130 | 130 | add_test(NAME enum_pass COMMAND enum -l 1) |
| 131 | 131 | add_test(NAME enum_fail COMMAND enum -l 4) |
| 132 | 132 | set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION |
| 133 | - "--level: 4 not in {0,1,2}") | |
| 133 | + "--level: 4 not in {High,Medium,Low} | 4 not in {0,1,2}") | |
| 134 | 134 | |
| 135 | 135 | add_cli_exe(modhelp modhelp.cpp) |
| 136 | 136 | add_test(NAME modhelp COMMAND modhelp -a test -h) | ... | ... |
examples/enum.cpp
| 1 | 1 | #include <CLI/CLI.hpp> |
| 2 | +#include <map> | |
| 2 | 3 | #include <sstream> |
| 3 | 4 | |
| 4 | 5 | enum class Level : int { High, Medium, Low }; |
| 5 | 6 | |
| 6 | -std::istream &operator>>(std::istream &in, Level &level) { | |
| 7 | - int i; | |
| 8 | - in >> i; | |
| 9 | - level = static_cast<Level>(i); | |
| 10 | - return in; | |
| 11 | -} | |
| 12 | - | |
| 13 | -std::ostream &operator<<(std::ostream &in, const Level &level) { return in << static_cast<int>(level); } | |
| 14 | - | |
| 15 | 7 | int main(int argc, char **argv) { |
| 16 | 8 | CLI::App app; |
| 17 | 9 | |
| 10 | + std::map<std::string, Level> map = {{"High", Level::High}, {"Medium", Level::Medium}, {"Low", Level::Low}}; | |
| 11 | + | |
| 18 | 12 | Level level; |
| 13 | + | |
| 19 | 14 | app.add_option("-l,--level", level, "Level settings") |
| 20 | - ->check(CLI::IsMember({Level::High, Level::Medium, Level::Low})) | |
| 21 | - ->type_name("enum/Level in {High=0, Medium=1, Low=2}"); | |
| 15 | + ->required() | |
| 16 | + ->transform(CLI::IsMember(map, CLI::ignore_case) | CLI::IsMember({Level::High, Level::Medium, Level::Low})); | |
| 22 | 17 | |
| 23 | 18 | CLI11_PARSE(app, argc, argv); |
| 24 | 19 | |
| 20 | + // CLI11's built in enum streaming can be used outside CLI11 like this: | |
| 21 | + using namespace CLI::enums; | |
| 22 | + std::cout << "Enum received: " << level << std::endl; | |
| 23 | + | |
| 25 | 24 | return 0; |
| 26 | 25 | } | ... | ... |
include/CLI/App.hpp
| ... | ... | @@ -695,32 +695,32 @@ class App { |
| 695 | 695 | } |
| 696 | 696 | |
| 697 | 697 | /// Add set of options, string only, ignore case (no default, static set) DEPRECATED |
| 698 | - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case)) instead") | |
| 698 | + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case)) instead") | |
| 699 | 699 | Option *add_set_ignore_case(std::string option_name, |
| 700 | 700 | std::string &member, ///< The selected member of the set |
| 701 | 701 | std::set<std::string> options, ///< The set of possibilities |
| 702 | 702 | std::string description = "") { |
| 703 | 703 | |
| 704 | 704 | Option *opt = add_option(option_name, member, std::move(description)); |
| 705 | - opt->check(IsMember{options, CLI::ignore_case}); | |
| 705 | + opt->transform(IsMember{options, CLI::ignore_case}); | |
| 706 | 706 | return opt; |
| 707 | 707 | } |
| 708 | 708 | |
| 709 | 709 | /// Add set of options, string only, ignore case (no default, set can be changed afterwards - do not destroy the |
| 710 | 710 | /// set) DEPRECATED |
| 711 | - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case)) with a (shared) pointer instead") | |
| 711 | + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case)) with a (shared) pointer instead") | |
| 712 | 712 | Option *add_mutable_set_ignore_case(std::string option_name, |
| 713 | 713 | std::string &member, ///< The selected member of the set |
| 714 | 714 | const std::set<std::string> &options, ///< The set of possibilities |
| 715 | 715 | std::string description = "") { |
| 716 | 716 | |
| 717 | 717 | Option *opt = add_option(option_name, member, std::move(description)); |
| 718 | - opt->check(IsMember{&options, CLI::ignore_case}); | |
| 718 | + opt->transform(IsMember{&options, CLI::ignore_case}); | |
| 719 | 719 | return opt; |
| 720 | 720 | } |
| 721 | 721 | |
| 722 | 722 | /// Add set of options, string only, ignore case (default, static set) DEPRECATED |
| 723 | - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case)) instead") | |
| 723 | + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case)) instead") | |
| 724 | 724 | Option *add_set_ignore_case(std::string option_name, |
| 725 | 725 | std::string &member, ///< The selected member of the set |
| 726 | 726 | std::set<std::string> options, ///< The set of possibilities |
| ... | ... | @@ -728,13 +728,13 @@ class App { |
| 728 | 728 | bool defaulted) { |
| 729 | 729 | |
| 730 | 730 | Option *opt = add_option(option_name, member, std::move(description), defaulted); |
| 731 | - opt->check(IsMember{options, CLI::ignore_case}); | |
| 731 | + opt->transform(IsMember{options, CLI::ignore_case}); | |
| 732 | 732 | return opt; |
| 733 | 733 | } |
| 734 | 734 | |
| 735 | 735 | /// Add set of options, string only, ignore case (default, set can be changed afterwards - do not destroy the set) |
| 736 | 736 | /// DEPRECATED |
| 737 | - CLI11_DEPRECATED("Use ->check(CLI::IsMember(...)) with a (shared) pointer instead") | |
| 737 | + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(...)) with a (shared) pointer instead") | |
| 738 | 738 | Option *add_mutable_set_ignore_case(std::string option_name, |
| 739 | 739 | std::string &member, ///< The selected member of the set |
| 740 | 740 | const std::set<std::string> &options, ///< The set of possibilities |
| ... | ... | @@ -742,37 +742,37 @@ class App { |
| 742 | 742 | bool defaulted) { |
| 743 | 743 | |
| 744 | 744 | Option *opt = add_option(option_name, member, std::move(description), defaulted); |
| 745 | - opt->check(IsMember{&options, CLI::ignore_case}); | |
| 745 | + opt->transform(IsMember{&options, CLI::ignore_case}); | |
| 746 | 746 | return opt; |
| 747 | 747 | } |
| 748 | 748 | |
| 749 | 749 | /// Add set of options, string only, ignore underscore (no default, static set) DEPRECATED |
| 750 | - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_underscore)) instead") | |
| 750 | + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) instead") | |
| 751 | 751 | Option *add_set_ignore_underscore(std::string option_name, |
| 752 | 752 | std::string &member, ///< The selected member of the set |
| 753 | 753 | std::set<std::string> options, ///< The set of possibilities |
| 754 | 754 | std::string description = "") { |
| 755 | 755 | |
| 756 | 756 | Option *opt = add_option(option_name, member, std::move(description)); |
| 757 | - opt->check(IsMember{options, CLI::ignore_underscore}); | |
| 757 | + opt->transform(IsMember{options, CLI::ignore_underscore}); | |
| 758 | 758 | return opt; |
| 759 | 759 | } |
| 760 | 760 | |
| 761 | 761 | /// Add set of options, string only, ignore underscore (no default, set can be changed afterwards - do not destroy |
| 762 | 762 | /// the set) DEPRECATED |
| 763 | - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead") | |
| 763 | + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead") | |
| 764 | 764 | Option *add_mutable_set_ignore_underscore(std::string option_name, |
| 765 | 765 | std::string &member, ///< The selected member of the set |
| 766 | 766 | const std::set<std::string> &options, ///< The set of possibilities |
| 767 | 767 | std::string description = "") { |
| 768 | 768 | |
| 769 | 769 | Option *opt = add_option(option_name, member, std::move(description)); |
| 770 | - opt->check(IsMember{options, CLI::ignore_underscore}); | |
| 770 | + opt->transform(IsMember{options, CLI::ignore_underscore}); | |
| 771 | 771 | return opt; |
| 772 | 772 | } |
| 773 | 773 | |
| 774 | 774 | /// Add set of options, string only, ignore underscore (default, static set) DEPRECATED |
| 775 | - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_underscore)) instead") | |
| 775 | + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) instead") | |
| 776 | 776 | Option *add_set_ignore_underscore(std::string option_name, |
| 777 | 777 | std::string &member, ///< The selected member of the set |
| 778 | 778 | std::set<std::string> options, ///< The set of possibilities |
| ... | ... | @@ -780,13 +780,13 @@ class App { |
| 780 | 780 | bool defaulted) { |
| 781 | 781 | |
| 782 | 782 | Option *opt = add_option(option_name, member, std::move(description), defaulted); |
| 783 | - opt->check(IsMember{options, CLI::ignore_underscore}); | |
| 783 | + opt->transform(IsMember{options, CLI::ignore_underscore}); | |
| 784 | 784 | return opt; |
| 785 | 785 | } |
| 786 | 786 | |
| 787 | 787 | /// Add set of options, string only, ignore underscore (default, set can be changed afterwards - do not destroy the |
| 788 | 788 | /// set) DEPRECATED |
| 789 | - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead") | |
| 789 | + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead") | |
| 790 | 790 | Option *add_mutable_set_ignore_underscore(std::string option_name, |
| 791 | 791 | std::string &member, ///< The selected member of the set |
| 792 | 792 | const std::set<std::string> &options, ///< The set of possibilities |
| ... | ... | @@ -794,38 +794,38 @@ class App { |
| 794 | 794 | bool defaulted) { |
| 795 | 795 | |
| 796 | 796 | Option *opt = add_option(option_name, member, std::move(description), defaulted); |
| 797 | - opt->check(IsMember{&options, CLI::ignore_underscore}); | |
| 797 | + opt->transform(IsMember{&options, CLI::ignore_underscore}); | |
| 798 | 798 | return opt; |
| 799 | 799 | } |
| 800 | 800 | |
| 801 | 801 | /// Add set of options, string only, ignore underscore and case (no default, static set) DEPRECATED |
| 802 | - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead") | |
| 802 | + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead") | |
| 803 | 803 | Option *add_set_ignore_case_underscore(std::string option_name, |
| 804 | 804 | std::string &member, ///< The selected member of the set |
| 805 | 805 | std::set<std::string> options, ///< The set of possibilities |
| 806 | 806 | std::string description = "") { |
| 807 | 807 | |
| 808 | 808 | Option *opt = add_option(option_name, member, std::move(description)); |
| 809 | - opt->check(IsMember{options, CLI::ignore_underscore, CLI::ignore_case}); | |
| 809 | + opt->transform(IsMember{options, CLI::ignore_underscore, CLI::ignore_case}); | |
| 810 | 810 | return opt; |
| 811 | 811 | } |
| 812 | 812 | |
| 813 | 813 | /// Add set of options, string only, ignore underscore and case (no default, set can be changed afterwards - do not |
| 814 | 814 | /// destroy the set) DEPRECATED |
| 815 | 815 | CLI11_DEPRECATED( |
| 816 | - "Use ->check(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead") | |
| 816 | + "Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead") | |
| 817 | 817 | Option *add_mutable_set_ignore_case_underscore(std::string option_name, |
| 818 | 818 | std::string &member, ///< The selected member of the set |
| 819 | 819 | const std::set<std::string> &options, ///< The set of possibilities |
| 820 | 820 | std::string description = "") { |
| 821 | 821 | |
| 822 | 822 | Option *opt = add_option(option_name, member, std::move(description)); |
| 823 | - opt->check(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case}); | |
| 823 | + opt->transform(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case}); | |
| 824 | 824 | return opt; |
| 825 | 825 | } |
| 826 | 826 | |
| 827 | 827 | /// Add set of options, string only, ignore underscore and case (default, static set) DEPRECATED |
| 828 | - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead") | |
| 828 | + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead") | |
| 829 | 829 | Option *add_set_ignore_case_underscore(std::string option_name, |
| 830 | 830 | std::string &member, ///< The selected member of the set |
| 831 | 831 | std::set<std::string> options, ///< The set of possibilities |
| ... | ... | @@ -833,14 +833,14 @@ class App { |
| 833 | 833 | bool defaulted) { |
| 834 | 834 | |
| 835 | 835 | Option *opt = add_option(option_name, member, std::move(description), defaulted); |
| 836 | - opt->check(IsMember{options, CLI::ignore_underscore, CLI::ignore_case}); | |
| 836 | + opt->transform(IsMember{options, CLI::ignore_underscore, CLI::ignore_case}); | |
| 837 | 837 | return opt; |
| 838 | 838 | } |
| 839 | 839 | |
| 840 | 840 | /// Add set of options, string only, ignore underscore and case (default, set can be changed afterwards - do not |
| 841 | 841 | /// destroy the set) DEPRECATED |
| 842 | 842 | CLI11_DEPRECATED( |
| 843 | - "Use ->check(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead") | |
| 843 | + "Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead") | |
| 844 | 844 | Option *add_mutable_set_ignore_case_underscore(std::string option_name, |
| 845 | 845 | std::string &member, ///< The selected member of the set |
| 846 | 846 | const std::set<std::string> &options, ///< The set of possibilities |
| ... | ... | @@ -848,7 +848,7 @@ class App { |
| 848 | 848 | bool defaulted) { |
| 849 | 849 | |
| 850 | 850 | Option *opt = add_option(option_name, member, std::move(description), defaulted); |
| 851 | - opt->check(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case}); | |
| 851 | + opt->transform(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case}); | |
| 852 | 852 | return opt; |
| 853 | 853 | } |
| 854 | 854 | ... | ... |
include/CLI/Option.hpp
| ... | ... | @@ -303,7 +303,12 @@ class Option : public OptionBase<Option> { |
| 303 | 303 | |
| 304 | 304 | /// Adds a validator with a built in type name |
| 305 | 305 | Option *check(const Validator &validator) { |
| 306 | - validators_.emplace_back(validator.func); | |
| 306 | + std::function<std::string(std::string &)> func = validator.func; | |
| 307 | + validators_.emplace_back([func](const std::string &value) { | |
| 308 | + /// Throw away changes to the string value | |
| 309 | + std::string ignore_changes_value = value; | |
| 310 | + return func(ignore_changes_value); | |
| 311 | + }); | |
| 307 | 312 | if(validator.tname_function) |
| 308 | 313 | type_name_fn(validator.tname_function); |
| 309 | 314 | else if(!validator.tname.empty()) |
| ... | ... | @@ -317,6 +322,16 @@ class Option : public OptionBase<Option> { |
| 317 | 322 | return this; |
| 318 | 323 | } |
| 319 | 324 | |
| 325 | + /// Adds a transforming validator with a built in type name | |
| 326 | + Option *transform(const Validator &validator) { | |
| 327 | + validators_.emplace_back(validator.func); | |
| 328 | + if(validator.tname_function) | |
| 329 | + type_name_fn(validator.tname_function); | |
| 330 | + else if(!validator.tname.empty()) | |
| 331 | + type_name(validator.tname); | |
| 332 | + return this; | |
| 333 | + } | |
| 334 | + | |
| 320 | 335 | /// Adds a validator-like function that can change result |
| 321 | 336 | Option *transform(std::function<std::string(std::string)> func) { |
| 322 | 337 | validators_.emplace_back([func](std::string &inout) { | ... | ... |
include/CLI/StringTools.hpp
| ... | ... | @@ -13,6 +13,31 @@ |
| 13 | 13 | #include <vector> |
| 14 | 14 | |
| 15 | 15 | namespace CLI { |
| 16 | + | |
| 17 | +/// Include the items in this namespace to get free conversion of enums to/from streams. | |
| 18 | +/// (This is available inside CLI as well, so CLI11 will use this without a using statement). | |
| 19 | +namespace enums { | |
| 20 | + | |
| 21 | +/// output streaming for enumerations | |
| 22 | +template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type> | |
| 23 | +std::ostream &operator<<(std::ostream &in, const T &item) { | |
| 24 | + // make sure this is out of the detail namespace otherwise it won't be found when needed | |
| 25 | + return in << static_cast<typename std::underlying_type<T>::type>(item); | |
| 26 | +} | |
| 27 | + | |
| 28 | +/// input streaming for enumerations | |
| 29 | +template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type> | |
| 30 | +std::istream &operator>>(std::istream &in, T &item) { | |
| 31 | + typename std::underlying_type<T>::type i; | |
| 32 | + in >> i; | |
| 33 | + item = static_cast<T>(i); | |
| 34 | + return in; | |
| 35 | +} | |
| 36 | +} // namespace enums | |
| 37 | + | |
| 38 | +/// Export to CLI namespace | |
| 39 | +using namespace enums; | |
| 40 | + | |
| 16 | 41 | namespace detail { |
| 17 | 42 | |
| 18 | 43 | // Based on http://stackoverflow.com/questions/236129/split-a-string-in-c | ... | ... |
include/CLI/TypeTools.hpp
| ... | ... | @@ -28,9 +28,17 @@ constexpr enabler dummy = {}; |
| 28 | 28 | /// We could check to see if C++14 is being used, but it does not hurt to redefine this |
| 29 | 29 | /// (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h) |
| 30 | 30 | /// It is not in the std namespace anyway, so no harm done. |
| 31 | - | |
| 32 | 31 | template <bool B, class T = void> using enable_if_t = typename std::enable_if<B, T>::type; |
| 33 | 32 | |
| 33 | +/// A copy of std::void_t from C++17 (helper for C++11 and C++14) | |
| 34 | +template <typename... Ts> struct make_void { using type = void; }; | |
| 35 | + | |
| 36 | +/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine | |
| 37 | +template <typename... Ts> using void_t = typename make_void<Ts...>::type; | |
| 38 | + | |
| 39 | +/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine | |
| 40 | +template <bool B, class T, class F> using conditional_t = typename std::conditional<B, T, F>::type; | |
| 41 | + | |
| 34 | 42 | /// Check to see if something is a vector (fail check by default) |
| 35 | 43 | template <typename T> struct is_vector : std::false_type {}; |
| 36 | 44 | |
| ... | ... | @@ -54,6 +62,16 @@ template <typename T> struct is_copyable_ptr { |
| 54 | 62 | static bool const value = is_shared_ptr<T>::value || std::is_pointer<T>::value; |
| 55 | 63 | }; |
| 56 | 64 | |
| 65 | +/// This can be specialized to override the type deduction for IsMember. | |
| 66 | +template <typename T> struct IsMemberType { using type = T; }; | |
| 67 | + | |
| 68 | +/// The main custom type needed here is const char * should be a string. | |
| 69 | +template <> struct IsMemberType<const char *> { using type = std::string; }; | |
| 70 | + | |
| 71 | +namespace detail { | |
| 72 | + | |
| 73 | +// These are utilites for IsMember | |
| 74 | + | |
| 57 | 75 | /// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that |
| 58 | 76 | /// pointer_traits<T> be valid. |
| 59 | 77 | template <typename T> struct element_type { |
| ... | ... | @@ -65,13 +83,34 @@ template <typename T> struct element_type { |
| 65 | 83 | /// the container |
| 66 | 84 | template <typename T> struct element_value_type { using type = typename element_type<T>::type::value_type; }; |
| 67 | 85 | |
| 68 | -/// This can be specialized to override the type deduction for IsMember. | |
| 69 | -template <typename T> struct IsMemberType { using type = T; }; | |
| 86 | +/// Adaptor for map-like structure: This just wraps a normal container in a few utilities that do almost nothing. | |
| 87 | +template <typename T, typename _ = void> struct pair_adaptor : std::false_type { | |
| 88 | + using value_type = typename T::value_type; | |
| 89 | + using first_type = typename std::remove_const<value_type>::type; | |
| 90 | + using second_type = typename std::remove_const<value_type>::type; | |
| 70 | 91 | |
| 71 | -/// The main custom type needed here is const char * should be a string. | |
| 72 | -template <> struct IsMemberType<const char *> { using type = std::string; }; | |
| 92 | + /// Get the first value (really just the underlying value) | |
| 93 | + template <typename Q> static first_type first(Q &&value) { return value; } | |
| 94 | + /// Get the second value (really just the underlying value) | |
| 95 | + template <typename Q> static second_type second(Q &&value) { return value; } | |
| 96 | +}; | |
| 73 | 97 | |
| 74 | -namespace detail { | |
| 98 | +/// Adaptor for map-like structure (true version, must have key_type and mapped_type). | |
| 99 | +/// This wraps a mapped container in a few utilities access it in a general way. | |
| 100 | +template <typename T> | |
| 101 | +struct pair_adaptor< | |
| 102 | + T, | |
| 103 | + conditional_t<false, void_t<typename T::value_type::first_type, typename T::value_type::second_type>, void>> | |
| 104 | + : std::true_type { | |
| 105 | + using value_type = typename T::value_type; | |
| 106 | + using first_type = typename std::remove_const<typename value_type::first_type>::type; | |
| 107 | + using second_type = typename std::remove_const<typename value_type::second_type>::type; | |
| 108 | + | |
| 109 | + /// Get the first value (really just the underlying value) | |
| 110 | + template <typename Q> static first_type first(Q &&value) { return value.first; } | |
| 111 | + /// Get the second value (really just the underlying value) | |
| 112 | + template <typename Q> static second_type second(Q &&value) { return value.second; } | |
| 113 | +}; | |
| 75 | 114 | |
| 76 | 115 | // Type name print |
| 77 | 116 | ... | ... |
include/CLI/Validators.hpp
| ... | ... | @@ -57,42 +57,46 @@ class Validator { |
| 57 | 57 | return func(value); |
| 58 | 58 | }; |
| 59 | 59 | |
| 60 | - /// Combining validators is a new validator | |
| 60 | + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the | |
| 61 | + /// same. | |
| 61 | 62 | Validator operator&(const Validator &other) const { |
| 62 | 63 | Validator newval; |
| 63 | 64 | newval.tname = (tname == other.tname ? tname : ""); |
| 65 | + newval.tname_function = tname_function; | |
| 64 | 66 | |
| 65 | 67 | // Give references (will make a copy in lambda function) |
| 66 | 68 | const std::function<std::string(std::string & filename)> &f1 = func; |
| 67 | 69 | const std::function<std::string(std::string & filename)> &f2 = other.func; |
| 68 | 70 | |
| 69 | - newval.func = [f1, f2](std::string &filename) { | |
| 70 | - std::string s1 = f1(filename); | |
| 71 | - std::string s2 = f2(filename); | |
| 71 | + newval.func = [f1, f2](std::string &input) { | |
| 72 | + std::string s1 = f1(input); | |
| 73 | + std::string s2 = f2(input); | |
| 72 | 74 | if(!s1.empty() && !s2.empty()) |
| 73 | - return s1 + " & " + s2; | |
| 75 | + return s1 + " AND " + s2; | |
| 74 | 76 | else |
| 75 | 77 | return s1 + s2; |
| 76 | 78 | }; |
| 77 | 79 | return newval; |
| 78 | 80 | } |
| 79 | 81 | |
| 80 | - /// Combining validators is a new validator | |
| 82 | + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the | |
| 83 | + /// same. | |
| 81 | 84 | Validator operator|(const Validator &other) const { |
| 82 | 85 | Validator newval; |
| 83 | 86 | newval.tname = (tname == other.tname ? tname : ""); |
| 87 | + newval.tname_function = tname_function; | |
| 84 | 88 | |
| 85 | 89 | // Give references (will make a copy in lambda function) |
| 86 | - const std::function<std::string(std::string & filename)> &f1 = func; | |
| 87 | - const std::function<std::string(std::string & filename)> &f2 = other.func; | |
| 90 | + const std::function<std::string(std::string &)> &f1 = func; | |
| 91 | + const std::function<std::string(std::string &)> &f2 = other.func; | |
| 88 | 92 | |
| 89 | - newval.func = [f1, f2](std::string &filename) { | |
| 90 | - std::string s1 = f1(filename); | |
| 91 | - std::string s2 = f2(filename); | |
| 93 | + newval.func = [f1, f2](std::string &input) { | |
| 94 | + std::string s1 = f1(input); | |
| 95 | + std::string s2 = f2(input); | |
| 92 | 96 | if(s1.empty() || s2.empty()) |
| 93 | 97 | return std::string(); |
| 94 | 98 | else |
| 95 | - return s1 + " & " + s2; | |
| 99 | + return s1 + " OR " + s2; | |
| 96 | 100 | }; |
| 97 | 101 | return newval; |
| 98 | 102 | } |
| ... | ... | @@ -274,6 +278,7 @@ auto smart_deref(T value) -> decltype(*value) { |
| 274 | 278 | template <typename T, enable_if_t<!is_copyable_ptr<T>::value, detail::enabler> = detail::dummy> T smart_deref(T value) { |
| 275 | 279 | return value; |
| 276 | 280 | } |
| 281 | + | |
| 277 | 282 | } // namespace detail |
| 278 | 283 | |
| 279 | 284 | /// Verify items are in a set |
| ... | ... | @@ -287,18 +292,19 @@ class IsMember : public Validator { |
| 287 | 292 | : IsMember(std::vector<T>(values), std::forward<Args>(args)...) {} |
| 288 | 293 | |
| 289 | 294 | /// This checks to see if an item is in a set (empty function) |
| 290 | - template <typename T> | |
| 291 | - explicit IsMember(T set) | |
| 292 | - : IsMember(std::move(set), | |
| 293 | - std::function<typename IsMemberType<typename element_value_type<T>::type>::type( | |
| 294 | - typename IsMemberType<typename element_value_type<T>::type>::type)>()) {} | |
| 295 | + template <typename T> explicit IsMember(T set) : IsMember(std::move(set), nullptr) {} | |
| 295 | 296 | |
| 296 | 297 | /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter |
| 297 | 298 | /// both sides of the comparison before computing the comparison. |
| 298 | 299 | template <typename T, typename F> explicit IsMember(T set, F filter_function) { |
| 300 | + | |
| 299 | 301 | // Get the type of the contained item - requires a container have ::value_type |
| 300 | - using item_t = typename element_value_type<T>::type; | |
| 301 | - using local_item_t = typename IsMemberType<item_t>::type; | |
| 302 | + // if the type does not have first_type and second_type, these are both value_type | |
| 303 | + using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed | |
| 304 | + using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map | |
| 305 | + | |
| 306 | + using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones | |
| 307 | + // (const char * to std::string) | |
| 302 | 308 | |
| 303 | 309 | // Make a local copy of the filter function, using a std::function if not one already |
| 304 | 310 | std::function<local_item_t(local_item_t)> filter_fn = filter_function; |
| ... | ... | @@ -306,15 +312,19 @@ class IsMember : public Validator { |
| 306 | 312 | // This is the type name for help, it will take the current version of the set contents |
| 307 | 313 | tname_function = [set]() { |
| 308 | 314 | std::stringstream out; |
| 309 | - out << detail::type_name<item_t>() << " in {" << detail::join(detail::smart_deref(set), ",") << "}"; | |
| 315 | + out << "{"; | |
| 316 | + int i = 0; // I don't like counters like this | |
| 317 | + for(const auto &v : detail::smart_deref(set)) | |
| 318 | + out << (i++ == 0 ? "" : ",") << detail::pair_adaptor<element_t>::first(v); | |
| 319 | + out << "}"; | |
| 310 | 320 | return out.str(); |
| 311 | 321 | }; |
| 312 | 322 | |
| 313 | 323 | // This is the function that validates |
| 314 | 324 | // It stores a copy of the set pointer-like, so shared_ptr will stay alive |
| 315 | 325 | func = [set, filter_fn](std::string &input) { |
| 316 | - for(const item_t &v : detail::smart_deref(set)) { | |
| 317 | - local_item_t a = v; | |
| 326 | + for(const auto &v : detail::smart_deref(set)) { | |
| 327 | + local_item_t a = detail::pair_adaptor<element_t>::first(v); | |
| 318 | 328 | local_item_t b; |
| 319 | 329 | if(!detail::lexical_cast(input, b)) |
| 320 | 330 | throw ValidationError(input); // name is added later |
| ... | ... | @@ -328,9 +338,10 @@ class IsMember : public Validator { |
| 328 | 338 | if(a == b) { |
| 329 | 339 | // Make sure the version in the input string is identical to the one in the set |
| 330 | 340 | // Requires std::stringstream << be supported on T. |
| 331 | - if(filter_fn) { | |
| 341 | + // If this is a map, output the map instead. | |
| 342 | + if(filter_fn || detail::pair_adaptor<element_t>::value) { | |
| 332 | 343 | std::stringstream out; |
| 333 | - out << v; | |
| 344 | + out << detail::pair_adaptor<element_t>::second(v); | |
| 334 | 345 | input = out.str(); |
| 335 | 346 | } |
| 336 | 347 | |
| ... | ... | @@ -340,11 +351,17 @@ class IsMember : public Validator { |
| 340 | 351 | } |
| 341 | 352 | |
| 342 | 353 | // If you reach this point, the result was not found |
| 343 | - return input + " not in {" + detail::join(detail::smart_deref(set), ",") + "}"; | |
| 354 | + std::stringstream out; | |
| 355 | + out << input << " not in {"; | |
| 356 | + int i = 0; // I still don't like counters like this | |
| 357 | + for(const auto &v : detail::smart_deref(set)) | |
| 358 | + out << (i++ == 0 ? "" : ",") << detail::pair_adaptor<element_t>::first(v); | |
| 359 | + out << "}"; | |
| 360 | + return out.str(); | |
| 344 | 361 | }; |
| 345 | 362 | } |
| 346 | 363 | |
| 347 | - /// You can pass in as many filter functions as you like, they nest | |
| 364 | + /// You can pass in as many filter functions as you like, they nest (string only currently) | |
| 348 | 365 | template <typename T, typename... Args> |
| 349 | 366 | IsMember(T set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) |
| 350 | 367 | : IsMember(std::move(set), | ... | ... |
tests/HelpTest.cpp
| ... | ... | @@ -237,6 +237,27 @@ TEST(THelp, ManualSetters) { |
| 237 | 237 | EXPECT_THAT(help, HasSubstr("=14")); |
| 238 | 238 | } |
| 239 | 239 | |
| 240 | +TEST(THelp, ManualSetterOverFunction) { | |
| 241 | + | |
| 242 | + CLI::App app{"My prog"}; | |
| 243 | + | |
| 244 | + int x = 1; | |
| 245 | + | |
| 246 | + CLI::Option *op1 = app.add_option("--op1", x)->check(CLI::IsMember({1, 2})); | |
| 247 | + CLI::Option *op2 = app.add_option("--op2", x)->transform(CLI::IsMember({1, 2})); | |
| 248 | + op1->default_str("12"); | |
| 249 | + op1->type_name("BIGGLES"); | |
| 250 | + op2->type_name("QUIGGLES"); | |
| 251 | + EXPECT_EQ(x, 1); | |
| 252 | + | |
| 253 | + std::string help = app.help(); | |
| 254 | + | |
| 255 | + EXPECT_THAT(help, HasSubstr("=12")); | |
| 256 | + EXPECT_THAT(help, HasSubstr("BIGGLES")); | |
| 257 | + EXPECT_THAT(help, HasSubstr("QUIGGLES")); | |
| 258 | + EXPECT_THAT(help, Not(HasSubstr("1,2"))); | |
| 259 | +} | |
| 260 | + | |
| 240 | 261 | TEST(THelp, Subcom) { |
| 241 | 262 | CLI::App app{"My prog"}; |
| 242 | 263 | |
| ... | ... | @@ -789,6 +810,19 @@ TEST(THelp, CombinedValidatorsPathyText) { |
| 789 | 810 | EXPECT_THAT(help, HasSubstr("PATH")); |
| 790 | 811 | } |
| 791 | 812 | |
| 813 | +// Don't do this in real life, please (and transform does nothing here) | |
| 814 | +TEST(THelp, CombinedValidatorsPathyTextAsTransform) { | |
| 815 | + CLI::App app; | |
| 816 | + | |
| 817 | + std::string filename; | |
| 818 | + app.add_option("--f1", filename)->transform(CLI::ExistingPath | CLI::NonexistentPath); | |
| 819 | + | |
| 820 | + // Combining validators with the same type string is OK | |
| 821 | + std::string help = app.help(); | |
| 822 | + EXPECT_THAT(help, Not(HasSubstr("TEXT"))); | |
| 823 | + EXPECT_THAT(help, HasSubstr("PATH")); | |
| 824 | +} | |
| 825 | + | |
| 792 | 826 | // #113 Part 2 |
| 793 | 827 | TEST(THelp, ChangingSet) { |
| 794 | 828 | CLI::App app; | ... | ... |
tests/SetTest.cpp
| 1 | 1 | #include "app_helper.hpp" |
| 2 | +#include <map> | |
| 2 | 3 | |
| 3 | 4 | static_assert(CLI::is_shared_ptr<std::shared_ptr<int>>::value == true, "is_shared_ptr should work on shared pointers"); |
| 4 | 5 | static_assert(CLI::is_shared_ptr<int *>::value == false, "is_shared_ptr should work on pointers"); |
| ... | ... | @@ -9,6 +10,82 @@ static_assert(CLI::is_copyable_ptr<std::shared_ptr<int>>::value == true, |
| 9 | 10 | static_assert(CLI::is_copyable_ptr<int *>::value == true, "is_copyable_ptr should work on pointers"); |
| 10 | 11 | static_assert(CLI::is_copyable_ptr<int>::value == false, "is_copyable_ptr should work on non-pointers"); |
| 11 | 12 | |
| 13 | +static_assert(CLI::detail::pair_adaptor<std::set<int>>::value == false, "Should not have pairs"); | |
| 14 | +static_assert(CLI::detail::pair_adaptor<std::map<int, int>>::value == true, "Should have pairs"); | |
| 15 | +static_assert(CLI::detail::pair_adaptor<std::vector<std::pair<int, int>>>::value == true, "Should have pairs"); | |
| 16 | + | |
| 17 | +TEST_F(TApp, SimpleMaps) { | |
| 18 | + int value; | |
| 19 | + std::map<std::string, int> map = {{"one", 1}, {"two", 2}}; | |
| 20 | + auto opt = app.add_option("-s,--set", value)->transform(CLI::IsMember(map)); | |
| 21 | + args = {"-s", "one"}; | |
| 22 | + run(); | |
| 23 | + EXPECT_EQ(1u, app.count("-s")); | |
| 24 | + EXPECT_EQ(1u, app.count("--set")); | |
| 25 | + EXPECT_EQ(1u, opt->count()); | |
| 26 | + EXPECT_EQ(value, 1); | |
| 27 | +} | |
| 28 | + | |
| 29 | +TEST_F(TApp, StringStringMap) { | |
| 30 | + std::string value; | |
| 31 | + std::map<std::string, std::string> map = {{"a", "b"}, {"b", "c"}}; | |
| 32 | + app.add_option("-s,--set", value)->transform(CLI::IsMember(map)); | |
| 33 | + args = {"-s", "a"}; | |
| 34 | + run(); | |
| 35 | + EXPECT_EQ(value, "b"); | |
| 36 | + | |
| 37 | + args = {"-s", "b"}; | |
| 38 | + run(); | |
| 39 | + EXPECT_EQ(value, "c"); | |
| 40 | + | |
| 41 | + args = {"-s", "c"}; | |
| 42 | + EXPECT_THROW(run(), CLI::ValidationError); | |
| 43 | +} | |
| 44 | + | |
| 45 | +TEST_F(TApp, StringStringMapNoModify) { | |
| 46 | + std::string value; | |
| 47 | + std::map<std::string, std::string> map = {{"a", "b"}, {"b", "c"}}; | |
| 48 | + app.add_option("-s,--set", value)->check(CLI::IsMember(map)); | |
| 49 | + args = {"-s", "a"}; | |
| 50 | + run(); | |
| 51 | + EXPECT_EQ(value, "a"); | |
| 52 | + | |
| 53 | + args = {"-s", "b"}; | |
| 54 | + run(); | |
| 55 | + EXPECT_EQ(value, "b"); | |
| 56 | + | |
| 57 | + args = {"-s", "c"}; | |
| 58 | + EXPECT_THROW(run(), CLI::ValidationError); | |
| 59 | +} | |
| 60 | + | |
| 61 | +enum SimpleEnum { SE_one = 1, SE_two = 2 }; | |
| 62 | + | |
| 63 | +TEST_F(TApp, EnumMap) { | |
| 64 | + SimpleEnum value; | |
| 65 | + std::map<std::string, SimpleEnum> map = {{"one", SE_one}, {"two", SE_two}}; | |
| 66 | + auto opt = app.add_option("-s,--set", value)->transform(CLI::IsMember(map)); | |
| 67 | + args = {"-s", "one"}; | |
| 68 | + run(); | |
| 69 | + EXPECT_EQ(1u, app.count("-s")); | |
| 70 | + EXPECT_EQ(1u, app.count("--set")); | |
| 71 | + EXPECT_EQ(1u, opt->count()); | |
| 72 | + EXPECT_EQ(value, SE_one); | |
| 73 | +} | |
| 74 | + | |
| 75 | +enum class SimpleEnumC { one = 1, two = 2 }; | |
| 76 | + | |
| 77 | +TEST_F(TApp, EnumCMap) { | |
| 78 | + SimpleEnumC value; | |
| 79 | + std::map<std::string, SimpleEnumC> map = {{"one", SimpleEnumC::one}, {"two", SimpleEnumC::two}}; | |
| 80 | + auto opt = app.add_option("-s,--set", value)->transform(CLI::IsMember(map)); | |
| 81 | + args = {"-s", "one"}; | |
| 82 | + run(); | |
| 83 | + EXPECT_EQ(1u, app.count("-s")); | |
| 84 | + EXPECT_EQ(1u, app.count("--set")); | |
| 85 | + EXPECT_EQ(1u, opt->count()); | |
| 86 | + EXPECT_EQ(value, SimpleEnumC::one); | |
| 87 | +} | |
| 88 | + | |
| 12 | 89 | TEST_F(TApp, SimpleSets) { |
| 13 | 90 | std::string value; |
| 14 | 91 | auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set<std::string>({"one", "two", "three"})}); |
| ... | ... | @@ -51,7 +128,7 @@ TEST_F(TApp, SimiShortcutSets) { |
| 51 | 128 | EXPECT_EQ(value, "one"); |
| 52 | 129 | |
| 53 | 130 | std::string value2; |
| 54 | - auto opt2 = app.add_option("--set2", value2)->check(CLI::IsMember({"One", "two", "three"}, CLI::ignore_case)); | |
| 131 | + auto opt2 = app.add_option("--set2", value2)->transform(CLI::IsMember({"One", "two", "three"}, CLI::ignore_case)); | |
| 55 | 132 | args = {"--set2", "onE"}; |
| 56 | 133 | run(); |
| 57 | 134 | EXPECT_EQ(1u, app.count("--set2")); |
| ... | ... | @@ -60,7 +137,7 @@ TEST_F(TApp, SimiShortcutSets) { |
| 60 | 137 | |
| 61 | 138 | std::string value3; |
| 62 | 139 | auto opt3 = app.add_option("--set3", value3) |
| 63 | - ->check(CLI::IsMember({"O_ne", "two", "three"}, CLI::ignore_case, CLI::ignore_underscore)); | |
| 140 | + ->transform(CLI::IsMember({"O_ne", "two", "three"}, CLI::ignore_case, CLI::ignore_underscore)); | |
| 64 | 141 | args = {"--set3", "onE"}; |
| 65 | 142 | run(); |
| 66 | 143 | EXPECT_EQ(1u, app.count("--set3")); |
| ... | ... | @@ -95,7 +172,7 @@ TEST_F(TApp, OtherTypeSets) { |
| 95 | 172 | EXPECT_THROW(run(), CLI::ValidationError); |
| 96 | 173 | |
| 97 | 174 | std::vector<int> set2 = {-2, 3, 4}; |
| 98 | - auto opt2 = app.add_option("--set2", value)->check(CLI::IsMember(set2, [](int x) { return std::abs(x); })); | |
| 175 | + auto opt2 = app.add_option("--set2", value)->transform(CLI::IsMember(set2, [](int x) { return std::abs(x); })); | |
| 99 | 176 | args = {"--set2", "-3"}; |
| 100 | 177 | run(); |
| 101 | 178 | EXPECT_EQ(1u, app.count("--set2")); |
| ... | ... | @@ -189,7 +266,7 @@ TEST_F(TApp, InSetWithDefault) { |
| 189 | 266 | TEST_F(TApp, InCaselessSetWithDefault) { |
| 190 | 267 | |
| 191 | 268 | std::string choice = "one"; |
| 192 | - app.add_option("-q,--quick", choice, "", true)->check(CLI::IsMember({"one", "two", "three"}, CLI::ignore_case)); | |
| 269 | + app.add_option("-q,--quick", choice, "", true)->transform(CLI::IsMember({"one", "two", "three"}, CLI::ignore_case)); | |
| 193 | 270 | |
| 194 | 271 | run(); |
| 195 | 272 | EXPECT_EQ("one", choice); |
| ... | ... | @@ -263,7 +340,7 @@ TEST_F(TApp, FailMutableSet) { |
| 263 | 340 | TEST_F(TApp, InSetIgnoreCase) { |
| 264 | 341 | |
| 265 | 342 | std::string choice; |
| 266 | - app.add_option("-q,--quick", choice)->check(CLI::IsMember({"one", "Two", "THREE"}, CLI::ignore_case)); | |
| 343 | + app.add_option("-q,--quick", choice)->transform(CLI::IsMember({"one", "Two", "THREE"}, CLI::ignore_case)); | |
| 267 | 344 | |
| 268 | 345 | args = {"--quick", "One"}; |
| 269 | 346 | run(); |
| ... | ... | @@ -288,7 +365,7 @@ TEST_F(TApp, InSetIgnoreCaseMutableValue) { |
| 288 | 365 | |
| 289 | 366 | std::set<std::string> options{"one", "Two", "THREE"}; |
| 290 | 367 | std::string choice; |
| 291 | - app.add_option("-q,--quick", choice)->check(CLI::IsMember(&options, CLI::ignore_case)); | |
| 368 | + app.add_option("-q,--quick", choice)->transform(CLI::IsMember(&options, CLI::ignore_case)); | |
| 292 | 369 | |
| 293 | 370 | args = {"--quick", "One"}; |
| 294 | 371 | run(); |
| ... | ... | @@ -311,7 +388,7 @@ TEST_F(TApp, InSetIgnoreCasePointer) { |
| 311 | 388 | |
| 312 | 389 | std::set<std::string> *options = new std::set<std::string>{"one", "Two", "THREE"}; |
| 313 | 390 | std::string choice; |
| 314 | - app.add_option("-q,--quick", choice)->check(CLI::IsMember(*options, CLI::ignore_case)); | |
| 391 | + app.add_option("-q,--quick", choice)->transform(CLI::IsMember(*options, CLI::ignore_case)); | |
| 315 | 392 | |
| 316 | 393 | args = {"--quick", "One"}; |
| 317 | 394 | run(); |
| ... | ... | @@ -341,7 +418,7 @@ TEST_F(TApp, InSetIgnoreUnderscore) { |
| 341 | 418 | |
| 342 | 419 | std::string choice; |
| 343 | 420 | app.add_option("-q,--quick", choice) |
| 344 | - ->check(CLI::IsMember({"option_one", "option_two", "optionthree"}, CLI::ignore_underscore)); | |
| 421 | + ->transform(CLI::IsMember({"option_one", "option_two", "optionthree"}, CLI::ignore_underscore)); | |
| 345 | 422 | |
| 346 | 423 | args = {"--quick", "option_one"}; |
| 347 | 424 | run(); |
| ... | ... | @@ -366,7 +443,8 @@ TEST_F(TApp, InSetIgnoreCaseUnderscore) { |
| 366 | 443 | |
| 367 | 444 | std::string choice; |
| 368 | 445 | app.add_option("-q,--quick", choice) |
| 369 | - ->check(CLI::IsMember({"Option_One", "option_two", "OptionThree"}, CLI::ignore_case, CLI::ignore_underscore)); | |
| 446 | + ->transform( | |
| 447 | + CLI::IsMember({"Option_One", "option_two", "OptionThree"}, CLI::ignore_case, CLI::ignore_underscore)); | |
| 370 | 448 | |
| 371 | 449 | args = {"--quick", "option_one"}; |
| 372 | 450 | run(); |
| ... | ... | @@ -423,8 +501,8 @@ TEST_F(TApp, AddRemoveSetItemsNoCase) { |
| 423 | 501 | std::set<std::string> items{"TYPE1", "TYPE2", "TYPE3", "TYPE4", "TYPE5"}; |
| 424 | 502 | |
| 425 | 503 | std::string type1, type2; |
| 426 | - app.add_option("--type1", type1)->check(CLI::IsMember(&items, CLI::ignore_case)); | |
| 427 | - app.add_option("--type2", type2, "", true)->check(CLI::IsMember(&items, CLI::ignore_case)); | |
| 504 | + app.add_option("--type1", type1)->transform(CLI::IsMember(&items, CLI::ignore_case)); | |
| 505 | + app.add_option("--type2", type2, "", true)->transform(CLI::IsMember(&items, CLI::ignore_case)); | |
| 428 | 506 | |
| 429 | 507 | args = {"--type1", "TYPe1", "--type2", "TyPE2"}; |
| 430 | 508 | ... | ... |