mirror of
https://github.com/CLIUtils/CLI11.git
synced 2025-05-04 14:23:51 +00:00
update documentation and tests on prefix matching and clean up close match function.
This commit is contained in:
parent
279e710544
commit
433ee18d0c
@ -914,6 +914,8 @@ option_groups. These are:
|
||||
is not allowed to have a single character short option starting with the same
|
||||
character as a single dash long form name; for example, `-s` and `-single` are
|
||||
not allowed in the same application.
|
||||
- `.allow_subcommand_prefix_matching()`:🚧 If this modifier is enabled, unambiguious prefix portions of a subcommand will match.
|
||||
For example `upgrade_package` would match on `upgrade_`, `upg`, `u` as long as no other subcommand would also match. It also disallows subcommand names that are full prefixes of another subcommand.
|
||||
- `.fallthrough()`: Allow extra unmatched options and positionals to "fall
|
||||
through" and be matched on a parent option. Subcommands by default are allowed
|
||||
to "fall through" as in they will first attempt to match on the current
|
||||
|
@ -105,6 +105,7 @@ at the point the subcommand is created:
|
||||
- Fallthrough
|
||||
- Group name
|
||||
- Max required subcommands
|
||||
- prefix_matching
|
||||
- validate positional arguments
|
||||
- validate optional arguments
|
||||
|
||||
@ -156,6 +157,11 @@ ignored, even if they could match. Git is the traditional example for prefix
|
||||
commands; if you run git with an unknown subcommand, like "`git thing`", it then
|
||||
calls another command called "`git-thing`" with the remaining options intact.
|
||||
|
||||
### prefix matching
|
||||
|
||||
A modifier is available for subcommand matching, `->allow_subcommand_prefix_matching()`. if this is enabled unambiguious prefix portions of a subcommand will match.
|
||||
For Example `upgrade_package` would match on `upgrade_`, `upg`, `u` as long as no other subcommand would also match. It also disallows subcommand names that are full prefixes of another subcommand.
|
||||
|
||||
### Silent subcommands
|
||||
|
||||
Subcommands can be modified by using the `silent` option. This will prevent the
|
||||
|
@ -252,6 +252,19 @@ set_property(TEST retired_deprecated PROPERTY PASS_REGULAR_EXPRESSION "deprecate
|
||||
|
||||
add_cli_exe(close_match close_match.cpp)
|
||||
|
||||
add_test(NAME close_match_test COMMAND close_match i)
|
||||
add_test(NAME close_match_test2 COMMAND close_match upg)
|
||||
add_test(NAME close_match_test3 COMMAND close_match rem)
|
||||
add_test(NAME close_match_test4 COMMAND close_match upgrde)
|
||||
|
||||
set_property(TEST close_match_test PROPERTY PASS_REGULAR_EXPRESSION "install")
|
||||
|
||||
set_property(TEST close_match_test2 PROPERTY PASS_REGULAR_EXPRESSION "upgrade")
|
||||
|
||||
set_property(TEST close_match_test3 PROPERTY PASS_REGULAR_EXPRESSION "remove")
|
||||
|
||||
set_property(TEST close_match_test4 PROPERTY PASS_REGULAR_EXPRESSION "closest match is upgrade")
|
||||
|
||||
#--------------------------------------------
|
||||
add_cli_exe(custom_parse custom_parse.cpp)
|
||||
add_test(NAME cp_test COMMAND custom_parse --dv 1.7)
|
||||
|
@ -12,29 +12,33 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <numeric>
|
||||
|
||||
// Levenshtein distance function code generated by chatgpt
|
||||
std::size_t levenshteinDistance(const std::string &s1, const std::string &s2) {
|
||||
size_t len1 = s1.size(), len2 = s2.size();
|
||||
std::vector<std::vector<std::size_t>> dp(len1 + 1, std::vector<std::size_t>(len2 + 1));
|
||||
// Levenshtein distance function code generated by chatgpt/copilot
|
||||
std::size_t levenshteinDistance(const std::string& s1, const std::string& s2) {
|
||||
std::size_t len1 = s1.size(), len2 = s2.size();
|
||||
if (len1 == 0) return len2;
|
||||
if (len2 == 0) return len1;
|
||||
|
||||
for(size_t ii = 0; ii <= len1; ++ii)
|
||||
dp[ii][0] = ii;
|
||||
for(size_t jj = 0; jj <= len2; ++jj)
|
||||
dp[0][jj] = jj;
|
||||
std::vector<std::size_t> prev(len2 + 1), curr(len2 + 1);
|
||||
std::iota(prev.begin(), prev.end(), 0); // Fill prev with {0, 1, ..., len2}
|
||||
|
||||
for(size_t ii = 1; ii <= len1; ++ii) {
|
||||
for(size_t jj = 1; jj <= len2; ++jj) {
|
||||
for (std::size_t ii = 1; ii <= len1; ++ii) {
|
||||
curr[0] = ii;
|
||||
for (std::size_t jj = 1; jj <= len2; ++jj) {
|
||||
// If characters match, no substitution cost; otherwise, cost is 1.
|
||||
std::size_t cost = (s1[ii - 1] == s2[jj - 1]) ? 0 : 1;
|
||||
dp[ii][jj] = (std::min)({
|
||||
dp[ii - 1][jj] + 1, // deletion
|
||||
dp[ii][jj - 1] + 1, // insertion
|
||||
dp[ii - 1][jj - 1] + cost // substitution
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return dp[len1][len2];
|
||||
// Compute the minimum cost between:
|
||||
// - Deleting a character from `s1` (prev[jj] + 1)
|
||||
// - Inserting a character into `s1` (curr[jj - 1] + 1)
|
||||
// - Substituting a character (prev[jj - 1] + cost)
|
||||
|
||||
curr[jj] = std::min({ prev[jj] + 1, curr[jj - 1] + 1, prev[jj - 1] + cost });
|
||||
}
|
||||
prev = std::exchange(curr, prev); // Swap vectors efficiently
|
||||
}
|
||||
return prev[len2];
|
||||
}
|
||||
|
||||
// Finds the closest string from a list (modified from chat gpt code)
|
||||
@ -53,14 +57,10 @@ std::pair<std::string, std::size_t> findClosestMatch(const std::string &input,
|
||||
return {closest, minDistance};
|
||||
}
|
||||
|
||||
void addCloseMatchDetection(CLI::App *app, std::size_t minDistance = 3) {
|
||||
void addSubcommandCloseMatchDetection(CLI::App *app, std::size_t minDistance = 3) {
|
||||
//if extras are not allowed then there will be no remaining
|
||||
app->allow_extras(true);
|
||||
|
||||
app->parse_complete_callback([&app, minDistance]() {
|
||||
auto extras = app->remaining();
|
||||
if(extras.empty()) {
|
||||
return;
|
||||
}
|
||||
// generate a list of subcommand names
|
||||
auto subs = app->get_subcommands(nullptr);
|
||||
std::vector<std::string> list;
|
||||
for(const auto *sub : subs) {
|
||||
@ -72,11 +72,18 @@ void addCloseMatchDetection(CLI::App *app, std::size_t minDistance = 3) {
|
||||
list.insert(list.end(), aliases.begin(), aliases.end());
|
||||
}
|
||||
}
|
||||
// add a callback that runs before a final callback and loops over the remaining arguments for subcommands
|
||||
app->parse_complete_callback([&app, minDistance,list]() {
|
||||
auto extras = app->remaining();
|
||||
if(extras.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(auto &extra : extras) {
|
||||
if(extra.front() != '-') {
|
||||
auto closest = findClosestMatch(extra, list);
|
||||
if(closest.second <= minDistance) {
|
||||
std::cout << "unmatched commands " << extra << ", closest match is " << closest.first << "\n";
|
||||
std::cout << "unmatched command \"" << extra << "\", closest match is " << closest.first << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -86,7 +93,7 @@ void addCloseMatchDetection(CLI::App *app, std::size_t minDistance = 3) {
|
||||
/** This example demonstrates the use of close match detection to detect invalid commands that are close matches to
|
||||
* existing ones
|
||||
*/
|
||||
int main(int argc, const char *argv[]) {
|
||||
int main(int argc, const char* argv[]) {
|
||||
|
||||
int value{0};
|
||||
CLI::App app{"cose string App"};
|
||||
@ -98,7 +105,14 @@ int main(int argc, const char *argv[]) {
|
||||
app.add_subcommand("upgrade", "");
|
||||
app.add_subcommand("remove", "");
|
||||
app.add_subcommand("test", "");
|
||||
addCloseMatchDetection(&app, 5);
|
||||
//enable close matching for subcommands
|
||||
addSubcommandCloseMatchDetection(&app, 5);
|
||||
CLI11_PARSE(app, argc, argv);
|
||||
|
||||
auto subs=app.get_subcommands();
|
||||
for (const auto& sub : subs)
|
||||
{
|
||||
std::cout<<sub->get_name()<<"\n";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user