Subcommand plurality (#1169)

Partially address #1168 fix subcommand plurality when subcommand_max count is 0

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Philip Top
2025-06-05 17:48:01 -07:00
committed by GitHub
parent 542ef71d8d
commit 328ac68432
7 changed files with 26 additions and 10 deletions

View File

@@ -29,6 +29,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
try {
if(pstring_start > 0) {
app->parse(parseString.substr(pstring_start));
// test to make sure no seg fault or other errors occur
std::ignore = app->help("", CLI::AppFormatMode::All);
} else {
app->parse(parseString);
}
@@ -39,7 +41,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
// this just indicates we caught an error known by CLI
return 0; // Non-zero return values are reserved for future use.
}
if(fuzzdata.support_config_file_only()) {
if(fuzzdata.supports_config_file()) {
CLI::FuzzApp fuzzdata2;
auto app2 = fuzzdata2.generateApp();
// should be able to write the config to a file and read from it again

View File

@@ -555,6 +555,16 @@ std::size_t FuzzApp::add_custom_options(CLI::App *app, const std::string &descri
}
}
current_index = end_option + 9;
} else if(description_string.compare(current_index, 11, "<subcommand") == 0) {
auto end_sub_label = description_string.find_first_of('>', current_index + 12);
if(end_sub_label == std::string::npos) {
break;
}
auto end_sub = description_string.find("</subcommand>", end_sub_label + 1);
if(end_sub == std::string::npos) {
break;
}
} else {
if(isspace(description_string[current_index]) != 0) {
++current_index;

View File

@@ -63,7 +63,7 @@ class FuzzApp {
/** modify an option based on string*/
static void modify_option(CLI::Option *opt, const std::string &modifier);
CLI11_NODISCARD bool support_config_file_only() const { return !non_config_required; }
CLI11_NODISCARD bool supports_config_file() const { return !non_config_required; }
int32_t val32{0};
int16_t val16{0};
int8_t val8{0};

View File

@@ -133,8 +133,7 @@ CLI11_INLINE std::string Formatter::make_usage(const App *app, std::string name)
[](const CLI::App *subc) { return ((!subc->get_disabled()) && (!subc->get_name().empty())); })
.empty()) {
out << ' ' << (app->get_require_subcommand_min() == 0 ? "[" : "")
<< get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND"
: "SUBCOMMANDS")
<< get_label(app->get_require_subcommand_max() == 1 ? "SUBCOMMAND" : "SUBCOMMANDS")
<< (app->get_require_subcommand_min() == 0 ? "]" : "");
}

View File

@@ -126,7 +126,7 @@ TEST_CASE("Formatter: AppCustomize", "[formatter]") {
app.add_subcommand("subcom2", "That");
std::string help = app.help();
CHECK_THAT(help, Contains("Run: [OPTIONS] [SUBCOMMAND]\n\n"));
CHECK_THAT(help, Contains("Run: [OPTIONS] [SUBCOMMANDS]\n\n"));
CHECK_THAT(help, Contains("\nSUBCOMMANDS:\n"));
CHECK_THAT(help, Contains(" subcom1 This \n"));
CHECK_THAT(help, Contains(" subcom2 That \n"));
@@ -142,7 +142,7 @@ TEST_CASE("Formatter: AppCustomizeSimple", "[formatter]") {
app.add_subcommand("subcom2", "That");
std::string help = app.help();
CHECK_THAT(help, Contains("Run: [OPTIONS] [SUBCOMMAND]\n\n"));
CHECK_THAT(help, Contains("Run: [OPTIONS] [SUBCOMMANDS]\n\n"));
CHECK_THAT(help, Contains("\nSUBCOMMANDS:\n"));
CHECK_THAT(help, Contains(" subcom1 This \n"));
CHECK_THAT(help, Contains(" subcom2 That \n"));

View File

@@ -322,10 +322,11 @@ TEST_CASE("app_roundtrip_custom") {
if(pstring_start > 0) {
app->parse(parseData.substr(pstring_start));
CHECK_NOTHROW(app->help("", CLI::AppFormatMode::All));
} else {
app->parse(parseData);
}
if(fuzzdata.support_config_file_only()) {
if(fuzzdata.supports_config_file()) {
// should be able to write the config to a file and read from it again
std::string configOut = app->config_to_str();
std::stringstream out(configOut);

View File

@@ -516,10 +516,14 @@ TEST_CASE("THelp: Subcom", "[help]") {
app.add_subcommand("sub2");
std::string help = app.help();
CHECK_THAT(help, Contains("Usage: [OPTIONS] [SUBCOMMAND]"));
CHECK_THAT(help, Contains("Usage: [OPTIONS] [SUBCOMMANDS]"));
app.require_subcommand();
help = app.help();
CHECK_THAT(help, Contains("Usage: [OPTIONS] SUBCOMMANDS"));
app.require_subcommand(1);
help = app.help();
CHECK_THAT(help, Contains("Usage: [OPTIONS] SUBCOMMAND"));
@@ -546,7 +550,7 @@ TEST_CASE("THelp: Subcom_alias", "[help]") {
app.add_subcommand("sub2", "Subcommand2 description test");
std::string help = app.help();
CHECK_THAT(help, Contains("Usage: [OPTIONS] [SUBCOMMAND]"));
CHECK_THAT(help, Contains("Usage: [OPTIONS] [SUBCOMMANDS]"));
CHECK_THAT(help, Contains("sub_alias1"));
CHECK_THAT(help, Contains("sub_alias2"));
}
@@ -561,7 +565,7 @@ TEST_CASE("THelp: Subcom_alias_group", "[help]") {
app.add_subcommand("sub2", "Subcommand2 description test");
std::string help = app.help();
CHECK_THAT(help, Contains("Usage: [OPTIONS] [SUBCOMMAND]"));
CHECK_THAT(help, Contains("Usage: [OPTIONS] [SUBCOMMANDS]"));
CHECK_THAT(help, Contains("sub_alias1"));
CHECK_THAT(help, Contains("sub_alias2"));
}