MINOR: actions: add a function to suggest an action ressembling a given word

action_suggest() will return a pointer to an action whose keyword more or
less ressembles the passed argument. It also accepts to be more tolerant
against prefixes (since actions taking arguments are handled as prefixes).
This will be used to suggest approaching words.
diff --git a/include/haproxy/action.h b/include/haproxy/action.h
index 7338a75..a4bec01 100644
--- a/include/haproxy/action.h
+++ b/include/haproxy/action.h
@@ -29,6 +29,7 @@
 
 int act_resolution_cb(struct resolv_requester *requester, struct dns_counters *counters);
 int act_resolution_error_cb(struct resolv_requester *requester, int error_code);
+const char *action_suggest(const char *word, const struct list *keywords, const char **extra);
 
 static inline struct action_kw *action_lookup(struct list *keywords, const char *kw)
 {
diff --git a/src/action.c b/src/action.c
index 8292fe9..c817376 100644
--- a/src/action.c
+++ b/src/action.c
@@ -206,3 +206,56 @@
 
 	return 0;
 }
+
+/* tries to find in list <keywords> a similar looking action as the one in
+ * <word>, and returns it otherwise NULL. <word> may be NULL or empty. An
+ * optional array of extra words to compare may be passed in <extra>, but it
+ * must then be terminated by a NULL entry. If unused it may be NULL.
+ */
+const char *action_suggest(const char *word, const struct list *keywords, const char **extra)
+{
+	uint8_t word_sig[1024];
+	uint8_t list_sig[1024];
+	const struct action_kw_list *kwl;
+	const struct action_kw *best_kw = NULL;
+	const char *best_ptr = NULL;
+	int dist, best_dist = INT_MAX;
+	int index;
+
+	if (!word || !*word)
+		return NULL;
+
+	make_word_fingerprint(word_sig, word);
+	list_for_each_entry(kwl, keywords, list) {
+		for (index = 0; kwl->kw[index].kw != NULL; index++) {
+			make_word_fingerprint(list_sig, kwl->kw[index].kw);
+			dist = word_fingerprint_distance(word_sig, list_sig);
+			if (dist < best_dist) {
+				best_dist = dist;
+				best_kw   = &kwl->kw[index];
+				best_ptr  = best_kw->kw;
+			}
+		}
+	}
+
+	while (extra && *extra) {
+		make_word_fingerprint(list_sig, *extra);
+		dist = word_fingerprint_distance(word_sig, list_sig);
+		if (dist < best_dist) {
+			best_dist = dist;
+			best_kw   = NULL;
+			best_ptr  = *extra;
+		}
+		extra++;
+	}
+
+	/* eliminate too different ones, with more tolerance for prefixes
+	 * when they're known to exist (not from extra list).
+	 */
+	if (best_ptr &&
+	    (best_dist > (2 + (best_kw && best_kw->match_pfx)) * strlen(word) ||
+	     best_dist > (2 + (best_kw && best_kw->match_pfx)) * strlen(best_ptr)))
+		best_ptr = NULL;
+
+	return best_ptr;
+}