From 5e51ba4d1f687955fd95b9819f2fdd3974fe7e6f Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 2 Jun 2020 13:51:17 -0500 Subject: [PATCH] DEPS: Update CLI11 to 1.9 (#6307) Updates CLI11 to 1.9. Doesn't look like any of the tests broke. * [x] I work here * [x] Ran existing tests --- dep/CLI11/CLI11.hpp | 4456 ++++++++++++++++++++++++++----------- dep/CLI11/README.md | 4 +- dep/CLI11/cgmanifest.json | 13 + 3 files changed, 3170 insertions(+), 1303 deletions(-) create mode 100644 dep/CLI11/cgmanifest.json diff --git a/dep/CLI11/CLI11.hpp b/dep/CLI11/CLI11.hpp index 5ba22d325..a7efa607c 100644 --- a/dep/CLI11/CLI11.hpp +++ b/dep/CLI11/CLI11.hpp @@ -1,11 +1,11 @@ #pragma once -// CLI11: Version 1.8.0 +// CLI11: Version 1.9.0 // Originally designed by Henry Schreiner // https://github.com/CLIUtils/CLI11 // // This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts -// from: v1.8.0 +// from: v1.9.0 // // From LICENSE: // @@ -39,14 +39,14 @@ #include #include -#include +#include #include #include #include #include #include -#include #include +#include #include #include #include @@ -55,8 +55,6 @@ #include #include #include -#include -#include #include #include #include @@ -65,9 +63,9 @@ // Verbatim copy from CLI/Version.hpp: #define CLI11_VERSION_MAJOR 1 -#define CLI11_VERSION_MINOR 8 +#define CLI11_VERSION_MINOR 9 #define CLI11_VERSION_PATCH 0 -#define CLI11_VERSION "1.8.0" +#define CLI11_VERSION "1.9.0" // Verbatim copy from CLI/Macros.hpp: @@ -104,106 +102,37 @@ #define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) #endif -// Verbatim copy from CLI/Optional.hpp: +// Verbatim copy from CLI/Validators.hpp: -// You can explicitly enable or disable support -// by defining to 1 or 0. Extra check here to ensure it's in the stdlib too. -// We nest the check for __has_include and it's usage -#ifndef CLI11_STD_OPTIONAL -#ifdef __has_include -#if defined(CLI11_CPP17) && __has_include() -#define CLI11_STD_OPTIONAL 1 +// C standard library +// Only needed for existence checking +#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM +#if __has_include() +// Filesystem cannot be used if targeting macOS < 10.15 +#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 +#define CLI11_HAS_FILESYSTEM 0 #else -#define CLI11_STD_OPTIONAL 0 -#endif +#include +#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 +#define CLI11_HAS_FILESYSTEM 1 #else -#define CLI11_STD_OPTIONAL 0 +#define CLI11_HAS_FILESYSTEM 0 +#endif +#endif #endif #endif -#ifndef CLI11_EXPERIMENTAL_OPTIONAL -#define CLI11_EXPERIMENTAL_OPTIONAL 0 -#endif - -#ifndef CLI11_BOOST_OPTIONAL -#define CLI11_BOOST_OPTIONAL 0 -#endif - -#if CLI11_BOOST_OPTIONAL -#include -#if BOOST_VERSION < 106100 -#error "This boost::optional version is not supported, use 1.61 or better" -#endif -#endif - -#if CLI11_STD_OPTIONAL -#include -#endif -#if CLI11_EXPERIMENTAL_OPTIONAL -#include -#endif -#if CLI11_BOOST_OPTIONAL -#include -#include +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include // NOLINT(build/include) +#else +#include +#include #endif // From CLI/Version.hpp: // From CLI/Macros.hpp: -// From CLI/Optional.hpp: - -namespace CLI -{ -#if CLI11_STD_OPTIONAL - template - std::istream& operator>>(std::istream& in, std::optional& val) - { - T v; - in >> v; - val = v; - return in; - } -#endif - -#if CLI11_EXPERIMENTAL_OPTIONAL - template - std::istream& operator>>(std::istream& in, std::experimental::optional& val) - { - T v; - in >> v; - val = v; - return in; - } -#endif - -#if CLI11_BOOST_OPTIONAL - template - std::istream& operator>>(std::istream& in, boost::optional& val) - { - T v; - in >> v; - val = v; - return in; - } -#endif - -// Export the best optional to the CLI namespace -#if CLI11_STD_OPTIONAL - using std::optional; -#elif CLI11_EXPERIMENTAL_OPTIONAL - using std::experimental::optional; -#elif CLI11_BOOST_OPTIONAL - using boost::optional; -#endif - -// This is true if any optional is found -#if CLI11_STD_OPTIONAL || CLI11_EXPERIMENTAL_OPTIONAL || CLI11_BOOST_OPTIONAL -#define CLI11_OPTIONAL 1 -#endif - -} // namespace CLI - // From CLI/StringTools.hpp: namespace CLI @@ -220,22 +149,16 @@ namespace CLI return in << static_cast::type>(item); } - /// input streaming for enumerations - template::value>::type> - std::istream& operator>>(std::istream& in, T& item) - { - typename std::underlying_type::type i; - in >> i; - item = static_cast(i); - return in; - } } // namespace enums /// Export to CLI namespace - using namespace enums; + using enums::operator<<; namespace detail { + /// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not + /// produce overflow for some expected uses + constexpr int expected_max_vector_size{ 1 << 29 }; // Based on http://stackoverflow.com/questions/236129/split-a-string-in-c /// Split a string by a delim inline std::vector split(const std::string& s, char delim) @@ -256,20 +179,6 @@ namespace CLI } return elems; } - /// simple utility to convert various types to a string - template - inline std::string as_string(const T& v) - { - std::ostringstream s; - s << v; - return s.str(); - } - // if the data type is already a string just forward it - template::value>::type> - inline auto as_string(T&& v) -> decltype(std::forward(v)) - { - return std::forward(v); - } /// Simple function to join a string template @@ -310,7 +219,7 @@ namespace CLI std::string rjoin(const T& v, std::string delim = ",") { std::ostringstream s; - for (size_t start = 0; start < v.size(); start++) + for (std::size_t start = 0; start < v.size(); start++) { if (start > 0) s << delim; @@ -367,6 +276,20 @@ namespace CLI return trim(s); } + /// remove quotes at the front and back of a string either '"' or '\'' + inline std::string& remove_quotes(std::string& str) + { + if (str.length() > 1 && (str.front() == '"' || str.front() == '\'')) + { + if (str.front() == str.back()) + { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; + } + /// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) inline std::string trim_copy(const std::string& str, const std::string& filter) { @@ -374,7 +297,7 @@ namespace CLI return trim(s, filter); } /// Print a two part "help" string - inline std::ostream& format_help(std::ostream& out, std::string name, std::string description, size_t wid) + inline std::ostream& format_help(std::ostream& out, std::string name, std::string description, std::size_t wid) { name = " " + name; out << std::setw(static_cast(wid)) << std::left << name; @@ -446,7 +369,7 @@ namespace CLI /// Find and replace a substring with another substring inline std::string find_and_replace(std::string str, std::string from, std::string to) { - size_t start_pos = 0; + std::size_t start_pos = 0; while ((start_pos = str.find(from, start_pos)) != std::string::npos) { @@ -521,7 +444,7 @@ namespace CLI template inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) { - size_t start_pos = 0; + std::size_t start_pos = 0; while ((start_pos = str.find(trigger, start_pos)) != std::string::npos) { start_pos = modify(str, start_pos); @@ -531,10 +454,12 @@ namespace CLI /// Split a string '"one two" "three"' into 'one two', 'three' /// Quote characters can be ` ' or " - inline std::vector split_up(std::string str) + inline std::vector split_up(std::string str, char delimiter = '\0') { const std::string delims("\'\"`"); - auto find_ws = [](char ch) { return std::isspace(ch, std::locale()); }; + auto find_ws = [delimiter](char ch) { + return (delimiter == '\0') ? (std::isspace(ch, std::locale()) != 0) : (ch == delimiter); + }; trim(str); std::vector output; @@ -569,7 +494,7 @@ namespace CLI { std::string value = std::string(str.begin(), it); output.push_back(value); - str = std::string(it, str.end()); + str = std::string(it + 1, str.end()); } else { @@ -592,7 +517,7 @@ namespace CLI /// at the start of the first line). `"; "` would be for ini files /// /// Can't use Regex, or this would be a subs. - inline std::string fix_newlines(std::string leader, std::string input) + inline std::string fix_newlines(const std::string& leader, std::string input) { std::string::size_type n = 0; while (n != std::string::npos && n < input.size()) @@ -611,7 +536,7 @@ namespace CLI /// then modifies the string to replace the equality with a space. This is needed /// to allow the split up function to work properly and is intended to be used with the find_and_modify function /// the return value is the offset+1 which is required by the find_and_modify function. - inline size_t escape_detect(std::string& str, size_t offset) + inline std::size_t escape_detect(std::string& str, std::size_t offset) { auto next = str[offset + 1]; if ((next == '\"') || (next == '\'') || (next == '`')) @@ -876,35 +801,41 @@ public: CLI11_ERROR_DEF(ParseError, RequiredError) explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} - static RequiredError Subcommand(size_t min_subcom) + static RequiredError Subcommand(std::size_t min_subcom) { if (min_subcom == 1) + { return RequiredError("A subcommand"); - else - return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", - ExitCodes::RequiredError); + } + return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", + ExitCodes::RequiredError); } - static RequiredError Option(size_t min_option, size_t max_option, size_t used, const std::string& option_list) + static RequiredError + Option(std::size_t min_option, std::size_t max_option, std::size_t used, const std::string& option_list) { if ((min_option == 1) && (max_option == 1) && (used == 0)) return RequiredError("Exactly 1 option from [" + option_list + "]"); - else if ((min_option == 1) && (max_option == 1) && (used > 1)) + if ((min_option == 1) && (max_option == 1) && (used > 1)) + { return RequiredError("Exactly 1 option from [" + option_list + "] is required and " + std::to_string(used) + " were given", ExitCodes::RequiredError); - else if ((min_option == 1) && (used == 0)) + } + if ((min_option == 1) && (used == 0)) return RequiredError("At least 1 option from [" + option_list + "]"); - else if (used < min_option) + if (used < min_option) + { return RequiredError("Requires at least " + std::to_string(min_option) + " options used and only " + std::to_string(used) + "were given from [" + option_list + "]", ExitCodes::RequiredError); - else if (max_option == 1) + } + if (max_option == 1) return RequiredError("Requires at most 1 options be given from [" + option_list + "]", ExitCodes::RequiredError); - else - return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " + - std::to_string(used) + "were given from [" + option_list + "]", - ExitCodes::RequiredError); + + return RequiredError("Requires at most " + std::to_string(max_option) + " options be used and " + + std::to_string(used) + "were given from [" + option_list + "]", + ExitCodes::RequiredError); } }; @@ -913,16 +844,22 @@ public: { CLI11_ERROR_DEF(ParseError, ArgumentMismatch) CLI11_ERROR_SIMPLE(ArgumentMismatch) - ArgumentMismatch(std::string name, int expected, size_t recieved) : + ArgumentMismatch(std::string name, int expected, std::size_t recieved) : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + ", got " + std::to_string(recieved)) : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + ", got " + std::to_string(recieved)), ExitCodes::ArgumentMismatch) {} - static ArgumentMismatch AtLeast(std::string name, int num) + static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) { - return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required"); + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required but received " + + std::to_string(received)); + } + static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) + { + return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " + + std::to_string(received)); } static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { @@ -958,6 +895,11 @@ public: ExtrasError((args.size() > 1 ? "The following arguments were not expected: " : "The following argument was not expected: ") + detail::rjoin(args, " "), ExitCodes::ExtrasError) {} + ExtrasError(const std::string& name, std::vector args) : + ExtrasError(name, + (args.size() > 1 ? "The following arguments were not expected: " : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} }; /// Thrown when extra values are found in an INI file @@ -1061,6 +1003,12 @@ namespace CLI { }; + /// Check to see if something is a vector (true if actually a const vector) + template + struct is_vector> : std::true_type + { + }; + /// Check to see if something is bool (fail check by default) template struct is_bool : std::false_type @@ -1114,15 +1062,22 @@ namespace CLI namespace detail { - // These are utilities for IsMember + // These are utilities for IsMember and other transforming objects /// 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 + + /// not a pointer + template struct element_type { - using type = - typename std::conditional::value, typename std::pointer_traits::element_type, T>::type; + using type = T; + }; + + template + struct element_type::value>::type> + { + using type = typename std::pointer_traits::element_type; }; /// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of @@ -1180,20 +1135,105 @@ namespace CLI } }; - // Check for streamability +// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a Wnarrowing warning +// in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in +// brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a +// little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out. +// But regardless some versions of gcc generate a warning when they shouldn't from the following code so that should be +// suppressed +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnarrowing" +#endif + // check for constructibility from a specific type and copy assignable used in the parse detection + template + class is_direct_constructible + { + template + static auto test(int, std::true_type) -> decltype( +// NVCC warns about narrowing conversions here +#ifdef __CUDACC__ +#pragma diag_suppress 2361 +#endif + TT { std::declval() } +#ifdef __CUDACC__ +#pragma diag_default 2361 +#endif + , + std::is_move_assignable()); + + template + static auto test(int, std::false_type) -> std::false_type; + + template + static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0, typename std::is_constructible::type()))::value; + }; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + // Check for output streamability // Based on https://stackoverflow.com/questions/22758291/how-can-i-detect-if-a-type-can-be-streamed-to-an-stdostream - template - class is_streamable + template + class is_ostreamable { - template + template static auto test(int) -> decltype(std::declval() << std::declval(), std::true_type()); template static auto test(...) -> std::false_type; public: - static const bool value = decltype(test(0))::value; + static constexpr bool value = decltype(test(0))::value; + }; + + /// Check for input streamability + template + class is_istreamable + { + template + static auto test(int) -> decltype(std::declval() >> std::declval(), std::true_type()); + + template + static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; + }; + + /// Templated operation to get a value from a stream + template::value, detail::enabler> = detail::dummy> + bool from_stream(const std::string& istring, T& obj) + { + std::istringstream is; + is.str(istring); + is >> obj; + return !is.fail() && !is.rdbuf()->in_avail(); + } + + template::value, detail::enabler> = detail::dummy> + bool from_stream(const std::string& /*istring*/, T& /*obj*/) + { + return false; + } + + // Check for tuple like types, as in classes with a tuple_size type trait + template + class is_tuple_like + { + template + // static auto test(int) + // -> decltype(std::conditional<(std::tuple_size::value > 0), std::true_type, std::false_type>::type()); + static auto test(int) -> decltype(std::tuple_size::value, std::true_type{}); + template + static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test(0))::value; }; /// Convert an object to a string (directly forward if this can become a string) @@ -1205,8 +1245,8 @@ namespace CLI /// Convert an object to a string (streaming must be supported for that type) template::value && is_streamable::value, - detail::enabler> = detail::dummy> + enable_if_t::value && is_ostreamable::value, detail::enabler> = + detail::dummy> std::string to_string(T&& value) { std::stringstream stream; @@ -1216,13 +1256,270 @@ namespace CLI /// If conversion is not supported, return an empty string (streaming is not supported for that type) template::value && !is_streamable::value, + enable_if_t::value && !is_ostreamable::value && + !is_vector::type>::type>::value, detail::enabler> = detail::dummy> std::string to_string(T&&) { return std::string{}; } + /// convert a vector to a string + template::value && !is_ostreamable::value && + is_vector::type>::type>::value, + detail::enabler> = detail::dummy> + std::string to_string(T&& variable) + { + std::vector defaults; + defaults.reserve(variable.size()); + auto cval = variable.begin(); + auto end = variable.end(); + while (cval != end) + { + defaults.emplace_back(CLI::detail::to_string(*cval)); + ++cval; + } + return std::string("[" + detail::join(defaults) + "]"); + } + + /// special template overload + template::value, detail::enabler> = detail::dummy> + auto checked_to_string(T&& value) -> decltype(to_string(std::forward(value))) + { + return to_string(std::forward(value)); + } + + /// special template overload + template::value, detail::enabler> = detail::dummy> + std::string checked_to_string(T&&) + { + return std::string{}; + } + /// get a string as a convertible value for arithmetic types + template::value, detail::enabler> = detail::dummy> + std::string value_string(const T& value) + { + return std::to_string(value); + } + /// get a string as a convertible value for enumerations + template::value, detail::enabler> = detail::dummy> + std::string value_string(const T& value) + { + return std::to_string(static_cast::type>(value)); + } + /// for other types just use the regular to_string function + template::value && !std::is_arithmetic::value, detail::enabler> = detail::dummy> + auto value_string(const T& value) -> decltype(to_string(value)) + { + return to_string(value); + } + + /// This will only trigger for actual void type + template + struct type_count + { + static const int value{ 0 }; + }; + + /// Set of overloads to get the type size of an object + template + struct type_count::value>::type> + { + static constexpr int value{ std::tuple_size::value }; + }; + /// Type size for regular object types that do not look like a tuple + template + struct type_count< + T, + typename std::enable_if::value && !is_tuple_like::value && !std::is_void::value>::type> + { + static constexpr int value{ 1 }; + }; + + /// Type size of types that look like a vector + template + struct type_count::value>::type> + { + static constexpr int value{ is_vector::value ? expected_max_vector_size : type_count::value }; + }; + + /// This will only trigger for actual void type + template + struct expected_count + { + static const int value{ 0 }; + }; + + /// For most types the number of expected items is 1 + template + struct expected_count::value && !std::is_void::value>::type> + { + static constexpr int value{ 1 }; + }; + /// number of expected items in a vector + template + struct expected_count::value>::type> + { + static constexpr int value{ expected_max_vector_size }; + }; + + // Enumeration of the different supported categorizations of objects + enum class object_category : int + { + integral_value = 2, + unsigned_integral = 4, + enumeration = 6, + boolean_value = 8, + floating_point = 10, + number_constructible = 12, + double_constructible = 14, + integer_constructible = 16, + vector_value = 30, + tuple_value = 35, + // string assignable or greater used in a condition so anything string like must come last + string_assignable = 50, + string_constructible = 60, + other = 200, + + }; + + /// some type that is not otherwise recognized + template + struct classify_object + { + static constexpr object_category value{ object_category::other }; + }; + + /// Set of overloads to classify an object according to type + template + struct classify_object::value && std::is_signed::value && + !is_bool::value && !std::is_enum::value>::type> + { + static constexpr object_category value{ object_category::integral_value }; + }; + + /// Unsigned integers + template + struct classify_object< + T, + typename std::enable_if::value && std::is_unsigned::value && !is_bool::value>::type> + { + static constexpr object_category value{ object_category::unsigned_integral }; + }; + + /// Boolean values + template + struct classify_object::value>::type> + { + static constexpr object_category value{ object_category::boolean_value }; + }; + + /// Floats + template + struct classify_object::value>::type> + { + static constexpr object_category value{ object_category::floating_point }; + }; + + /// String and similar direct assignment + template + struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + std::is_assignable::value && !is_vector::value>::type> + { + static constexpr object_category value{ object_category::string_assignable }; + }; + + /// String and similar constructible and copy assignment + template + struct classify_object< + T, + typename std::enable_if::value && !std::is_integral::value && + !std::is_assignable::value && + std::is_constructible::value && !is_vector::value>::type> + { + static constexpr object_category value{ object_category::string_constructible }; + }; + + /// Enumerations + template + struct classify_object::value>::type> + { + static constexpr object_category value{ object_category::enumeration }; + }; + + /// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, + /// vectors, and enumerations + template + struct uncommon_type + { + using type = typename std::conditional::value && !std::is_integral::value && + !std::is_assignable::value && + !std::is_constructible::value && !is_vector::value && + !std::is_enum::value, + std::true_type, + std::false_type>::type; + static constexpr bool value = type::value; + }; + + /// Assignable from double or int + template + struct classify_object::value && type_count::value == 1 && + is_direct_constructible::value && + is_direct_constructible::value>::type> + { + static constexpr object_category value{ object_category::number_constructible }; + }; + + /// Assignable from int + template + struct classify_object::value && type_count::value == 1 && + !is_direct_constructible::value && + is_direct_constructible::value>::type> + { + static constexpr object_category value{ object_category::integer_constructible }; + }; + + /// Assignable from double + template + struct classify_object::value && type_count::value == 1 && + is_direct_constructible::value && + !is_direct_constructible::value>::type> + { + static constexpr object_category value{ object_category::double_constructible }; + }; + + /// Tuple type + template + struct classify_object::value >= 2 && !is_vector::value) || + (is_tuple_like::value && uncommon_type::value && + !is_direct_constructible::value && + !is_direct_constructible::value)>::type> + { + static constexpr object_category value{ object_category::tuple_value }; + }; + + /// Vector type + template + struct classify_object::value>::type> + { + static constexpr object_category value{ object_category::vector_value }; + }; + // Type name print /// Was going to be based on @@ -1230,48 +1527,100 @@ namespace CLI /// But this is cleaner and works better in this case template::value && std::is_signed::value, detail::enabler> = detail::dummy> + enable_if_t::value == object_category::integral_value || + classify_object::value == object_category::integer_constructible, + detail::enabler> = detail::dummy> constexpr const char* type_name() { return "INT"; } template::value && std::is_unsigned::value, detail::enabler> = detail::dummy> + enable_if_t::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> constexpr const char* type_name() { return "UINT"; } - template::value, detail::enabler> = detail::dummy> + template::value == object_category::floating_point || + classify_object::value == object_category::number_constructible || + classify_object::value == object_category::double_constructible, + detail::enabler> = detail::dummy> constexpr const char* type_name() { return "FLOAT"; } - /// This one should not be used, since vector types print the internal type - template::value, detail::enabler> = detail::dummy> - constexpr const char* type_name() - { - return "VECTOR"; - } /// Print name for enumeration types - template::value, detail::enabler> = detail::dummy> + template::value == object_category::enumeration, detail::enabler> = detail::dummy> constexpr const char* type_name() { return "ENUM"; } + /// Print name for enumeration types + template::value == object_category::boolean_value, detail::enabler> = detail::dummy> + constexpr const char* type_name() + { + return "BOOLEAN"; + } + /// Print for all other types template::value && !std::is_integral::value && !is_vector::value && - !std::is_enum::value, - detail::enabler> = detail::dummy> + enable_if_t::value >= object_category::string_assignable, detail::enabler> = detail::dummy> constexpr const char* type_name() { return "TEXT"; } + /// Print name for single element tuple types + template::value == object_category::tuple_value && type_count::value == 1, + detail::enabler> = detail::dummy> + inline std::string type_name() + { + return type_name::type>(); + } + + /// Empty string if the index > tuple size + template + inline typename std::enable_if::value, std::string>::type tuple_name() + { + return std::string{}; + } + + /// Recursively generate the tuple type name + template + inline typename std::enable_if < I::value, std::string>::type tuple_name() + { + std::string str = std::string(type_name::type>()) + ',' + tuple_name(); + if (str.back() == ',') + str.pop_back(); + return str; + } + + /// Print type name for tuples with 2 or more elements + template::value == object_category::tuple_value && type_count::value >= 2, + detail::enabler> = detail::dummy> + std::string type_name() + { + auto tname = std::string(1, '[') + tuple_name(); + tname.push_back(']'); + return tname; + } + + /// This one should not be used normally, since vector types print the internal type + template::value == object_category::vector_value, detail::enabler> = detail::dummy> + inline std::string type_name() + { + return type_name(); + } + // Lexical cast /// Convert a flag into an integer value typically binary flags @@ -1291,6 +1640,10 @@ namespace CLI int64_t ret; if (val.size() == 1) { + if (val[0] >= '1' && val[0] <= '9') + { + return (static_cast(val[0]) - '0'); + } switch (val[0]) { case '0': @@ -1299,22 +1652,11 @@ namespace CLI case '-': ret = -1; break; - case '1': case 't': case 'y': case '+': ret = 1; break; - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - ret = val[0] - '0'; - break; default: throw std::invalid_argument("unrecognized character"); } @@ -1336,18 +1678,16 @@ namespace CLI } /// Signed integers - template< - typename T, - enable_if_t::value && std::is_signed::value && !is_bool::value && !std::is_enum::value, - detail::enabler> = detail::dummy> - bool lexical_cast(std::string input, T& output) + template::value == object_category::integral_value, detail::enabler> = detail::dummy> + bool lexical_cast(const std::string& input, T& output) { try { - size_t n = 0; - long long output_ll = std::stoll(input, &n, 0); + std::size_t n = 0; + std::int64_t output_ll = std::stoll(input, &n, 0); output = static_cast(output_ll); - return n == input.size() && static_cast(output) == output_ll; + return n == input.size() && static_cast(output) == output_ll; } catch (const std::invalid_argument&) { @@ -1361,19 +1701,18 @@ namespace CLI /// Unsigned integers template::value && std::is_unsigned::value && !is_bool::value, detail::enabler> = - detail::dummy> - bool lexical_cast(std::string input, T& output) + enable_if_t::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> + bool lexical_cast(const std::string& input, T& output) { if (!input.empty() && input.front() == '-') return false; // std::stoull happily converts negative values to junk without any errors. try { - size_t n = 0; - unsigned long long output_ll = std::stoull(input, &n, 0); + std::size_t n = 0; + std::uint64_t output_ll = std::stoull(input, &n, 0); output = static_cast(output_ll); - return n == input.size() && static_cast(output) == output_ll; + return n == input.size() && static_cast(output) == output_ll; } catch (const std::invalid_argument&) { @@ -1386,8 +1725,9 @@ namespace CLI } /// Boolean values - template::value, detail::enabler> = detail::dummy> - bool lexical_cast(std::string input, T& output) + template::value == object_category::boolean_value, detail::enabler> = detail::dummy> + bool lexical_cast(const std::string& input, T& output) { try { @@ -1399,15 +1739,23 @@ namespace CLI { return false; } + catch (const std::out_of_range&) + { + // if the number is out of the range of a 64 bit value then it is still a number and for this purpose is still + // valid all we care about the sign + output = (input[0] != '-'); + return true; + } } /// Floats - template::value, detail::enabler> = detail::dummy> - bool lexical_cast(std::string input, T& output) + template::value == object_category::floating_point, detail::enabler> = detail::dummy> + bool lexical_cast(const std::string& input, T& output) { try { - size_t n = 0; + std::size_t n = 0; output = static_cast(std::stold(input, &n)); return n == input.size(); } @@ -1421,20 +1769,29 @@ namespace CLI } } - /// String and similar + /// String and similar direct assignment template::value && !std::is_integral::value && - std::is_assignable::value, - detail::enabler> = detail::dummy> - bool lexical_cast(std::string input, T& output) + enable_if_t::value == object_category::string_assignable, detail::enabler> = detail::dummy> + bool lexical_cast(const std::string& input, T& output) { output = input; return true; } + /// String and similar constructible and copy assignment + template< + typename T, + enable_if_t::value == object_category::string_constructible, detail::enabler> = detail::dummy> + bool lexical_cast(const std::string& input, T& output) + { + output = T(input); + return true; + } + /// Enumerations - template::value, detail::enabler> = detail::dummy> - bool lexical_cast(std::string input, T& output) + template::value == object_category::enumeration, detail::enabler> = detail::dummy> + bool lexical_cast(const std::string& input, T& output) { typename std::underlying_type::type val; bool retval = detail::lexical_cast(input, val); @@ -1446,22 +1803,324 @@ namespace CLI return true; } - /// Non-string parsable - template::value && !std::is_integral::value && - !std::is_assignable::value && !std::is_enum::value, - detail::enabler> = detail::dummy> - bool lexical_cast(std::string input, T& output) + /// Assignable from double or int + template< + typename T, + enable_if_t::value == object_category::number_constructible, detail::enabler> = detail::dummy> + bool lexical_cast(const std::string& input, T& output) { - std::istringstream is; - - is.str(input); - is >> output; - return !is.fail() && !is.rdbuf()->in_avail(); + int val; + if (lexical_cast(input, val)) + { + output = T(val); + return true; + } + else + { + double dval; + if (lexical_cast(input, dval)) + { + output = T{ dval }; + return true; + } + } + return from_stream(input, output); } + /// Assignable from int + template< + typename T, + enable_if_t::value == object_category::integer_constructible, detail::enabler> = detail::dummy> + bool lexical_cast(const std::string& input, T& output) + { + int val; + if (lexical_cast(input, val)) + { + output = T(val); + return true; + } + return from_stream(input, output); + } + + /// Assignable from double + template< + typename T, + enable_if_t::value == object_category::double_constructible, detail::enabler> = detail::dummy> + bool lexical_cast(const std::string& input, T& output) + { + double val; + if (lexical_cast(input, val)) + { + output = T{ val }; + return true; + } + return from_stream(input, output); + } + + /// Non-string parsable by a stream + template::value == object_category::other, detail::enabler> = detail::dummy> + bool lexical_cast(const std::string& input, T& output) + { + static_assert(is_istreamable::value, + "option object type must have a lexical cast overload or streaming input operator(>>) defined, if it " + "is convertible from another type use the add_option(...) with XC being the known type"); + return from_stream(input, output); + } + + /// Assign a value through lexical cast operations + template< + typename T, + typename XC, + enable_if_t::value && (classify_object::value == object_category::string_assignable || + classify_object::value == object_category::string_constructible), + detail::enabler> = detail::dummy> + bool lexical_assign(const std::string& input, T& output) + { + return lexical_cast(input, output); + } + + /// Assign a value through lexical cast operations + template::value && classify_object::value != object_category::string_assignable && + classify_object::value != object_category::string_constructible, + detail::enabler> = detail::dummy> + bool lexical_assign(const std::string& input, T& output) + { + if (input.empty()) + { + output = T{}; + return true; + } + return lexical_cast(input, output); + } + + /// Assign a value converted from a string in lexical cast to the output value directly + template< + typename T, + typename XC, + enable_if_t::value && std::is_assignable::value, detail::enabler> = detail::dummy> + bool lexical_assign(const std::string& input, T& output) + { + XC val{}; + bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; + if (parse_result) + { + output = val; + } + return parse_result; + } + + /// Assign a value from a lexical cast through constructing a value and move assigning it + template::value && !std::is_assignable::value && + std::is_move_assignable::value, + detail::enabler> = detail::dummy> + bool lexical_assign(const std::string& input, T& output) + { + XC val{}; + bool parse_result = input.empty() ? true : lexical_cast(input, val); + if (parse_result) + { + output = T(val); // use () form of constructor to allow some implicit conversions + } + return parse_result; + } + /// Lexical conversion if there is only one element + template< + typename T, + typename XC, + enable_if_t::value && !is_tuple_like::value && !is_vector::value && !is_vector::value, + detail::enabler> = detail::dummy> + bool lexical_conversion(const std::vector& strings, T& output) + { + return lexical_assign(strings[0], output); + } + + /// Lexical conversion if there is only one element but the conversion type is for two call a two element constructor + template::value == 1 && type_count::value == 2, detail::enabler> = detail::dummy> + bool lexical_conversion(const std::vector& strings, T& output) + { + typename std::tuple_element<0, XC>::type v1; + typename std::tuple_element<1, XC>::type v2; + bool retval = lexical_assign(strings[0], v1); + if (strings.size() > 1) + { + retval = retval && lexical_assign(strings[1], v2); + } + if (retval) + { + output = T{ v1, v2 }; + } + return retval; + } + + /// Lexical conversion of a vector types + template::value == expected_max_vector_size && + expected_count::value == expected_max_vector_size && type_count::value == 1, + detail::enabler> = detail::dummy> + bool lexical_conversion(const std::vector& strings, T& output) + { + output.clear(); + output.reserve(strings.size()); + for (const auto& elem : strings) + { + output.emplace_back(); + bool retval = lexical_assign(elem, output.back()); + if (!retval) + { + return false; + } + } + return (!output.empty()); + } + + /// Lexical conversion of a vector types with type size of two + template::value == expected_max_vector_size && + expected_count::value == expected_max_vector_size && type_count::value == 2, + detail::enabler> = detail::dummy> + bool lexical_conversion(const std::vector& strings, T& output) + { + output.clear(); + for (std::size_t ii = 0; ii < strings.size(); ii += 2) + { + typename std::tuple_element<0, typename XC::value_type>::type v1; + typename std::tuple_element<1, typename XC::value_type>::type v2; + bool retval = lexical_assign(strings[ii], v1); + if (strings.size() > ii + 1) + { + retval = retval && lexical_assign(strings[ii + 1], v2); + } + if (retval) + { + output.emplace_back(v1, v2); + } + else + { + return false; + } + } + return (!output.empty()); + } + + /// Conversion to a vector type using a particular single type as the conversion type + template::value == expected_max_vector_size) && (expected_count::value == 1) && + (type_count::value == 1), + detail::enabler> = detail::dummy> + bool lexical_conversion(const std::vector& strings, T& output) + { + bool retval = true; + output.clear(); + output.reserve(strings.size()); + for (const auto& elem : strings) + { + output.emplace_back(); + retval = retval && lexical_assign(elem, output.back()); + } + return (!output.empty()) && retval; + } + // This one is last since it can call other lexical_conversion functions + /// Lexical conversion if there is only one element but the conversion type is a vector + template::value && !is_vector::value && is_vector::value, detail::enabler> = + detail::dummy> + bool lexical_conversion(const std::vector& strings, T& output) + { + if (strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) + { + XC val; + auto retval = lexical_conversion(strings, val); + output = T{ val }; + return retval; + } + output = T{}; + return true; + } + + /// function template for converting tuples if the static Index is greater than the tuple size + template + inline typename std::enable_if= type_count::value, bool>::type tuple_conversion(const std::vector&, + T&) + { + return true; + } + /// Tuple conversion operation + template + inline typename std::enable_if < + I::value, bool>::type tuple_conversion(const std::vector& strings, T& output) + { + bool retval = true; + if (strings.size() > I) + { + retval = retval && lexical_assign::type, + typename std::conditional::value, + typename std::tuple_element::type, + XC>::type>(strings[I], std::get(output)); + } + retval = retval && tuple_conversion(strings, output); + return retval; + } + + /// Conversion for tuples + template::value, detail::enabler> = detail::dummy> + bool lexical_conversion(const std::vector& strings, T& output) + { + static_assert( + !is_tuple_like::value || type_count::value == type_count::value, + "if the conversion type is defined as a tuple it must be the same size as the type you are converting to"); + return tuple_conversion(strings, output); + } + + /// Lexical conversion of a vector types with type_size >2 + template::value == expected_max_vector_size && + expected_count::value == expected_max_vector_size && (type_count::value > 2), + detail::enabler> = detail::dummy> + bool lexical_conversion(const std::vector& strings, T& output) + { + bool retval = true; + output.clear(); + std::vector temp; + std::size_t ii = 0; + std::size_t icount = 0; + std::size_t xcm = type_count::value; + while (ii < strings.size()) + { + temp.push_back(strings[ii]); + ++ii; + ++icount; + if (icount == xcm || temp.back().empty()) + { + if (static_cast(xcm) == expected_max_vector_size) + { + temp.pop_back(); + } + output.emplace_back(); + retval = retval && lexical_conversion(temp, output.back()); + temp.clear(); + if (!retval) + { + return false; + } + icount = 0; + } + } + return retval; + } /// Sum a vector of flag representations - /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is by + /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is + /// by /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most /// common true and false strings then uses stoll to convert the rest for summing template split_names(std::string current) { std::vector output; - size_t val; + std::size_t val; while ((val = current.find(",")) != std::string::npos) { output.push_back(trim_copy(current.substr(0, val))); @@ -1613,8 +2270,10 @@ namespace CLI for (std::string name : input) { if (name.length() == 0) + { continue; - else if (name.length() > 1 && name[0] == '-' && name[1] != '-') + } + if (name.length() > 1 && name[0] == '-' && name[1] != '-') { if (name.length() == 2 && valid_first_char(name[1])) short_names.emplace_back(1, name[1]); @@ -1654,43 +2313,17 @@ namespace CLI { class App; - namespace detail - { - /// Comma separated join, adds quotes if needed - inline std::string ini_join(std::vector args) - { - std::ostringstream s; - size_t start = 0; - for (const auto& arg : args) - { - if (start++ > 0) - s << " "; - - auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace(ch, std::locale()); }); - if (it == arg.end()) - s << arg; - else if (arg.find_first_of('\"') == std::string::npos) - s << '\"' << arg << '\"'; - else - s << '\'' << arg << '\''; - } - - return s.str(); - } - - } // namespace detail - /// Holds values to load into Options struct ConfigItem { /// This is the list of parents - std::vector parents; + std::vector parents{}; /// This is the name - std::string name; + std::string name{}; /// Listing of inputs - std::vector inputs; + std::vector inputs{}; /// The list of parents and name joined by "." std::string fullname() const @@ -1705,7 +2338,7 @@ namespace CLI class Config { protected: - std::vector items; + std::vector items{}; public: /// Convert an app into a configuration @@ -1738,68 +2371,69 @@ namespace CLI virtual ~Config() = default; }; - /// This converter works with INI files - class ConfigINI : public Config + /// This converter works with INI/TOML files; to write proper TOML files use ConfigTOML + class ConfigBase : public Config { + protected: + /// the character used for comments + char commentChar = ';'; + /// the character used to start an array '\0' is a default to not use + char arrayStart = '\0'; + /// the character used to end an array '\0' is a default to not use + char arrayEnd = '\0'; + /// the character used to separate elements in an array + char arraySeparator = ' '; + /// the character used separate the name from the value + char valueDelimiter = '='; + public: - std::string to_config(const App*, bool default_also, bool write_description, std::string prefix) const override; + std::string + to_config(const App* /*app*/, bool default_also, bool write_description, std::string prefix) const override; - std::vector from_config(std::istream& input) const override + std::vector from_config(std::istream& input) const override; + /// Specify the configuration for comment characters + ConfigBase* comment(char cchar) { - std::string line; - std::string section = "default"; - - std::vector output; - - while (getline(input, line)) - { - std::vector items_buffer; - - detail::trim(line); - size_t len = line.length(); - if (len > 1 && line[0] == '[' && line[len - 1] == ']') - { - section = line.substr(1, len - 2); - } - else if (len > 0 && line[0] != ';') - { - output.emplace_back(); - ConfigItem& out = output.back(); - - // Find = in string, split and recombine - auto pos = line.find('='); - if (pos != std::string::npos) - { - out.name = detail::trim_copy(line.substr(0, pos)); - std::string item = detail::trim_copy(line.substr(pos + 1)); - items_buffer = detail::split_up(item); - } - else - { - out.name = detail::trim_copy(line); - items_buffer = { "ON" }; - } - - if (detail::to_lower(section) != "default") - { - out.parents = { section }; - } - - if (out.name.find('.') != std::string::npos) - { - std::vector plist = detail::split(out.name, '.'); - out.name = plist.back(); - plist.pop_back(); - out.parents.insert(out.parents.end(), plist.begin(), plist.end()); - } - - out.inputs.insert(std::end(out.inputs), std::begin(items_buffer), std::end(items_buffer)); - } - } - return output; + commentChar = cchar; + return this; + } + /// Specify the start and end characters for an array + ConfigBase* arrayBounds(char aStart, char aEnd) + { + arrayStart = aStart; + arrayEnd = aEnd; + return this; + } + /// Specify the delimiter character for an array + ConfigBase* arrayDelimiter(char aSep) + { + arraySeparator = aSep; + return this; + } + /// Specify the delimiter between a name and value + ConfigBase* valueSeparator(char vSep) + { + valueDelimiter = vSep; + return this; } }; + /// the default Config is the INI file format + using ConfigINI = ConfigBase; + + /// ConfigTOML generates a TOML compliant output + class ConfigTOML : public ConfigINI + { + public: + ConfigTOML() + { + commentChar = '#'; + arrayStart = '['; + arrayEnd = ']'; + arraySeparator = ','; + valueDelimiter = '='; + } + }; } // namespace CLI // From CLI/Validators.hpp: @@ -1824,11 +2458,13 @@ namespace CLI /// This is the description function, if empty the description_ will be used std::function desc_function_{ []() { return std::string{}; } }; - /// This it the base function that is to be called. + /// This is the base function that is to be called. /// Returns a string error message if validation fails. std::function func_{ [](std::string&) { return std::string{}; } }; /// The name for search purposes of the Validator - std::string name_; + std::string name_{}; + /// A Validator will only apply to an indexed value (-1 is all elements) + int application_index_ = -1; /// Enable for Validator to allow it to be disabled if need be bool active_{ true }; /// specify that a validator should not modify the input @@ -1839,7 +2475,7 @@ namespace CLI /// Construct a Validator with just the description string explicit Validator(std::string validator_desc) : desc_function_([validator_desc]() { return validator_desc; }) {} - // Construct Validator from basic information + /// Construct Validator from basic information Validator(std::function op, std::string validator_desc, std::string validator_name = "") : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(op)), @@ -1884,6 +2520,13 @@ namespace CLI desc_function_ = [validator_desc]() { return validator_desc; }; return *this; } + /// Specify the type string + Validator description(std::string validator_desc) const + { + Validator newval(*this); + newval.desc_function_ = [validator_desc]() { return validator_desc; }; + return newval; + } /// Generate type description information for the Validator std::string get_description() const { @@ -1899,6 +2542,13 @@ namespace CLI name_ = std::move(validator_name); return *this; } + /// Specify the type string + Validator name(std::string validator_name) const + { + Validator newval(*this); + newval.name_ = std::move(validator_name); + return newval; + } /// Get the name of the Validator const std::string& get_name() const { return name_; } /// Specify whether the Validator is active or not @@ -1907,6 +2557,13 @@ namespace CLI active_ = active_val; return *this; } + /// Specify whether the Validator is active or not + Validator active(bool active_val = true) const + { + Validator newval(*this); + newval.active_ = active_val; + return newval; + } /// Specify whether the Validator can be modifying or not Validator& non_modifying(bool no_modify = true) @@ -1914,7 +2571,21 @@ namespace CLI non_modifying_ = no_modify; return *this; } - + /// Specify the application index of a validator + Validator& application_index(int app_index) + { + application_index_ = app_index; + return *this; + }; + /// Specify the application index of a validator + Validator application_index(int app_index) const + { + Validator newval(*this); + newval.application_index_ = app_index; + return newval; + }; + /// Get the current value of the application index + int get_application_index() const { return application_index_; } /// Get a boolean if the validator is active bool get_active() const { return active_; } @@ -1943,6 +2614,7 @@ namespace CLI }; newval.active_ = (active_ & other.active_); + newval.application_index_ = application_index_; return newval; } @@ -1963,10 +2635,11 @@ namespace CLI std::string s2 = f2(input); if (s1.empty() || s2.empty()) return std::string(); - else - return std::string("(") + s1 + ") OR (" + s2 + ")"; + + return std::string("(") + s1 + ") OR (" + s2 + ")"; }; newval.active_ = (active_ & other.active_); + newval.application_index_ = application_index_; return newval; } @@ -1988,10 +2661,10 @@ namespace CLI { return std::string("check ") + dfunc1() + " succeeded improperly"; } - else - return std::string{}; + return std::string{}; }; newval.active_ = active_; + newval.application_index_ = application_index_; return newval; } @@ -2008,10 +2681,10 @@ namespace CLI { return f1 + f2; } - return std::string("(") + f1 + ")" + merger + "(" + f2 + ")"; + return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')'; }; } - }; + }; // namespace CLI /// Class wrapping some of the accessors of Validator class CustomValidator : public Validator @@ -2023,6 +2696,62 @@ namespace CLI // Therefore, this is in detail. namespace detail { + /// CLI enumeration of different file types + enum class path_type + { + nonexistant, + file, + directory + }; + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 + /// get the type of the path from a file name + inline path_type check_path(const char* file) noexcept + { + std::error_code ec; + auto stat = std::filesystem::status(file, ec); + if (ec) + { + return path_type::nonexistant; + } + switch (stat.type()) + { + case std::filesystem::file_type::none: + case std::filesystem::file_type::not_found: + return path_type::nonexistant; + case std::filesystem::file_type::directory: + return path_type::directory; + case std::filesystem::file_type::symlink: + case std::filesystem::file_type::block: + case std::filesystem::file_type::character: + case std::filesystem::file_type::fifo: + case std::filesystem::file_type::socket: + case std::filesystem::file_type::regular: + case std::filesystem::file_type::unknown: + default: + return path_type::file; + } + } +#else + /// get the type of the path from a file name + inline path_type check_path(const char* file) noexcept + { +#if defined(_MSC_VER) + struct __stat64 buffer; + if (_stat64(file, &buffer) == 0) + { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#else + struct stat buffer; + if (stat(file, &buffer) == 0) + { + return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; + } +#endif + return path_type::nonexistant; + } +#endif /// Check for an existing file (returns error message if check fails) class ExistingFileValidator : public Validator { @@ -2031,14 +2760,12 @@ namespace CLI Validator("FILE") { func_ = [](std::string& filename) { - struct stat buffer; - bool exist = stat(filename.c_str(), &buffer) == 0; - bool is_dir = (buffer.st_mode & S_IFDIR) != 0; - if (!exist) + auto path_result = check_path(filename.c_str()); + if (path_result == path_type::nonexistant) { return "File does not exist: " + filename; } - else if (is_dir) + if (path_result == path_type::directory) { return "File is actually a directory: " + filename; } @@ -2055,14 +2782,12 @@ namespace CLI Validator("DIR") { func_ = [](std::string& filename) { - struct stat buffer; - bool exist = stat(filename.c_str(), &buffer) == 0; - bool is_dir = (buffer.st_mode & S_IFDIR) != 0; - if (!exist) + auto path_result = check_path(filename.c_str()); + if (path_result == path_type::nonexistant) { return "Directory does not exist: " + filename; } - else if (!is_dir) + if (path_result == path_type::file) { return "Directory is actually a file: " + filename; } @@ -2079,9 +2804,8 @@ namespace CLI Validator("PATH(existing)") { func_ = [](std::string& filename) { - struct stat buffer; - bool const exist = stat(filename.c_str(), &buffer) == 0; - if (!exist) + auto path_result = check_path(filename.c_str()); + if (path_result == path_type::nonexistant) { return "Path does not exist: " + filename; } @@ -2098,9 +2822,8 @@ namespace CLI Validator("PATH(non-existing)") { func_ = [](std::string& filename) { - struct stat buffer; - bool exist = stat(filename.c_str(), &buffer) == 0; - if (exist) + auto path_result = check_path(filename.c_str()); + if (path_result != path_type::nonexistant) { return "Path already exists: " + filename; } @@ -2120,20 +2843,19 @@ namespace CLI auto result = CLI::detail::split(ip_addr, '.'); if (result.size() != 4) { - return "Invalid IPV4 address must have four parts " + ip_addr; + return std::string("Invalid IPV4 address must have four parts (") + ip_addr + ')'; } int num; - bool retval = true; for (const auto& var : result) { - retval &= detail::lexical_cast(var, num); + bool retval = detail::lexical_cast(var, num); if (!retval) { - return "Failed parsing number " + var; + return std::string("Failed parsing number (") + var + ')'; } if (num < 0 || num > 255) { - return "Each IP number must be between 0 and 255 " + var; + return std::string("Each IP number must be between 0 and 255 ") + var; } } return std::string(); @@ -2141,7 +2863,7 @@ namespace CLI } }; - /// Validate the argument is a number and greater than or equal to 0 + /// Validate the argument is a number and greater than 0 class PositiveNumber : public Validator { public: @@ -2149,21 +2871,42 @@ namespace CLI Validator("POSITIVE") { func_ = [](std::string& number_str) { - int number; + double number; if (!detail::lexical_cast(number_str, number)) { - return "Failed parsing number " + number_str; + return std::string("Failed parsing number: (") + number_str + ')'; + } + if (number <= 0) + { + return std::string("Number less or equal to 0: (") + number_str + ')'; + } + return std::string(); + }; + } + }; + /// Validate the argument is a number and greater than or equal to 0 + class NonNegativeNumber : public Validator + { + public: + NonNegativeNumber() : + Validator("NONNEGATIVE") + { + func_ = [](std::string& number_str) { + double number; + if (!detail::lexical_cast(number_str, number)) + { + return std::string("Failed parsing number: (") + number_str + ')'; } if (number < 0) { - return "Number less then 0 " + number_str; + return std::string("Number less than 0: (") + number_str + ')'; } return std::string(); }; } }; - /// Validate the argument is a number and greater than or equal to 0 + /// Validate the argument is a number class Number : public Validator { public: @@ -2174,7 +2917,7 @@ namespace CLI double number; if (!detail::lexical_cast(number_str, number)) { - return "Failed parsing as a number " + number_str; + return std::string("Failed parsing as a number (") + number_str + ')'; } return std::string(); }; @@ -2203,6 +2946,9 @@ namespace CLI /// Check for a positive number const detail::PositiveNumber PositiveNumber; + /// Check for a non-negative number + const detail::NonNegativeNumber NonNegativeNumber; + /// Check for a number const detail::Number Number; @@ -2225,7 +2971,8 @@ namespace CLI T val; bool converted = detail::lexical_cast(input, val); if ((!converted) || (val < min || val > max)) - return "Value " + input + " not in range " + std::to_string(min) + " to " + std::to_string(max); + return std::string("Value ") + input + " not in range " + std::to_string(min) + " to " + + std::to_string(max); return std::string(); }; @@ -2259,14 +3006,14 @@ namespace CLI bool converted = detail::lexical_cast(input, val); if (!converted) { - return "Value " + input + " could not be converted"; + return std::string("Value ") + input + " could not be converted"; } if (val < min) - input = detail::as_string(min); + input = detail::to_string(min); else if (val > max) - input = detail::as_string(max); + input = detail::to_string(max); - return std::string(); + return std::string{}; }; } @@ -2319,10 +3066,12 @@ namespace CLI out.append(detail::join( detail::smart_deref(map), [key_only](const iteration_type_t& v) { - auto res = detail::as_string(detail::pair_adaptor::first(v)); + std::string res{ detail::to_string(detail::pair_adaptor::first(v)) }; + if (!key_only) { - res += "->" + detail::as_string(detail::pair_adaptor::second(v)); + res.append("->"); + res += detail::to_string(detail::pair_adaptor::second(v)); } return res; }, @@ -2331,20 +3080,16 @@ namespace CLI return out; } - template - struct sfinae_true : std::true_type + template + struct has_find { - }; - /// Function to check for the existence of a member find function which presumably is more efficient than looping over - /// everything - template - static auto test_find(int) -> sfinae_true().find(std::declval()))>; - template - static auto test_find(long) -> std::false_type; + template + static auto test(int) -> decltype(std::declval().find(std::declval()), std::true_type()); + template + static auto test(...) -> decltype(std::false_type()); - template - struct has_find : decltype(test_find(0)) - { + static const auto value = decltype(test(0))::value; + using type = std::integral_constant; }; /// A search function @@ -2383,28 +3128,54 @@ namespace CLI // if we haven't found it do the longer linear search with all the element translations auto& setref = detail::smart_deref(set); auto it = std::find_if(std::begin(setref), std::end(setref), [&](decltype(*std::begin(setref)) v) { - V a = detail::pair_adaptor::first(v); + V a{ detail::pair_adaptor::first(v) }; a = filter_function(a); return (a == val); }); return { (it != std::end(setref)), it }; } + // the following suggestion was made by Nikita Ofitserov(@himikof) + // done in templates to prevent compiler warnings on negation of unsigned numbers + + /// Do a check for overflow on signed numbers + template + inline typename std::enable_if::value, T>::type overflowCheck(const T& a, const T& b) + { + if ((a > 0) == (b > 0)) + { + return ((std::numeric_limits::max)() / (std::abs)(a) < (std::abs)(b)); + } + else + { + return ((std::numeric_limits::min)() / (std::abs)(a) > -(std::abs)(b)); + } + } + /// Do a check for overflow on unsigned numbers + template + inline typename std::enable_if::value, T>::type overflowCheck(const T& a, const T& b) + { + return ((std::numeric_limits::max)() / a < b); + } + /// Performs a *= b; if it doesn't cause integer overflow. Returns false otherwise. template typename std::enable_if::value, bool>::type checked_multiply(T& a, T b) { - if (a == 0 || b == 0) + if (a == 0 || b == 0 || a == 1 || b == 1) { a *= b; return true; } - T c = a * b; - if (c / a != b) + if (a == (std::numeric_limits::min)() || b == (std::numeric_limits::min)()) { return false; } - a = c; + if (overflowCheck(a, b)) + { + return false; + } + a *= b; return true; } @@ -2430,7 +3201,7 @@ namespace CLI /// This allows in-place construction using an initializer list template - explicit IsMember(std::initializer_list values, Args&&... args) : + IsMember(std::initializer_list values, Args&&... args) : IsMember(std::vector(values), std::forward(args)...) { } @@ -2479,7 +3250,7 @@ namespace CLI // Make sure the version in the input string is identical to the one in the set if (filter_fn) { - input = detail::as_string(detail::pair_adaptor::first(*(res.second))); + input = detail::value_string(detail::pair_adaptor::first(*(res.second))); } // Return empty error string (success) @@ -2516,7 +3287,7 @@ namespace CLI /// This allows in-place construction template - explicit Transformer(std::initializer_list> values, Args&&... args) : + Transformer(std::initializer_list> values, Args&&... args) : Transformer(TransformPairs(values), std::forward(args)...) { } @@ -2562,7 +3333,7 @@ namespace CLI auto res = detail::search(mapping, b, filter_fn); if (res.first) { - input = detail::as_string(detail::pair_adaptor::second(*res.second)); + input = detail::value_string(detail::pair_adaptor::second(*res.second)); } return std::string{}; }; @@ -2587,7 +3358,7 @@ namespace CLI /// This allows in-place construction template - explicit CheckedTransformer(std::initializer_list> values, Args&&... args) : + CheckedTransformer(std::initializer_list> values, Args&&... args) : CheckedTransformer(TransformPairs(values), std::forward(args)...) { } @@ -2623,7 +3394,7 @@ namespace CLI out += detail::generate_map(detail::smart_deref(mapping)) + " OR {"; out += detail::join( detail::smart_deref(mapping), - [](const iteration_type_t& v) { return detail::as_string(detail::pair_adaptor::second(v)); }, + [](const iteration_type_t& v) { return detail::to_string(detail::pair_adaptor::second(v)); }, ","); out.push_back('}'); return out; @@ -2643,13 +3414,13 @@ namespace CLI auto res = detail::search(mapping, b, filter_fn); if (res.first) { - input = detail::as_string(detail::pair_adaptor::second(*res.second)); + input = detail::value_string(detail::pair_adaptor::second(*res.second)); return std::string{}; } } for (const auto& v : detail::smart_deref(mapping)) { - auto output_string = detail::as_string(detail::pair_adaptor::second(v)); + auto output_string = detail::value_string(detail::pair_adaptor::second(v)); if (output_string == input) { return std::string(); @@ -2738,7 +3509,7 @@ namespace CLI } std::string unit{ unit_begin, input.end() }; - input.resize(static_cast(std::distance(input.begin(), unit_begin))); + input.resize(static_cast(std::distance(input.begin(), unit_begin))); detail::trim(input); if (opts & UNIT_REQUIRED && unit.empty()) @@ -2753,7 +3524,8 @@ namespace CLI bool converted = detail::lexical_cast(input, num); if (!converted) { - throw ValidationError("Value " + input + " could not be converted to " + detail::type_name()); + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); } if (unit.empty()) @@ -2776,10 +3548,10 @@ namespace CLI bool ok = detail::checked_multiply(num, it->second); if (!ok) { - throw ValidationError(detail::as_string(num) + " multiplied by " + unit + + throw ValidationError(detail::to_string(num) + " multiplied by " + unit + " factor would cause number overflow. Use smaller value."); } - input = detail::as_string(num); + input = detail::to_string(num); return {}; }; @@ -2812,7 +3584,8 @@ namespace CLI auto s = detail::to_lower(kv.first); if (lower_mapping.count(s)) { - throw ValidationError("Several matching lowercase unit representations are found: " + s); + throw ValidationError(std::string("Several matching lowercase unit representations are found: ") + + s); } lower_mapping[detail::to_lower(kv.first)] = kv.second; } @@ -2924,7 +3697,7 @@ namespace CLI std::pair vals; trim(commandline); auto esp = commandline.find_first_of(' ', 1); - while (!ExistingFile(commandline.substr(0, esp)).empty()) + while (detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) { esp = commandline.find_first_of(' ', esp + 1); if (esp == std::string::npos) @@ -2978,11 +3751,11 @@ namespace CLI ///@{ /// The width of the first column - size_t column_width_{ 30 }; + std::size_t column_width_{ 30 }; /// @brief The required help printout labels (user changeable) /// Values are Needs, Excludes, etc. - std::map labels_; + std::map labels_{}; ///@} /// @name Basic @@ -3007,7 +3780,7 @@ namespace CLI void label(std::string key, std::string val) { labels_[key] = val; } /// Set the column width - void column_width(size_t val) { column_width_ = val; } + void column_width(std::size_t val) { column_width_ = val; } ///@} /// @name Getters @@ -3023,7 +3796,7 @@ namespace CLI } /// Get the current column width - size_t get_column_width() const { return column_width_; } + std::size_t get_column_width() const { return column_width_; } ///@} }; @@ -3092,7 +3865,7 @@ namespace CLI virtual std::string make_usage(const App* app, std::string name) const; /// This puts everything together - std::string make_help(const App*, std::string, AppFormatMode) const override; + std::string make_help(const App* /*app*/, std::string, AppFormatMode) const override; ///@} /// @name Options @@ -3129,19 +3902,21 @@ namespace CLI namespace CLI { using results_t = std::vector; - using callback_t = std::function; + /// callback function definition + using callback_t = std::function; class Option; class App; using Option_p = std::unique_ptr