Commit 17d05b000c773d6ebbf81bbe059c7e1f1a890e9c

Authored by Henry Schreiner
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
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&lt;Option&gt; {
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&lt;Option&gt; {
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 &lt;typename T&gt; 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 &lt;typename T&gt; 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) -&gt; 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&lt;std::shared_ptr&lt;int&gt;&gt;::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  
... ...