diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a01b68ce..ee991712 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,10 @@ All changes to this project will be documented in this file. +## [11.2.10] - 2021-07-27 + +(ws) bump CLI command line parsing library from 1.8 to 2.0 + ## [11.2.9] - 2021-06-08 (ws) ws connect has a -g option to gzip decompress messages for API such as the websocket Huobi Global. diff --git a/ixwebsocket/IXWebSocketVersion.h b/ixwebsocket/IXWebSocketVersion.h index 62e92a89..0e7cb045 100644 --- a/ixwebsocket/IXWebSocketVersion.h +++ b/ixwebsocket/IXWebSocketVersion.h @@ -6,4 +6,4 @@ #pragma once -#define IX_WEBSOCKET_VERSION "11.2.9" +#define IX_WEBSOCKET_VERSION "11.2.10" diff --git a/makefile.dev b/makefile.dev index b2f3267f..e4c57c37 100644 --- a/makefile.dev +++ b/makefile.dev @@ -22,7 +22,7 @@ install: brew # Default rule does not use python as that requires first time users to have Python3 installed # brew: - mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=ON -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; ninja install) + mkdir -p build && (cd build ; cmake -GNinja -DCMAKE_UNITY_BUILD=OFF -DCMAKE_INSTALL_MESSAGE=LAZY -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_BUILD_TYPE=Debug -DUSE_TLS=1 -DUSE_WS=1 -DUSE_TEST=1 .. ; ninja install) # Docker default target. We've had problems with OpenSSL and TLS 1.3 (on the # server side ?) and I can't work-around it easily, so we're using mbedtls on diff --git a/third_party/cli11/CLI11.hpp b/third_party/cli11/CLI11.hpp index 5c880875..ce1d06e3 100644 --- a/third_party/cli11/CLI11.hpp +++ b/third_party/cli11/CLI11.hpp @@ -1,20 +1,16 @@ -#pragma once - -// CLI11: Version 1.8.0 +// CLI11: Version 2.0.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: v2.0.0 (added include gaurd) // -// From LICENSE: -// -// CLI11 1.8 Copyright (c) 2017-2019 University of Cincinnati, developed by Henry +// CLI11 2.0.0 Copyright (c) 2017-2020 University of Cincinnati, developed by Henry // Schreiner under NSF AWARD 1414736. All rights reserved. -// +// // Redistribution and use in source and binary forms of CLI11, with or without // modification, are permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright notice, @@ -23,7 +19,7 @@ // 3. Neither the name of the copyright holder nor the names of its contributors // may be used to endorse or promote products derived from this software without // specific prior written permission. -// +// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -35,50 +31,42 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#pragma once // Standard combined includes: - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include -// Verbatim copy from CLI/Version.hpp: - - -#define CLI11_VERSION_MAJOR 1 -#define CLI11_VERSION_MINOR 8 +#define CLI11_VERSION_MAJOR 2 +#define CLI11_VERSION_MINOR 0 #define CLI11_VERSION_PATCH 0 -#define CLI11_VERSION "1.8.0" +#define CLI11_VERSION "2.0.0" -// Verbatim copy from CLI/Macros.hpp: - - -// The following version macro is very similar to the one in PyBind11 +// The following version macro is very similar to the one in pybind11 #if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) #if __cplusplus >= 201402L #define CLI11_CPP14 @@ -114,109 +102,42 @@ -// Verbatim copy from CLI/Optional.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 +#include +#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 +#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9 +#define CLI11_HAS_FILESYSTEM 1 +#elif defined(__GLIBCXX__) +// if we are using gcc and Version <9 default to no filesystem +#define CLI11_HAS_FILESYSTEM 0 +#else +#define CLI11_HAS_FILESYSTEM 1 #endif #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 { /// Include the items in this namespace to get free conversion of enums to/from streams. /// (This is available inside CLI as well, so CLI11 will use this without a using statement). @@ -229,29 +150,23 @@ std::ostream &operator<<(std::ostream &in, const T &item) { 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 +} // 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) { std::vector elems; // Check to see if empty string, give consistent result - if(s.empty()) + if(s.empty()) { elems.emplace_back(); - else { + } else { std::stringstream ss; ss.str(s); std::string item; @@ -261,17 +176,6 @@ inline std::vector split(const std::string &s, char delim) { } 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 std::string join(const T &v, std::string delim = ",") { @@ -294,10 +198,14 @@ std::string join(const T &v, Callable func, std::string delim = ",") { std::ostringstream s; auto beg = std::begin(v); auto end = std::end(v); - if(beg != end) - s << func(*beg++); + auto loc = s.tellp(); while(beg != end) { - s << delim << func(*beg++); + auto nloc = s.tellp(); + if(nloc > loc) { + s << delim; + loc = nloc; + } + s << func(*beg++); } return s.str(); } @@ -305,7 +213,7 @@ std::string join(const T &v, Callable func, std::string delim = ",") { /// Join a string in reverse order template 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; s << v[v.size() - start - 1]; @@ -356,13 +264,24 @@ inline std::string trim_copy(const std::string &str) { 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) { std::string s = str; 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, const std::string &description, std::size_t wid) { name = " " + name; out << std::setw(static_cast(wid)) << std::left << name; if(!description.empty()) { @@ -379,6 +298,24 @@ inline std::ostream &format_help(std::ostream &out, std::string name, std::strin return out; } +/// Print subcommand aliases +inline std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid) { + if(!aliases.empty()) { + out << std::setw(static_cast(wid)) << " aliases: "; + bool front = true; + for(const auto &alias : aliases) { + if(!front) { + out << ", "; + } else { + front = false; + } + out << alias; + } + out << "\n"; + } + return out; +} + /// Verify the first character of an option template bool valid_first_char(T c) { return std::isalnum(c, std::locale()) || c == '_' || c == '?' || c == '@'; @@ -397,6 +334,12 @@ inline bool valid_name_string(const std::string &str) { return true; } +/// check if a string is a container segment separator (empty or "%%") +inline bool is_separator(const std::string &str) { + static const std::string sep("%%"); + return (str.empty() || str == sep); +} + /// Verify that str consists of letters only inline bool isalpha(const std::string &str) { return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); }); @@ -419,7 +362,7 @@ inline std::string remove_underscore(std::string str) { /// 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) { str.replace(start_pos, from.length(), to); @@ -471,8 +414,9 @@ inline std::ptrdiff_t find_member(std::string name, it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { return detail::remove_underscore(local_name) == name; }); - } else + } else { it = std::find(std::begin(names), std::end(names), name); + } return (it != std::end(names)) ? (it - std::begin(names)) : (-1); } @@ -480,7 +424,7 @@ inline std::ptrdiff_t find_member(std::string name, /// Find a trigger string and call a modify callable function that takes the current string and starting position of the /// trigger and returns the position in the string to search for the next trigger string 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); } @@ -489,10 +433,12 @@ template inline std::string find_and_modify(std::string str, /// 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; @@ -502,13 +448,18 @@ inline std::vector split_up(std::string str) { if(delims.find_first_of(str[0]) != std::string::npos) { keyChar = str[0]; auto end = str.find_first_of(keyChar, 1); - while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes + while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes end = str.find_first_of(keyChar, end + 1); embeddedQuote = true; } if(end != std::string::npos) { output.push_back(str.substr(1, end - 1)); - str = str.substr(end + 1); + if(end + 2 < str.size()) { + str = str.substr(end + 2); + } else { + str.clear(); + } + } else { output.push_back(str.substr(1)); str = ""; @@ -518,7 +469,7 @@ inline std::vector split_up(std::string str) { if(it != std::end(str)) { 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 { output.push_back(str); str = ""; @@ -538,7 +489,7 @@ inline std::vector split_up(std::string str) { /// 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()) { n = input.find('\n', n); @@ -554,13 +505,13 @@ inline std::string fix_newlines(std::string leader, std::string input) { /// 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 == '`')) { auto astart = str.find_last_of("-/ \"\'`", offset - 1); if(astart != std::string::npos) { if(str[astart] == ((str[offset] == '=') ? '-' : '/')) - str[offset] = ' '; // interpret this as a space so the split_up works properly + str[offset] = ' '; // interpret this as a space so the split_up works properly } } return offset + 1; @@ -578,13 +529,10 @@ inline std::string &add_quotes_if_needed(std::string &str) { return str; } -} // namespace detail +} // namespace detail -} // namespace CLI -// From CLI/Error.hpp: -namespace CLI { // Use one of these on all error classes. // These are temporary and are undef'd at the end of this file. @@ -726,19 +674,26 @@ class Success : public ParseError { }; /// -h or --help on command line -class CallForHelp : public ParseError { - CLI11_ERROR_DEF(ParseError, CallForHelp) +class CallForHelp : public Success { + CLI11_ERROR_DEF(Success, CallForHelp) CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} }; /// Usually something like --help-all on command line -class CallForAllHelp : public ParseError { - CLI11_ERROR_DEF(ParseError, CallForAllHelp) +class CallForAllHelp : public Success { + CLI11_ERROR_DEF(Success, CallForAllHelp) CallForAllHelp() : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} }; -/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. +/// -v or --version on command line +class CallForVersion : public Success { + CLI11_ERROR_DEF(Success, CallForVersion) + CallForVersion() + : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Does not output a diagnostic in CLI11_PARSE, but allows main() to return with a specific error code. class RuntimeError : public ParseError { CLI11_ERROR_DEF(ParseError, RuntimeError) explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} @@ -778,33 +733,36 @@ class ValidationError : public ParseError { class RequiredError : public ParseError { CLI11_ERROR_DEF(ParseError, RequiredError) explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} - static RequiredError Subcommand(size_t min_subcom) { - if(min_subcom == 1) + 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); } }; @@ -812,15 +770,20 @@ class RequiredError : public ParseError { class ArgumentMismatch : public ParseError { 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 received) : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + - ", got " + std::to_string(recieved)) + ", got " + std::to_string(received)) : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + - ", got " + std::to_string(recieved)), + ", got " + std::to_string(received)), ExitCodes::ArgumentMismatch) {} - static ArgumentMismatch AtLeast(std::string name, int num) { - return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required"); + static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) { + 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) { return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); @@ -852,6 +815,12 @@ class ExtrasError : public ParseError { : "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 @@ -892,11 +861,8 @@ class OptionNotFound : public Error { /// @} -} // namespace CLI -// From CLI/TypeTools.hpp: -namespace CLI { // Type tools @@ -908,7 +874,7 @@ enum class enabler {}; /// An instance to use in EnableIf constexpr enabler dummy = {}; -} // namespace detail +} // namespace detail /// A copy of enable_if_t from C++14, compatible with C++11. /// @@ -926,12 +892,6 @@ 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 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 {}; - -/// Check to see if something is a vector (true if actually a 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 {}; @@ -960,13 +920,16 @@ template <> struct IsMemberType { using type = std::string; }; 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 struct element_type { - using type = - typename std::conditional::value, typename std::pointer_traits::element_type, T>::type; + +/// not a pointer +template struct element_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 @@ -1010,28 +973,158 @@ struct pair_adaptor< } }; -// 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 +template class is_ostreamable { + 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; +}; + +/// Check for complex +template class is_complex { + template + static auto test(int) -> decltype(std::declval().real(), std::declval().imag(), 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 to see if an object is a mutable container (fail by default) +template struct is_mutable_container : std::false_type {}; + +/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and +/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed +/// from a std::string +template +struct is_mutable_container< + T, + conditional_t().end()), + decltype(std::declval().clear()), + decltype(std::declval().insert(std::declval().end())>(), + std::declval()))>, + void>> + : public conditional_t::value, std::false_type, std::true_type> {}; + +// check to see if an object is a mutable container (fail by default) +template struct is_readable_container : std::false_type {}; + +/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an end +/// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from +/// a std::string +template +struct is_readable_container< + T, + conditional_t().end()), decltype(std::declval().begin())>, void>> + : public std::true_type {}; + +// check to see if an object is a wrapper (fail by default) +template struct is_wrapper : std::false_type {}; + +// check if an object is a wrapper (it has a value_type defined) +template +struct is_wrapper, void>> : public std::true_type {}; + +// 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::type>::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) -template ::value, detail::enabler> = detail::dummy> +template ::value, detail::enabler> = detail::dummy> auto to_string(T &&value) -> decltype(std::forward(value)) { return std::forward(value); } +/// Construct a string from the object +template ::value && !std::is_convertible::value, + detail::enabler> = detail::dummy> +std::string to_string(const T &value) { + return std::string(value); +} + /// Convert an object to a string (streaming must be supported for that type) template ::value && is_streamable::value, + enable_if_t::value && !std::is_constructible::value && + is_ostreamable::value, detail::enabler> = detail::dummy> std::string to_string(T &&value) { std::stringstream stream; @@ -1041,12 +1134,380 @@ std::string to_string(T &&value) { /// 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_readable_container::type>::value, detail::enabler> = detail::dummy> std::string to_string(T &&) { return std::string{}; } +/// convert a readable container to a string +template ::value && !is_ostreamable::value && + is_readable_container::value, + detail::enabler> = detail::dummy> +std::string to_string(T &&variable) { + std::vector defaults; + 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); +} + +/// template to get the underlying value type if it exists or use a default +template struct wrapped_type { using type = def; }; + +/// Type size for regular object types that do not look like a tuple +template struct wrapped_type::value>::type> { + using type = typename T::value_type; +}; + +/// This will only trigger for actual void type +template struct type_count_base { static const int value{0}; }; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_base::value && !is_mutable_container::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// the base tuple size +template +struct type_count_base::value && !is_mutable_container::value>::type> { + static constexpr int value{std::tuple_size::value}; +}; + +/// Type count base for containers is the type_count_base of the individual element +template struct type_count_base::value>::type> { + static constexpr int value{type_count_base::value}; +}; + +/// Set of overloads to get the type size of an object + +/// forward declare the subtype_count structure +template struct subtype_count; + +/// forward declare the subtype_count_min structure +template struct subtype_count_min; + +/// This will only trigger for actual void type +template struct type_count { static const int value{0}; }; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count::value && !is_tuple_like::value && !is_complex::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count::value>::type> { + static constexpr int value{2}; +}; + +/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template struct type_count::value>::type> { + static constexpr int value{subtype_count::value}; +}; + +/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes) +template +struct type_count::value && !is_complex::value && !is_tuple_like::value && + !is_mutable_container::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size() { + return subtype_count::type>::value + tuple_type_size(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template struct type_count::value>::type> { + static constexpr int value{tuple_type_size()}; +}; + +/// definition of subtype count +template struct subtype_count { + static constexpr int value{is_mutable_container::value ? expected_max_vector_size : type_count::value}; +}; + +/// This will only trigger for actual void type +template struct type_count_min { static const int value{0}; }; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_min< + T, + typename std::enable_if::value && !is_tuple_like::value && !is_wrapper::value && + !is_complex::value && !std::is_void::value>::type> { + static constexpr int value{type_count::value}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template struct type_count_min::value>::type> { + static constexpr int value{1}; +}; + +/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template +struct type_count_min< + T, + typename std::enable_if::value && !is_complex::value && !is_tuple_like::value>::type> { + static constexpr int value{subtype_count_min::value}; +}; + +/// 0 if the index > tuple size +template +constexpr typename std::enable_if::value, int>::type tuple_type_size_min() { + return 0; +} + +/// Recursively generate the tuple type name +template + constexpr typename std::enable_if < I::value, int>::type tuple_type_size_min() { + return subtype_count_min::type>::value + tuple_type_size_min(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template struct type_count_min::value>::type> { + static constexpr int value{tuple_type_size_min()}; +}; + +/// definition of subtype count +template struct subtype_count_min { + static constexpr int value{is_mutable_container::value + ? ((type_count::value < expected_max_vector_size) ? type_count::value : 0) + : type_count_min::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 && !is_wrapper::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}; +}; + +/// number of expected items in a vector +template +struct expected_count::value && is_wrapper::value>::type> { + static constexpr int value{expected_count::value}; +}; + +// Enumeration of the different supported categorizations of objects +enum class object_category : int { + char_value = 1, + integral_value = 2, + unsigned_integral = 4, + enumeration = 6, + boolean_value = 8, + floating_point = 10, + number_constructible = 12, + double_constructible = 14, + integer_constructible = 16, + // string like types + string_assignable = 23, + string_constructible = 24, + other = 45, + // special wrapper or container types + wrapper_value = 50, + complex_number = 60, + tuple_value = 70, + container_value = 80, + +}; + +/// Set of overloads to classify an object according to type + +/// some type that is not otherwise recognized +template struct classify_object { + static constexpr object_category value{object_category::other}; +}; + +/// Signed integers +template +struct classify_object< + T, + typename std::enable_if::value && !std::is_same::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::value && std::is_unsigned::value && + !std::is_same::value && !is_bool::value>::type> { + static constexpr object_category value{object_category::unsigned_integral}; +}; + +/// single character values +template +struct classify_object::value && !std::is_enum::value>::type> { + static constexpr object_category value{object_category::char_value}; +}; + +/// 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::value && !std::is_integral::value && + std::is_assignable::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 && (type_count::value == 1) && + std::is_constructible::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}; +}; + +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::complex_number}; +}; + +/// 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_complex::value && + !is_mutable_container::value && !std::is_enum::value, + std::true_type, + std::false_type>::type; + static constexpr bool value = type::value; +}; + +/// wrapper type +template +struct classify_object::value && is_wrapper::value && + !is_tuple_like::value && uncommon_type::value)>::type> { + static constexpr object_category value{object_category::wrapper_value}; +}; + +/// Assignable from double or int +template +struct classify_object::value && type_count::value == 1 && + !is_wrapper::value && 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_wrapper::value && !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_wrapper::value && is_direct_constructible::value && + !is_direct_constructible::value>::type> { + static constexpr object_category value{object_category::double_constructible}; +}; + +/// Tuple type +template +struct classify_object< + T, + typename std::enable_if::value && + ((type_count::value >= 2 && !is_wrapper::value) || + (uncommon_type::value && !is_direct_constructible::value && + !is_direct_constructible::value))>::type> { + static constexpr object_category value{object_category::tuple_value}; + // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be + // constructed from just the first element so tuples of can be constructed from a string, which + // could lead to issues so there are two variants of the condition, the first isolates things with a type size >=2 + // mainly to get tuples on Xcode with the exception of wrappers, the second is the main one and just separating out + // those cases that are caught by other object classifications +}; + +/// container type +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::container_value}; +}; + // Type name print /// Was going to be based on @@ -1054,46 +1515,147 @@ std::string to_string(T &&) { /// 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::char_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "CHAR"; +} + +template ::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 name for enumeration types +template ::value == object_category::complex_number, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "COMPLEX"; +} + /// Print for all other types template ::value && !std::is_integral::value && !is_vector::value && - !std::is_enum::value, + enable_if_t::value >= object_category::string_assignable && + classify_object::value <= object_category::other, detail::enabler> = detail::dummy> constexpr const char *type_name() { return "TEXT"; } +/// typename for tuple value +template ::value == object_category::tuple_value && type_count_base::value >= 2, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Generate type name for a wrapper or container value +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Print name for single element tuple types +template ::value == object_category::tuple_value && type_count_base::value == 1, + detail::enabler> = detail::dummy> +inline std::string type_name() { + return type_name::type>::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 < type_count_base::value), std::string>::type tuple_name() { + std::string str = std::string(type_name::type>::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_base::value >= 2, + detail::enabler>> +inline std::string type_name() { + auto tname = std::string(1, '[') + tuple_name(); + tname.push_back(']'); + return tname; +} + +/// get the type name for a type that has a value_type member +template ::value == object_category::container_value || + classify_object::value == object_category::wrapper_value, + detail::enabler>> +inline std::string type_name() { + return type_name(); +} // Lexical cast +/// Convert to an unsigned integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty()) { + return false; + } + char *val = nullptr; + std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); + output = static_cast(output_ll); + return val == (input.c_str() + input.size()) && static_cast(output) == output_ll; +} + +/// Convert to a signed integral +template ::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty()) { + return false; + } + char *val = nullptr; + std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); + output = static_cast(output_ll); + return val == (input.c_str() + input.size()) && static_cast(output) == output_ll; +} + /// Convert a flag into an integer value typically binary flags -inline int64_t to_flag_value(std::string val) { +inline std::int64_t to_flag_value(std::string val) { static const std::string trueString("true"); static const std::string falseString("false"); if(val == trueString) { @@ -1103,8 +1665,11 @@ inline int64_t to_flag_value(std::string val) { return -1; } val = detail::to_lower(val); - int64_t ret; + std::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': case 'f': @@ -1112,22 +1677,11 @@ inline int64_t to_flag_value(std::string val) { 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"); } @@ -1143,113 +1697,648 @@ inline int64_t to_flag_value(std::string val) { return ret; } -/// 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) { - try { - size_t n = 0; - long long output_ll = std::stoll(input, &n, 0); - output = static_cast(output_ll); - return n == input.size() && static_cast(output) == output_ll; - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { - return false; - } +/// Integer conversion +template ::value == object_category::integral_value || + classify_object::value == object_category::unsigned_integral, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + return integral_conversion(input, output); } -/// Unsigned integers +/// char values template ::value && std::is_unsigned::value && !is_bool::value, detail::enabler> = - detail::dummy> -bool lexical_cast(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); - output = static_cast(output_ll); - return n == input.size() && static_cast(output) == output_ll; - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { - return false; + enable_if_t::value == object_category::char_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.size() == 1) { + output = static_cast(input[0]); + return true; } + return integral_conversion(input, output); } /// 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 { auto out = to_flag_value(input); output = (out > 0); return true; } catch(const std::invalid_argument &) { 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) { - try { - size_t n = 0; - output = static_cast(std::stold(input, &n)); - return n == input.size(); - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { +template ::value == object_category::floating_point, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + if(input.empty()) { return false; } + char *val = nullptr; + auto output_ld = std::strtold(input.c_str(), &val); + output = static_cast(output_ld); + return val == (input.c_str() + input.size()); } -/// String and similar +/// complex 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::complex_number, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + using XC = typename wrapped_type::type; + XC x{0.0}, y{0.0}; + auto str1 = input; + bool worked = false; + auto nloc = str1.find_last_of("+-"); + if(nloc != std::string::npos && nloc > 0) { + worked = detail::lexical_cast(str1.substr(0, nloc), x); + str1 = str1.substr(nloc); + if(str1.back() == 'i' || str1.back() == 'j') + str1.pop_back(); + worked = worked && detail::lexical_cast(str1, y); + } else { + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + worked = detail::lexical_cast(str1, y); + x = XC{0}; + } else { + worked = detail::lexical_cast(str1, x); + y = XC{0}; + } + } + if(worked) { + output = T{x, y}; + return worked; + } + return from_stream(input, output); +} + +/// String and similar direct assignment +template ::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); - if(!retval) { + if(!integral_conversion(input, val)) { return false; } output = static_cast(val); return true; } -/// Non-string parsable +/// wrapper types template ::value && !std::is_integral::value && - !std::is_assignable::value && !std::is_enum::value, + enable_if_t::value == object_category::wrapper_value && + std::is_assignable::value, detail::enabler> = detail::dummy> -bool lexical_cast(std::string input, T &output) { - std::istringstream is; +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = val; + return true; + } + return from_stream(input, output); +} - is.str(input); - is >> output; - return !is.fail() && !is.rdbuf()->in_avail(); +template ::value == object_category::wrapper_value && + !std::is_assignable::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, 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) { + int val; + if(integral_conversion(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(integral_conversion(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 convertible from an int +template ::value == object_category::other && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val; + if(integral_conversion(input, val)) { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4800) +#endif + // with Atomic this could produce a warning due to the conversion but if atomic gets here it is an old style + // so will most likely still work + output = val; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + return true; + } + // LCOV_EXCL_START + // This version of cast is only used for odd cases in an older compilers the fail over + // from_stream is tested elsewhere an not relevant for coverage here + return from_stream(input, output); + // LCOV_EXCL_STOP +} + +/// Non-string parsable by a stream +template ::value == object_category::other && !std::is_assignable::value, + 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 +/// Strings can be empty so we need to do a little different +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, AssignTo &output) { + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && std::is_assignable::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, AssignTo &output) { + if(input.empty()) { + output = AssignTo{}; + return true; + } + + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations +template ::value && !std::is_assignable::value && + classify_object::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + typename AssignTo::value_type emptyVal{}; + output = emptyVal; + return true; + } + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations for int compatible values +/// mainly for atomic operations on some compilers +template ::value && !std::is_assignable::value && + classify_object::value != object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = 0; + return true; + } + int val; + if(lexical_cast(input, val)) { + output = val; + return true; + } + return false; +} + +/// Assign a value converted from a string in lexical cast to the output value directly +template ::value && std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo 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 < + typename AssignTo, + typename ConvertTo, + enable_if_t::value && !std::is_assignable::value && + std::is_move_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = input.empty() ? true : lexical_cast(input, val); + if(parse_result) { + output = AssignTo(val); // use () form of constructor to allow some implicit conversions + } + return parse_result; +} + +/// primary lexical conversion operation, 1 string to 1 type of some kind +template ::value <= object_category::other && + classify_object::value <= object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + return lexical_assign(strings[0], output); +} + +/// Lexical conversion if there is only one element but the conversion type is for two, then call a two element +/// constructor +template ::value <= 2) && expected_count::value == 1 && + is_tuple_like::value && type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + // the remove const is to handle pair types coming from a container + typename std::remove_const::type>::type v1; + typename std::tuple_element<1, ConvertTo>::type v2; + bool retval = lexical_assign(strings[0], v1); + if(strings.size() > 1) { + retval = retval && lexical_assign(strings[1], v2); + } + if(retval) { + output = AssignTo{v1, v2}; + } + return retval; +} + +/// Lexical conversion of a container types of single elements +template ::value && is_mutable_container::value && + type_count::value == 1, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + output.erase(output.begin(), output.end()); + for(const auto &elem : strings) { + typename AssignTo::value_type out; + bool retval = lexical_assign(elem, out); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(out)); + } + return (!output.empty()); +} + +/// Lexical conversion for complex types +template ::value, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() >= 2 && !strings[1].empty()) { + using XC2 = typename wrapped_type::type; + XC2 x{0.0}, y{0.0}; + auto str1 = strings[1]; + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + } + auto worked = detail::lexical_cast(strings[0], x) && detail::lexical_cast(str1, y); + if(worked) { + output = ConvertTo{x, y}; + } + return worked; + } else { + return lexical_assign(strings[0], output); + } +} + +/// Conversion to a vector type using a particular single type as the conversion type +template ::value && (expected_count::value == 1) && + (type_count::value == 1), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &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; +} + +// forward declaration + +/// Lexical conversion of a container types with conversion type of two elements +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(std::vector strings, AssignTo &output); + +/// Lexical conversion of a vector types with type_size >2 forward declaration +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); + +/// Conversion for tuples +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output); // forward declaration + +/// Conversion for operations where the assigned type is some class but the conversion is a mutable container or large +/// tuple +template ::value && !is_mutable_container::value && + classify_object::value != object_category::wrapper_value && + (is_mutable_container::value || type_count::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + + if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) { + ConvertTo val; + auto retval = lexical_conversion(strings, val); + output = AssignTo{val}; + return retval; + } + output = AssignTo{}; + return true; +} + +/// function template for converting tuples if the static Index is greater than the tuple size +template +inline typename std::enable_if<(I >= type_count_base::value), bool>::type +tuple_conversion(const std::vector &, AssignTo &) { + return true; +} + +/// Conversion of a tuple element where the type size ==1 and not a mutable container +template +inline typename std::enable_if::value && type_count::value == 1, bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_assign(strings[0], output); + strings.erase(strings.begin()); + return retval; +} + +/// Conversion of a tuple element where the type size !=1 but the size is fixed and not a mutable container +template +inline typename std::enable_if::value && (type_count::value > 1) && + type_count::value == type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + auto retval = lexical_conversion(strings, output); + strings.erase(strings.begin(), strings.begin() + type_count::value); + return retval; +} + +/// Conversion of a tuple element where the type is a mutable container or a type with different min and max type sizes +template +inline typename std::enable_if::value || + type_count::value != type_count_min::value, + bool>::type +tuple_type_conversion(std::vector &strings, AssignTo &output) { + + std::size_t index{subtype_count_min::value}; + const std::size_t mx_count{subtype_count::value}; + const std::size_t mx{(std::max)(mx_count, strings.size())}; + + while(index < mx) { + if(is_separator(strings[index])) { + break; + } + ++index; + } + bool retval = lexical_conversion( + std::vector(strings.begin(), strings.begin() + static_cast(index)), output); + strings.erase(strings.begin(), strings.begin() + static_cast(index) + 1); + return retval; +} + +/// Tuple conversion operation +template +inline typename std::enable_if<(I < type_count_base::value), bool>::type +tuple_conversion(std::vector strings, AssignTo &output) { + bool retval = true; + using ConvertToElement = typename std:: + conditional::value, typename std::tuple_element::type, ConvertTo>::type; + if(!strings.empty()) { + retval = retval && tuple_type_conversion::type, ConvertToElement>( + strings, std::get(output)); + } + retval = retval && tuple_conversion(std::move(strings), output); + return retval; +} + +/// Lexical conversion of a container types with tuple elements of size 2 +template ::value && is_mutable_container::value && + type_count_base::value == 2, + detail::enabler>> +bool lexical_conversion(std::vector strings, AssignTo &output) { + output.clear(); + while(!strings.empty()) { + + typename std::remove_const::type>::type v1; + typename std::tuple_element<1, typename ConvertTo::value_type>::type v2; + bool retval = tuple_type_conversion(strings, v1); + if(!strings.empty()) { + retval = retval && tuple_type_conversion(strings, v2); + } + if(retval) { + output.insert(output.end(), typename AssignTo::value_type{v1, v2}); + } else { + return false; + } + } + return (!output.empty()); +} + +/// lexical conversion of tuples with type count>2 or tuples of types of some element with a type size>=2 +template ::value && is_tuple_like::value && + (type_count_base::value != type_count::value || + type_count::value > 2), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + static_assert( + !is_tuple_like::value || type_count_base::value == type_count_base::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 for everything but tuples of two elements and types of size 1 +template ::value && is_mutable_container::value && + type_count_base::value != 2 && + ((type_count::value > 2) || + (type_count::value > type_count_base::value)), + detail::enabler>> +bool lexical_conversion(const std::vector &strings, AssignTo &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}; + auto ii_max = strings.size(); + while(ii < ii_max) { + temp.push_back(strings[ii]); + ++ii; + ++icount; + if(icount == xcm || is_separator(temp.back()) || ii == ii_max) { + if(static_cast(xcm) > type_count_min::value && is_separator(temp.back())) { + temp.pop_back(); + } + typename AssignTo::value_type temp_out; + retval = retval && + lexical_conversion(temp, temp_out); + temp.clear(); + if(!retval) { + return false; + } + output.insert(output.end(), std::move(temp_out)); + icount = 0; + } + } + return retval; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + if(strings.empty() || strings.front().empty()) { + output = ConvertTo{}; + return true; + } + typename ConvertTo::value_type val; + if(lexical_conversion(strings, val)) { + output = ConvertTo{val}; + return true; + } + return false; +} + +/// conversion for wrapper types +template ::value == object_category::wrapper_value && + !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + using ConvertType = typename ConvertTo::value_type; + if(strings.empty() || strings.front().empty()) { + output = ConvertType{}; + return true; + } + ConvertType val; + if(lexical_conversion(strings, val)) { + output = val; + return true; + } + return false; } /// 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 ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> +template ::value, detail::enabler> = detail::dummy> void sum_flag_vector(const std::vector &flags, T &output) { - int64_t count{0}; + std::int64_t count{0}; for(auto &flag : flags) { count += detail::to_flag_value(flag); } @@ -1257,25 +2346,50 @@ void sum_flag_vector(const std::vector &flags, T &output) { } /// 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 ::value && std::is_signed::value, detail::enabler> = detail::dummy> +template ::value, detail::enabler> = detail::dummy> void sum_flag_vector(const std::vector &flags, T &output) { - int64_t count{0}; + std::int64_t count{0}; for(auto &flag : flags) { count += detail::to_flag_value(flag); } output = static_cast(count); } -} // namespace detail -} // namespace CLI +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4800) +#endif +// with Atomic this could produce a warning due to the conversion but if atomic gets here it is an old style so will +// most likely still work + +/// 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 +/// "-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 ::value && !std::is_unsigned::value, detail::enabler> = detail::dummy> +void sum_flag_vector(const std::vector &flags, T &output) { + std::int64_t count{0}; + for(auto &flag : flags) { + count += detail::to_flag_value(flag); + } + std::string out = detail::to_string(count); + lexical_cast(out, output); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +} // namespace detail + -// From CLI/Split.hpp: -namespace CLI { namespace detail { // Returns false if not a short option. Otherwise, sets opt name and rest and returns true @@ -1284,8 +2398,8 @@ inline bool split_short(const std::string ¤t, std::string &name, std::stri name = current.substr(1, 1); rest = current.substr(2); return true; - } else - return false; + } + return false; } // Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true @@ -1300,8 +2414,8 @@ inline bool split_long(const std::string ¤t, std::string &name, std::strin value = ""; } return true; - } else - return false; + } + return false; } // Returns false if not a windows style option. Otherwise, sets opt name and value and returns true @@ -1316,14 +2430,14 @@ inline bool split_windows_style(const std::string ¤t, std::string &name, s value = ""; } return true; - } else - return false; + } + return false; } // Splits a string into multiple long and short names inline std::vector 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))); current = current.substr(val + 1); @@ -1368,9 +2482,10 @@ get_names(const std::vector &input) { std::string pos_name; for(std::string name : input) { - if(name.length() == 0) + 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]); else @@ -1394,49 +2509,22 @@ get_names(const std::vector &input) { short_names, long_names, pos_name); } -} // namespace detail -} // namespace CLI +} // namespace detail -// From CLI/ConfigFwd.hpp: -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 { @@ -1449,7 +2537,7 @@ struct ConfigItem { /// This class provides a converter for configuration files. class Config { protected: - std::vector items; + std::vector items{}; public: /// Convert an app into a configuration @@ -1479,62 +2567,75 @@ class Config { virtual ~Config() = default; }; -/// This converter works with INI files -class ConfigINI : public Config { +/// This converter works with INI/TOML files; to write INI files use ConfigINI +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 = '['; + /// the character used to end an array '\0' is a default to not use + char arrayEnd = ']'; + /// the character used to separate elements in an array + char arraySeparator = ','; + /// the character used separate the name from the value + char valueDelimiter = '='; + /// the character to use around strings + char stringQuote = '"'; + /// the character to use around single characters + char characterQuote = '\''; + 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::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; + std::vector from_config(std::istream &input) const override; + /// Specify the configuration for comment characters + ConfigBase *comment(char cchar) { + 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; + } + /// Specify the quote characters used around strings and characters + ConfigBase *quoteCharacter(char qString, char qChar) { + stringQuote = qString; + characterQuote = qChar; + return this; } }; -} // namespace CLI +/// the default Config is the TOML file format +using ConfigTOML = ConfigBase; + +/// ConfigINI generates a "standard" INI compliant output +class ConfigINI : public ConfigTOML { + + public: + ConfigINI() { + commentChar = ';'; + arrayStart = '\0'; + arrayEnd = '\0'; + arraySeparator = ' '; + valueDelimiter = '='; + } +}; -// From CLI/Validators.hpp: -namespace CLI { class Option; @@ -1553,11 +2654,13 @@ class Validator { /// 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 @@ -1567,7 +2670,7 @@ class Validator { Validator() = default; /// 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)), name_(std::move(validator_name)) {} @@ -1589,20 +2692,26 @@ class Validator { } } return retstring; - }; + } /// This is the required operator for a Validator - provided to help /// users (CLI11 uses the member `func` directly) std::string operator()(const std::string &str) const { std::string value = str; return (active_) ? func_(value) : std::string{}; - }; + } /// Specify the type string Validator &description(std::string validator_desc) { 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 { if(active_) { @@ -1615,6 +2724,12 @@ class Validator { 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 @@ -1622,13 +2737,31 @@ class Validator { 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) { 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_; } @@ -1656,6 +2789,7 @@ class Validator { }; newval.active_ = (active_ & other.active_); + newval.application_index_ = application_index_; return newval; } @@ -1675,10 +2809,11 @@ class Validator { 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; } @@ -1697,10 +2832,11 @@ class Validator { std::string s1 = f1(test); if(s1.empty()) { return std::string("check ") + dfunc1() + " succeeded improperly"; - } else - return std::string{}; + } + return std::string{}; }; newval.active_ = active_; + newval.application_index_ = application_index_; return newval; } @@ -1716,10 +2852,10 @@ class Validator { if((f1.empty()) || (f2.empty())) { 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 { @@ -1730,17 +2866,61 @@ class CustomValidator : public Validator { // Therefore, this is in detail. namespace detail { +/// CLI enumeration of different file types +enum class path_type { nonexistent, 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::nonexistent; + } + switch(stat.type()) { + case std::filesystem::file_type::none: + case std::filesystem::file_type::not_found: + return path_type::nonexistent; + 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::nonexistent; +} +#endif /// Check for an existing file (returns error message if check fails) class ExistingFileValidator : public Validator { public: ExistingFileValidator() : 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::nonexistent) { return "File does not exist: " + filename; - } else if(is_dir) { + } + if(path_result == path_type::directory) { return "File is actually a directory: " + filename; } return std::string(); @@ -1753,12 +2933,11 @@ class ExistingDirectoryValidator : public Validator { public: ExistingDirectoryValidator() : 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::nonexistent) { return "Directory does not exist: " + filename; - } else if(!is_dir) { + } + if(path_result == path_type::file) { return "Directory is actually a file: " + filename; } return std::string(); @@ -1771,9 +2950,8 @@ class ExistingPathValidator : public Validator { public: ExistingPathValidator() : 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::nonexistent) { return "Path does not exist: " + filename; } return std::string(); @@ -1786,9 +2964,8 @@ class NonexistentPathValidator : public Validator { public: NonexistentPathValidator() : 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::nonexistent) { return "Path already exists: " + filename; } return std::string(); @@ -1803,17 +2980,16 @@ class IPV4Validator : public Validator { func_ = [](std::string &ip_addr) { 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(); @@ -1821,38 +2997,7 @@ class IPV4Validator : public Validator { } }; -/// Validate the argument is a number and greater than or equal to 0 -class PositiveNumber : public Validator { - public: - PositiveNumber() : Validator("POSITIVE") { - func_ = [](std::string &number_str) { - int number; - if(!detail::lexical_cast(number_str, number)) { - return "Failed parsing number " + number_str; - } - if(number < 0) { - return "Number less then 0 " + number_str; - } - return std::string(); - }; - } -}; - -/// Validate the argument is a number and greater than or equal to 0 -class Number : public Validator { - public: - Number() : Validator("NUMBER") { - func_ = [](std::string &number_str) { - double number; - if(!detail::lexical_cast(number_str, number)) { - return "Failed parsing as a number " + number_str; - } - return std::string(); - }; - } -}; - -} // namespace detail +} // namespace detail // Static is not needed here, because global const implies static. @@ -1871,11 +3016,23 @@ const detail::NonexistentPathValidator NonexistentPath; /// Check for an IP4 address const detail::IPV4Validator ValidIPV4; -/// Check for a positive number -const detail::PositiveNumber PositiveNumber; +/// Validate the input as a particular type +template class TypeValidator : public Validator { + public: + explicit TypeValidator(const std::string &validator_name) : Validator(validator_name) { + func_ = [](std::string &input_string) { + auto val = DesiredType(); + if(!detail::lexical_cast(input_string, val)) { + return std::string("Failed parsing ") + input_string + " as a " + detail::type_name(); + } + return std::string(); + }; + } + TypeValidator() : TypeValidator(detail::type_name()) {} +}; /// Check for a number -const detail::Number Number; +const TypeValidator Number("NUMBER"); /// Produce a range (factory). Min and max are inclusive. class Range : public Validator { @@ -1884,25 +3041,37 @@ class Range : public Validator { /// /// Note that the constructor is templated, but the struct is not, so C++17 is not /// needed to provide nice syntax for Range(a,b). - template Range(T min, T max) { - std::stringstream out; - out << detail::type_name() << " in [" << min << " - " << max << "]"; - description(out.str()); + template + Range(T min, T max, const std::string &validator_name = std::string{}) : Validator(validator_name) { + if(validator_name.empty()) { + std::stringstream out; + out << detail::type_name() << " in [" << min << " - " << max << "]"; + description(out.str()); + } func_ = [min, max](std::string &input) { 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(); }; } /// Range of one value is 0 to value - template explicit Range(T max) : Range(static_cast(0), max) {} + template + explicit Range(T max, const std::string &validator_name = std::string{}) + : Range(static_cast(0), max, validator_name) {} }; +/// Check for a non negative number +const Range NonNegativeNumber(std::numeric_limits::max(), "NONNEGATIVE"); + +/// Check for a positive valued number (val>0.0), min() her is the smallest positive number +const Range PositiveNumber(std::numeric_limits::min(), std::numeric_limits::max(), "POSITIVE"); + /// Produce a bounded range (factory). Min and max are inclusive. class Bound : public Validator { public: @@ -1919,14 +3088,14 @@ class Bound : public Validator { T val; 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{}; }; } @@ -1950,11 +3119,12 @@ typename std::remove_reference::type &smart_deref(T &value) { /// Generate a string representation of a set template std::string generate_set(const T &set) { using element_t = typename detail::element_type::type; - using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair std::string out(1, '{'); - out.append(detail::join(detail::smart_deref(set), - [](const iteration_type_t &v) { return detail::pair_adaptor::first(v); }, - ",")); + out.append(detail::join( + detail::smart_deref(set), + [](const iteration_type_t &v) { return detail::pair_adaptor::first(v); }, + ",")); out.push_back('}'); return out; } @@ -1962,29 +3132,32 @@ template std::string generate_set(const T &set) { /// Generate a string representation of a map template std::string generate_map(const T &map, bool key_only = false) { using element_t = typename detail::element_type::type; - using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair std::string out(1, '{'); - 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)); - if(!key_only) { - res += "->" + detail::as_string(detail::pair_adaptor::second(v)); - } - return res; - }, - ",")); + out.append(detail::join( + detail::smart_deref(map), + [key_only](const iteration_type_t &v) { + std::string res{detail::to_string(detail::pair_adaptor::first(v))}; + + if(!key_only) { + res.append("->"); + res += detail::to_string(detail::pair_adaptor::second(v)); + } + return res; + }, + ",")); out.push_back('}'); return out; } -template struct sfinae_true : std::true_type {}; -/// 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 struct has_find { + 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 template ::value, detail::enabler> = detail::dummy> @@ -2018,24 +3191,44 @@ auto search(const T &set, const V &val, const std::function &filter_functi // 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; } @@ -2050,7 +3243,7 @@ typename std::enable_if::value, bool>::type checked_mu return true; } -} // namespace detail +} // namespace detail /// Verify items are in a set class IsMember : public Validator { public: @@ -2058,7 +3251,7 @@ class IsMember : public Validator { /// 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)...) {} /// This checks to see if an item is in a set (empty function) @@ -2070,11 +3263,11 @@ class IsMember : public Validator { // Get the type of the contained item - requires a container have ::value_type // 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 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) + 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; @@ -2087,7 +3280,7 @@ class IsMember : public Validator { func_ = [set, filter_fn](std::string &input) { local_item_t b; if(!detail::lexical_cast(input, b)) { - throw ValidationError(input); // name is added later + throw ValidationError(input); // name is added later } if(filter_fn) { b = filter_fn(b); @@ -2096,7 +3289,7 @@ class IsMember : public Validator { if(res.first) { // 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) @@ -2104,18 +3297,17 @@ class IsMember : public Validator { } // If you reach this point, the result was not found - std::string out(" not in "); - out += detail::generate_set(detail::smart_deref(set)); - return out; + return input + " not in " + detail::generate_set(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::forward(set), - [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, - other...) {} + : IsMember( + std::forward(set), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} }; /// definition of the default transformation object @@ -2128,7 +3320,7 @@ class Transformer : public Validator { /// 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)...) {} /// direct map of std::string to std::string @@ -2142,10 +3334,10 @@ class Transformer : public Validator { "mapping must produce value pairs"); // Get the type of the contained item - requires a container have ::value_type // 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) + 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; // 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; @@ -2164,7 +3356,7 @@ class Transformer : public Validator { } 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{}; }; @@ -2173,9 +3365,10 @@ class Transformer : public Validator { /// You can pass in as many filter functions as you like, they nest template Transformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) - : Transformer(std::forward(mapping), - [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, - other...) {} + : Transformer( + std::forward(mapping), + [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, + other...) {} }; /// translate named items to other or a value set @@ -2185,7 +3378,7 @@ class CheckedTransformer : public Validator { /// 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)...) {} /// direct map of std::string to std::string @@ -2199,12 +3392,11 @@ class CheckedTransformer : public Validator { "mapping must produce value pairs"); // Get the type of the contained item - requires a container have ::value_type // 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) - using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair // - // the type of the object pair + 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; // Will convert bad types to good ones + // (const char * to std::string) + using iteration_type_t = typename detail::pair_adaptor::value_type; // the type of the object pair // Make a local copy of the filter function, using a std::function if not one already std::function filter_fn = filter_function; @@ -2214,7 +3406,7 @@ class CheckedTransformer : public Validator { 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; @@ -2231,12 +3423,12 @@ class CheckedTransformer : public Validator { } 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(); } @@ -2249,9 +3441,10 @@ class CheckedTransformer : public Validator { /// You can pass in as many filter functions as you like, they nest template CheckedTransformer(T &&mapping, filter_fn_t filter_fn_1, filter_fn_t filter_fn_2, Args &&... other) - : CheckedTransformer(std::forward(mapping), - [filter_fn_1, filter_fn_2](std::string a) { return filter_fn_2(filter_fn_1(a)); }, - other...) {} + : CheckedTransformer( + std::forward(mapping), + [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 or Transform @@ -2315,7 +3508,7 @@ class AsNumberWithUnit : public Validator { } 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()) { @@ -2324,13 +3517,11 @@ class AsNumberWithUnit : public Validator { if(opts & CASE_INSENSITIVE) { unit = detail::to_lower(unit); } - - bool converted = detail::lexical_cast(input, num); - if(!converted) { - throw ValidationError("Value " + input + " could not be converted to " + detail::type_name()); - } - if(unit.empty()) { + if(!detail::lexical_cast(input, num)) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } // No need to modify input if no unit passed return {}; } @@ -2344,13 +3535,23 @@ class AsNumberWithUnit : public Validator { detail::generate_map(mapping, true)); } - // perform safe multiplication - bool ok = detail::checked_multiply(num, it->second); - if(!ok) { - throw ValidationError(detail::as_string(num) + " multiplied by " + unit + - " factor would cause number overflow. Use smaller value."); + if(!input.empty()) { + bool converted = detail::lexical_cast(input, num); + if(!converted) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name()); + } + // perform safe multiplication + bool ok = detail::checked_multiply(num, it->second); + if(!ok) { + throw ValidationError(detail::to_string(num) + " multiplied by " + unit + + " factor would cause number overflow. Use smaller value."); + } + } else { + num = static_cast(it->second); } - input = detail::as_string(num); + + input = detail::to_string(num); return {}; }; @@ -2375,7 +3576,8 @@ class AsNumberWithUnit : public Validator { for(auto &kv : mapping) { 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; } @@ -2409,7 +3611,7 @@ class AsNumberWithUnit : public Validator { /// "2 EiB" => 2^61 // Units up to exibyte are supported class AsSizeValue : public AsNumberWithUnit { public: - using result_t = uint64_t; + using result_t = std::uint64_t; /// If kb_is_1000 is true, /// interpret 'kb', 'k' as 1000 and 'kib', 'ki' as 1024 @@ -2468,31 +3670,52 @@ inline std::pair split_program_name(std::string comman 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) { // if we have reached the end and haven't found a valid file just assume the first argument is the // program name - esp = commandline.find_first_of(' ', 1); + if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') { + bool embeddedQuote = false; + auto keyChar = commandline[0]; + auto end = commandline.find_first_of(keyChar, 1); + while((end != std::string::npos) && (commandline[end - 1] == '\\')) { // deal with escaped quotes + end = commandline.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + vals.first = commandline.substr(1, end - 1); + esp = end + 1; + if(embeddedQuote) { + vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar)); + embeddedQuote = false; + } + } else { + esp = commandline.find_first_of(' ', 1); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + break; } } - vals.first = commandline.substr(0, esp); - rtrim(vals.first); + if(vals.first.empty()) { + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + } + // strip the program name vals.second = (esp != std::string::npos) ? commandline.substr(esp + 1) : std::string{}; ltrim(vals.second); return vals; } -} // namespace detail +} // namespace detail /// @} -} // namespace CLI -// From CLI/FormatterFwd.hpp: -namespace CLI { class Option; class App; @@ -2503,9 +3726,9 @@ class App; /// the second argument. enum class AppFormatMode { - Normal, //< The normal, detailed help - All, //< A fully expanded help - Sub, //< Used when printed as part of expanded subcommand + Normal, ///< The normal, detailed help + All, ///< A fully expanded help + Sub, ///< Used when printed as part of expanded subcommand }; /// This is the minimum requirements to run a formatter. @@ -2518,11 +3741,11 @@ class FormatterBase { ///@{ /// 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 @@ -2534,7 +3757,7 @@ class FormatterBase { FormatterBase(FormatterBase &&) = default; /// Adding a destructor in this form to work around bug in GCC 4.7 - virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default) + virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default) /// This is the key method that puts together help virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; @@ -2547,7 +3770,7 @@ class FormatterBase { 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 @@ -2562,7 +3785,7 @@ class FormatterBase { } /// Get the current column width - size_t get_column_width() const { return column_width_; } + std::size_t get_column_width() const { return column_width_; } ///@} }; @@ -2579,7 +3802,7 @@ class FormatterLambda final : public FormatterBase { explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} /// Adding a destructor (mostly to make GCC 4.7 happy) - ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default) + ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default) /// This will simply call the lambda function std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { @@ -2627,7 +3850,7 @@ class Formatter : public FormatterBase { 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 @@ -2656,21 +3879,25 @@ class Formatter : public FormatterBase { ///@} }; -} // namespace CLI -// From CLI/Option.hpp: -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