#include "macro.h"
#include "process-util.h"
#include "string-util.h"
+#include "strv.h"
#include "verbs.h"
#include "virt.h"
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);
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)
};
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);