add an example of finding close matches (#1152)

continue discussion on #1149 
Adds subcommand prefix matching as a modifier to CLI.  
Adds an example of close matching logic for further exploration.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Philip Top
2025-05-21 19:39:00 -07:00
committed by GitHub
parent 50591fb666
commit 5602f2b438
8 changed files with 330 additions and 10 deletions

View File

@@ -110,6 +110,104 @@ TEST_CASE_METHOD(TApp, "CrazyNameSubcommand", "[subcom]") {
CHECK(1u == sub1->count());
}
TEST_CASE_METHOD(TApp, "subcommandPrefix", "[subcom]") {
app.allow_subcommand_prefix_matching();
auto *sub1 = app.add_subcommand("sub1");
CHECK(app.get_allow_subcommand_prefix_matching());
// name can be set to whatever
CHECK_NOTHROW(sub1->name("crazy name with spaces"));
args = {"crazy name with spaces"};
run();
CHECK(app.got_subcommand("crazy name with spaces"));
CHECK(1u == sub1->count());
args = {"crazy name"};
run();
CHECK(app.got_subcommand("crazy name with spaces"));
CHECK(1u == sub1->count());
args = {"crazy"};
run();
CHECK(app.got_subcommand("crazy name"));
CHECK(1u == sub1->count());
args = {"cr"};
run();
CHECK(app.got_subcommand("crazy"));
CHECK(1u == sub1->count());
args = {"c"};
run();
CHECK(app.got_subcommand("crazy"));
CHECK(1u == sub1->count());
}
TEST_CASE_METHOD(TApp, "subcommandPrefixAlias", "[subcom]") {
app.allow_subcommand_prefix_matching();
auto *sub1 = app.add_subcommand("sub1");
CHECK(app.get_allow_subcommand_prefix_matching());
// name can be set to whatever
sub1->alias("crazy name with spaces");
args = {"crazy name with spaces"};
run();
CHECK(app.got_subcommand("crazy name with spaces"));
CHECK(1u == sub1->count());
args = {"crazy name"};
run();
CHECK(app.got_subcommand("crazy name with spaces"));
CHECK(1u == sub1->count());
args = {"crazy"};
run();
CHECK(app.got_subcommand("crazy name"));
CHECK(1u == sub1->count());
args = {"cr"};
run();
CHECK(app.got_subcommand("crazy"));
CHECK(1u == sub1->count());
args = {"c"};
run();
CHECK(app.got_subcommand("crazy"));
CHECK(1u == sub1->count());
}
TEST_CASE_METHOD(TApp, "subcommandPrefixMultiple", "[subcom]") {
app.allow_subcommand_prefix_matching();
auto *sub1 = app.add_subcommand("sub_long_prefix");
auto *sub2 = app.add_subcommand("sub_elong_prefix");
// name can be set to whatever
args = {"sub_long"};
run();
CHECK(app.got_subcommand("sub_long_prefix"));
CHECK(1u == sub1->count());
args = {"sub_e"};
run();
CHECK(app.got_subcommand("sub_elong_prefix"));
CHECK(1u == sub2->count());
args = {"sub_"};
CHECK_THROWS_AS(run(), CLI::ExtrasError);
}
TEST_CASE_METHOD(TApp, "RequiredAndSubcommands", "[subcom]") {
std::string baz;
@@ -1773,6 +1871,24 @@ TEST_CASE_METHOD(TApp, "AliasErrors", "[subcom]") {
sub2->ignore_underscore();
CHECK_THROWS_AS(sub2->alias("les3"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "DuplicateErrorsPrefix", "[subcom]") {
app.allow_subcommand_prefix_matching(true);
auto *sub1 = app.add_subcommand("sub_test");
auto *sub2 = app.add_subcommand("sub_deploy");
CHECK_THROWS_AS(app.add_subcommand("sub"), CLI::OptionAlreadyAdded);
// cannot alias to an existing subcommand
CHECK_THROWS_AS(sub2->alias("sub"), CLI::OptionAlreadyAdded);
app.ignore_case();
// this needs to be opposite of the subcommand the alias is being tested on to check for ambiguity
sub2->ignore_case();
CHECK_THROWS_AS(sub1->alias("SUB_"), CLI::OptionAlreadyAdded);
app.ignore_underscore();
sub1->ignore_underscore();
CHECK_THROWS_AS(sub2->alias("su_bt"), CLI::OptionAlreadyAdded);
}
// test adding a subcommand via the pointer
TEST_CASE_METHOD(TApp, "ExistingSubcommandMatch", "[subcom]") {
auto sshared = std::make_shared<CLI::App>("documenting the subcommand", "sub1");