}
const Verb* verbs_find_verb(const char *name, const Verb verbs[]) {
+ assert(verbs);
+
for (size_t i = 0; verbs[i].dispatch; i++)
- if (streq_ptr(name, verbs[i].verb) ||
- (!name && FLAGS_SET(verbs[i].flags, VERB_DEFAULT)))
- return &verbs[i];
+ if (name ? streq(name, verbs[i].verb) : FLAGS_SET(verbs[i].flags, VERB_DEFAULT))
+ return verbs + i;
/* At the end of the list? */
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;
verb = verbs_find_verb(name, verbs);
if (!verb) {
- if (name)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Unknown command verb %s.", name);
- else
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Command verb required.");
+ 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)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Unknown command verb '%s', did you mean '%s'?", name, verb->verb);
+
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown command verb '%s'.", name);
+ }
+
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Command verb required.");
}
if (!name)
if (verb->min_args != VERB_ANY &&
(unsigned) left < verb->min_args)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Too few arguments.");
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too few arguments.");
if (verb->max_args != VERB_ANY &&
(unsigned) left > verb->max_args)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Too many arguments.");
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments.");
if ((verb->flags & VERB_ONLINE_ONLY) && running_in_chroot_or_offline()) {
log_info("Running in chroot, ignoring command '%s'", name ?: verb->verb);
return 0;
}
- if (name)
- return verb->dispatch(left, argv, userdata);
- else {
- char* fake[2] = {
- (char*) verb->verb,
- NULL
- };
+ if (!name)
+ return verb->dispatch(1, STRV_MAKE(verb->verb), userdata);
- return verb->dispatch(1, fake, userdata);
+ return verb->dispatch(left, argv, userdata);
}
-}