diff --git a/CMakeLists.txt b/CMakeLists.txt index 127c63cfda..91fac2f887 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ endif() set( CLI_CLIENT_EXECUTABLE_NAME cleos ) set( NODE_EXECUTABLE_NAME nodeos ) set( KEY_STORE_EXECUTABLE_NAME keosd ) +set( LEAP_UTIL_EXECUTABLE_NAME leap-util ) # http://stackoverflow.com/a/18369825 if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") @@ -244,6 +245,14 @@ install(FILES libraries/yubihsm/LICENSE DESTINATION ${CMAKE_INSTALL_FULL_DATAROO install(FILES libraries/eos-vm/LICENSE DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/licenses/leap/ RENAME LICENSE.eos-vm COMPONENT base) install(FILES libraries/fc/libraries/ff/LICENSE DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/licenses/leap/ RENAME LICENSE.libff COMPONENT base) +configure_file(${CMAKE_SOURCE_DIR}/libraries/cli11/bash-completion/completions/leap-util + ${CMAKE_BINARY_DIR}/programs/leap-util/bash-completion/completions/leap-util COPYONLY) +configure_file(${CMAKE_SOURCE_DIR}/libraries/cli11/bash-completion/completions/cleos + ${CMAKE_BINARY_DIR}/programs/cleos/bash-completion/completions/cleos COPYONLY) + +install(FILES libraries/cli11/bash-completion/completions/leap-util DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/bash-completion/completions COMPONENT base) +install(FILES libraries/cli11/bash-completion/completions/cleos DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/bash-completion/completions COMPONENT base) + add_custom_target(dev-install COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" COMMAND "${CMAKE_COMMAND}" --install "${CMAKE_BINARY_DIR}" diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 9598cd4d1f..50c007a693 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory( chain ) add_subdirectory( testing ) add_subdirectory( version ) add_subdirectory( state_history ) +add_subdirectory( cli11 ) set(USE_EXISTING_SOFTFLOAT ON CACHE BOOL "use pre-exisiting softfloat lib") set(ENABLE_TOOLS OFF CACHE BOOL "Build tools") diff --git a/libraries/cli11/CMakeLists.txt b/libraries/cli11/CMakeLists.txt new file mode 100644 index 0000000000..2d141222ac --- /dev/null +++ b/libraries/cli11/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(leap-cli11 INTERFACE) +target_include_directories(leap-cli11 INTERFACE include) \ No newline at end of file diff --git a/libraries/cli11/README.md b/libraries/cli11/README.md new file mode 100644 index 0000000000..4bf7d7a786 --- /dev/null +++ b/libraries/cli11/README.md @@ -0,0 +1,26 @@ + +## instructions for building custom leap-cli11 library + +leap-cli11 interface only library created in order to simplify integration of modified version of command line parsing library cli11 + +to update included in this library include/cli11/CLI11.hpp file it needs to be (re)geenerated from repository containing forked/modified version of it, in particular: + + +```bash +git clone https://github.com/AntelopeIO/CLI11.git +cd CLI11 +mkdir build +cd build +cmake -DCLI11_SINGLE_FILE=ON .. +make -j +``` + +Resulting single-header will be located in: + +```cpp +build/include/CLI11.hpp +``` + +And is ready to be copied to include/cli11/CLI11.hpp of leap-cli11 library + +Automated CLI11 subproject build / import of CLI11.hpp header will be added in future versions. diff --git a/libraries/cli11/bash-completion/completions/cleos b/libraries/cli11/bash-completion/completions/cleos new file mode 100644 index 0000000000..1d21f2a6cd --- /dev/null +++ b/libraries/cli11/bash-completion/completions/cleos @@ -0,0 +1,26 @@ +#/usr/bin/env bash + +_cleos_complete() +{ + # Get cmdline while ignoring the last word + cmdline=${COMP_LINE} + if [[ "${cmdline:(-1)}" != " " ]]; then + cmdline=${COMP_WORDS[@]:0:${COMP_CWORD}} + fi + + # Get complete candidates + words=$(${cmdline} --_autocomplete "${COMP_WORDS[${COMP_CWORD}]}") + + # Remove candidates that begins with `-` if `-` is not passed as the first character + # TODO: move this logic inside CLI11? including the compgen? + if [[ ! ${COMP_WORDS[${COMP_CWORD}]} = -* ]]; then + words=(${words}) + for index in "${!words[@]}" ; do [[ ${words[$index]} =~ ^- ]] && unset -v 'words[$index]' ; done + words="${words[@]}" + fi + + # Get matches + COMPREPLY=($(compgen -W "${words}" -- "${COMP_WORDS[${COMP_CWORD}]}")) +} + +complete -F _cleos_complete cleos \ No newline at end of file diff --git a/libraries/cli11/bash-completion/completions/leap-util b/libraries/cli11/bash-completion/completions/leap-util new file mode 100644 index 0000000000..0e242be638 --- /dev/null +++ b/libraries/cli11/bash-completion/completions/leap-util @@ -0,0 +1,26 @@ +#/usr/bin/env bash + +_leap_complete() +{ + # Get cmdline while ignoring the last word + cmdline=${COMP_LINE} + if [[ "${cmdline:(-1)}" != " " ]]; then + cmdline=${COMP_WORDS[@]:0:${COMP_CWORD}} + fi + + # Get complete candidates + words=$(${cmdline} --_autocomplete "${COMP_WORDS[${COMP_CWORD}]}") + + # Remove candidates that begins with `-` if `-` is not passed as the first character + # TODO: move this logic inside CLI11? including the compgen? + if [[ ! ${COMP_WORDS[${COMP_CWORD}]} = -* ]]; then + words=(${words}) + for index in "${!words[@]}" ; do [[ ${words[$index]} =~ ^- ]] && unset -v 'words[$index]' ; done + words="${words[@]}" + fi + + # Get matches + COMPREPLY=($(compgen -W "${words}" -- "${COMP_WORDS[${COMP_CWORD}]}")) +} + +complete -F _leap_complete leap-util \ No newline at end of file diff --git a/programs/cleos/CLI11.hpp b/libraries/cli11/include/cli11/CLI11.hpp similarity index 74% rename from programs/cleos/CLI11.hpp rename to libraries/cli11/include/cli11/CLI11.hpp index 8f958076a8..66af6ce62a 100644 --- a/programs/cleos/CLI11.hpp +++ b/libraries/cli11/include/cli11/CLI11.hpp @@ -1,20 +1,16 @@ -#pragma once - -// CLI11: Version 1.9.0 +// CLI11: Version 2.2.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.9.0 +// from: 1d3b0a7 // -// From LICENSE: -// -// CLI11 1.8 Copyright (c) 2017-2019 University of Cincinnati, developed by Henry +// CLI11 2.2.0 Copyright (c) 2017-2022 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,48 +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 - - -// Verbatim copy from CLI/Version.hpp: +#include +#include +#include -#define CLI11_VERSION_MAJOR 1 -#define CLI11_VERSION_MINOR 9 +#define CLI11_VERSION_MAJOR 2 +#define CLI11_VERSION_MINOR 2 #define CLI11_VERSION_PATCH 0 -#define CLI11_VERSION "1.9.0" - - +#define CLI11_VERSION "2.2.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 @@ -94,7 +84,7 @@ #define CLI11_CPP14 #if _MSVC_LANG > 201402L && _MSC_VER >= 1910 #define CLI11_CPP17 -#if __MSVC_LANG > 201703L && _MSC_VER >= 1910 +#if _MSVC_LANG > 201703L && _MSC_VER >= 1910 #define CLI11_CPP20 #endif #endif @@ -109,12 +99,25 @@ #define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) #endif +/** detection of rtti */ +#ifndef CLI11_USE_STATIC_RTTI +#if(defined(_HAS_STATIC_RTTI) && _HAS_STATIC_RTTI) +#define CLI11_USE_STATIC_RTTI 1 +#elif defined(__cpp_rtti) +#if(defined(_CPPRTTI) && _CPPRTTI == 0) +#define CLI11_USE_STATIC_RTTI 1 +#else +#define CLI11_USE_STATIC_RTTI 0 +#endif +#elif(defined(__GCC_RTTI) && __GXX_RTTI) +#define CLI11_USE_STATIC_RTTI 0 +#else +#define CLI11_USE_STATIC_RTTI 1 +#endif +#endif -// Verbatim copy from CLI/Validators.hpp: - - // C standard library // Only needed for existence checking #if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM @@ -122,10 +125,20 @@ // 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 +#elif defined(__wasi__) +// As of wasi-sdk-14, filesystem is not implemented +#define CLI11_HAS_FILESYSTEM 0 #else #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_HAS_FILESYSTEM 0 #endif @@ -134,7 +147,7 @@ #endif #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 -#include // NOLINT(build/include) +#include // NOLINT(build/include) #else #include #include @@ -142,18 +155,9 @@ -// From CLI/Version.hpp: - - - -// From CLI/Macros.hpp: - - - -// 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). namespace enums { @@ -165,7 +169,7 @@ std::ostream &operator<<(std::ostream &in, const T &item) { return in << static_cast::type>(item); } -} // namespace enums +} // namespace enums /// Export to CLI namespace using enums::operator<<; @@ -179,9 +183,9 @@ constexpr int expected_max_vector_size{1 << 29}; 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; @@ -213,10 +217,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(); } @@ -286,13 +294,29 @@ inline std::string &remove_quotes(std::string &str) { return str; } +/// Add a leader to the beginning of all new lines (nothing is added +/// 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(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); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + /// 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, std::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()) { @@ -309,24 +333,60 @@ inline std::ostream &format_help(std::ostream &out, std::string name, std::strin 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 == '@'; +/// 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 << detail::fix_newlines(" ", alias); + } + out << "\n"; + } + return out; } +/// Verify the first character of an option +/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with +template bool valid_first_char(T c) { return ((c != '-') && (c != '!') && (c != ' ') && c != '\n'); } + /// Verify following characters of an option -template bool valid_later_char(T c) { return valid_first_char(c) || c == '.' || c == '-'; } +template bool valid_later_char(T c) { + // = and : are value separators, { has special meaning for option defaults, + // and \n would just be annoying to deal with in many places allowing space here has too much potential for + // inadvertent entry errors and bugs + return ((c != '=') && (c != ':') && (c != '{') && (c != ' ') && c != '\n'); +} -/// Verify an option name +/// Verify an option/subcommand name inline bool valid_name_string(const std::string &str) { - if(str.empty() || !valid_first_char(str[0])) + if(str.empty() || !valid_first_char(str[0])) { return false; - for(auto c : str.substr(1)) - if(!valid_later_char(c)) + } + auto e = str.end(); + for(auto c = str.begin() + 1; c != e; ++c) + if(!valid_later_char(*c)) return false; return true; } +/// Verify an app name +inline bool valid_alias_name_string(const std::string &str) { + static const std::string badChars(std::string("\n") + '\0'); + return (str.find_first_of(badChars) == std::string::npos); +} + +/// 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()); }); @@ -365,7 +425,7 @@ inline bool has_default_flag_values(const std::string &flags) { } inline void remove_default_flag_values(std::string &flags) { - auto loc = flags.find_first_of('{'); + auto loc = flags.find_first_of('{', 2); while(loc != std::string::npos) { auto finish = flags.find_first_of("},", loc + 1); if((finish != std::string::npos) && (flags[finish] == '}')) { @@ -401,8 +461,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); } @@ -434,13 +495,18 @@ inline std::vector split_up(std::string str, char delimiter = '\0') 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 = ""; @@ -466,22 +532,6 @@ inline std::vector split_up(std::string str, char delimiter = '\0') return output; } -/// Add a leader to the beginning of all new lines (nothing is added -/// 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(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); - if(n != std::string::npos) { - input = input.substr(0, n + 1) + leader + input.substr(n + 1); - n += leader.size(); - } - } - return input; -} - /// This function detects an equal or colon followed by an escaped quote after an argument /// 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 @@ -492,7 +542,7 @@ inline std::size_t escape_detect(std::string &str, std::size_t offset) { 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; @@ -510,13 +560,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. @@ -658,19 +705,32 @@ 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) {} +}; + +class AutocompleteMsg : public ParseError { + CLI11_ERROR_DEF(ParseError, AutocompleteMsg) + AutocompleteMsg(const std::vector &completions) : + AutocompleteMsg(detail::join(completions, "\n"), 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) {} @@ -747,11 +807,11 @@ class RequiredError : public ParseError { class ArgumentMismatch : public ParseError { CLI11_ERROR_DEF(ParseError, ArgumentMismatch) CLI11_ERROR_SIMPLE(ArgumentMismatch) - ArgumentMismatch(std::string name, int expected, std::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, std::size_t received) { @@ -768,6 +828,10 @@ class ArgumentMismatch : public ParseError { static ArgumentMismatch FlagOverride(std::string name) { return ArgumentMismatch(name + " was given a disallowed flag override"); } + static ArgumentMismatch PartialType(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + type + " only partially specified: " + std::to_string(num) + + " required for each element"); + } }; /// Thrown when a requires option is missing @@ -838,11 +902,8 @@ class OptionNotFound : public Error { /// @} -} // namespace CLI -// From CLI/TypeTools.hpp: -namespace CLI { // Type tools @@ -854,12 +915,12 @@ 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. /// /// We could check to see if C++14 is being used, but it does not hurt to redefine this -/// (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h) +/// (even Google does this: https://github.com/google/skia/blob/main/include/private/SkTLogic.h) /// It is not in the std namespace anyway, so no harm done. template using enable_if_t = typename std::enable_if::type; @@ -872,15 +933,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 a vector (true if actually a const vector) -template struct is_vector> : std::true_type {}; - /// Check to see if something is bool (fail check by default) template struct is_bool : std::false_type {}; @@ -1022,6 +1074,17 @@ template class is_istreamable { 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) { @@ -1036,12 +1099,49 @@ 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::value, std::true_type{}); + static auto test(int) -> decltype(std::tuple_size::type>::value, std::true_type{}); template static auto test(...) -> std::false_type; public: @@ -1049,15 +1149,24 @@ template class is_tuple_like { }; /// 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_ostreamable::value, detail::enabler> = - detail::dummy> + enable_if_t::value && !std::is_constructible::value && + is_ostreamable::value, + detail::enabler> = detail::dummy> std::string to_string(T &&value) { std::stringstream stream; stream << value; @@ -1067,22 +1176,24 @@ 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_ostreamable::value && - !is_vector::type>::type>::value, + !is_readable_container::type>::value, detail::enabler> = detail::dummy> std::string to_string(T &&) { return std::string{}; } -/// convert a vector to a string +/// convert a readable container to a string template ::value && !is_ostreamable::value && - is_vector::type>::type>::value, + is_readable_container::value, detail::enabler> = detail::dummy> std::string to_string(T &&variable) { - std::vector defaults; - defaults.reserve(variable.size()); auto cval = variable.begin(); auto end = variable.end(); + if(cval == end) { + return std::string("{}"); + } + std::vector defaults; while(cval != end) { defaults.emplace_back(CLI::detail::to_string(*cval)); ++cval; @@ -1124,25 +1235,142 @@ 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 { static const int value{0}; }; +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{std::tuple_size::value}; + 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< +struct type_count_min< T, - typename std::enable_if::value && !is_tuple_like::value && !std::is_void::value>::type> { + 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 of types that look like a vector -template struct type_count::value>::type> { - static constexpr int value{is_vector::value ? expected_max_vector_size - : type_count::value}; +/// 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 @@ -1150,16 +1378,25 @@ template struct expected_count { static con /// For most types the number of expected items is 1 template -struct expected_count::value && !std::is_void::value>::type> { +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> { +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, @@ -1168,36 +1405,48 @@ enum class object_category : int { number_constructible = 12, double_constructible = 14, integer_constructible = 16, - vector_value = 30, - tuple_value = 35, - // string assignable or greater used in a condition so anything string like must come last - string_assignable = 50, - string_constructible = 60, - other = 200, + // 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}; }; -/// Set of overloads to classify an object according to type +/// Signed integers template -struct classify_object::value && std::is_signed::value && - !is_bool::value && !std::is_enum::value>::type> { +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< - T, - typename std::enable_if::value && std::is_unsigned::value && !is_bool::value>::type> { +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}; @@ -1210,10 +1459,9 @@ template struct classify_object -struct classify_object< - T, - typename std::enable_if::value && !std::is_integral::value && - std::is_assignable::value && !is_vector::value>::type> { +struct classify_object::value && !std::is_integral::value && + std::is_assignable::value>::type> { static constexpr object_category value{object_category::string_assignable}; }; @@ -1222,8 +1470,8 @@ template struct classify_object< T, typename std::enable_if::value && !std::is_integral::value && - !std::is_assignable::value && - std::is_constructible::value && !is_vector::value>::type> { + !std::is_assignable::value && (type_count::value == 1) && + std::is_constructible::value>::type> { static constexpr object_category value{object_category::string_constructible}; }; @@ -1232,23 +1480,35 @@ template struct classify_object 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_vector::value && - !std::is_enum::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_direct_constructible::value && + !is_wrapper::value && is_direct_constructible::value && is_direct_constructible::value>::type> { static constexpr object_category value{object_category::number_constructible}; }; @@ -1257,7 +1517,7 @@ struct classify_object struct classify_object::value && type_count::value == 1 && - !is_direct_constructible::value && + !is_wrapper::value && !is_direct_constructible::value && is_direct_constructible::value>::type> { static constexpr object_category value{object_category::integer_constructible}; }; @@ -1266,24 +1526,30 @@ struct classify_object struct classify_object::value && type_count::value == 1 && - is_direct_constructible::value && + !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::value >= 2 && !is_vector::value) || - (is_tuple_like::value && uncommon_type::value && - !is_direct_constructible::value && - !is_direct_constructible::value)>::type> { +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 }; -/// Vector type -template struct classify_object::value>::type> { - static constexpr object_category value{object_category::vector_value}; +/// container type +template struct classify_object::value>::type> { + static constexpr object_category value{object_category::container_value}; }; // Type name print @@ -1292,6 +1558,12 @@ template struct classify_object::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, @@ -1329,31 +1601,53 @@ 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 >= object_category::string_assignable, detail::enabler> = detail::dummy> + 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::value == 1, + enable_if_t::value == object_category::tuple_value && type_count_base::value == 1, detail::enabler> = detail::dummy> inline std::string type_name() { - return type_name::type>(); + return type_name::type>::type>(); } /// Empty string if the index > tuple size template -inline typename std::enable_if::value, std::string>::type tuple_name() { +inline typename std::enable_if::value, std::string>::type tuple_name() { return std::string{}; } /// Recursively generate the tuple type name template - inline typename std::enable_if < I::value, std::string>::type tuple_name() { - std::string str = std::string(type_name::type>()) + ',' + tuple_name(); +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; @@ -1361,25 +1655,68 @@ template /// Print type name for tuples with 2 or more elements template ::value == object_category::tuple_value && type_count::value >= 2, - detail::enabler> = detail::dummy> -std::string type_name() { + enable_if_t::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; } -/// This one should not be used normally, since vector types print the internal type +/// get the type name for a type that has a value_type member template ::value == object_category::vector_value, detail::enabler> = detail::dummy> + enable_if_t::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); + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + val = nullptr; + std::int64_t output_sll = std::strtoll(input.c_str(), &val, 0); + if(val == (input.c_str() + input.size())) { + output = (output_sll < 0) ? static_cast(0) : static_cast(output_sll); + return (static_cast(output) == output_sll); + } + return false; +} + +/// 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); + if(val == (input.c_str() + input.size()) && static_cast(output) == output_ll) { + return true; + } + if(input == "true") { + // this is to deal with a few oddities with flags and wrapper int types + output = static_cast(1); + return true; + } + return false; +} + /// 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) { @@ -1389,10 +1726,10 @@ 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'); + return (static_cast(val[0]) - '0'); } switch(val[0]) { case '0': @@ -1421,39 +1758,24 @@ inline int64_t to_flag_value(std::string val) { return ret; } -/// Signed integers +/// Integer conversion template ::value == object_category::integral_value, detail::enabler> = detail::dummy> + enable_if_t::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) { - try { - std::size_t n = 0; - std::int64_t output_ll = std::stoll(input, &n, 0); - output = static_cast(output_ll); - return n == input.size() && static_cast(output) == output_ll; - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { - return false; - } + return integral_conversion(input, output); } -/// Unsigned integers +/// char values template ::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> + enable_if_t::value == object_category::char_value, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { - if(!input.empty() && input.front() == '-') - return false; // std::stoull happily converts negative values to junk without any errors. - - try { - std::size_t n = 0; - std::uint64_t output_ll = std::stoull(input, &n, 0); - output = static_cast(output_ll); - return n == input.size() && static_cast(output) == output_ll; - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { - return false; + if(input.size() == 1) { + output = static_cast(input[0]); + return true; } + return integral_conversion(input, output); } /// Boolean values @@ -1478,15 +1800,45 @@ bool lexical_cast(const std::string &input, T &output) { template ::value == object_category::floating_point, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { - try { - std::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 &) { + 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()); +} + +/// complex +template ::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 @@ -1511,21 +1863,47 @@ 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; } +/// wrapper types +template ::value == object_category::wrapper_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 = val; + return true; + } + return from_stream(input, output); +} + +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(lexical_cast(input, val)) { + if(integral_conversion(input, val)) { output = T(val); return true; } else { @@ -1544,7 +1922,7 @@ template < enable_if_t::value == object_category::integer_constructible, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { int val; - if(lexical_cast(input, val)) { + if(integral_conversion(input, val)) { output = T(val); return true; } @@ -1564,8 +1942,36 @@ bool lexical_cast(const std::string &input, T &output) { 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, detail::enabler> = detail::dummy> +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 " @@ -1574,38 +1980,77 @@ bool lexical_cast(const std::string &input, T &output) { } /// Assign a value through lexical cast operations -template < - typename T, - typename XC, - enable_if_t::value && (classify_object::value == object_category::string_assignable || - classify_object::value == object_category::string_constructible), - detail::enabler> = detail::dummy> -bool lexical_assign(const std::string &input, T &output) { +/// 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 && classify_object::value != object_category::string_assignable && - classify_object::value != object_category::string_constructible, +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, T &output) { +bool lexical_assign(const std::string &input, AssignTo &output) { if(input.empty()) { - output = T{}; + 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 < - typename T, - typename XC, - enable_if_t::value && std::is_assignable::value, detail::enabler> = detail::dummy> -bool lexical_assign(const std::string &input, T &output) { - XC val{}; - bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; +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; } @@ -1613,224 +2058,388 @@ bool lexical_assign(const std::string &input, T &output) { } /// Assign a value from a lexical cast through constructing a value and move assigning it -template ::value && !std::is_assignable::value && - std::is_move_assignable::value, - detail::enabler> = detail::dummy> -bool lexical_assign(const std::string &input, T &output) { - XC val{}; - bool parse_result = input.empty() ? true : lexical_cast(input, val); +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 = T(val); // use () form of constructor to allow some implicit conversions + output = AssignTo(val); // use () form of constructor to allow some implicit conversions } return parse_result; } -/// Lexical conversion if there is only one element -template < - typename T, - typename XC, - enable_if_t::value && !is_tuple_like::value && !is_vector::value && !is_vector::value, - detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector &strings, T &output) { - return lexical_assign(strings[0], output); + +/// 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 call a two element constructor -template ::value == 1 && type_count::value == 2, detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector &strings, T &output) { - typename std::tuple_element<0, XC>::type v1; - typename std::tuple_element<1, XC>::type v2; +/// 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 = T{v1, v2}; + output = AssignTo{v1, v2}; } return retval; } -/// Lexical conversion of a vector types -template ::value == expected_max_vector_size && - expected_count::value == expected_max_vector_size && type_count::value == 1, +/// 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, T &output) { - output.clear(); - output.reserve(strings.size()); +bool lexical_conversion(const std::vector &strings, AssignTo &output) { + output.erase(output.begin(), output.end()); + if(strings.size() == 1 && strings[0] == "{}") { + return true; + } + bool skip_remaining = false; + if(strings.size() == 2 && strings[0] == "{}" && is_separator(strings[1])) { + skip_remaining = true; + } for(const auto &elem : strings) { - - output.emplace_back(); - bool retval = lexical_assign(elem, output.back()); + typename AssignTo::value_type out; + bool retval = lexical_assign(elem, out); if(!retval) { return false; } + output.insert(output.end(), std::move(out)); + if(skip_remaining) { + break; + } } return (!output.empty()); } -/// Lexical conversion of a vector types with type size of two -template ::value == expected_max_vector_size && - expected_count::value == expected_max_vector_size && type_count::value == 2, - detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector &strings, T &output) { - output.clear(); - for(std::size_t ii = 0; ii < strings.size(); ii += 2) { +/// Lexical conversion for complex types +template ::value, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector &strings, AssignTo &output) { - typename std::tuple_element<0, typename XC::value_type>::type v1; - typename std::tuple_element<1, typename XC::value_type>::type v2; - bool retval = lexical_assign(strings[ii], v1); - if(strings.size() > ii + 1) { - retval = retval && lexical_assign(strings[ii + 1], v2); + if(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(); } - if(retval) { - output.emplace_back(v1, v2); - } else { - return false; + 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); } - return (!output.empty()); } /// Conversion to a vector type using a particular single type as the conversion type -template ::value == expected_max_vector_size) && (expected_count::value == 1) && - (type_count::value == 1), +template ::value && (expected_count::value == 1) && + (type_count::value == 1), detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector &strings, T &output) { +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()); + retval = retval && lexical_assign(elem, output.back()); } return (!output.empty()) && retval; } -// This one is last since it can call other lexical_conversion functions -/// Lexical conversion if there is only one element but the conversion type is a vector -template ::value && !is_vector::value && is_vector::value, detail::enabler> = - detail::dummy> -bool lexical_conversion(const std::vector &strings, T &output) { - if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) { - XC val; - auto retval = lexical_conversion(strings, val); - output = T{val}; - return retval; +// 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; } - output = T{}; - return true; + 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; } -/// function template for converting tuples if the static Index is greater than the tuple size -template -inline typename std::enable_if= type_count::value, bool>::type tuple_conversion(const std::vector &, - T &) { - return true; -} /// Tuple conversion operation -template - inline typename std::enable_if < - I::value, bool>::type tuple_conversion(const std::vector &strings, T &output) { +template +inline typename std::enable_if<(I < type_count_base::value), bool>::type +tuple_conversion(std::vector strings, AssignTo &output) { bool retval = true; - if(strings.size() > I) { - retval = retval && lexical_assign::type, - typename std::conditional::value, - typename std::tuple_element::type, - XC>::type>(strings[I], std::get(output)); + 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(strings, output); + retval = retval && tuple_conversion(std::move(strings), output); return retval; } -/// Conversion for tuples -template ::value, detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector &strings, T &output) { - static_assert( - !is_tuple_like::value || type_count::value == type_count::value, - "if the conversion type is defined as a tuple it must be the same size as the type you are converting to"); - return tuple_conversion(strings, output); +/// Lexical conversion of a 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 a vector types with type_size >2 -template ::value == expected_max_vector_size && - expected_count::value == expected_max_vector_size && (type_count::value > 2), - detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector &strings, T &output) { +/// 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; - while(ii < strings.size()) { + 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 || temp.back().empty()) { - if(static_cast(xcm) == expected_max_vector_size) { + 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(); } - output.emplace_back(); - retval = retval && lexical_conversion(temp, output.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; } -/// 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) { - int64_t count{0}; - for(auto &flag : flags) { - count += detail::to_flag_value(flag); + +/// 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; } - output = (count > 0) ? static_cast(count) : T{0}; + 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 -/// "-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> -void sum_flag_vector(const std::vector &flags, T &output) { - int64_t count{0}; - for(auto &flag : flags) { - count += detail::to_flag_value(flag); +/// 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; } - output = static_cast(count); + return false; } -} // namespace detail -} // namespace CLI +/// Sum a vector of strings +inline std::string sum_string_vector(const std::vector &values) { + double val{0.0}; + bool fail{false}; + std::string output; + for(const auto &arg : values) { + double tv{0.0}; + auto comp = detail::lexical_cast(arg, tv); + if(!comp) { + try { + tv = static_cast(detail::to_flag_value(arg)); + } catch(const std::exception &) { + fail = true; + break; + } + } + val += tv; + } + if(fail) { + for(const auto &arg : values) { + output.append(arg); + } + } else { + if(val <= static_cast(std::numeric_limits::min()) || + val >= static_cast(std::numeric_limits::max()) || + val == static_cast(static_cast(val))) { + output = detail::value_string(static_cast(val)); + } else { + output = detail::value_string(val); + } + } + return output; +} + +} // 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 @@ -1950,12 +2559,9 @@ 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; @@ -1995,6 +2601,9 @@ class Config { if(item.inputs.size() == 1) { return item.inputs.at(0); } + if(item.inputs.empty()) { + return "{}"; + } throw ConversionError::TooManyInputsFlag(item.fullname()); } @@ -2011,19 +2620,31 @@ class Config { virtual ~Config() = default; }; -/// This converter works with INI/TOML files; to write proper TOML files use ConfigTOML +/// 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 = ';'; + char commentChar = '#'; /// the character used to start an array '\0' is a default to not use - char arrayStart = '\0'; + char arrayStart = '['; /// the character used to end an array '\0' is a default to not use - char arrayEnd = '\0'; + char arrayEnd = ']'; /// the character used to separate elements in an array - char arraySeparator = ' '; + 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 = '\''; + /// the maximum number of layers to allow + uint8_t maximumLayers{255}; + /// the separator used to separator parent layers + char parentSeparatorChar{'.'}; + /// Specify the configuration index to use for arrayed sections + int16_t configIndex{-1}; + /// Specify the configuration section that should be used + std::string configSection{}; public: std::string @@ -2051,28 +2672,60 @@ class ConfigBase : public Config { 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; + } + /// Specify the maximum number of parents + ConfigBase *maxLayers(uint8_t layers) { + maximumLayers = layers; + return this; + } + /// Specify the separator to use for parent layers + ConfigBase *parentSeparator(char sep) { + parentSeparatorChar = sep; + return this; + } + /// get a reference to the configuration section + std::string §ionRef() { return configSection; } + /// get the section + const std::string §ion() const { return configSection; } + /// specify a particular section of the configuration file to use + ConfigBase *section(const std::string §ionName) { + configSection = sectionName; + return this; + } + + /// get a reference to the configuration index + int16_t &indexRef() { return configIndex; } + /// get the section index + int16_t index() const { return configIndex; } + /// specify a particular index in the section to use (-1) for all sections to use + ConfigBase *index(int16_t sectionIndex) { + configIndex = sectionIndex; + return this; + } }; -/// the default Config is the INI file format -using ConfigINI = ConfigBase; +/// the default Config is the TOML file format +using ConfigTOML = ConfigBase; -/// ConfigTOML generates a TOML compliant output -class ConfigTOML : public ConfigINI { +/// ConfigINI generates a "standard" INI compliant output +class ConfigINI : public ConfigTOML { public: - ConfigTOML() { - commentChar = '#'; - arrayStart = '['; - arrayEnd = ']'; - arraySeparator = ','; + ConfigINI() { + commentChar = ';'; + arrayStart = '\0'; + arrayEnd = '\0'; + arraySeparator = ' '; valueDelimiter = '='; } }; -} // namespace CLI -// From CLI/Validators.hpp: -namespace CLI { class Option; @@ -2103,6 +2756,8 @@ class Validator { /// specify that a validator should not modify the input bool non_modifying_{false}; + std::function(const std::string &)> autocomplete_func_{[](const std::string &) { return std::vector{}; }}; + public: Validator() = default; /// Construct a Validator with just the description string @@ -2129,14 +2784,14 @@ 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) { @@ -2156,6 +2811,12 @@ class Validator { } return std::string{}; } + std::vector get_completions(const std::string &str) const { + if(active_) { + return autocomplete_func_(str); + } + return {}; + } /// Specify the type string Validator &name(std::string validator_name) { name_ = std::move(validator_name); @@ -2190,13 +2851,13 @@ class 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 @@ -2225,6 +2886,8 @@ class Validator { return s1 + s2; }; + // TODO: support autocomplete_func_ + newval.active_ = (active_ & other.active_); newval.application_index_ = application_index_; return newval; @@ -2241,6 +2904,9 @@ class Validator { const std::function &f1 = func_; const std::function &f2 = other.func_; + const std::function(const std::string &)> &acf1 = autocomplete_func_; + const std::function(const std::string &)> &acf2 = other.autocomplete_func_; + newval.func_ = [f1, f2](std::string &input) { std::string s1 = f1(input); std::string s2 = f2(input); @@ -2249,6 +2915,13 @@ class Validator { return std::string("(") + s1 + ") OR (" + s2 + ")"; }; + newval.autocomplete_func_ = [acf1, acf2](const std::string &input) { + // Note: Not adding a delimiter since we are adding it after every completion word + auto completions1 = acf1(input); + auto completions2 = acf2(input); + completions1.insert(completions1.end(), completions2.begin(), completions2.end()); + return completions1; + }; newval.active_ = (active_ & other.active_); newval.application_index_ = application_index_; return newval; @@ -2274,6 +2947,9 @@ class Validator { }; newval.active_ = active_; newval.application_index_ = application_index_; + + // TODO: support autocomplete_func_ + return newval; } @@ -2292,7 +2968,7 @@ class Validator { return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')'; }; } -}; // namespace CLI +}; // namespace CLI /// Class wrapping some of the accessors of Validator class CustomValidator : public Validator { @@ -2304,7 +2980,7 @@ class CustomValidator : public Validator { namespace detail { /// CLI enumeration of different file types -enum class path_type { nonexistant, file, directory }; +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 @@ -2312,12 +2988,12 @@ inline path_type check_path(const char *file) noexcept { std::error_code ec; auto stat = std::filesystem::status(file, ec); if(ec) { - return path_type::nonexistant; + return path_type::nonexistent; } switch(stat.type()) { case std::filesystem::file_type::none: case std::filesystem::file_type::not_found: - return path_type::nonexistant; + return path_type::nonexistent; case std::filesystem::file_type::directory: return path_type::directory; case std::filesystem::file_type::symlink: @@ -2345,7 +3021,7 @@ inline path_type check_path(const char *file) noexcept { return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; } #endif - return path_type::nonexistant; + return path_type::nonexistent; } #endif /// Check for an existing file (returns error message if check fails) @@ -2354,7 +3030,7 @@ class ExistingFileValidator : public Validator { ExistingFileValidator() : Validator("FILE") { func_ = [](std::string &filename) { auto path_result = check_path(filename.c_str()); - if(path_result == path_type::nonexistant) { + if(path_result == path_type::nonexistent) { return "File does not exist: " + filename; } if(path_result == path_type::directory) { @@ -2362,6 +3038,12 @@ class ExistingFileValidator : public Validator { } return std::string(); }; + autocomplete_func_ = [](const std::string &path) { + // TODO: return a special string (or a new CompletionOption object) indicating a file/dir so we will search + // only when needed? Not sure if it will work well with the AND/OR/! validators... + (void) path; + return std::vector{}; + }; } }; @@ -2371,7 +3053,7 @@ class ExistingDirectoryValidator : public Validator { ExistingDirectoryValidator() : Validator("DIR") { func_ = [](std::string &filename) { auto path_result = check_path(filename.c_str()); - if(path_result == path_type::nonexistant) { + if(path_result == path_type::nonexistent) { return "Directory does not exist: " + filename; } if(path_result == path_type::file) { @@ -2379,6 +3061,26 @@ class ExistingDirectoryValidator : public Validator { } return std::string(); }; + autocomplete_func_ = [](const std::string &path) { + /* + Bash `complete` command cannot append a completion to a previous one, so we cannot write our own file/dir + completions without outputting the entire path (instead of just the new relative addition) as a match. + `readline` supports this special behavior only when working with files by setting + rl_filename_completion_desired. complete.c::printable_part() uses rl_filename_completion_desired to + cut the path and keep just the last part of it. One can set rl_filename_completion_desired by passing + COPT_FILENAMES as an option. For example, _filedir is passing "-o filenames". + + For shell builtins source code, see https://www.gnu.org/software/bash/bash.html. + Some reading material about this topic: + https://unix.stackexchange.com/questions/462114/bash-completion-overwrites-current-word + https://stackoverflow.com/questions/3951628/showing-only-the-substrings-of-compreply-bash-completion-options-to-the-user + https://stackoverflow.com/questions/51254702/dont-replace-previous-input-if-only-the-substrings-of-compreply-bash-completion + */ + // TODO: return a special string (or a new CompletionOption object) indicating a file/dir so we will search + // only when needed? Not sure if it will work well with the AND/OR/! validators... + (void) path; + return std::vector{}; + }; } }; @@ -2388,7 +3090,7 @@ class ExistingPathValidator : public Validator { ExistingPathValidator() : Validator("PATH(existing)") { func_ = [](std::string &filename) { auto path_result = check_path(filename.c_str()); - if(path_result == path_type::nonexistant) { + if(path_result == path_type::nonexistent) { return "Path does not exist: " + filename; } return std::string(); @@ -2402,7 +3104,7 @@ class NonexistentPathValidator : public Validator { NonexistentPathValidator() : Validator("PATH(non-existing)") { func_ = [](std::string &filename) { auto path_result = check_path(filename.c_str()); - if(path_result != path_type::nonexistant) { + if(path_result != path_type::nonexistent) { return "Path already exists: " + filename; } return std::string(); @@ -2434,54 +3136,7 @@ class IPV4Validator : public Validator { } }; -/// Validate the argument is a number and greater than 0 -class PositiveNumber : public Validator { - public: - PositiveNumber() : Validator("POSITIVE") { - func_ = [](std::string &number_str) { - double number; - if(!detail::lexical_cast(number_str, number)) { - return std::string("Failed parsing number: (") + number_str + ')'; - } - if(number <= 0) { - return std::string("Number less or equal to 0: (") + number_str + ')'; - } - return std::string(); - }; - } -}; -/// Validate the argument is a number and greater than or equal to 0 -class NonNegativeNumber : public Validator { - public: - NonNegativeNumber() : Validator("NONNEGATIVE") { - func_ = [](std::string &number_str) { - double number; - if(!detail::lexical_cast(number_str, number)) { - return std::string("Failed parsing number: (") + number_str + ')'; - } - if(number < 0) { - return std::string("Number less than 0: (") + number_str + ')'; - } - return std::string(); - }; - } -}; - -/// Validate the argument is a number -class Number : public Validator { - public: - Number() : Validator("NUMBER") { - func_ = [](std::string &number_str) { - double number; - if(!detail::lexical_cast(number_str, number)) { - return std::string("Failed parsing as a number (") + number_str + ')'; - } - return std::string(); - }; - } -}; - -} // namespace detail +} // namespace detail // Static is not needed here, because global const implies static. @@ -2500,14 +3155,51 @@ const detail::NonexistentPathValidator NonexistentPath; /// Check for an IP4 address const detail::IPV4Validator ValidIPV4; -/// Check for a positive number -const detail::PositiveNumber PositiveNumber; - -/// Check for a non-negative number -const detail::NonNegativeNumber NonNegativeNumber; +/// 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"); + +/// Modify a path if the file is a particular default location, can be used as Check or transform +/// with the error return optionally disabled +class FileOnDefaultPath : public Validator { + public: + explicit FileOnDefaultPath(std::string default_path, bool enableErrorReturn = true) : Validator("FILE") { + func_ = [default_path, enableErrorReturn](std::string &filename) { + auto path_result = detail::check_path(filename.c_str()); + if(path_result == detail::path_type::nonexistent) { + std::string test_file_path = default_path; + if(default_path.back() != '/' && default_path.back() != '\\') { + // Add folder separator + test_file_path += '/'; + } + test_file_path.append(filename); + path_result = detail::check_path(test_file_path.c_str()); + if(path_result == detail::path_type::file) { + filename = test_file_path; + } else { + if(enableErrorReturn) { + return "File does not exist: " + filename; + } + } + } + return std::string{}; + }; + } +}; /// Produce a range (factory). Min and max are inclusive. class Range : public Validator { @@ -2516,26 +3208,39 @@ 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_val, T max_val, const std::string &validator_name = std::string{}) : Validator(validator_name) { + if(validator_name.empty()) { + std::stringstream out; + out << detail::type_name() << " in [" << min_val << " - " << max_val << "]"; + description(out.str()); + } - func_ = [min, max](std::string &input) { + func_ = [min_val, max_val](std::string &input) { T val; bool converted = detail::lexical_cast(input, val); - if((!converted) || (val < min || val > max)) - return std::string("Value ") + input + " not in range " + std::to_string(min) + " to " + - std::to_string(max); - - return std::string(); + if((!converted) || (val < min_val || val > max_val)) { + std::stringstream out; + out << "Value " << input << " not in range ["; + out << min_val << " - " << max_val << "]"; + return out.str(); + } + 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_val, const std::string &validator_name = std::string{}) + : Range(static_cast(0), max_val, 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: @@ -2543,28 +3248,28 @@ class Bound : 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 Bound(T min, T max) { + template Bound(T min_val, T max_val) { std::stringstream out; - out << detail::type_name() << " bounded to [" << min << " - " << max << "]"; + out << detail::type_name() << " bounded to [" << min_val << " - " << max_val << "]"; description(out.str()); - func_ = [min, max](std::string &input) { + func_ = [min_val, max_val](std::string &input) { T val; bool converted = detail::lexical_cast(input, val); if(!converted) { return std::string("Value ") + input + " could not be converted"; } - if(val < min) - input = detail::to_string(min); - else if(val > max) - input = detail::to_string(max); + if(val < min_val) + input = detail::to_string(min_val); + else if(val > max_val) + input = detail::to_string(max_val); return std::string{}; }; } /// Range of one value is 0 to value - template explicit Bound(T max) : Bound(static_cast(0), max) {} + template explicit Bound(T max_val) : Bound(static_cast(0), max_val) {} }; namespace detail { @@ -2583,7 +3288,7 @@ 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), @@ -2596,7 +3301,7 @@ 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), @@ -2707,7 +3412,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: @@ -2715,7 +3420,7 @@ class IsMember : public Validator { /// This allows in-place construction using an initializer list template - 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) @@ -2727,11 +3432,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; @@ -2744,7 +3449,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); @@ -2761,15 +3466,13 @@ 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(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)); }, @@ -2786,7 +3489,7 @@ class Transformer : public Validator { /// This allows in-place construction template - 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 @@ -2800,10 +3503,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; @@ -2830,7 +3533,7 @@ 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(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)); }, @@ -2844,7 +3547,7 @@ class CheckedTransformer : public Validator { /// This allows in-place construction template - 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 @@ -2858,12 +3561,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; @@ -2903,11 +3605,21 @@ class CheckedTransformer : public Validator { return "Check " + input + " " + tfunc() + " FAILED"; }; + + autocomplete_func_ = [mapping](const std::string &str) { + (void) str; + std::vector completions; + for(const auto &completion : detail::smart_deref(mapping)) { + auto output_string = detail::value_string(detail::pair_adaptor::first(completion)); + completions.emplace_back(output_string); + } + return completions; + }; } /// 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(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)); }, @@ -2984,14 +3696,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(std::string("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 {}; } @@ -3005,12 +3714,22 @@ 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::to_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::to_string(num); return {}; @@ -3071,7 +3790,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 @@ -3135,26 +3854,46 @@ inline std::pair split_program_name(std::string comman 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)); + } + } 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; @@ -3165,9 +3904,11 @@ 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 + AllCompact, ///< A compact version of fully expanded help + SubCompact, ///< Compact version of expanded subcommand }; /// This is the minimum requirements to run a formatter. @@ -3196,7 +3937,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; @@ -3241,7 +3982,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 { @@ -3277,7 +4018,7 @@ class Formatter : public FormatterBase { virtual std::string make_subcommand(const App *sub) const; /// This prints out a subcommand in help-all - virtual std::string make_expanded(const App *sub) const; + virtual std::string make_expanded(const App *sub, AppFormatMode mode = AppFormatMode::Sub) const; /// This prints out all the groups of options virtual std::string make_footer(const App *app) const; @@ -3318,11 +4059,8 @@ class Formatter : public FormatterBase { ///@} }; -} // namespace CLI -// From CLI/Option.hpp: -namespace CLI { using results_t = std::vector; /// callback function definition @@ -3334,11 +4072,12 @@ class App; using Option_p = std::unique_ptr