diff --git a/.gitmodules b/.gitmodules index 7f7182b0..ead476e8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "googletest"] path = ThirdParty/googletest url = https://github.com/google/googletest.git -[submodule "ThirdParty/googletest"] - path = ThirdParty/googletest - url = https://github.com/google/googletest.git diff --git a/ThirdParty/CLI11.hpp b/ThirdParty/CLI11.hpp index 2e8c021a..8d793492 100644 --- a/ThirdParty/CLI11.hpp +++ b/ThirdParty/CLI11.hpp @@ -1,11 +1,11 @@ -// CLI11: Version 2.4.2 +// CLI11: Version 2.5.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: v2.4.2-29-g2be38c5 +// from: v2.5.0-10-g37b143e // -// CLI11 2.4.2 Copyright (c) 2017-2024 University of Cincinnati, developed by Henry +// CLI11 2.5.0 Copyright (c) 2017-2025 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 @@ -65,9 +65,9 @@ #define CLI11_VERSION_MAJOR 2 -#define CLI11_VERSION_MINOR 4 -#define CLI11_VERSION_PATCH 2 -#define CLI11_VERSION "2.4.2" +#define CLI11_VERSION_MINOR 5 +#define CLI11_VERSION_PATCH 0 +#define CLI11_VERSION "2.5.0" @@ -80,11 +80,17 @@ #define CLI11_CPP17 #if __cplusplus > 201703L #define CLI11_CPP20 +#if __cplusplus > 202002L +#define CLI11_CPP23 +#if __cplusplus > 202302L +#define CLI11_CPP26 +#endif +#endif #endif #endif #endif #elif defined(_MSC_VER) && __cplusplus == 199711L -// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) +// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard was fully implemented) // Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer #if _MSVC_LANG >= 201402L #define CLI11_CPP14 @@ -92,6 +98,9 @@ #define CLI11_CPP17 #if _MSVC_LANG > 201703L && _MSC_VER >= 1910 #define CLI11_CPP20 +#if _MSVC_LANG > 202002L && _MSC_VER >= 1922 +#define CLI11_CPP23 +#endif #endif #endif #endif @@ -115,15 +124,15 @@ /** detection of rtti */ #ifndef CLI11_USE_STATIC_RTTI -#if(defined(_HAS_STATIC_RTTI) && _HAS_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) +#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) +#elif (defined(__GCC_RTTI) && __GXX_RTTI) #define CLI11_USE_STATIC_RTTI 0 #else #define CLI11_USE_STATIC_RTTI 1 @@ -158,12 +167,22 @@ #endif /** availability */ +#if !defined(CLI11_CPP26) && !defined(CLI11_HAS_CODECVT) #if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 5 #define CLI11_HAS_CODECVT 0 #else #define CLI11_HAS_CODECVT 1 #include #endif +#else +#if defined(CLI11_HAS_CODECVT) +#if CLI11_HAS_CODECVT > 0 +#include +#endif +#else +#define CLI11_HAS_CODECVT 0 +#endif +#endif /** disable deprecations */ #if defined(__GNUC__) // GCC or clang @@ -450,7 +469,8 @@ namespace enums { template ::value>::type> std::ostream &operator<<(std::ostream &in, const T &item) { // make sure this is out of the detail namespace otherwise it won't be found when needed - return in << static_cast::type>(item); + // https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number + return in << +static_cast::type>(item); } } // namespace enums @@ -558,9 +578,6 @@ 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 -CLI11_INLINE std::ostream & -format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid); /// Print subcommand aliases CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector &aliases, std::size_t wid); @@ -663,7 +680,7 @@ CLI11_INLINE bool has_escapable_character(const std::string &str); /// @brief escape all escapable characters /// @param str the string to escape -/// @return a string with the escapble characters escaped with '\' +/// @return a string with the escapable characters escaped with '\' CLI11_INLINE std::string add_escaped_characters(const std::string &str); /// @brief replace the escaped characters with their equivalent @@ -680,6 +697,14 @@ CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string /// process a quoted string, remove the quotes and if appropriate handle escaped characters CLI11_INLINE bool process_quoted_string(std::string &str, char string_char = '\"', char literal_char = '\''); +/// This function formats the given text as a paragraph with fixed width and applies correct line wrapping +/// with a custom line prefix. The paragraph will get streamed to the given ostream. +CLI11_INLINE std::ostream &streamOutAsParagraph(std::ostream &out, + const std::string &text, + std::size_t paragraphWidth, + const std::string &linePrefix = "", + bool skipPrefixOnFirstLine = false); + } // namespace detail @@ -759,24 +784,6 @@ CLI11_INLINE std::string fix_newlines(const std::string &leader, std::string inp return input; } -CLI11_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()) { - if(name.length() >= wid) - out << "\n" << std::setw(static_cast(wid)) << ""; - for(const char c : description) { - out.put(c); - if(c == '\n') { - out << std::setw(static_cast(wid)) << ""; - } - } - } - out << "\n"; - return out; -} - CLI11_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: "; @@ -1115,6 +1122,13 @@ CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escap stream << std::hex << static_cast(static_cast(c)); std::string code = stream.str(); escaped_string += std::string("\\x") + (code.size() < 2 ? "0" : "") + code; + } else if(c == 'x' || c == 'X') { + // need to check for inadvertent binary sequences + if(!escaped_string.empty() && escaped_string.back() == '\\') { + escaped_string += std::string("\\x") + (c == 'x' ? "78" : "58"); + } else { + escaped_string.push_back(c); + } } else { escaped_string.push_back(c); @@ -1194,12 +1208,25 @@ CLI11_INLINE void remove_quotes(std::vector &args) { } } +CLI11_INLINE void handle_secondary_array(std::string &str) { + if(str.size() >= 2 && str.front() == '[' && str.back() == ']') { + // handle some special array processing for arguments if it might be interpreted as a secondary array + std::string tstr{"[["}; + for(std::size_t ii = 1; ii < str.size(); ++ii) { + tstr.push_back(str[ii]); + tstr.push_back(str[ii]); + } + str = std::move(tstr); + } +} + CLI11_INLINE bool process_quoted_string(std::string &str, char string_char, char literal_char) { if(str.size() <= 1) { return false; } if(detail::is_binary_escaped_string(str)) { str = detail::extract_binary_string(str); + handle_secondary_array(str); return true; } if(str.front() == string_char && str.back() == string_char) { @@ -1207,10 +1234,12 @@ CLI11_INLINE bool process_quoted_string(std::string &str, char string_char, char if(str.find_first_of('\\') != std::string::npos) { str = detail::remove_escaped_characters(str); } + handle_secondary_array(str); return true; } if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) { detail::remove_outer(str, str.front()); + handle_secondary_array(str); return true; } return false; @@ -1237,6 +1266,37 @@ std::string get_environment_value(const std::string &env_name) { return ename_string; } +CLI11_INLINE std::ostream &streamOutAsParagraph(std::ostream &out, + const std::string &text, + std::size_t paragraphWidth, + const std::string &linePrefix, + bool skipPrefixOnFirstLine) { + if(!skipPrefixOnFirstLine) + out << linePrefix; // First line prefix + + std::istringstream lss(text); + std::string line = ""; + while(std::getline(lss, line)) { + std::istringstream iss(line); + std::string word = ""; + std::size_t charsWritten = 0; + + while(iss >> word) { + if(word.length() + charsWritten > paragraphWidth) { + out << '\n' << linePrefix; + charsWritten = 0; + } + + out << word << " "; + charsWritten += word.length() + 1; + } + + if(!lss.eof()) + out << '\n' << linePrefix; + } + return out; +} + } // namespace detail @@ -1493,7 +1553,7 @@ class ArgumentMismatch : public ParseError { std::to_string(received)); } static ArgumentMismatch AtMost(std::string name, int num, std::size_t received) { - return ArgumentMismatch(name + ": At Most " + std::to_string(num) + " required but received " + + return ArgumentMismatch(name + ": At most " + std::to_string(num) + " required but received " + std::to_string(received)); } static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { @@ -1565,7 +1625,7 @@ class HorribleError : public ParseError { // After parsing -/// Thrown when counting a non-existent option +/// Thrown when counting a nonexistent option class OptionNotFound : public Error { CLI11_ERROR_DEF(Error, OptionNotFound) explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} @@ -1715,7 +1775,7 @@ struct pair_adaptor< } }; -// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a Wnarrowing warning +// Warning is suppressed due to "bug" in gcc<5.0 and gcc 7.0 with c++17 enabled that generates a -Wnarrowing warning // in the unevaluated context even if the function that was using this wasn't used. The standard says narrowing in // brace initialization shouldn't be allowed but for backwards compatibility gcc allows it in some contexts. It is a // little fuzzy what happens in template constructs and I think that was something GCC took a little while to work out. @@ -1737,7 +1797,7 @@ template class is_direct_constructible { #pragma diag_suppress 2361 #endif #endif - TT{std::declval()} + TT{std::declval()} #ifdef __CUDACC__ #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ #pragma nv_diag_default 2361 @@ -1745,8 +1805,8 @@ template class is_direct_constructible { #pragma diag_default 2361 #endif #endif - , - std::is_move_assignable()); + , + std::is_move_assignable()); template static auto test(int, std::false_type) -> std::false_type; @@ -1831,9 +1891,8 @@ struct is_mutable_container< // 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 +/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, and an end +/// method. template struct is_readable_container< T, @@ -1848,8 +1907,9 @@ template struct is_wrapper, void>> : public std::true_type {}; // Check for tuple like types, as in classes with a tuple_size type trait +// Even though in C++26 std::complex gains a std::tuple interface, for our purposes we treat is as NOT a tuple template class is_tuple_like { - template + template ::value, detail::enabler> = detail::dummy> // static auto test(int) // -> decltype(std::conditional<(std::tuple_size::value > 0), std::true_type, std::false_type>::type()); static auto test(int) -> decltype(std::tuple_size::type>::value, std::true_type{}); @@ -1859,6 +1919,31 @@ template class is_tuple_like { static constexpr bool value = decltype(test(0))::value; }; +/// This will only trigger for actual void type +template struct type_count_base { + static const int value{0}; +}; + +/// Type size for regular object types that do not look like a tuple +template +struct type_count_base::value && !is_mutable_container::value && + !std::is_void::value>::type> { + static constexpr int value{1}; +}; + +/// the base tuple size +template +struct type_count_base::value && !is_mutable_container::value>::type> { + static constexpr int value{// cppcheck-suppress unusedStructMember + std::tuple_size::type>::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}; +}; + /// Convert an object to a string (directly forward if this can become a string) template ::value, detail::enabler> = detail::dummy> auto to_string(T &&value) -> decltype(std::forward(value)) { @@ -1869,13 +1954,13 @@ auto to_string(T &&value) -> decltype(std::forward(value)) { template ::value && !std::is_convertible::value, detail::enabler> = detail::dummy> -std::string to_string(const T &value) { +std::string to_string(T &&value) { return std::string(value); // NOLINT(google-readability-casting) } /// Convert an object to a string (streaming must be supported for that type) template ::value && !std::is_constructible::value && + enable_if_t::value && !std::is_constructible::value && is_ostreamable::value, detail::enabler> = detail::dummy> std::string to_string(T &&value) { @@ -1884,21 +1969,39 @@ std::string to_string(T &&value) { return stream.str(); } -/// If conversion is not supported, return an empty string (streaming is not supported for that type) +// additional forward declarations + +/// Print tuple value string for tuples of size ==1 template ::value && !is_ostreamable::value && - !is_readable_container::type>::value, + enable_if_t::value && !std::is_constructible::value && + !is_ostreamable::value && is_tuple_like::value && type_count_base::value == 1, detail::enabler> = detail::dummy> -std::string to_string(T &&) { +inline std::string to_string(T &&value); + +/// Print tuple value string for tuples of size > 1 +template ::value && !std::is_constructible::value && + !is_ostreamable::value && is_tuple_like::value && type_count_base::value >= 2, + detail::enabler> = detail::dummy> +inline std::string to_string(T &&value); + +/// If conversion is not supported, return an empty string (streaming is not supported for that type) +template < + typename T, + enable_if_t::value && !std::is_constructible::value && + !is_ostreamable::value && !is_readable_container::type>::value && + !is_tuple_like::value, + detail::enabler> = detail::dummy> +inline std::string to_string(T &&) { return {}; } /// convert a readable container to a string template ::value && !is_ostreamable::value && - is_readable_container::value, + enable_if_t::value && !std::is_constructible::value && + !is_ostreamable::value && is_readable_container::value, detail::enabler> = detail::dummy> -std::string to_string(T &&variable) { +inline std::string to_string(T &&variable) { auto cval = variable.begin(); auto end = variable.end(); if(cval == end) { @@ -1912,6 +2015,51 @@ std::string to_string(T &&variable) { return {"[" + detail::join(defaults) + "]"}; } +/// Convert a tuple like object to a string + +/// forward declarations for tuple_value_strings +template +inline typename std::enable_if::value, std::string>::type tuple_value_string(T && /*value*/); + +/// Recursively generate the tuple value string +template +inline typename std::enable_if<(I < type_count_base::value), std::string>::type tuple_value_string(T &&value); + +/// Print tuple value string for tuples of size ==1 +template ::value && !std::is_constructible::value && + !is_ostreamable::value && is_tuple_like::value && type_count_base::value == 1, + detail::enabler>> +inline std::string to_string(T &&value) { + return to_string(std::get<0>(value)); +} + +/// Print tuple value string for tuples of size > 1 +template ::value && !std::is_constructible::value && + !is_ostreamable::value && is_tuple_like::value && type_count_base::value >= 2, + detail::enabler>> +inline std::string to_string(T &&value) { + auto tname = std::string(1, '[') + tuple_value_string(value); + tname.push_back(']'); + return tname; +} + +/// Empty string if the index > tuple size +template +inline typename std::enable_if::value, std::string>::type tuple_value_string(T && /*value*/) { + return std::string{}; +} + +/// Recursively generate the tuple value string +template +inline typename std::enable_if<(I < type_count_base::value), std::string>::type tuple_value_string(T &&value) { + auto str = std::string{to_string(std::get(value))} + ',' + tuple_value_string(value); + if(str.back() == ',') + str.pop_back(); + return str; +} + /// special template overload template struct wrapped_type 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 @@ -2456,7 +2580,10 @@ bool integral_conversion(const std::string &input, T &output) noexcept { nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); return integral_conversion(nstring, output); } - if(input.compare(0, 2, "0o") == 0) { + if(std::isspace(static_cast(input.back()))) { + return integral_conversion(trim_copy(input), output); + } + if(input.compare(0, 2, "0o") == 0 || input.compare(0, 2, "0O") == 0) { val = nullptr; errno = 0; output_ll = std::strtoull(input.c_str() + 2, &val, 8); @@ -2466,7 +2593,10 @@ bool integral_conversion(const std::string &input, T &output) noexcept { output = static_cast(output_ll); return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); } - if(input.compare(0, 2, "0b") == 0) { + if(input.compare(0, 2, "0b") == 0 || input.compare(0, 2, "0B") == 0) { + // LCOV_EXCL_START + // In some new compilers including the coverage testing one binary strings are handled properly in strtoull + // automatically so this coverage is missing but is well tested in other compilers val = nullptr; errno = 0; output_ll = std::strtoull(input.c_str() + 2, &val, 2); @@ -2475,6 +2605,7 @@ bool integral_conversion(const std::string &input, T &output) noexcept { } output = static_cast(output_ll); return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + // LCOV_EXCL_STOP } return false; } @@ -2500,14 +2631,17 @@ bool integral_conversion(const std::string &input, T &output) noexcept { output = static_cast(1); return true; } - // remove separators + // remove separators and trailing spaces if(input.find_first_of("_'") != std::string::npos) { std::string nstring = input; nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); return integral_conversion(nstring, output); } - if(input.compare(0, 2, "0o") == 0) { + if(std::isspace(static_cast(input.back()))) { + return integral_conversion(trim_copy(input), output); + } + if(input.compare(0, 2, "0o") == 0 || input.compare(0, 2, "0O") == 0) { val = nullptr; errno = 0; output_ll = std::strtoll(input.c_str() + 2, &val, 8); @@ -2517,7 +2651,10 @@ bool integral_conversion(const std::string &input, T &output) noexcept { output = static_cast(output_ll); return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); } - if(input.compare(0, 2, "0b") == 0) { + if(input.compare(0, 2, "0b") == 0 || input.compare(0, 2, "0B") == 0) { + // LCOV_EXCL_START + // In some new compilers including the coverage testing one binary strings are handled properly in strtoll + // automatically so this coverage is missing but is well tested in other compilers val = nullptr; errno = 0; output_ll = std::strtoll(input.c_str() + 2, &val, 2); @@ -2526,6 +2663,7 @@ bool integral_conversion(const std::string &input, T &output) noexcept { } output = static_cast(output_ll); return (val == (input.c_str() + input.size()) && static_cast(output) == output_ll); + // LCOV_EXCL_STOP } return false; } @@ -2627,6 +2765,13 @@ bool lexical_cast(const std::string &input, T &output) { if(val == (input.c_str() + input.size())) { return true; } + while(std::isspace(static_cast(*val))) { + ++val; + if(val == (input.c_str() + input.size())) { + return true; + } + } + // remove separators if(input.find_first_of("_'") != std::string::npos) { std::string nstring = input; @@ -2866,7 +3011,7 @@ bool lexical_assign(const std::string &input, AssignTo &output) { } return lexical_cast(input, output); -} +} // LCOV_EXCL_LINE /// Assign a value through lexical cast operations template > get_default_flag_v /// Get a vector of short names, one of long names, and a single name CLI11_INLINE std::tuple, std::vector, std::string> -get_names(const std::vector &input); +get_names(const std::vector &input, bool allow_non_standard = false); } // namespace detail @@ -3417,7 +3562,7 @@ CLI11_INLINE std::vector> get_default_flag_v } CLI11_INLINE std::tuple, std::vector, std::string> -get_names(const std::vector &input) { +get_names(const std::vector &input, bool allow_non_standard) { std::vector short_names; std::vector long_names; @@ -3427,23 +3572,35 @@ get_names(const std::vector &input) { continue; } if(name.length() > 1 && name[0] == '-' && name[1] != '-') { - if(name.length() == 2 && valid_first_char(name[1])) + if(name.length() == 2 && valid_first_char(name[1])) { short_names.emplace_back(1, name[1]); - else if(name.length() > 2) - throw BadNameString::MissingDash(name); - else + } else if(name.length() > 2) { + if(allow_non_standard) { + name = name.substr(1); + if(valid_name_string(name)) { + short_names.push_back(name); + } else { + throw BadNameString::BadLongName(name); + } + } else { + throw BadNameString::MissingDash(name); + } + } else { throw BadNameString::OneCharName(name); + } } else if(name.length() > 2 && name.substr(0, 2) == "--") { name = name.substr(2); - if(valid_name_string(name)) + if(valid_name_string(name)) { long_names.push_back(name); - else + } else { throw BadNameString::BadLongName(name); + } } else if(name == "-" || name == "--" || name == "++") { throw BadNameString::ReservedName(name); } else { - if(!pos_name.empty()) + if(!pos_name.empty()) { throw BadNameString::MultiPositionalNames(name); + } if(valid_name_string(name)) { pos_name = name; } else { @@ -3469,12 +3626,14 @@ struct ConfigItem { std::string name{}; /// Listing of inputs std::vector inputs{}; - + /// @brief indicator if a multiline vector separator was inserted + bool multiline{false}; /// The list of parents and name joined by "." CLI11_NODISCARD std::string fullname() const { std::vector tmp = parents; tmp.emplace_back(name); return detail::join(tmp, "."); + (void)multiline; // suppression for cppcheck false positive } }; @@ -3535,6 +3694,10 @@ class ConfigBase : public Config { uint8_t maximumLayers{255}; /// the separator used to separator parent layers char parentSeparatorChar{'.'}; + /// comment default values + bool commentDefaultsBool = false; + /// specify the config reader should collapse repeated field names to a single vector + bool allowMultipleDuplicateFields{false}; /// Specify the configuration index to use for arrayed sections int16_t configIndex{-1}; /// Specify the configuration section that should be used @@ -3582,6 +3745,11 @@ class ConfigBase : public Config { parentSeparatorChar = sep; return this; } + /// comment default value options + ConfigBase *commentDefaults(bool comDef = true) { + commentDefaultsBool = comDef; + return this; + } /// get a reference to the configuration section std::string §ionRef() { return configSection; } /// get the section @@ -3601,6 +3769,11 @@ class ConfigBase : public Config { configIndex = sectionIndex; return this; } + /// specify that multiple duplicate arguments should be merged even if not sequential + ConfigBase *allowDuplicateFields(bool value = true) { + allowMultipleDuplicateFields = value; + return this; + } }; /// the default Config is the TOML file format @@ -4841,9 +5014,18 @@ class FormatterBase { /// @name Options ///@{ - /// The width of the first column + /// The width of the left column (options/flags/subcommands) std::size_t column_width_{30}; + /// The width of the right column (description of options/flags/subcommands) + std::size_t right_column_width_{65}; + + /// The width of the description paragraph at the top of help + std::size_t description_paragraph_width_{80}; + + /// The width of the footer paragraph + std::size_t footer_paragraph_width_{80}; + /// @brief The required help printout labels (user changeable) /// Values are Needs, Excludes, etc. std::map labels_{}; @@ -4872,9 +5054,18 @@ class FormatterBase { /// Set the "REQUIRED" label void label(std::string key, std::string val) { labels_[key] = val; } - /// Set the column width + /// Set the left column width (options/flags/subcommands) void column_width(std::size_t val) { column_width_ = val; } + /// Set the right column width (description of options/flags/subcommands) + void right_column_width(std::size_t val) { right_column_width_ = val; } + + /// Set the description paragraph width at the top of help + void description_paragraph_width(std::size_t val) { description_paragraph_width_ = val; } + + /// Set the footer paragraph width + void footer_paragraph_width(std::size_t val) { footer_paragraph_width_ = val; } + ///@} /// @name Getters ///@{ @@ -4886,9 +5077,18 @@ class FormatterBase { return labels_.at(key); } - /// Get the current column width + /// Get the current left column width (options/flags/subcommands) CLI11_NODISCARD std::size_t get_column_width() const { return column_width_; } + /// Get the current right column width (description of options/flags/subcommands) + CLI11_NODISCARD std::size_t get_right_column_width() const { return right_column_width_; } + + /// Get the current description paragraph width at the top of help + CLI11_NODISCARD std::size_t get_description_paragraph_width() const { return description_paragraph_width_; } + + /// Get the current footer paragraph width + CLI11_NODISCARD std::size_t get_footer_paragraph_width() const { return footer_paragraph_width_; } + ///@} }; @@ -4943,7 +5143,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) const; /// This prints out all the groups of options virtual std::string make_footer(const App *app) const; @@ -4955,19 +5155,14 @@ class Formatter : public FormatterBase { virtual std::string make_usage(const App *app, std::string name) const; /// This puts everything together - std::string make_help(const App * /*app*/, std::string, AppFormatMode) const override; + std::string make_help(const App *app, std::string, AppFormatMode mode) const override; ///@} /// @name Options ///@{ /// This prints out an option help line, either positional or optional form - virtual std::string make_option(const Option *opt, bool is_positional) const { - std::stringstream out; - detail::format_help( - out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_); - return out.str(); - } + virtual std::string make_option(const Option *, bool) const; /// @brief This is the name part of an option, Default: left column virtual std::string make_option_name(const Option *, bool) const; @@ -5013,7 +5208,7 @@ template class OptionBase { protected: /// The group membership - std::string group_ = std::string("Options"); + std::string group_ = std::string("OPTIONS"); /// True if this is a required option bool required_{false}; @@ -5300,9 +5495,13 @@ class Option : public OptionBase