]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
firstboot: add similar input suggestion
authorMichael Ferrari <nekkodroid404@gmail.com>
Sat, 14 Sep 2024 00:01:52 +0000 (02:01 +0200)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Thu, 19 Sep 2024 23:34:03 +0000 (08:34 +0900)
This uses the same logic as similar verb suggestion for command line
utilities. Try to be helpful when the user entered something invalid
instead of just showing the prompt again.

src/basic/strv.c
src/basic/strv.h
src/firstboot/firstboot.c

index 6c71f990c8313e254d603aed02fdc242203b9d8e..5b71c36dbfe74f5f6e7b898a1338600319d04bb6 100644 (file)
@@ -66,6 +66,33 @@ char* strv_find_startswith(char * const *l, const char *name) {
         return NULL;
 }
 
+char* strv_find_closest_by_levenshtein(char * const *l, const char *name) {
+        ssize_t best_distance = SSIZE_MAX;
+        char *best = NULL;
+
+        assert(name);
+
+        STRV_FOREACH(i, l) {
+                ssize_t distance;
+
+                distance = strlevenshtein(*i, name);
+                if (distance < 0) {
+                        log_debug_errno(distance, "Failed to determine Levenshtein distance between %s and %s: %m", *i, name);
+                        return NULL;
+                }
+
+                if (distance > 5) /* If the distance is just too far off, don't make a bad suggestion */
+                        continue;
+
+                if (distance < best_distance) {
+                        best_distance = distance;
+                        best = *i;
+                }
+        }
+
+        return best;
+}
+
 char* strv_find_first_field(char * const *needles, char * const *haystack) {
         STRV_FOREACH(k, needles) {
                 char *value = strv_env_pairs_get((char **)haystack, *k);
index d38d5bf538456a6042afd7ebe471523d15dc4a74..b7845eeeccb27870633a5d860c6d468c524576a7 100644 (file)
@@ -17,6 +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_;
 /* 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_;
index f4601c021013af1715a1542eee26e7efcf999489..3899835d4c6cc1b87179e947290bf2f18a67376c 100644 (file)
@@ -187,6 +187,7 @@ 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): ",
@@ -219,12 +220,20 @@ static int prompt_loop(const char *text, char **l, unsigned percentage, bool (*i
                         return free_and_strdup_warn(ret, l[u-1]);
                 }
 
-                if (!is_valid(p)) {
-                        log_error("Entered data invalid.");
-                        continue;
-                }
+                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);
 
-                return free_and_replace(*ret, p);
+                if (best_match)
+                        log_error("Invalid data '%s', did you mean '%s'?", p, best_match);
+                else
+                        log_error("Invalid data '%s'.", p);
         }
 }