[systemd-devel] [PATCH 2/2] verbs: allow matching by prefix

Dave Reisner dreisner at archlinux.org
Sat Dec 20 08:19:51 PST 2014


Be nice to command line users and allow matching prefixes of verbs,
similar to the way getopt_long operates. If the prefix cannot be
resolved to a singular verb, log the ambiguity and return an error.
---
I'm not thrilled about adding this to every manpage out of concern that it
might get out of sync, but I don't really see a better way.

 man/bootctl.xml        |  3 ++-
 man/busctl.xml         |  3 ++-
 man/coredumpctl.xml    |  3 ++-
 man/hostnamectl.xml    |  3 ++-
 man/kernel-install.xml |  3 ++-
 man/localectl.xml      |  3 ++-
 man/loginctl.xml       |  3 ++-
 man/systemctl.xml      |  3 ++-
 man/telinit.xml        |  3 ++-
 src/shared/verbs.c     | 58 ++++++++++++++++++++++++++++++++++++++------------
 src/test/test-verbs.c  |  9 ++++++++
 11 files changed, 71 insertions(+), 23 deletions(-)

diff --git a/man/bootctl.xml b/man/bootctl.xml
index 5254022..93363bd 100644
--- a/man/bootctl.xml
+++ b/man/bootctl.xml
@@ -79,7 +79,8 @@
                         <xi:include href="standard-options.xml" xpointer="version" />
                 </variablelist>
 
-                <para>The following commands are understood:</para>
+                <para>Commands can be specified by exact match or unambiguous prefix match.
+                The following commands are understood:</para>
 
                 <variablelist>
                         <varlistentry>
diff --git a/man/busctl.xml b/man/busctl.xml
index 285725e..a215a7e 100644
--- a/man/busctl.xml
+++ b/man/busctl.xml
@@ -260,7 +260,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
   <refsect1>
     <title>Commands</title>
 
-    <para>The following commands are understood:</para>
+    <para>Commands can be specified by exact match or unambiguous prefix match.
+    The following commands are understood:</para>
 
     <variablelist>
       <varlistentry>
diff --git a/man/coredumpctl.xml b/man/coredumpctl.xml
index ed84621..28cce85 100644
--- a/man/coredumpctl.xml
+++ b/man/coredumpctl.xml
@@ -111,7 +111,8 @@
 
                 </variablelist>
 
-                <para>The following commands are understood:</para>
+                <para>Commands can be specified by exact match or unambiguous prefix match.
+                The following commands are understood:</para>
 
                 <variablelist>
                         <varlistentry>
diff --git a/man/hostnamectl.xml b/man/hostnamectl.xml
index de15402..51cb995 100644
--- a/man/hostnamectl.xml
+++ b/man/hostnamectl.xml
@@ -135,7 +135,8 @@
                         <xi:include href="standard-options.xml" xpointer="version" />
                 </variablelist>
 
-                <para>The following commands are understood:</para>
+                <para>Commands can be specified by exact match or unambiguous prefix match.
+                The following commands are understood:</para>
 
                 <variablelist>
                         <varlistentry>
diff --git a/man/kernel-install.xml b/man/kernel-install.xml
index d4d0180..aa6aca2 100644
--- a/man/kernel-install.xml
+++ b/man/kernel-install.xml
@@ -79,7 +79,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
 
   <refsect1>
     <title>Commands</title>
-    <para>The following commands are understood:</para>
+    <para>Commands can be specified by exact match or unambiguous prefix match.
+    The following commands are understood:</para>
     <variablelist>
       <varlistentry>
         <term><command>add <replaceable>KERNEL-VERSION</replaceable> <replaceable>KERNEL-IMAGE</replaceable></command></term>
diff --git a/man/localectl.xml b/man/localectl.xml
index b472b6b..8f5b442 100644
--- a/man/localectl.xml
+++ b/man/localectl.xml
@@ -113,7 +113,8 @@
                         <xi:include href="standard-options.xml" xpointer="no-pager" />
                 </variablelist>
 
-                <para>The following commands are understood:</para>
+                <para>Commands can be specified by exact match or unambiguous prefix match.
+                The following commands are understood:</para>
 
                 <variablelist>
                         <varlistentry>
diff --git a/man/loginctl.xml b/man/loginctl.xml
index 749db92..f2162ac 100644
--- a/man/loginctl.xml
+++ b/man/loginctl.xml
@@ -165,7 +165,8 @@
                         <xi:include href="standard-options.xml" xpointer="no-pager" />
                 </variablelist>
 
-                <para>The following commands are understood:</para>
+                <para>Commands can be specified by exact match or unambiguous prefix match.
+                The following commands are understood:</para>
 
                 <variablelist>
                         <varlistentry>
diff --git a/man/systemctl.xml b/man/systemctl.xml
index d1991e0..91fcba3 100644
--- a/man/systemctl.xml
+++ b/man/systemctl.xml
@@ -557,7 +557,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
   <refsect1>
     <title>Commands</title>
 
-    <para>The following commands are understood:</para>
+    <para>Commands can be specified by exact match or unambiguous prefix match.
+    The following commands are understood:</para>
 
     <refsect2>
       <title>Unit Commands</title>
diff --git a/man/telinit.xml b/man/telinit.xml
index 33ea118..20cb9ac 100644
--- a/man/telinit.xml
+++ b/man/telinit.xml
@@ -86,7 +86,8 @@
                         </varlistentry>
                 </variablelist>
 
-                <para>The following commands are understood:</para>
+                <para>Commands can be specified by exact match or unambiguous prefix match.
+                The following commands are understood:</para>
 
                 <variablelist>
                         <varlistentry>
diff --git a/src/shared/verbs.c b/src/shared/verbs.c
index bc1ae43..da3f2b3 100644
--- a/src/shared/verbs.c
+++ b/src/shared/verbs.c
@@ -19,14 +19,28 @@
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#include "strv.h"
 #include "util.h"
 #include "verbs.h"
 
+static int log_ambiguous(const char *opname, char **verbs) {
+        _cleanup_free_ char *possibilities = NULL;
+
+        possibilities = strv_join(verbs, ", ");
+        if (possibilities == NULL)
+                return -ENOMEM;
+
+        log_error("Operation %s is ambiguous; possibilities: %s\n", opname, possibilities);
+        return -EBADMSG;
+}
+
 int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) {
         const Verb *verb;
         const char *name;
         unsigned i;
         int left;
+        bool exact_match = false;
+        _cleanup_strv_free_ char **prefix_matches = NULL;
 
         assert(verbs);
         assert(verbs[0].dispatch);
@@ -37,26 +51,42 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) {
         left = argc - optind;
         name = argv[optind];
 
-        for (i = 0;; i++) {
-                bool found;
+        for (i = 0; verbs[i].dispatch; i++) {
+                if (name) {
+                        exact_match = streq(name, verbs[i].verb);
+                        if (!exact_match)
+                                if (startswith(verbs[i].verb, name)) {
+                                        int r;
+
+                                        /* Assume this is our candidate. We'll
+                                         * catch any ambiguity later. */
+                                        verb = &verbs[i];
+
+                                        r = strv_extend(&prefix_matches, (char *)verbs[i].verb);
+                                        if (r < 0)
+                                                return r;
+                                }
+                } else
+                        exact_match = !!(verbs[i].flags & VERB_DEFAULT);
+
+                if (exact_match) {
+                        verb = &verbs[i];
+                        break;
+                }
+        }
 
-                /* At the end of the list? */
-                if (!verbs[i].dispatch) {
+        if (!exact_match) {
+                switch (strv_length(prefix_matches)) {
+                case 0:
                         if (name)
                                 log_error("Unknown operation %s.", name);
                         else
                                 log_error("Requires operation parameter.");
                         return -EINVAL;
-                }
-
-                if (name)
-                        found = streq(name, verbs[i].verb);
-                else
-                        found = !!(verbs[i].flags & VERB_DEFAULT);
-
-                if (found) {
-                        verb = &verbs[i];
+                case 1:
                         break;
+                default:
+                        return log_ambiguous(name, prefix_matches);
                 }
         }
 
@@ -73,7 +103,7 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) {
 
         if (verb->max_args != VERB_ANY &&
             (unsigned) left > verb->max_args) {
-                log_error("Too many argument.s");
+                log_error("Too many arguments.");
                 return -EINVAL;
         }
 
diff --git a/src/test/test-verbs.c b/src/test/test-verbs.c
index 0fcdd9e..afd9ba8 100644
--- a/src/test/test-verbs.c
+++ b/src/test/test-verbs.c
@@ -59,6 +59,15 @@ static void test_verbs(void) {
 
         /* no verb, but a default is set */
         test_dispatch_one(STRV_MAKE_EMPTY, verbs, 0);
+
+        /* prefix match */
+        test_dispatch_one(STRV_MAKE("lo", "foo"), verbs, 0);
+
+        /* exact match with prefix match */
+        test_dispatch_one(STRV_MAKE("list", "foo"), verbs, 0);
+
+        /* ambiguous prefix match */
+        test_dispatch_one(STRV_MAKE("l"), verbs, -EBADMSG);
 }
 
 static void test_verbs_no_default(void) {
-- 
2.2.0


More information about the systemd-devel mailing list