firstboot: add similar input suggestion

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.
This commit is contained in:
Michael Ferrari
2024-09-20 08:34:03 +09:00
committed by Yu Watanabe
parent eda6223942
commit 1e1ac5d53b
3 changed files with 42 additions and 5 deletions
+27
View 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);
+1
View 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_;
+14 -5
View 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);
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);
if (best_match)
log_error("Invalid data '%s', did you mean '%s'?", p, best_match);
else
log_error("Invalid data '%s'.", p);
}
}