From 0549f7c45034102d706d3644c3f5b8cdd4e89d34 Mon Sep 17 00:00:00 2001 From: Philip Top Date: Mon, 21 Apr 2025 09:06:14 -0700 Subject: [PATCH] add an example of finding close matches --- examples/CMakeLists.txt | 2 + examples/close_match.cpp | 107 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 examples/close_match.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 07c8f64d..eef7f782 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -250,6 +250,8 @@ set_property(TEST retired_retired_test3 PROPERTY PASS_REGULAR_EXPRESSION "WARNIN set_property(TEST retired_deprecated PROPERTY PASS_REGULAR_EXPRESSION "deprecated.*not_deprecated") +add_cli_exe(close_match close_match.cpp) + #-------------------------------------------- add_cli_exe(custom_parse custom_parse.cpp) add_test(NAME cp_test COMMAND custom_parse --dv 1.7) diff --git a/examples/close_match.cpp b/examples/close_match.cpp new file mode 100644 index 00000000..2eae5007 --- /dev/null +++ b/examples/close_match.cpp @@ -0,0 +1,107 @@ +// Copyright (c) 2017-2025, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +// Code inspired by discussion from https://github.com/CLIUtils/CLI11/issues/1149 + +#include +#include +#include +#include +#include + +// Levenshtein distance function code generated by chatgpt +int levenshteinDistance(const std::string& s1, const std::string& s2) { + size_t len1 = s1.size(), len2 = s2.size(); + std::vector> dp(len1 + 1, std::vector(len2 + 1)); + + for (size_t i = 0; i <= len1; ++i) dp[i][0] = i; + for (size_t j = 0; j <= len2; ++j) dp[0][j] = j; + + for (size_t i = 1; i <= len1; ++i) { + for (size_t j = 1; j <= len2; ++j) { + int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1; + dp[i][j] = std::min({ + dp[i - 1][j] + 1, // deletion + dp[i][j - 1] + 1, // insertion + dp[i - 1][j - 1] + cost // substitution + }); + } + } + + return dp[len1][len2]; +} + +// Finds the closest string from a list +std::pair findClosestMatch(const std::string& input, const std::vector& candidates) { + std::string closest; + int minDistance = std::numeric_limits::max(); + + for (const auto& candidate : candidates) { + int distance = levenshteinDistance(input, candidate); + if (distance < minDistance) { + minDistance = distance; + closest = candidate; + } + } + + return { closest,minDistance }; +} + +/** This example demonstrates the use of `prefix_command` on a subcommand +to capture all subsequent arguments along with an alias to make it appear as a regular options. + +All the values after the "sub" or "--sub" are available in the remaining() method. +*/ +int main(int argc, const char *argv[]) { + + int value{0}; + CLI::App app{"cose string App"}; + app.add_option("-v", value, "value"); + + app.add_subcommand("install", ""); + app.add_subcommand("upgrade",""); + app.add_subcommand("remove",""); + app.add_subcommand("test",""); + app.allow_extras(true); + + app.parse_complete_callback([&app]() { + auto extras = app.remaining(); + if (extras.empty()) + { + return; + } + auto subs = app.get_subcommands(nullptr); + std::vector list; + for (const auto* sub : subs) { + if (!sub->get_name().empty()) + { + list.push_back(sub->get_name()); + } + auto aliases = sub->get_aliases(); + if (!aliases.empty()) + { + list.insert(list.end(), aliases.begin(), aliases.end()); + } + + } + for (auto& extra : extras) + { + if (extra.front() != '-') + { + auto closest = findClosestMatch(extra,list); + if (closest.second <= 3) + { + std::cout<<"unmatched commands "<