]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
verbs: use strv_find_closest() 34502/head
authorYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 20 Sep 2024 00:31:46 +0000 (09:31 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 20 Sep 2024 20:10:47 +0000 (05:10 +0900)
This also makes the list of verbs is always shown on failure.

src/shared/verbs.c
src/test/test-verbs.c

index 7e7ac2a2554069e8ce14a5dd005ecc082b77722b..8442963b37128f0764abd64208c227f32de8ab8b 100644 (file)
@@ -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)
index 6e30794fa3eb8787b08bcb9982e8bd78a26b4d6d..a033d07ee721182b8479bd00e588b78f1bfbed68 100644 (file)
@@ -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);