diff --git a/.codecov.yml b/.codecov.yml index 61c2e2f2..7ba78c16 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -5,3 +5,11 @@ ignore: - "docs" - "test_package" - "fuzz" + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index b95165c6..24a5be64 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -75,8 +75,8 @@ set_property( "Working on count: 2, direct count: 2, opt count: 2" "Some value: 1.2") # test shows that the help prints out for unnamed subcommands add_test(NAME subcom_partitioned_help COMMAND subcom_partitioned --help) -set_property(TEST subcom_partitioned_help PROPERTY PASS_REGULAR_EXPRESSION - "-f,--file TEXT REQUIRED" "-d,--double FLOAT") +set_property(TEST subcom_partitioned_help + PROPERTY PASS_REGULAR_EXPRESSION "-f,[ \\t]*--file TEXT REQUIRED" "-d,--double FLOAT") #################################################### add_cli_exe(config_app config_app.cpp) @@ -145,8 +145,8 @@ add_cli_exe(validators validators.cpp) add_test(NAME validators_help COMMAND validators --help) set_property( TEST validators_help - PROPERTY PASS_REGULAR_EXPRESSION " -f,--file TEXT:FILE[\\r\\n\\t ]+File name" - " -v,--value INT:INT in [3 - 6][\\r\\n\\t ]+Value in range") + PROPERTY PASS_REGULAR_EXPRESSION " -f,[ \\t]*--file TEXT:FILE[\\r\\n\\t ]+File name" + " -v,[ \\t]*--value INT:INT in [3 - 6][\\r\\n\\t ]+Value in range") add_test(NAME validators_file COMMAND validators --file nonex.xxx) set_property( TEST validators_file PROPERTY PASS_REGULAR_EXPRESSION "--file: File does not exist: nonex.xxx" diff --git a/fuzz/fuzzApp.cpp b/fuzz/fuzzApp.cpp index 16123ce1..092c2397 100644 --- a/fuzz/fuzzApp.cpp +++ b/fuzz/fuzzApp.cpp @@ -44,6 +44,7 @@ std::optional> tcomplex; std::string_view vstrv; std::shared_ptr FuzzApp::generateApp() { auto fApp = std::make_shared("fuzzing App", "fuzzer"); fApp->set_config("--config"); + fApp->set_help_all_flag("--help-all"); fApp->add_flag("-a,--flag"); fApp->add_flag("-b,--flag2,!--nflag2", flag1); fApp->add_flag("-c{34},--flag3{1}", flagCnt)->disable_flag_override(); diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 892bd1f2..f98463f9 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -275,7 +275,7 @@ class App { App *parent_{nullptr}; /// The group membership INHERITABLE - std::string group_{"Subcommands"}; + std::string group_{"SUBCOMMANDS"}; /// Alias names for the subcommand std::vector aliases_{}; diff --git a/include/CLI/FormatterFwd.hpp b/include/CLI/FormatterFwd.hpp index ad3f49c3..f86226c9 100644 --- a/include/CLI/FormatterFwd.hpp +++ b/include/CLI/FormatterFwd.hpp @@ -44,9 +44,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_{}; @@ -75,9 +84,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 ///@{ @@ -89,9 +107,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_; } + ///@} }; @@ -146,7 +173,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; @@ -158,19 +185,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; diff --git a/include/CLI/Option.hpp b/include/CLI/Option.hpp index 40b7f6f6..f6f848aa 100644 --- a/include/CLI/Option.hpp +++ b/include/CLI/Option.hpp @@ -54,7 +54,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}; diff --git a/include/CLI/StringTools.hpp b/include/CLI/StringTools.hpp index 81bdf152..e2df32fb 100644 --- a/include/CLI/StringTools.hpp +++ b/include/CLI/StringTools.hpp @@ -141,9 +141,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); @@ -263,6 +260,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 ostrean. +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 // [CLI11:string_tools_hpp:end] diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index a812ed94..4cd7a9f5 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -904,7 +904,7 @@ 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(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); @@ -914,7 +914,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); @@ -923,6 +926,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; } @@ -955,7 +959,7 @@ 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(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); @@ -965,7 +969,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); @@ -974,6 +981,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; } diff --git a/include/CLI/impl/App_inl.hpp b/include/CLI/impl/App_inl.hpp index 12b095a2..b66bab7c 100644 --- a/include/CLI/impl/App_inl.hpp +++ b/include/CLI/impl/App_inl.hpp @@ -2265,11 +2265,14 @@ CLI11_INLINE void retire_option(App *app, Option *opt) { ->expected(option_copy->get_expected_min(), option_copy->get_expected_max()) ->allow_extra_args(option_copy->get_allow_extra_args()); + // LCOV_EXCL_START + // something odd with coverage on new compilers Validator retired_warning{[opt2](std::string &) { std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n"; return std::string(); }, ""}; + // LCOV_EXCL_STOP retired_warning.application_index(0); opt2->check(retired_warning); } @@ -2287,11 +2290,14 @@ CLI11_INLINE void retire_option(App *app, const std::string &option_name) { ->type_name("RETIRED") ->expected(0, 1) ->default_str("RETIRED"); + // LCOV_EXCL_START + // something odd with coverage on new compilers Validator retired_warning{[opt2](std::string &) { std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n"; return std::string(); }, ""}; + // LCOV_EXCL_STOP retired_warning.application_index(0); opt2->check(retired_warning); } diff --git a/include/CLI/impl/Config_inl.hpp b/include/CLI/impl/Config_inl.hpp index 848f0b26..83cd4432 100644 --- a/include/CLI/impl/Config_inl.hpp +++ b/include/CLI/impl/Config_inl.hpp @@ -517,18 +517,18 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::vector groups = app->get_groups(); bool defaultUsed = false; - groups.insert(groups.begin(), std::string("Options")); + groups.insert(groups.begin(), std::string("OPTIONS")); if(write_description && (app->get_configurable() || app->get_parent() == nullptr || app->get_name().empty())) { out << commentLead << detail::fix_newlines(commentLead, app->get_description()) << '\n'; } for(auto &group : groups) { - if(group == "Options" || group.empty()) { + if(group == "OPTIONS" || group.empty()) { if(defaultUsed) { continue; } defaultUsed = true; } - if(write_description && group != "Options" && !group.empty()) { + if(write_description && group != "OPTIONS" && !group.empty()) { out << '\n' << commentLead << group << " Options\n"; } for(const Option *opt : app->get_options({})) { @@ -536,7 +536,7 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description, // Only process options that are configurable if(opt->get_configurable()) { if(opt->get_group() != group) { - if(!(group == "Options" && opt->get_group().empty())) { + if(!(group == "OPTIONS" && opt->get_group().empty())) { continue; } } diff --git a/include/CLI/impl/Formatter_inl.hpp b/include/CLI/impl/Formatter_inl.hpp index a5b8d043..9a5e9f7b 100644 --- a/include/CLI/impl/Formatter_inl.hpp +++ b/include/CLI/impl/Formatter_inl.hpp @@ -39,7 +39,7 @@ CLI11_INLINE std::string Formatter::make_positionals(const App *app) const { if(opts.empty()) return {}; - return make_group(get_label("Positionals"), true, opts); + return make_group(get_label("POSITIONALS"), true, opts); } CLI11_INLINE std::string Formatter::make_groups(const App *app, AppFormatMode mode) const { @@ -58,8 +58,9 @@ CLI11_INLINE std::string Formatter::make_groups(const App *app, AppFormatMode mo if(!group.empty() && !opts.empty()) { out << make_group(group, false, opts); - if(group != groups.back()) - out << "\n"; + // Removed double newline between groups for consistency of help text + // if(group != groups.back()) + // out << "\n"; } } @@ -95,12 +96,16 @@ CLI11_INLINE std::string Formatter::make_description(const App *app) const { CLI11_INLINE std::string Formatter::make_usage(const App *app, std::string name) const { std::string usage = app->get_usage(); if(!usage.empty()) { - return usage + "\n"; + return usage + "\n\n"; } std::stringstream out; + out << '\n'; - out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name; + if(name.empty()) + out << get_label("Usage") << ':'; + else + out << name; std::vector groups = app->get_groups(); @@ -128,13 +133,13 @@ CLI11_INLINE std::string Formatter::make_usage(const App *app, std::string name) if(!app->get_subcommands( [](const CLI::App *subc) { return ((!subc->get_disabled()) && (!subc->get_name().empty())); }) .empty()) { - out << " " << (app->get_require_subcommand_min() == 0 ? "[" : "") + out << ' ' << (app->get_require_subcommand_min() == 0 ? "[" : "") << get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND" : "SUBCOMMANDS") << (app->get_require_subcommand_min() == 0 ? "]" : ""); } - out << '\n'; + out << "\n\n"; return out.str(); } @@ -144,29 +149,29 @@ CLI11_INLINE std::string Formatter::make_footer(const App *app) const { if(footer.empty()) { return std::string{}; } - return "\n" + footer + "\n"; + return '\n' + footer + "\n\n"; } CLI11_INLINE std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const { - // This immediately forwards to the make_expanded method. This is done this way so that subcommands can // have overridden formatters if(mode == AppFormatMode::Sub) - return make_expanded(app); + return make_expanded(app, mode); std::stringstream out; if((app->get_name().empty()) && (app->get_parent() != nullptr)) { - if(app->get_group() != "Subcommands") { + if(app->get_group() != "SUBCOMMANDS") { out << app->get_group() << ':'; } } - out << make_description(app); out << make_usage(app, name); + detail::streamOutAsParagraph( + out, make_description(app), description_paragraph_width_, " "); // Format description as paragraph out << make_positionals(app); out << make_groups(app, mode); out << make_subcommands(app, mode); - out << make_footer(app); + detail::streamOutAsParagraph(out, make_footer(app), footer_paragraph_width_); // Format footer as paragraph return out.str(); } @@ -181,7 +186,7 @@ CLI11_INLINE std::string Formatter::make_subcommands(const App *app, AppFormatMo for(const App *com : subcommands) { if(com->get_name().empty()) { if(!com->get_group().empty() && com->get_group().front() != '+') { - out << make_expanded(com); + out << make_expanded(com, mode); } continue; } @@ -195,7 +200,7 @@ CLI11_INLINE std::string Formatter::make_subcommands(const App *app, AppFormatMo // For each group, filter out and print subcommands for(const std::string &group : subcmd_groups_seen) { - out << "\n" << group << ":\n"; + out << '\n' << group << ":\n"; std::vector subcommands_group = app->get_subcommands( [&group](const App *sub_app) { return detail::to_lower(sub_app->get_group()) == detail::to_lower(group); }); for(const App *new_com : subcommands_group) { @@ -205,7 +210,7 @@ CLI11_INLINE std::string Formatter::make_subcommands(const App *app, AppFormatMo out << make_subcommand(new_com); } else { out << new_com->help(new_com->get_name(), AppFormatMode::Sub); - out << "\n"; + out << '\n'; } } } @@ -215,31 +220,123 @@ CLI11_INLINE std::string Formatter::make_subcommands(const App *app, AppFormatMo CLI11_INLINE std::string Formatter::make_subcommand(const App *sub) const { std::stringstream out; - detail::format_help(out, - sub->get_display_name(true) + (sub->get_required() ? " " + get_label("REQUIRED") : ""), - sub->get_description(), - column_width_); + std::string name = " " + sub->get_display_name(true) + (sub->get_required() ? " " + get_label("REQUIRED") : ""); + + out << std::setw(static_cast(column_width_)) << std::left << name; + detail::streamOutAsParagraph( + out, sub->get_description(), right_column_width_, std::string(column_width_, ' '), true); + out << '\n'; return out.str(); } -CLI11_INLINE std::string Formatter::make_expanded(const App *sub) const { +CLI11_INLINE std::string Formatter::make_expanded(const App *sub, AppFormatMode mode) const { std::stringstream out; - out << sub->get_display_name(true) << "\n"; + out << sub->get_display_name(true) << '\n'; + + detail::streamOutAsParagraph( + out, make_description(sub), description_paragraph_width_, " "); // Format description as paragraph - out << make_description(sub); if(sub->get_name().empty() && !sub->get_aliases().empty()) { detail::format_aliases(out, sub->get_aliases(), column_width_ + 2); } + out << make_positionals(sub); - out << make_groups(sub, AppFormatMode::Sub); - out << make_subcommands(sub, AppFormatMode::Sub); + out << make_groups(sub, mode); + out << make_subcommands(sub, mode); + detail::streamOutAsParagraph(out, make_footer(sub), footer_paragraph_width_); // Format footer as paragraph - // Drop blank spaces - std::string tmp = detail::find_and_replace(out.str(), "\n\n", "\n"); - tmp = tmp.substr(0, tmp.size() - 1); // Remove the final '\n' + out << '\n'; + return out.str(); +} - // Indent all but the first line (the name) - return detail::find_and_replace(tmp, "\n", "\n ") + "\n"; +CLI11_INLINE std::string Formatter::make_option(const Option *opt, bool is_positional) const { + std::stringstream out; + if(is_positional) { + const std::string left = " " + make_option_name(opt, true) + make_option_opts(opt); + const std::string desc = make_option_desc(opt); + out << std::setw(static_cast(column_width_)) << std::left << left; + + if(!desc.empty()) { + bool skipFirstLinePrefix = true; + if(left.length() >= column_width_) { + out << '\n'; + skipFirstLinePrefix = false; + } + detail::streamOutAsParagraph( + out, desc, right_column_width_, std::string(column_width_, ' '), skipFirstLinePrefix); + } + } else { + const std::string namesCombined = make_option_name(opt, false); + const std::string opts = make_option_opts(opt); + const std::string desc = make_option_desc(opt); + + // Split all names at comma and sort them into short names and long names + const auto names = detail::split(namesCombined, ','); + std::vector vshortNames; + std::vector vlongNames; + std::for_each(names.begin(), names.end(), [&vshortNames, &vlongNames](const std::string &name) { + if(name.find("--", 0) != std::string::npos) + vlongNames.push_back(name); + else + vshortNames.push_back(name); + }); + + // Assemble short and long names + std::string shortNames = detail::join(vshortNames, ", "); + std::string longNames = detail::join(vlongNames, ", "); + + // Calculate setw sizes + const auto shortNamesColumnWidth = static_cast(column_width_ / 3); // 33% left for short names + const auto longNamesColumnWidth = static_cast(std::ceil( + static_cast(column_width_) / 3.0f * 2.0f)); // 66% right for long names and options, ceil result + int shortNamesOverSize = 0; + + // Print short names + if(shortNames.length() > 0) { + shortNames = " " + shortNames; // Indent + if(longNames.length() == 0 && opts.length() > 0) + shortNames += opts; // Add opts if only short names and no long names + if(longNames.length() > 0) + shortNames += ","; + if(static_cast(shortNames.length()) >= shortNamesColumnWidth) { + shortNames += " "; + shortNamesOverSize = static_cast(shortNames.length()) - shortNamesColumnWidth; + } + out << std::setw(shortNamesColumnWidth) << std::left << shortNames; + } else { + out << std::setw(shortNamesColumnWidth) << std::left << ""; + } + + // Adjust long name column width in case of short names column reaching into long names column + shortNamesOverSize = + (std::min)(shortNamesOverSize, longNamesColumnWidth); // Prevent negative result with unsigned integers + const auto adjustedLongNamesColumnWidth = longNamesColumnWidth - shortNamesOverSize; + + // Print long names + if(longNames.length() > 0) { + if(opts.length() > 0) + longNames += opts; + if(static_cast(longNames.length()) >= adjustedLongNamesColumnWidth) + longNames += " "; + + out << std::setw(adjustedLongNamesColumnWidth) << std::left << longNames; + } else { + out << std::setw(adjustedLongNamesColumnWidth) << std::left << ""; + } + + if(!desc.empty()) { + bool skipFirstLinePrefix = true; + if(out.str().length() > column_width_) { + out << '\n'; + skipFirstLinePrefix = false; + } + detail::streamOutAsParagraph( + out, desc, right_column_width_, std::string(column_width_, ' '), skipFirstLinePrefix); + } + } + + out << '\n'; + return out.str(); } CLI11_INLINE std::string Formatter::make_option_name(const Option *opt, bool is_positional) const { diff --git a/include/CLI/impl/StringTools_inl.hpp b/include/CLI/impl/StringTools_inl.hpp index e3775619..1dffb126 100644 --- a/include/CLI/impl/StringTools_inl.hpp +++ b/include/CLI/impl/StringTools_inl.hpp @@ -95,24 +95,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: "; @@ -573,6 +555,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 // [CLI11:string_tools_inl_hpp:end] } // namespace CLI diff --git a/tests/CreationTest.cpp b/tests/CreationTest.cpp index 46f57770..1907115a 100644 --- a/tests/CreationTest.cpp +++ b/tests/CreationTest.cpp @@ -438,7 +438,7 @@ TEST_CASE_METHOD(TApp, "OptionFromDefaultsSubcommands", "[creation]") { CHECK(!app.option_defaults()->get_ignore_underscore()); CHECK(!app.option_defaults()->get_disable_flag_override()); CHECK(app.option_defaults()->get_configurable()); - CHECK("Options" == app.option_defaults()->get_group()); + CHECK("OPTIONS" == app.option_defaults()->get_group()); app.option_defaults() ->required() @@ -498,7 +498,7 @@ TEST_CASE_METHOD(TApp, "SubcommandDefaults", "[creation]") { CHECK(app.get_usage().empty()); CHECK(app.get_footer().empty()); - CHECK("Subcommands" == app.get_group()); + CHECK("SUBCOMMANDS" == app.get_group()); CHECK(0u == app.get_require_subcommand_min()); CHECK(0u == app.get_require_subcommand_max()); diff --git a/tests/FormatterTest.cpp b/tests/FormatterTest.cpp index 215dcb10..b88232b4 100644 --- a/tests/FormatterTest.cpp +++ b/tests/FormatterTest.cpp @@ -57,11 +57,9 @@ TEST_CASE("Formatter: OptCustomize", "[formatter]") { std::string help = app.help(); CHECK_THAT(help, Contains("(MUST HAVE)")); - CHECK(help == "My prog\n" - "Usage: [OPTIONS]\n\n" - "Options:\n" - " -h,--help Print this help message and exit\n" - " --opt INT (MUST HAVE) Something\n"); + CHECK_THAT(help, Contains("Something")); + CHECK_THAT(help, Contains("--opt INT")); + CHECK_THAT(help, Contains("-h, --help Print")); } TEST_CASE("Formatter: OptCustomizeSimple", "[formatter]") { @@ -76,11 +74,10 @@ TEST_CASE("Formatter: OptCustomizeSimple", "[formatter]") { std::string help = app.help(); CHECK_THAT(help, Contains("(MUST HAVE)")); - CHECK(help == "My prog\n" - "Usage: [OPTIONS]\n\n" - "Options:\n" - " -h,--help Print this help message and exit\n" - " --opt INT (MUST HAVE) Something\n"); + CHECK_THAT(help, Contains("(MUST HAVE)")); + CHECK_THAT(help, Contains("Something")); + CHECK_THAT(help, Contains("--opt INT")); + CHECK_THAT(help, Contains("-h, --help Print")); } TEST_CASE("Formatter: OptCustomizeOptionText", "[formatter]") { @@ -94,11 +91,6 @@ TEST_CASE("Formatter: OptCustomizeOptionText", "[formatter]") { std::string help = app.help(); CHECK_THAT(help, Contains("(ARG)")); - CHECK(help == "My prog\n" - "Usage: [OPTIONS]\n\n" - "Options:\n" - " -h,--help Print this help message and exit\n" - " --opt (ARG) Something\n"); } TEST_CASE("Formatter: FalseFlagExample", "[formatter]") { @@ -129,16 +121,13 @@ TEST_CASE("Formatter: AppCustomize", "[formatter]") { appfmt->label("Usage", "Run"); app.formatter(appfmt); - app.add_subcommand("subcom2", "This"); + app.add_subcommand("subcom2", "That"); std::string help = app.help(); - CHECK(help == "My prog\n" - "Run: [OPTIONS] [SUBCOMMAND]\n\n" - "Options:\n" - " -h,--help Print this help message and exit\n\n" - "Subcommands:\n" - " subcom1 This\n" - " subcom2 This\n"); + CHECK_THAT(help, Contains("Run: [OPTIONS] [SUBCOMMAND]\n\n")); + CHECK_THAT(help, Contains("\nSUBCOMMANDS:\n")); + CHECK_THAT(help, Contains(" subcom1 This \n")); + CHECK_THAT(help, Contains(" subcom2 That \n")); } TEST_CASE("Formatter: AppCustomizeSimple", "[formatter]") { @@ -148,16 +137,13 @@ TEST_CASE("Formatter: AppCustomizeSimple", "[formatter]") { app.get_formatter()->column_width(20); app.get_formatter()->label("Usage", "Run"); - app.add_subcommand("subcom2", "This"); + app.add_subcommand("subcom2", "That"); std::string help = app.help(); - CHECK(help == "My prog\n" - "Run: [OPTIONS] [SUBCOMMAND]\n\n" - "Options:\n" - " -h,--help Print this help message and exit\n\n" - "Subcommands:\n" - " subcom1 This\n" - " subcom2 This\n"); + CHECK_THAT(help, Contains("Run: [OPTIONS] [SUBCOMMAND]\n\n")); + CHECK_THAT(help, Contains("\nSUBCOMMANDS:\n")); + CHECK_THAT(help, Contains(" subcom1 This \n")); + CHECK_THAT(help, Contains(" subcom2 That \n")); } TEST_CASE("Formatter: AllSub", "[formatter]") { diff --git a/tests/HelpTest.cpp b/tests/HelpTest.cpp index 50b4cd42..68c5db34 100644 --- a/tests/HelpTest.cpp +++ b/tests/HelpTest.cpp @@ -21,8 +21,8 @@ TEST_CASE("THelp: Basic", "[help]") { std::string help = app.help(); CHECK_THAT(help, Contains("My prog")); - CHECK_THAT(help, Contains("-h,--help")); - CHECK_THAT(help, Contains("Options:")); + CHECK_THAT(help, Contains("-h, --help")); + CHECK_THAT(help, Contains("OPTIONS:")); CHECK_THAT(help, Contains("Usage:")); } @@ -32,9 +32,6 @@ TEST_CASE("THelp: Usage", "[help]") { std::string help = app.help(); - CHECK_THAT(help, Contains("My prog")); - CHECK_THAT(help, Contains("-h,--help")); - CHECK_THAT(help, Contains("Options:")); CHECK_THAT(help, Contains("use: just use it")); } @@ -43,10 +40,6 @@ TEST_CASE("THelp: UsageCallback", "[help]") { app.usage([]() { return "use: just use it"; }); std::string help = app.help(); - - CHECK_THAT(help, Contains("My prog")); - CHECK_THAT(help, Contains("-h,--help")); - CHECK_THAT(help, Contains("Options:")); CHECK_THAT(help, Contains("use: just use it")); } @@ -56,9 +49,6 @@ TEST_CASE("THelp: UsageCallbackBoth", "[help]") { app.usage("like 1, 2, and 3"); std::string help = app.help(); - CHECK_THAT(help, Contains("My prog")); - CHECK_THAT(help, Contains("-h,--help")); - CHECK_THAT(help, Contains("Options:")); CHECK_THAT(help, Contains("use: just use it")); CHECK_THAT(help, Contains("like 1, 2, and 3")); } @@ -69,10 +59,6 @@ TEST_CASE("THelp: Footer", "[help]") { std::string help = app.help(); - CHECK_THAT(help, Contains("My prog")); - CHECK_THAT(help, Contains("-h,--help")); - CHECK_THAT(help, Contains("Options:")); - CHECK_THAT(help, Contains("Usage:")); CHECK_THAT(help, Contains("Report bugs to bugs@example.com")); } @@ -82,10 +68,6 @@ TEST_CASE("THelp: FooterCallback", "[help]") { std::string help = app.help(); - CHECK_THAT(help, Contains("My prog")); - CHECK_THAT(help, Contains("-h,--help")); - CHECK_THAT(help, Contains("Options:")); - CHECK_THAT(help, Contains("Usage:")); CHECK_THAT(help, Contains("Report bugs to bugs@example.com")); } @@ -95,10 +77,6 @@ TEST_CASE("THelp: FooterCallbackBoth", "[help]") { app.footer(" foot!!!!"); std::string help = app.help(); - CHECK_THAT(help, Contains("My prog")); - CHECK_THAT(help, Contains("-h,--help")); - CHECK_THAT(help, Contains("Options:")); - CHECK_THAT(help, Contains("Usage:")); CHECK_THAT(help, Contains("Report bugs to bugs@example.com")); CHECK_THAT(help, Contains("foot!!!!")); } @@ -111,13 +89,11 @@ TEST_CASE("THelp: OptionalPositional", "[help]") { std::string help = app.help(); - CHECK_THAT(help, Contains("My prog")); - CHECK_THAT(help, Contains("-h,--help")); - CHECK_THAT(help, Contains("Options:")); - CHECK_THAT(help, Contains("Positionals:")); + CHECK_THAT(help, Contains("OPTIONS:")); + CHECK_THAT(help, Contains("POSITIONALS:")); CHECK_THAT(help, Contains("something TEXT")); CHECK_THAT(help, Contains("My option here")); - CHECK_THAT(help, Contains("Usage: program [OPTIONS] [something]")); + CHECK_THAT(help, Contains("program [OPTIONS] [something]")); } TEST_CASE("THelp: Hidden", "[help]") { @@ -130,9 +106,7 @@ TEST_CASE("THelp: Hidden", "[help]") { std::string help = app.help(); - CHECK_THAT(help, Contains("My prog")); - CHECK_THAT(help, Contains("-h,--help")); - CHECK_THAT(help, Contains("Options:")); + CHECK_THAT(help, Contains("OPTIONS:")); CHECK_THAT(help, !Contains("[something]")); CHECK_THAT(help, !Contains("something ")); CHECK_THAT(help, !Contains("another")); @@ -176,7 +150,7 @@ TEST_CASE("THelp: deprecatedOptions2", "[help]") { TEST_CASE("THelp: deprecatedOptions3", "[help]") { CLI::App app{"My prog"}; - + app.get_formatter()->right_column_width(100); std::string x; app.add_option("--something", x, "Some Description"); app.add_option("--something_else", x, "Some other description"); @@ -258,9 +232,7 @@ TEST_CASE("THelp: HiddenGroup", "[help]") { std::string help = app.help(); - CHECK_THAT(help, Contains("My prog")); - CHECK_THAT(help, Contains("-h,--help")); - CHECK_THAT(help, Contains("Options:")); + CHECK_THAT(help, Contains("OPTIONS:")); CHECK_THAT(help, !Contains("[something]")); CHECK_THAT(help, !Contains("something ")); CHECK_THAT(help, !Contains("another")); @@ -311,10 +283,7 @@ TEST_CASE("THelp: OptionalPositionalAndOptions", "[help]") { std::string help = app.help(); - CHECK_THAT(help, Contains("My prog")); - CHECK_THAT(help, Contains("-h,--help")); - CHECK_THAT(help, Contains("Options:")); - CHECK_THAT(help, Contains("Usage: AnotherProgram [OPTIONS] [something]")); + CHECK_THAT(help, Contains("AnotherProgram [OPTIONS] [something]")); } TEST_CASE("THelp: RequiredPositionalAndOptions", "[help]") { @@ -326,10 +295,8 @@ TEST_CASE("THelp: RequiredPositionalAndOptions", "[help]") { std::string help = app.help(); - CHECK_THAT(help, Contains("My prog")); - CHECK_THAT(help, Contains("-h,--help")); - CHECK_THAT(help, Contains("Options:")); - CHECK_THAT(help, Contains("Positionals:")); + CHECK_THAT(help, Contains("OPTIONS:")); + CHECK_THAT(help, Contains("POSITIONALS:")); CHECK_THAT(help, Contains("Usage: [OPTIONS] something")); } @@ -342,7 +309,7 @@ TEST_CASE("THelp: MultiOpts", "[help]") { std::string help = app.help(); CHECK_THAT(help, Contains("My prog")); - CHECK_THAT(help, !Contains("Positionals:")); + CHECK_THAT(help, !Contains("POSITIONALS:")); CHECK_THAT(help, Contains("Usage: [OPTIONS]")); CHECK_THAT(help, Contains("INT x 2")); CHECK_THAT(help, Contains("INT ...")); @@ -369,8 +336,8 @@ TEST_CASE("THelp: MultiPosOpts", "[help]") { std::string help = app.help(); CHECK_THAT(help, Contains("My prog")); - CHECK_THAT(help, Contains("Positionals:")); - CHECK_THAT(help, Contains("Usage: program [OPTIONS]")); + CHECK_THAT(help, Contains("POSITIONALS:")); + CHECK_THAT(help, Contains("program [OPTIONS]")); CHECK_THAT(help, Contains("INT x 2")); CHECK_THAT(help, Contains("INT ...")); CHECK_THAT(help, Contains("[quick(2x)]")); @@ -408,7 +375,7 @@ TEST_CASE("THelp: NeedsPositional", "[help]") { std::string help = app.help(); - CHECK_THAT(help, Contains("Positionals:")); + CHECK_THAT(help, Contains("POSITIONALS:")); CHECK_THAT(help, Contains("Needs: op1")); } @@ -433,7 +400,7 @@ TEST_CASE("THelp: ExcludesPositional", "[help]") { std::string help = app.help(); - CHECK_THAT(help, Contains("Positionals:")); + CHECK_THAT(help, Contains("POSITIONALS:")); CHECK_THAT(help, Contains("Excludes: op1")); } @@ -520,7 +487,7 @@ TEST_CASE("THelp: Subcom", "[help]") { CHECK_THAT(help, Contains("Usage: [OPTIONS] SUBCOMMAND")); help = sub1->help(); - CHECK_THAT(help, Contains("Usage: sub1")); + CHECK_THAT(help, Contains("sub1 [OPTIONS]")); char x[] = "./myprogram"; // NOLINT(modernize-avoid-c-arrays) char y[] = "sub2"; // NOLINT(modernize-avoid-c-arrays) @@ -529,7 +496,7 @@ TEST_CASE("THelp: Subcom", "[help]") { app.parse(static_cast(args.size()), args.data()); help = app.help(); - CHECK_THAT(help, Contains("Usage: ./myprogram sub2")); + CHECK_THAT(help, Contains("./myprogram sub2")); } TEST_CASE("THelp: Subcom_alias", "[help]") { @@ -570,7 +537,7 @@ TEST_CASE("THelp: MasterName", "[help]") { std::vector args = {x}; app.parse(static_cast(args.size()), args.data()); - CHECK_THAT(app.help(), Contains("Usage: MyRealName")); + CHECK_THAT(app.help(), Contains("MyRealName")); } TEST_CASE("THelp: IntDefaults", "[help]") { @@ -654,7 +621,7 @@ TEST_CASE("THelp: RemoveHelp", "[help]") { CHECK_THAT(help, Contains("My prog")); CHECK_THAT(help, !Contains("-h,--help")); - CHECK_THAT(help, !Contains("Options:")); + CHECK_THAT(help, !Contains("OPTIONS:")); CHECK_THAT(help, Contains("Usage:")); std::vector input{"--help"}; @@ -675,7 +642,7 @@ TEST_CASE("THelp: RemoveOtherMethodHelp", "[help]") { CHECK_THAT(help, Contains("My prog")); CHECK_THAT(help, !Contains("-h,--help")); - CHECK_THAT(help, !Contains("Options:")); + CHECK_THAT(help, !Contains("OPTIONS:")); CHECK_THAT(help, Contains("Usage:")); std::vector input{"--help"}; @@ -697,7 +664,7 @@ TEST_CASE("THelp: RemoveOtherMethodHelpAll", "[help]") { CHECK_THAT(help, Contains("My prog")); CHECK_THAT(help, !Contains("--help-all")); - CHECK_THAT(help, Contains("Options:")); + CHECK_THAT(help, Contains("OPTIONS:")); CHECK_THAT(help, Contains("Usage:")); std::vector input{"--help-all"}; @@ -716,7 +683,7 @@ TEST_CASE("THelp: NoHelp", "[help]") { CHECK_THAT(help, Contains("My prog")); CHECK_THAT(help, !Contains("-h,--help")); - CHECK_THAT(help, !Contains("Options:")); + CHECK_THAT(help, !Contains("OPTIONS:")); CHECK_THAT(help, Contains("Usage:")); std::vector input{"--help"}; @@ -738,7 +705,7 @@ TEST_CASE("THelp: CustomHelp", "[help]") { CHECK_THAT(help, Contains("My prog")); CHECK_THAT(help, !Contains("-h,--help")); CHECK_THAT(help, Contains("--yelp")); - CHECK_THAT(help, Contains("Options:")); + CHECK_THAT(help, Contains("OPTIONS:")); CHECK_THAT(help, Contains("Usage:")); std::vector input{"--yelp"}; @@ -774,7 +741,176 @@ TEST_CASE("THelp: NextLineShouldBeAlignmentInMultilineDescription", "[help]") { const std::string help = app.help(); const auto width = app.get_formatter()->get_column_width(); - CHECK_THAT(help, Contains(first + "\n" + std::string(width, ' ') + second)); + auto first_loc = help.find("first"); + auto first_new_line = help.find_last_of('\n', first_loc); + auto second_loc = help.find("second"); + auto second_new_line = help.find_last_of('\n', second_loc); + CHECK(first_loc - first_new_line - 1 == width); + CHECK(second_loc - second_new_line - 1 == width); + CHECK(second_new_line > first_loc); +} + +TEST_CASE("THelp: CheckRightWidth", "[help]") { + CLI::App app; + int i{0}; + const std::string first{"first line"}; + const std::string second{"second line"}; + app.add_option("-i,--int", i, first + "\n" + second); + app.get_formatter()->column_width(24); + CHECK(app.get_formatter()->get_column_width() == 24); + const std::string help = app.help(); + auto first_loc = help.find("first"); + auto first_new_line = help.find_last_of('\n', first_loc); + auto second_loc = help.find("second"); + auto second_new_line = help.find_last_of('\n', second_loc); + CHECK(first_loc - first_new_line - 1 == 24); + CHECK(second_loc - second_new_line - 1 == 24); + CHECK(second_new_line > first_loc); +} + +static const std::string long_string{ + "AAARG this is a long line description that will span across multiple lines and still go on and on. This is meant " + "to test how the help handler handles things like this"}; + +TEST_CASE("THelp: longLineAlignment", "[help]") { + CLI::App app; + int i{0}; + + app.add_option("-i,--int,--int_very_long_option_name_that_just_keeps_going_on_and_on_and_on_and_on_and_on_possibly_" + "to_infitinty,--and_another_long_name_just_for_fun", + i, + long_string); + + std::string help = app.help(); + auto width = app.get_formatter()->get_right_column_width(); + auto first_loc = help.find("AAARG"); + auto first_new_line = help.find_first_of('\n', first_loc); + + CHECK(first_new_line - first_loc - 1 < width); + app.get_formatter()->right_column_width(30); + width = app.get_formatter()->get_right_column_width(); + CHECK(width == 30); + help = app.help(); + first_loc = help.find("AAARG"); + first_new_line = help.find_first_of('\n', first_loc); + + CHECK(first_new_line - first_loc - 1 < width); +} + +TEST_CASE("THelp: longPositional", "[help]") { + CLI::App app; + int i{0}; + + app.add_option("int_very_long_option_name_that_just_keeps_going_on_and_on_and_on_and_on_and_on_possibly_" + "to_infitinty", + i, + long_string); + + std::string help = app.help(); + auto width = app.get_formatter()->get_right_column_width(); + auto first_loc = help.find("AAARG"); + auto first_new_line = help.find_first_of('\n', first_loc); + + CHECK(first_new_line - first_loc - 1 < width); +} + +TEST_CASE("THelp: SubcommandNewLineDescription", "[help]") { + + const std::string nl_description{"this is a description with aX \n X\\n in it and just for fun \n\t another"}; + + CLI::App app; + int i{0}; + app.add_option("-i,--int", i); + app.add_subcommand("subcom1", nl_description); + std::string help = app.help(); + auto width = app.get_formatter()->get_column_width(); + auto first_X = help.find_first_of('X'); + auto first_new_line = help.find_first_of('\n', first_X); + auto second_X = help.find_first_of('X', first_new_line); + CHECK(second_X - first_new_line > width); +} + +TEST_CASE("THelp: longDescription", "[help]") { + + CLI::App app(long_string, "long_desc"); + int i{0}; + + app.add_option("-i,--int", i); + + std::string help = app.help(); + auto width = app.get_formatter()->get_description_paragraph_width(); + auto first_loc = help.find("AAARG"); + auto first_new_line = help.find_first_of('\n', first_loc); + + CHECK(first_new_line - first_loc - 1 < width); + app.get_formatter()->description_paragraph_width(30); + width = app.get_formatter()->get_description_paragraph_width(); + CHECK(width == 30); + help = app.help(); + first_loc = help.find("AAARG"); + first_new_line = help.find_first_of('\n', first_loc); + + CHECK(first_new_line - first_loc - 1 < width); +} + +TEST_CASE("THelp: longSubcommandDescription", "[help]") { + + CLI::App app; + int i{0}; + + app.add_option("-i,--int", i); + app.add_subcommand("test1", long_string); + std::string help = app.help(); + auto width = app.get_formatter()->get_right_column_width(); + auto first_loc = help.find("AAARG"); + auto first_new_line = help.find_first_of('\n', first_loc); + + CHECK(first_new_line - first_loc - 1 < width); + app.get_formatter()->right_column_width(30); + width = 30; + help = app.help(); + first_loc = help.find("AAARG"); + first_new_line = help.find_first_of('\n', first_loc); + + CHECK(first_new_line - first_loc - 1 < width); +} + +TEST_CASE("THelp: longSubcommandDescriptionExpanded", "[help]") { + + CLI::App app; + int i{0}; + + app.add_option("-i,--int", i); + app.add_subcommand("test1", long_string); + + auto help = app.help("", CLI::AppFormatMode::All); + auto width = app.get_formatter()->get_description_paragraph_width(); + auto first_loc = help.find("AAARG"); + auto first_new_line = help.find_first_of('\n', first_loc); + + CHECK(first_new_line - first_loc - 1 < width); +} + +TEST_CASE("THelp: longFooter", "[help]") { + CLI::App app("test long footer", "long_desc"); + int i{0}; + app.footer(long_string); + app.add_option("-i,--int", i); + + std::string help = app.help(); + auto width = app.get_formatter()->get_footer_paragraph_width(); + auto first_loc = help.find("AAARG"); + auto first_new_line = help.find_first_of('\n', first_loc); + + CHECK(first_new_line - first_loc - 1 < width); + app.get_formatter()->footer_paragraph_width(30); + width = app.get_formatter()->get_footer_paragraph_width(); + CHECK(width == 30); + help = app.help(); + first_loc = help.find("AAARG"); + first_new_line = help.find_first_of('\n', first_loc); + + CHECK(first_new_line - first_loc - 1 < width); } TEST_CASE("THelp: NiceName", "[help]") { @@ -882,20 +1018,9 @@ TEST_CASE_METHOD(CapturedHelp, "CallForAllHelpOutput", "[help]") { CHECK_THAT(out.str(), Contains("one")); CHECK_THAT(out.str(), Contains("two")); CHECK_THAT(out.str(), Contains("--three")); - - CHECK(out.str() == "My Test Program\n" - "Usage: [OPTIONS] [SUBCOMMAND]\n" - "\n" - "Options:\n" - " -h,--help Print this help message and exit\n" - " --help-all Help all\n" - "\n" - "Subcommands:\n" - "one\n" - " One description\n\n" - "two\n" - " Options:\n" - " --three \n\n"); + CHECK_THAT(out.str(), Contains("SUBCOMMANDS:")); + CHECK_THAT(out.str(), Contains("--help-all")); + CHECK_THAT(out.str(), Contains("My Test Program")); } TEST_CASE_METHOD(CapturedHelp, "NewFormattedHelp", "[help]") { app.formatter_fn([](const CLI::App *, std::string, CLI::AppFormatMode) { return "New Help"; }); diff --git a/tests/OptionTypeTest.cpp b/tests/OptionTypeTest.cpp index b7f99868..5e25a929 100644 --- a/tests/OptionTypeTest.cpp +++ b/tests/OptionTypeTest.cpp @@ -280,6 +280,8 @@ static const std::map testValuesInt{ {"-995'862'275", -995862275}, {"0b11010110", 0xD6}, {"0b1101'0110", 0xD6}, + {"0B11010110", 0xD6}, + {"0B1101'0110", 0xD6}, {"1_2_3_4_5", 12345}, }; @@ -309,6 +311,10 @@ TEST_CASE_METHOD(TApp, "intConversionsErange", "[optiontype]") { args = {"--val", "0b1011000001101011001100110011111000101010101011111111111111111111111001010111011100"}; CHECK_THROWS_AS(run(), CLI::ParseError); + + args = {"--val", "0B1011000001101011001100110011111000101010101011111111111111111111111001010111011100"}; + + CHECK_THROWS_AS(run(), CLI::ParseError); } static const std::map testValuesUInt{ @@ -329,6 +335,8 @@ static const std::map testValuesUInt{ {"995'862'275", 995862275}, {"0b11010110", 0xD6}, {"0b1101'0110", 0xD6}, + {"0B11010110", 0xD6}, + {"0B1101'0110", 0xD6}, {"1_2_3_4_5", 12345}, }; @@ -358,6 +366,10 @@ TEST_CASE_METHOD(TApp, "uintConversionsErange", "[optiontype]") { args = {"--val", "0b1011000001101011001100110011111000101010101011111111111111111111111001010111011100"}; CHECK_THROWS_AS(run(), CLI::ParseError); + + args = {"--val", "0B1011000001101011001100110011111000101010101011111111111111111111111001010111011100"}; + + CHECK_THROWS_AS(run(), CLI::ParseError); } TEST_CASE_METHOD(TApp, "CharOption", "[optiontype]") { diff --git a/tests/SubcommandTest.cpp b/tests/SubcommandTest.cpp index f64f04b9..b7a8bb97 100644 --- a/tests/SubcommandTest.cpp +++ b/tests/SubcommandTest.cpp @@ -1013,18 +1013,18 @@ TEST_CASE_METHOD(SubcommandProgram, "Subcommand Groups", "[subcom]") { std::string help = app.help(); CHECK_THAT(help, !Contains("More Commands:")); - CHECK_THAT(help, Contains("Subcommands:")); + CHECK_THAT(help, Contains("SUBCOMMANDS:")); start->group("More Commands"); help = app.help(); CHECK_THAT(help, Contains("More Commands:")); - CHECK_THAT(help, Contains("Subcommands:")); + CHECK_THAT(help, Contains("SUBCOMMANDS:")); // Case is ignored but for the first subcommand in a group. stop->group("more commands"); help = app.help(); CHECK_THAT(help, Contains("More Commands:")); - CHECK_THAT(help, !Contains("Subcommands:")); + CHECK_THAT(help, !Contains("SUBCOMMANDS:")); } TEST_CASE_METHOD(SubcommandProgram, "Subcommand ExtrasErrors", "[subcom]") {