From: Yu Watanabe Date: Fri, 20 Sep 2024 00:31:46 +0000 (+0900) Subject: verbs: use strv_find_closest() X-Git-Tag: v257-rc1~379^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fpull%2F34502%2Fhead;p=thirdparty%2Fsystemd.git verbs: use strv_find_closest() This also makes the list of verbs is always shown on failure. --- diff --git a/src/shared/verbs.c b/src/shared/verbs.c index 7e7ac2a2554..8442963b371 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -10,6 +10,7 @@ #include "macro.h" #include "process-util.h" #include "string-util.h" +#include "strv.h" #include "verbs.h" #include "virt.h" @@ -54,70 +55,14 @@ const Verb* verbs_find_verb(const char *name, const Verb verbs[]) { return NULL; } -static const Verb* verbs_find_prefix_verb(const char *name, const Verb verbs[]) { - size_t best_distance = SIZE_MAX; - const Verb *best = NULL; - - assert(verbs); - - if (!name) - return NULL; - - for (size_t i = 0; verbs[i].dispatch; i++) { - const char *e; - size_t l; - - e = startswith(verbs[i].verb, name); - if (!e) - continue; - - l = strlen(e); - if (l < best_distance) { - best_distance = l; - best = verbs + i; - } - } - - return best; -} - -static const Verb* verbs_find_closest_verb(const char *name, const Verb verbs[]) { - ssize_t best_distance = SSIZE_MAX; - const Verb *best = NULL; - - assert(verbs); - - if (!name) - return NULL; - - for (size_t i = 0; verbs[i].dispatch; i++) { - ssize_t distance; - - distance = strlevenshtein(verbs[i].verb, name); - if (distance < 0) { - log_debug_errno(distance, "Failed to determine Levenshtein distance between %s and %s: %m", verbs[i].verb, 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 = verbs + i; - } - } - - return best; -} - int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { const Verb *verb; const char *name; - int left; + int r, left; assert(verbs); assert(verbs[0].dispatch); + assert(verbs[0].verb); assert(argc >= 0); assert(argv); assert(argc >= optind); @@ -129,32 +74,34 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { verb = verbs_find_verb(name, verbs); if (!verb) { + _cleanup_strv_free_ char **verb_strv = NULL; + + for (size_t i = 0; verbs[i].dispatch; i++) { + r = strv_extend(&verb_strv, verbs[i].verb); + if (r < 0) + return log_oom(); + } + if (name) { - /* 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. */ - verb = verbs_find_prefix_verb(name, verbs); - if (!verb) - verb = verbs_find_closest_verb(name, verbs); - if (verb) + /* Be more helpful to the user, and give a hint what the user might have wanted to type. */ + const char *found = strv_find_closest(verb_strv, name); + if (found) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown command verb '%s', did you mean '%s'?", name, verb->verb); + "Unknown command verb '%s', did you mean '%s'?", name, found); return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown command verb '%s'.", name); } - _cleanup_free_ char *verb_list = NULL; - size_t i; - - for (i = 0; verbs[i].dispatch; i++) - if (!strextend_with_separator(&verb_list, ", ", verbs[i].verb)) + if (strv_length(verb_strv) >= 2) { + _cleanup_free_ char *joined = strv_join(verb_strv, ", "); + if (!joined) return log_oom(); - if (i > 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Command verb required (one of %s).", verb_list); + "Command verb required (one of %s).", joined); + } - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Command verb required."); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Command verb '%s' required.", verbs[0].verb); } if (!name) diff --git a/src/test/test-verbs.c b/src/test/test-verbs.c index 6e30794fa3e..a033d07ee72 100644 --- a/src/test/test-verbs.c +++ b/src/test/test-verbs.c @@ -54,6 +54,28 @@ TEST(verbs_no_default) { }; test_dispatch_one(STRV_MAKE(NULL), verbs, -EINVAL); + test_dispatch_one(STRV_MAKE("hel"), verbs, -EINVAL); + test_dispatch_one(STRV_MAKE("helpp"), verbs, -EINVAL); + test_dispatch_one(STRV_MAKE("hgrejgoraoiosafso"), verbs, -EINVAL); +} + +TEST(verbs_no_default_many) { + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "list-images", VERB_ANY, 1, 0, noop_dispatcher }, + { "list", VERB_ANY, 2, 0, noop_dispatcher }, + { "status", 2, VERB_ANY, 0, noop_dispatcher }, + { "show", VERB_ANY, VERB_ANY, 0, noop_dispatcher }, + { "terminate", 2, VERB_ANY, 0, noop_dispatcher }, + { "login", 2, 2, 0, noop_dispatcher }, + { "copy-to", 3, 4, 0, noop_dispatcher }, + {} + }; + + test_dispatch_one(STRV_MAKE(NULL), verbs, -EINVAL); + test_dispatch_one(STRV_MAKE("hel"), verbs, -EINVAL); + test_dispatch_one(STRV_MAKE("helpp"), verbs, -EINVAL); + test_dispatch_one(STRV_MAKE("hgrejgoraoiosafso"), verbs, -EINVAL); } DEFINE_TEST_MAIN(LOG_INFO);