From 3712bf596aab15fb70768e75c47877e89835dc7e Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Tue, 19 Feb 2019 17:51:23 +0100 Subject: [PATCH 1/6] Adding first draft of mapping --- include/CLI/TypeTools.hpp | 32 ++++++++++++++- include/CLI/Validators.hpp | 84 +++++++++++++++++++++++++++++++++++++- tests/SetTest.cpp | 16 ++++++++ 3 files changed, 130 insertions(+), 2 deletions(-) diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index 7eafbbae7..0bb0733a7 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -28,9 +28,18 @@ constexpr enabler dummy = {}; /// We could check to see if C++14 is being used, but it does not hurt to redefine this /// (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h) /// It is not in the std namespace anyway, so no harm done. - template using enable_if_t = typename std::enable_if::type; +/// A copy of std::void_t from C++17 (helper for C++11 and C++14) +template struct make_void { using type = void; }; + +/// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine +template using void_t = typename make_void::type; + +/// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine +template< bool B, class T, class F > +using conditional_t = typename std::conditional::type; + /// Check to see if something is a vector (fail check by default) template struct is_vector : std::false_type {}; @@ -65,6 +74,27 @@ template struct element_type { /// the container template struct element_value_type { using type = typename element_type::type::value_type; }; +/// Combination of the element type and important types for maps - remove pointer (including smart pointers) +template struct element_map_type { + using value_type = typename element_type::type::value_type; + using mapped_type = typename element_type::type::mapped_type; + using key_type = typename element_type::type::key_type; +}; + +/// Check for a map-like structure (false version) +template struct has_mapped_key : std::false_type {}; + +/// Check for a map-like structure (true version, must have key_type and mapped_type) +template struct has_mapped_key , + void + > + > : std::true_type {}; + /// This can be specialized to override the type deduction for IsMember. template struct IsMemberType { using type = T; }; diff --git a/include/CLI/Validators.hpp b/include/CLI/Validators.hpp index 5d4146ed4..b5e84165a 100644 --- a/include/CLI/Validators.hpp +++ b/include/CLI/Validators.hpp @@ -10,6 +10,7 @@ #include #include #include +#include // C standard library // Only needed for existence checking @@ -274,6 +275,7 @@ auto smart_deref(T value) -> decltype(*value) { template ::value, detail::enabler> = detail::dummy> T smart_deref(T value) { return value; } + } // namespace detail /// Verify items are in a set @@ -344,7 +346,7 @@ class IsMember : public Validator { }; } - /// You can pass in as many filter functions as you like, they nest + /// You can pass in as many filter functions as you like, they nest (string only currently) template IsMember(T set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) : IsMember(std::move(set), @@ -352,6 +354,86 @@ class IsMember : public Validator { other...) {} }; +/// Verify items are in a mapping +class Mapping : public Validator { + public: + using filter_fn_t = std::function; + + /// This allows in-place construction using an initializer list + template + explicit Mapping(std::initializer_list> values, Args &&... args) + : Mapping(std::map(values), std::forward(args)...) {} + + /// This checks to see if an item is in a set (empty function) + template + explicit Mapping(T set) + : Mapping(std::move(set), + std::function::mapped_type>::type( + typename IsMemberType::mapped_type>::type)>()) {} + + /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter + /// both sides of the comparison before computing the comparison. + template explicit Mapping(T set, F filter_function) { + + // Get the type of the contained item - requires a container have ::value_type, key_type, and mapped_type + using map_item_t = typename element_map_type::mapped_type; + using key_item_t = typename element_map_type::key_type; + using local_map_item_t = typename IsMemberType::type; + using local_key_item_t = typename IsMemberType::type; + + // Make a local copy of the filter function, using a std::function if not one already + std::function filter_fn = filter_function; + + // This is the type name for help, it will take the current version of the set contents + tname_function = [set]() { + std::stringstream out; + out << detail::type_name() << " in {"; + for(auto &pairlike : detail::smart_deref(set)) + out << pairlike.first << ":" << pairlike.second << ","; + out << "}"; + return out.str(); + }; + + // This is the function that validates + // It stores a copy of the set pointer-like, so shared_ptr will stay alive + func = [set, filter_fn](std::string &input) { + for(const auto &v : detail::smart_deref(set)) { + local_map_item_t a = v.second; + local_map_item_t b; + if(!detail::lexical_cast(input, b)) + throw ValidationError(input); // name is added later + + // The filter function might be empty, so don't filter if it is. + if(filter_fn) { + a = filter_fn(a); + b = filter_fn(b); + } + + if(a == b) { + // Make sure the version in the input string is identical to the one in the set + // Requires std::stringstream << be supported on T. + std::stringstream out; + out << v.first; + input = out.str(); + + // Return empty error string (success) + return std::string(); + } + } + + // If you reach this point, the result was not found + return input + " not in {...}"; + }; + } + + /// You can pass in as many filter functions as you like, they nest + template + Mapping(T set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) + : Mapping(std::move(set), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} +}; + /// Helper function to allow ignore_case to be passed to IsMember inline std::string ignore_case(std::string item) { return detail::to_lower(item); } diff --git a/tests/SetTest.cpp b/tests/SetTest.cpp index 2db748ecb..9f6dd17c8 100644 --- a/tests/SetTest.cpp +++ b/tests/SetTest.cpp @@ -1,4 +1,5 @@ #include "app_helper.hpp" +#include static_assert(CLI::is_shared_ptr>::value == true, "is_shared_ptr should work on shared pointers"); static_assert(CLI::is_shared_ptr::value == false, "is_shared_ptr should work on pointers"); @@ -9,6 +10,21 @@ static_assert(CLI::is_copyable_ptr>::value == true, static_assert(CLI::is_copyable_ptr::value == true, "is_copyable_ptr should work on pointers"); static_assert(CLI::is_copyable_ptr::value == false, "is_copyable_ptr should work on non-pointers"); +static_assert(CLI::has_mapped_key>::value == false, "Should not have keys"); +static_assert(CLI::has_mapped_key>::value == true, "Should have keys"); + +TEST_F(TApp, SimpleMaps) { + int value; + std::map map = {{1, "one"}, {2, "two"}}; + auto opt = app.add_option("-s,--set", value)->check(CLI::Mapping(map)); + args = {"-s", "one"}; + run(); + EXPECT_EQ(1u, app.count("-s")); + EXPECT_EQ(1u, app.count("--set")); + EXPECT_EQ(1u, opt->count()); + EXPECT_EQ(value, 1); +} + TEST_F(TApp, SimpleSets) { std::string value; auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set({"one", "two", "three"})}); From fdd4fb41e3da1cbdd2b7bc35238afdb394fcb11f Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Tue, 19 Feb 2019 21:47:27 +0100 Subject: [PATCH 2/6] IsMember now supports maps --- include/CLI/TypeTools.hpp | 63 +++++++++++--------- include/CLI/Validators.hpp | 119 ++++++++----------------------------- tests/SetTest.cpp | 8 +-- 3 files changed, 63 insertions(+), 127 deletions(-) diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index 0bb0733a7..be269bb09 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -34,11 +34,10 @@ template using enable_if_t = typename std::enable_if struct make_void { using type = void; }; /// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine -template using void_t = typename make_void::type; +template using void_t = typename make_void::type; /// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine -template< bool B, class T, class F > -using conditional_t = typename std::conditional::type; +template using conditional_t = typename std::conditional::type; /// Check to see if something is a vector (fail check by default) template struct is_vector : std::false_type {}; @@ -63,6 +62,16 @@ template struct is_copyable_ptr { static bool const value = is_shared_ptr::value || std::is_pointer::value; }; +/// This can be specialized to override the type deduction for IsMember. +template struct IsMemberType { using type = T; }; + +/// The main custom type needed here is const char * should be a string. +template <> struct IsMemberType { using type = std::string; }; + +namespace detail { + +// These are utilites for IsMember + /// Handy helper to access the element_type generically. This is not part of is_copyable_ptr because it requires that /// pointer_traits be valid. template struct element_type { @@ -74,34 +83,30 @@ template struct element_type { /// the container template struct element_value_type { using type = typename element_type::type::value_type; }; -/// Combination of the element type and important types for maps - remove pointer (including smart pointers) -template struct element_map_type { - using value_type = typename element_type::type::value_type; - using mapped_type = typename element_type::type::mapped_type; - using key_type = typename element_type::type::key_type; -}; - -/// Check for a map-like structure (false version) -template struct has_mapped_key : std::false_type {}; - -/// Check for a map-like structure (true version, must have key_type and mapped_type) -template struct has_mapped_key , - void - > - > : std::true_type {}; - -/// This can be specialized to override the type deduction for IsMember. -template struct IsMemberType { using type = T; }; +/// Adaptor for map-like structure: This just wraps a normal container in a few utilities that do almost nothing. +template struct key_map_adaptor : std::false_type { + using mapped_type = typename T::value_type; + using key_type = typename T::value_type; -/// The main custom type needed here is const char * should be a string. -template <> struct IsMemberType { using type = std::string; }; + /// Get the first value (really just the underlying value) + template static key_type first(Q &&value) { return value; } + /// Get the second value (really just the underlying value) + template static mapped_type second(Q &&value) { return value; } +}; -namespace detail { +/// Adaptor for map-like structure (true version, must have key_type and mapped_type). +/// This wraps a mapped container in a few utilities access it in a general way. +template +struct key_map_adaptor, void>> + : std::true_type { + using mapped_type = typename T::mapped_type; + using key_type = typename T::key_type; + + /// Get the first value (really just the underlying value) + template static key_type first(Q &&value) { return value.first; } + /// Get the second value (really just the underlying value) + template static mapped_type second(Q &&value) { return value.second; } +}; // Type name print diff --git a/include/CLI/Validators.hpp b/include/CLI/Validators.hpp index b5e84165a..7bcfbcc06 100644 --- a/include/CLI/Validators.hpp +++ b/include/CLI/Validators.hpp @@ -10,7 +10,6 @@ #include #include #include -#include // C standard library // Only needed for existence checking @@ -289,18 +288,19 @@ class IsMember : public Validator { : IsMember(std::vector(values), std::forward(args)...) {} /// This checks to see if an item is in a set (empty function) - template - explicit IsMember(T set) - : IsMember(std::move(set), - std::function::type>::type( - typename IsMemberType::type>::type)>()) {} + template explicit IsMember(T set) : IsMember(std::move(set), nullptr) {} /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter /// both sides of the comparison before computing the comparison. template explicit IsMember(T set, F filter_function) { + // Get the type of the contained item - requires a container have ::value_type - using item_t = typename element_value_type::type; - using local_item_t = typename IsMemberType::type; + // if the type does not have mapped_type an key_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::key_map_adaptor::mapped_type; // Is value_type if not a map + + using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones + // (const char * to std::string) // Make a local copy of the filter function, using a std::function if not one already std::function filter_fn = filter_function; @@ -308,15 +308,19 @@ class IsMember : public Validator { // This is the type name for help, it will take the current version of the set contents tname_function = [set]() { std::stringstream out; - out << detail::type_name() << " in {" << detail::join(detail::smart_deref(set), ",") << "}"; + out << detail::type_name() << " in {"; + int i = 0; // I don't like counters like this + for(const auto &v : detail::smart_deref(set)) + out << (i++ == 0 ? "" : ",") << detail::key_map_adaptor::second(v); + out << "}"; return out.str(); }; // This is the function that validates // It stores a copy of the set pointer-like, so shared_ptr will stay alive func = [set, filter_fn](std::string &input) { - for(const item_t &v : detail::smart_deref(set)) { - local_item_t a = v; + for(const auto &v : detail::smart_deref(set)) { + local_item_t a = detail::key_map_adaptor::second(v); local_item_t b; if(!detail::lexical_cast(input, b)) throw ValidationError(input); // name is added later @@ -330,9 +334,10 @@ class IsMember : public Validator { if(a == b) { // Make sure the version in the input string is identical to the one in the set // Requires std::stringstream << be supported on T. - if(filter_fn) { + // If this is a map, ouptut the map instead. + if(filter_fn || detail::key_map_adaptor::value) { std::stringstream out; - out << v; + out << detail::key_map_adaptor::first(v); input = out.str(); } @@ -342,94 +347,20 @@ class IsMember : public Validator { } // If you reach this point, the result was not found - return input + " not in {" + detail::join(detail::smart_deref(set), ",") + "}"; - }; - } - - /// You can pass in as many filter functions as you like, they nest (string only currently) - template - IsMember(T set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) - : IsMember(std::move(set), - [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, - other...) {} -}; - -/// Verify items are in a mapping -class Mapping : public Validator { - public: - using filter_fn_t = std::function; - - /// This allows in-place construction using an initializer list - template - explicit Mapping(std::initializer_list> values, Args &&... args) - : Mapping(std::map(values), std::forward(args)...) {} - - /// This checks to see if an item is in a set (empty function) - template - explicit Mapping(T set) - : Mapping(std::move(set), - std::function::mapped_type>::type( - typename IsMemberType::mapped_type>::type)>()) {} - - /// This checks to see if an item is in a set: pointer or copy version. You can pass in a function that will filter - /// both sides of the comparison before computing the comparison. - template explicit Mapping(T set, F filter_function) { - - // Get the type of the contained item - requires a container have ::value_type, key_type, and mapped_type - using map_item_t = typename element_map_type::mapped_type; - using key_item_t = typename element_map_type::key_type; - using local_map_item_t = typename IsMemberType::type; - using local_key_item_t = typename IsMemberType::type; - - // Make a local copy of the filter function, using a std::function if not one already - std::function filter_fn = filter_function; - - // This is the type name for help, it will take the current version of the set contents - tname_function = [set]() { std::stringstream out; - out << detail::type_name() << " in {"; - for(auto &pairlike : detail::smart_deref(set)) - out << pairlike.first << ":" << pairlike.second << ","; + out << input << " not in {"; + int i = 0; // I still don't like counters like this + for(const auto &v : detail::smart_deref(set)) + out << (i++ == 0 ? "" : ",") << detail::key_map_adaptor::second(v); out << "}"; return out.str(); }; - - // This is the function that validates - // It stores a copy of the set pointer-like, so shared_ptr will stay alive - func = [set, filter_fn](std::string &input) { - for(const auto &v : detail::smart_deref(set)) { - local_map_item_t a = v.second; - local_map_item_t b; - if(!detail::lexical_cast(input, b)) - throw ValidationError(input); // name is added later - - // The filter function might be empty, so don't filter if it is. - if(filter_fn) { - a = filter_fn(a); - b = filter_fn(b); - } - - if(a == b) { - // Make sure the version in the input string is identical to the one in the set - // Requires std::stringstream << be supported on T. - std::stringstream out; - out << v.first; - input = out.str(); - - // Return empty error string (success) - return std::string(); - } - } - - // If you reach this point, the result was not found - return input + " not in {...}"; - }; } - /// You can pass in as many filter functions as you like, they nest + /// You can pass in as many filter functions as you like, they nest (string only currently) template - Mapping(T set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) - : Mapping(std::move(set), + IsMember(T set, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) + : IsMember(std::move(set), [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, other...) {} }; diff --git a/tests/SetTest.cpp b/tests/SetTest.cpp index 9f6dd17c8..5d2db7a50 100644 --- a/tests/SetTest.cpp +++ b/tests/SetTest.cpp @@ -10,13 +10,13 @@ static_assert(CLI::is_copyable_ptr>::value == true, static_assert(CLI::is_copyable_ptr::value == true, "is_copyable_ptr should work on pointers"); static_assert(CLI::is_copyable_ptr::value == false, "is_copyable_ptr should work on non-pointers"); -static_assert(CLI::has_mapped_key>::value == false, "Should not have keys"); -static_assert(CLI::has_mapped_key>::value == true, "Should have keys"); +static_assert(CLI::detail::key_map_adaptor>::value == false, "Should not have keys"); +static_assert(CLI::detail::key_map_adaptor>::value == true, "Should have keys"); TEST_F(TApp, SimpleMaps) { int value; - std::map map = {{1, "one"}, {2, "two"}}; - auto opt = app.add_option("-s,--set", value)->check(CLI::Mapping(map)); + std::map map = {{1, "one"}, {2, "two"}}; + auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember(map)); args = {"-s", "one"}; run(); EXPECT_EQ(1u, app.count("-s")); From ded999bf9c91a63a0e9f899a6bba9dc4fcd5b6a8 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Tue, 19 Feb 2019 23:24:10 +0100 Subject: [PATCH 3/6] Adding example, better Val combs, and cleanup --- examples/CMakeLists.txt | 2 +- examples/enum.cpp | 10 +++++++-- include/CLI/Validators.hpp | 28 +++++++++++++----------- tests/SetTest.cpp | 44 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+), 15 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ba303b241..01590c319 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -130,7 +130,7 @@ add_cli_exe(enum enum.cpp) add_test(NAME enum_pass COMMAND enum -l 1) add_test(NAME enum_fail COMMAND enum -l 4) set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION - "--level: 4 not in {0,1,2}") + "--level: 4 not in {High,Medium,Low} | 4 not in {0,1,2}") add_cli_exe(modhelp modhelp.cpp) add_test(NAME modhelp COMMAND modhelp -a test -h) diff --git a/examples/enum.cpp b/examples/enum.cpp index 68c78f63c..88d5c134f 100644 --- a/examples/enum.cpp +++ b/examples/enum.cpp @@ -1,4 +1,5 @@ #include +#include #include enum class Level : int { High, Medium, Low }; @@ -15,12 +16,17 @@ std::ostream &operator<<(std::ostream &in, const Level &level) { return in << st int main(int argc, char **argv) { CLI::App app; + std::map map = {{Level::High, "High"}, {Level::Medium, "Medium"}, {Level::Low, "Low"}}; + Level level; + app.add_option("-l,--level", level, "Level settings") - ->check(CLI::IsMember({Level::High, Level::Medium, Level::Low})) - ->type_name("enum/Level in {High=0, Medium=1, Low=2}"); + ->required() + ->check(CLI::IsMember(map, CLI::ignore_case) | CLI::IsMember({Level::High, Level::Medium, Level::Low})); CLI11_PARSE(app, argc, argv); + std::cout << "Enum received: " << level << std::endl; + return 0; } diff --git a/include/CLI/Validators.hpp b/include/CLI/Validators.hpp index 7bcfbcc06..3c4a29e88 100644 --- a/include/CLI/Validators.hpp +++ b/include/CLI/Validators.hpp @@ -57,18 +57,20 @@ class Validator { return func(value); }; - /// Combining validators is a new validator + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. Validator operator&(const Validator &other) const { Validator newval; newval.tname = (tname == other.tname ? tname : ""); + newval.tname_function = tname_function; // Give references (will make a copy in lambda function) const std::function &f1 = func; const std::function &f2 = other.func; - newval.func = [f1, f2](std::string &filename) { - std::string s1 = f1(filename); - std::string s2 = f2(filename); + newval.func = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); if(!s1.empty() && !s2.empty()) return s1 + " & " + s2; else @@ -77,22 +79,24 @@ class Validator { return newval; } - /// Combining validators is a new validator + /// Combining validators is a new validator. Type comes from left validator if function, otherwise only set if the + /// same. Validator operator|(const Validator &other) const { Validator newval; newval.tname = (tname == other.tname ? tname : ""); + newval.tname_function = tname_function; // Give references (will make a copy in lambda function) - const std::function &f1 = func; - const std::function &f2 = other.func; + const std::function &f1 = func; + const std::function &f2 = other.func; - newval.func = [f1, f2](std::string &filename) { - std::string s1 = f1(filename); - std::string s2 = f2(filename); + newval.func = [f1, f2](std::string &input) { + std::string s1 = f1(input); + std::string s2 = f2(input); if(s1.empty() || s2.empty()) return std::string(); else - return s1 + " & " + s2; + return s1 + " | " + s2; }; return newval; } @@ -308,7 +312,7 @@ class IsMember : public Validator { // This is the type name for help, it will take the current version of the set contents tname_function = [set]() { std::stringstream out; - out << detail::type_name() << " in {"; + out << "{"; int i = 0; // I don't like counters like this for(const auto &v : detail::smart_deref(set)) out << (i++ == 0 ? "" : ",") << detail::key_map_adaptor::second(v); diff --git a/tests/SetTest.cpp b/tests/SetTest.cpp index 5d2db7a50..fe3c15491 100644 --- a/tests/SetTest.cpp +++ b/tests/SetTest.cpp @@ -25,6 +25,50 @@ TEST_F(TApp, SimpleMaps) { EXPECT_EQ(value, 1); } +enum SimpleEnum { SE_one = 1, SE_two = 2 }; + +std::istream &operator>>(std::istream &in, SimpleEnum &e) { + int i; + in >> i; + e = static_cast(i); + return in; +} + +TEST_F(TApp, EnumMap) { + SimpleEnum value; + std::map map = {{SE_one, "one"}, {SE_two, "two"}}; + auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember(map)); + args = {"-s", "one"}; + run(); + EXPECT_EQ(1u, app.count("-s")); + EXPECT_EQ(1u, app.count("--set")); + EXPECT_EQ(1u, opt->count()); + EXPECT_EQ(value, SE_one); +} + +enum class SimpleEnumC { one = 1, two = 2 }; + +std::istream &operator>>(std::istream &in, SimpleEnumC &e) { + int i; + in >> i; + e = static_cast(i); + return in; +} + +std::ostream &operator<<(std::ostream &in, const SimpleEnumC &e) { return in << static_cast(e); } + +TEST_F(TApp, EnumCMap) { + SimpleEnumC value; + std::map map = {{SimpleEnumC::one, "one"}, {SimpleEnumC::two, "two"}}; + auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember(map)); + args = {"-s", "one"}; + run(); + EXPECT_EQ(1u, app.count("-s")); + EXPECT_EQ(1u, app.count("--set")); + EXPECT_EQ(1u, opt->count()); + EXPECT_EQ(value, SimpleEnumC::one); +} + TEST_F(TApp, SimpleSets) { std::string value; auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember{std::set({"one", "two", "three"})}); From a2580b565bf3fccb112441af1fc835b0a868d8c1 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Wed, 20 Feb 2019 09:43:01 +0100 Subject: [PATCH 4/6] Reversing order of map, adding pair support --- examples/enum.cpp | 2 +- include/CLI/TypeTools.hpp | 24 ++++++++++++++---------- include/CLI/Validators.hpp | 18 +++++++++--------- tests/SetTest.cpp | 11 ++++++----- 4 files changed, 30 insertions(+), 25 deletions(-) diff --git a/examples/enum.cpp b/examples/enum.cpp index 88d5c134f..4c83763b4 100644 --- a/examples/enum.cpp +++ b/examples/enum.cpp @@ -16,7 +16,7 @@ std::ostream &operator<<(std::ostream &in, const Level &level) { return in << st int main(int argc, char **argv) { CLI::App app; - std::map map = {{Level::High, "High"}, {Level::Medium, "Medium"}, {Level::Low, "Low"}}; + std::map map = {{"High", Level::High}, {"Medium", Level::Medium}, {"Low", Level::Low}}; Level level; diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index be269bb09..6904eb2ae 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -84,28 +84,32 @@ template struct element_type { template struct element_value_type { using type = typename element_type::type::value_type; }; /// Adaptor for map-like structure: This just wraps a normal container in a few utilities that do almost nothing. -template struct key_map_adaptor : std::false_type { - using mapped_type = typename T::value_type; - using key_type = typename T::value_type; +template struct pair_adaptor : std::false_type { + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; /// Get the first value (really just the underlying value) - template static key_type first(Q &&value) { return value; } + template static first_type first(Q &&value) { return value; } /// Get the second value (really just the underlying value) - template static mapped_type second(Q &&value) { return value; } + template static second_type second(Q &&value) { return value; } }; /// Adaptor for map-like structure (true version, must have key_type and mapped_type). /// This wraps a mapped container in a few utilities access it in a general way. template -struct key_map_adaptor, void>> +struct pair_adaptor< + T, + conditional_t, void>> : std::true_type { - using mapped_type = typename T::mapped_type; - using key_type = typename T::key_type; + using value_type = typename T::value_type; + using first_type = typename std::remove_const::type; + using second_type = typename std::remove_const::type; /// Get the first value (really just the underlying value) - template static key_type first(Q &&value) { return value.first; } + template static first_type first(Q &&value) { return value.first; } /// Get the second value (really just the underlying value) - template static mapped_type second(Q &&value) { return value.second; } + template static second_type second(Q &&value) { return value.second; } }; // Type name print diff --git a/include/CLI/Validators.hpp b/include/CLI/Validators.hpp index 3c4a29e88..d879a3e5f 100644 --- a/include/CLI/Validators.hpp +++ b/include/CLI/Validators.hpp @@ -299,9 +299,9 @@ class IsMember : public Validator { template explicit IsMember(T set, F filter_function) { // Get the type of the contained item - requires a container have ::value_type - // if the type does not have mapped_type an key_type, these are both value_type - using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed - using item_t = typename detail::key_map_adaptor::mapped_type; // Is value_type if not a map + // if the type does not have first_type and second_type, these are both value_type + using element_t = typename detail::element_type::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor::first_type; // Is value_type if not a map using local_item_t = typename IsMemberType::type; // This will convert bad types to good ones // (const char * to std::string) @@ -315,7 +315,7 @@ class IsMember : public Validator { out << "{"; int i = 0; // I don't like counters like this for(const auto &v : detail::smart_deref(set)) - out << (i++ == 0 ? "" : ",") << detail::key_map_adaptor::second(v); + out << (i++ == 0 ? "" : ",") << detail::pair_adaptor::first(v); out << "}"; return out.str(); }; @@ -324,7 +324,7 @@ class IsMember : public Validator { // It stores a copy of the set pointer-like, so shared_ptr will stay alive func = [set, filter_fn](std::string &input) { for(const auto &v : detail::smart_deref(set)) { - local_item_t a = detail::key_map_adaptor::second(v); + local_item_t a = detail::pair_adaptor::first(v); local_item_t b; if(!detail::lexical_cast(input, b)) throw ValidationError(input); // name is added later @@ -338,10 +338,10 @@ class IsMember : public Validator { if(a == b) { // Make sure the version in the input string is identical to the one in the set // Requires std::stringstream << be supported on T. - // If this is a map, ouptut the map instead. - if(filter_fn || detail::key_map_adaptor::value) { + // If this is a map, output the map instead. + if(filter_fn || detail::pair_adaptor::value) { std::stringstream out; - out << detail::key_map_adaptor::first(v); + out << detail::pair_adaptor::second(v); input = out.str(); } @@ -355,7 +355,7 @@ class IsMember : public Validator { out << input << " not in {"; int i = 0; // I still don't like counters like this for(const auto &v : detail::smart_deref(set)) - out << (i++ == 0 ? "" : ",") << detail::key_map_adaptor::second(v); + out << (i++ == 0 ? "" : ",") << detail::pair_adaptor::first(v); out << "}"; return out.str(); }; diff --git a/tests/SetTest.cpp b/tests/SetTest.cpp index fe3c15491..7dca267f9 100644 --- a/tests/SetTest.cpp +++ b/tests/SetTest.cpp @@ -10,12 +10,13 @@ static_assert(CLI::is_copyable_ptr>::value == true, static_assert(CLI::is_copyable_ptr::value == true, "is_copyable_ptr should work on pointers"); static_assert(CLI::is_copyable_ptr::value == false, "is_copyable_ptr should work on non-pointers"); -static_assert(CLI::detail::key_map_adaptor>::value == false, "Should not have keys"); -static_assert(CLI::detail::key_map_adaptor>::value == true, "Should have keys"); +static_assert(CLI::detail::pair_adaptor>::value == false, "Should not have pairs"); +static_assert(CLI::detail::pair_adaptor>::value == true, "Should have pairs"); +static_assert(CLI::detail::pair_adaptor>>::value == true, "Should have pairs"); TEST_F(TApp, SimpleMaps) { int value; - std::map map = {{1, "one"}, {2, "two"}}; + std::map map = {{"one", 1}, {"two", 2}}; auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember(map)); args = {"-s", "one"}; run(); @@ -36,7 +37,7 @@ std::istream &operator>>(std::istream &in, SimpleEnum &e) { TEST_F(TApp, EnumMap) { SimpleEnum value; - std::map map = {{SE_one, "one"}, {SE_two, "two"}}; + std::map map = {{"one", SE_one}, {"two", SE_two}}; auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember(map)); args = {"-s", "one"}; run(); @@ -59,7 +60,7 @@ std::ostream &operator<<(std::ostream &in, const SimpleEnumC &e) { return in << TEST_F(TApp, EnumCMap) { SimpleEnumC value; - std::map map = {{SimpleEnumC::one, "one"}, {SimpleEnumC::two, "two"}}; + std::map map = {{"one", SimpleEnumC::one}, {"two", SimpleEnumC::two}}; auto opt = app.add_option("-s,--set", value)->check(CLI::IsMember(map)); args = {"-s", "one"}; run(); From 67c27abab0baa5c4cc2d99c301f5b598de5e77f8 Mon Sep 17 00:00:00 2001 From: Henry Fredrick Schreiner Date: Wed, 20 Feb 2019 09:49:40 +0100 Subject: [PATCH 5/6] Check/Transform suppport for Validators --- include/CLI/App.hpp | 48 +++++++++++++++++++++--------------------- include/CLI/Option.hpp | 17 ++++++++++++++- tests/SetTest.cpp | 29 +++++++++++++------------ 3 files changed, 55 insertions(+), 39 deletions(-) diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 6ab804e89..d1aee0e98 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -695,32 +695,32 @@ class App { } /// Add set of options, string only, ignore case (no default, static set) DEPRECATED - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case)) instead") + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case)) instead") Option *add_set_ignore_case(std::string option_name, std::string &member, ///< The selected member of the set std::set options, ///< The set of possibilities std::string description = "") { Option *opt = add_option(option_name, member, std::move(description)); - opt->check(IsMember{options, CLI::ignore_case}); + opt->transform(IsMember{options, CLI::ignore_case}); return opt; } /// Add set of options, string only, ignore case (no default, set can be changed afterwards - do not destroy the /// set) DEPRECATED - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case)) with a (shared) pointer instead") + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case)) with a (shared) pointer instead") Option *add_mutable_set_ignore_case(std::string option_name, std::string &member, ///< The selected member of the set const std::set &options, ///< The set of possibilities std::string description = "") { Option *opt = add_option(option_name, member, std::move(description)); - opt->check(IsMember{&options, CLI::ignore_case}); + opt->transform(IsMember{&options, CLI::ignore_case}); return opt; } /// Add set of options, string only, ignore case (default, static set) DEPRECATED - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case)) instead") + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case)) instead") Option *add_set_ignore_case(std::string option_name, std::string &member, ///< The selected member of the set std::set options, ///< The set of possibilities @@ -728,13 +728,13 @@ class App { bool defaulted) { Option *opt = add_option(option_name, member, std::move(description), defaulted); - opt->check(IsMember{options, CLI::ignore_case}); + opt->transform(IsMember{options, CLI::ignore_case}); return opt; } /// Add set of options, string only, ignore case (default, set can be changed afterwards - do not destroy the set) /// DEPRECATED - CLI11_DEPRECATED("Use ->check(CLI::IsMember(...)) with a (shared) pointer instead") + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(...)) with a (shared) pointer instead") Option *add_mutable_set_ignore_case(std::string option_name, std::string &member, ///< The selected member of the set const std::set &options, ///< The set of possibilities @@ -742,37 +742,37 @@ class App { bool defaulted) { Option *opt = add_option(option_name, member, std::move(description), defaulted); - opt->check(IsMember{&options, CLI::ignore_case}); + opt->transform(IsMember{&options, CLI::ignore_case}); return opt; } /// Add set of options, string only, ignore underscore (no default, static set) DEPRECATED - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_underscore)) instead") + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) instead") Option *add_set_ignore_underscore(std::string option_name, std::string &member, ///< The selected member of the set std::set options, ///< The set of possibilities std::string description = "") { Option *opt = add_option(option_name, member, std::move(description)); - opt->check(IsMember{options, CLI::ignore_underscore}); + opt->transform(IsMember{options, CLI::ignore_underscore}); return opt; } /// Add set of options, string only, ignore underscore (no default, set can be changed afterwards - do not destroy /// the set) DEPRECATED - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead") + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead") Option *add_mutable_set_ignore_underscore(std::string option_name, std::string &member, ///< The selected member of the set const std::set &options, ///< The set of possibilities std::string description = "") { Option *opt = add_option(option_name, member, std::move(description)); - opt->check(IsMember{options, CLI::ignore_underscore}); + opt->transform(IsMember{options, CLI::ignore_underscore}); return opt; } /// Add set of options, string only, ignore underscore (default, static set) DEPRECATED - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_underscore)) instead") + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) instead") Option *add_set_ignore_underscore(std::string option_name, std::string &member, ///< The selected member of the set std::set options, ///< The set of possibilities @@ -780,13 +780,13 @@ class App { bool defaulted) { Option *opt = add_option(option_name, member, std::move(description), defaulted); - opt->check(IsMember{options, CLI::ignore_underscore}); + opt->transform(IsMember{options, CLI::ignore_underscore}); return opt; } /// Add set of options, string only, ignore underscore (default, set can be changed afterwards - do not destroy the /// set) DEPRECATED - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead") + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_underscore)) with a (shared) pointer instead") Option *add_mutable_set_ignore_underscore(std::string option_name, std::string &member, ///< The selected member of the set const std::set &options, ///< The set of possibilities @@ -794,38 +794,38 @@ class App { bool defaulted) { Option *opt = add_option(option_name, member, std::move(description), defaulted); - opt->check(IsMember{&options, CLI::ignore_underscore}); + opt->transform(IsMember{&options, CLI::ignore_underscore}); return opt; } /// Add set of options, string only, ignore underscore and case (no default, static set) DEPRECATED - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead") + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead") Option *add_set_ignore_case_underscore(std::string option_name, std::string &member, ///< The selected member of the set std::set options, ///< The set of possibilities std::string description = "") { Option *opt = add_option(option_name, member, std::move(description)); - opt->check(IsMember{options, CLI::ignore_underscore, CLI::ignore_case}); + opt->transform(IsMember{options, CLI::ignore_underscore, CLI::ignore_case}); return opt; } /// Add set of options, string only, ignore underscore and case (no default, set can be changed afterwards - do not /// destroy the set) DEPRECATED CLI11_DEPRECATED( - "Use ->check(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead") + "Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead") Option *add_mutable_set_ignore_case_underscore(std::string option_name, std::string &member, ///< The selected member of the set const std::set &options, ///< The set of possibilities std::string description = "") { Option *opt = add_option(option_name, member, std::move(description)); - opt->check(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case}); + opt->transform(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case}); return opt; } /// Add set of options, string only, ignore underscore and case (default, static set) DEPRECATED - CLI11_DEPRECATED("Use ->check(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead") + CLI11_DEPRECATED("Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) instead") Option *add_set_ignore_case_underscore(std::string option_name, std::string &member, ///< The selected member of the set std::set options, ///< The set of possibilities @@ -833,14 +833,14 @@ class App { bool defaulted) { Option *opt = add_option(option_name, member, std::move(description), defaulted); - opt->check(IsMember{options, CLI::ignore_underscore, CLI::ignore_case}); + opt->transform(IsMember{options, CLI::ignore_underscore, CLI::ignore_case}); return opt; } /// Add set of options, string only, ignore underscore and case (default, set can be changed afterwards - do not /// destroy the set) DEPRECATED CLI11_DEPRECATED( - "Use ->check(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead") + "Use ->transform(CLI::IsMember(..., CLI::ignore_case, CLI::ignore_underscore)) with a (shared) pointer instead") Option *add_mutable_set_ignore_case_underscore(std::string option_name, std::string &member, ///< The selected member of the set const std::set &options, ///< The set of possibilities @@ -848,7 +848,7 @@ class App { bool defaulted) { Option *opt = add_option(option_name, member, std::move(description), defaulted); - opt->check(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case}); + opt->transform(IsMember{&options, CLI::ignore_underscore, CLI::ignore_case}); return opt; } diff --git a/include/CLI/Option.hpp b/include/CLI/Option.hpp index 4c57af83b..9a0a9f865 100644 --- a/include/CLI/Option.hpp +++ b/include/CLI/Option.hpp @@ -303,7 +303,12 @@ class Option : public OptionBase