From: Yu Watanabe Date: Fri, 20 Sep 2024 00:09:28 +0000 (+0900) Subject: strv: introduce strv_find_closest() X-Git-Tag: v257-rc1~379^2~1 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ffdf4978604672ee7b327a5f65d821daeac26149;p=thirdparty%2Fsystemd.git strv: introduce strv_find_closest() Follow-up for 1e1ac5d53b0f126b6c4419506c7c42b67c07537f. --- diff --git a/src/basic/strv.c b/src/basic/strv.c index 5b71c36dbfe..414123e5586 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -66,7 +66,28 @@ char* strv_find_startswith(char * const *l, const char *name) { return NULL; } -char* strv_find_closest_by_levenshtein(char * const *l, const char *name) { +static char* strv_find_closest_prefix(char * const *l, const char *name) { + size_t best_distance = SIZE_MAX; + char *best = NULL; + + assert(name); + + STRV_FOREACH(s, l) { + char *e = startswith(*s, name); + if (!e) + continue; + + size_t n = strlen(e); + if (n < best_distance) { + best_distance = n; + best = *s; + } + } + + return best; +} + +static char* strv_find_closest_by_levenshtein(char * const *l, const char *name) { ssize_t best_distance = SSIZE_MAX; char *best = NULL; @@ -93,6 +114,20 @@ char* strv_find_closest_by_levenshtein(char * const *l, const char *name) { return best; } +char* strv_find_closest(char * const *l, const char *name) { + assert(name); + + /* Be more helpful to the user, and give a hint what the user might have wanted to type. We search + * with two mechanisms: a simple prefix match and – if that didn't yield results –, a Levenshtein + * word distance based match. */ + + char *found = strv_find_closest_prefix(l, name); + if (found) + return found; + + return strv_find_closest_by_levenshtein(l, name); +} + char* strv_find_first_field(char * const *needles, char * const *haystack) { STRV_FOREACH(k, needles) { char *value = strv_env_pairs_get((char **)haystack, *k); diff --git a/src/basic/strv.h b/src/basic/strv.h index b7845eeeccb..1afb1bf6596 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -17,7 +17,7 @@ char* strv_find(char * const *l, const char *name) _pure_; char* strv_find_case(char * const *l, const char *name) _pure_; char* strv_find_prefix(char * const *l, const char *name) _pure_; char* strv_find_startswith(char * const *l, const char *name) _pure_; -char* strv_find_closest_by_levenshtein(char * const *l, const char *name) _pure_; +char* strv_find_closest(char * const *l, const char *name) _pure_; /* Given two vectors, the first a list of keys and the second a list of key-value pairs, returns the value * of the first key from the first vector that is found in the second vector. */ char* strv_find_first_field(char * const *needles, char * const *haystack) _pure_; diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 3899835d4c6..236c64634c9 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -187,7 +187,6 @@ static int prompt_loop(const char *text, char **l, unsigned percentage, bool (*i for (;;) { _cleanup_free_ char *p = NULL; - char *best_match = NULL; unsigned u; r = ask_string(&p, "%s %s (empty to skip, \"list\" to list options): ", @@ -223,13 +222,8 @@ static int prompt_loop(const char *text, char **l, unsigned percentage, bool (*i if (is_valid(p)) return free_and_replace(*ret, p); - /* Be helperful to the user, and give a hint what the user might have wanted to - * type. We search with two mechanisms: a simple prefix match and – if that didn't - * yield results –, a Levenshtein word distance based match. */ - best_match = strv_find_prefix(l, p); - if (!best_match) - best_match = strv_find_closest_by_levenshtein(l, p); - + /* Be more helpful to the user, and give a hint what the user might have wanted to type. */ + const char *best_match = strv_find_closest(l, p); if (best_match) log_error("Invalid data '%s', did you mean '%s'?", p, best_match); else diff --git a/src/test/test-strv.c b/src/test/test-strv.c index f7b6ee2bb2d..7c25d59bf43 100644 --- a/src/test/test-strv.c +++ b/src/test/test-strv.c @@ -1188,4 +1188,31 @@ TEST(strv_rebreak_lines) { } } +TEST(strv_find_closest) { + char **l = STRV_MAKE("aaa", "aaaa", "bbb", "ccc"); + + /* prefix match */ + ASSERT_STREQ(strv_find_closest(l, "a"), "aaa"); + ASSERT_STREQ(strv_find_closest(l, "aa"), "aaa"); + ASSERT_STREQ(strv_find_closest(l, "aaa"), "aaa"); + ASSERT_STREQ(strv_find_closest(l, "aaaa"), "aaaa"); + ASSERT_STREQ(strv_find_closest(l, "b"), "bbb"); + ASSERT_STREQ(strv_find_closest(l, "bb"), "bbb"); + ASSERT_STREQ(strv_find_closest(l, "bbb"), "bbb"); + ASSERT_STREQ(strv_find_closest(l, "c"), "ccc"); + ASSERT_STREQ(strv_find_closest(l, "cc"), "ccc"); + ASSERT_STREQ(strv_find_closest(l, "ccc"), "ccc"); + + /* levenshtein match */ + ASSERT_STREQ(strv_find_closest(l, "aab"), "aaa"); + ASSERT_STREQ(strv_find_closest(l, "abb"), "bbb"); + ASSERT_STREQ(strv_find_closest(l, "cbc"), "ccc"); + ASSERT_STREQ(strv_find_closest(l, "aax"), "aaa"); + ASSERT_STREQ(strv_find_closest(l, "bbbb"), "bbb"); + ASSERT_STREQ(strv_find_closest(l, "cbbb"), "bbb"); + ASSERT_STREQ(strv_find_closest(l, "bbbx"), "bbb"); + + ASSERT_NULL(strv_find_closest(l, "sfajosajfosdjaofjdsaf")); +} + DEFINE_TEST_MAIN(LOG_INFO);