mirror of
https://github.com/KeqingMoe/argparse.git
synced 2025-07-03 22:54:39 +00:00
Closes #285
This commit is contained in:
parent
7657a22001
commit
62052fefcb
@ -452,6 +452,46 @@ template <typename T> struct IsChoiceTypeSupported {
|
||||
std::is_same<CleanType, const char *>::value;
|
||||
};
|
||||
|
||||
template <typename StringType>
|
||||
int get_levenshtein_distance(const StringType &s1, const StringType &s2) {
|
||||
std::vector<std::vector<int>> dp(s1.size() + 1,
|
||||
std::vector<int>(s2.size() + 1, 0));
|
||||
|
||||
for (int i = 0; i <= s1.size(); ++i) {
|
||||
for (int j = 0; j <= s2.size(); ++j) {
|
||||
if (i == 0) {
|
||||
dp[i][j] = j;
|
||||
} else if (j == 0) {
|
||||
dp[i][j] = i;
|
||||
} else if (s1[i - 1] == s2[j - 1]) {
|
||||
dp[i][j] = dp[i - 1][j - 1];
|
||||
} else {
|
||||
dp[i][j] = 1 + std::min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dp[s1.size()][s2.size()];
|
||||
}
|
||||
|
||||
template <typename ValueType>
|
||||
std::string_view
|
||||
get_most_similar_string(const std::map<std::string_view, ValueType> &map,
|
||||
const std::string_view input) {
|
||||
std::string_view most_similar{};
|
||||
int min_distance = std::numeric_limits<int>::max();
|
||||
|
||||
for (const auto &entry : map) {
|
||||
int distance = get_levenshtein_distance(entry.first, input);
|
||||
if (distance < min_distance) {
|
||||
min_distance = distance;
|
||||
most_similar = entry.first;
|
||||
}
|
||||
}
|
||||
|
||||
return most_similar;
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
|
||||
enum class nargs_pattern { optional, any, at_least_one };
|
||||
@ -1804,6 +1844,15 @@ private:
|
||||
|
||||
if (m_positional_arguments.empty()) {
|
||||
|
||||
/// Check sub-parsers first
|
||||
if (!m_subparser_map.empty()) {
|
||||
throw std::runtime_error(
|
||||
"Failed to parse '" + current_argument + "', did you mean '" +
|
||||
std::string{details::get_most_similar_string(
|
||||
m_subparser_map, current_argument)} +
|
||||
"'");
|
||||
}
|
||||
|
||||
if (!m_optional_arguments.empty()) {
|
||||
bool not_help_or_version{true};
|
||||
for (const auto &opt : m_optional_arguments) {
|
||||
|
@ -67,3 +67,31 @@ TEST_CASE("Missing optional argument name with other positional arguments" *
|
||||
std::runtime_error);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Detect unknown subcommand" * test_suite("error_reporting")) {
|
||||
argparse::ArgumentParser program("git");
|
||||
argparse::ArgumentParser log_command("log");
|
||||
argparse::ArgumentParser notes_command("notes");
|
||||
argparse::ArgumentParser add_command("add");
|
||||
program.add_subparser(log_command);
|
||||
program.add_subparser(notes_command);
|
||||
program.add_subparser(add_command);
|
||||
|
||||
SUBCASE("Typo for 'notes'") {
|
||||
REQUIRE_THROWS_WITH_AS(program.parse_args({"git", "tote"}),
|
||||
"Failed to parse 'tote', did you mean 'notes'",
|
||||
std::runtime_error);
|
||||
}
|
||||
|
||||
SUBCASE("Typo for 'add'") {
|
||||
REQUIRE_THROWS_WITH_AS(program.parse_args({"git", "bad"}),
|
||||
"Failed to parse 'bad', did you mean 'add'",
|
||||
std::runtime_error);
|
||||
}
|
||||
|
||||
SUBCASE("Typo for 'log'") {
|
||||
REQUIRE_THROWS_WITH_AS(program.parse_args({"git", "logic"}),
|
||||
"Failed to parse 'logic', did you mean 'log'",
|
||||
std::runtime_error);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user