aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2018-02-22 18:48:49 -0800
committerGarrett D'Amore <garrett@damore.org>2018-02-22 18:48:49 -0800
commit1ba0631ca545a69488f4a60e051476fea067a154 (patch)
tree009e8a0cde0b3bed8b61c0af221e9299ce724296
parent6a344797283f05ac77e84be0312cf668f7953b6e (diff)
downloadnng-1ba0631ca545a69488f4a60e051476fea067a154.tar.gz
nng-1ba0631ca545a69488f4a60e051476fea067a154.tar.bz2
nng-1ba0631ca545a69488f4a60e051476fea067a154.zip
Add nng_opts_parse() API for handling command line options.
We have implemented this alternative to getopt() so that we can create nngcat. The reason we did not just use getopt() is that getopt() does not understand long options (which nanocat uses, and we want to preserve for compatibility) and getopt() is not available on Windows (and possibly other non-POSIX platforms.) This function handles long and short options, but does not have support for option clustering. It also is threadsafe & reentrant, unlike getopt.
-rw-r--r--src/supplemental/util/CMakeLists.txt8
-rw-r--r--src/supplemental/util/options.c124
-rw-r--r--src/supplemental/util/options.h48
-rw-r--r--src/supplemental/util/platform.h6
-rw-r--r--tests/CMakeLists.txt1
-rw-r--r--tests/options.c252
6 files changed, 432 insertions, 7 deletions
diff --git a/src/supplemental/util/CMakeLists.txt b/src/supplemental/util/CMakeLists.txt
index 443473d6..f94e2b45 100644
--- a/src/supplemental/util/CMakeLists.txt
+++ b/src/supplemental/util/CMakeLists.txt
@@ -8,8 +8,8 @@
# found online at https://opensource.org/licenses/MIT.
#
-set(SUPP_PLATFORM_SOURCES supplemental/util/platform.c)
-set(SUPP_PLATFORM_HEADERS supplemental/util/platform.h)
+set(SUPP_UTIL_SOURCES supplemental/util/options.c supplemental/util/platform.c)
+set(SUPP_UTIL_HEADERS supplemental/util/options.h supplemental/util/platform.h)
-set(NNG_SOURCES ${NNG_SOURCES} ${SUPP_PLATFORM_SOURCES} PARENT_SCOPE)
-set(NNG_HEADERS ${NNG_HEADERS} ${SUPP_PLATFORM_HEADERS} PARENT_SCOPE)
+set(NNG_SOURCES ${NNG_SOURCES} ${SUPP_UTIL_SOURCES} PARENT_SCOPE)
+set(NNG_HEADERS ${NNG_HEADERS} ${SUPP_UTIL_HEADERS} PARENT_SCOPE)
diff --git a/src/supplemental/util/options.c b/src/supplemental/util/options.c
new file mode 100644
index 00000000..d8f78deb
--- /dev/null
+++ b/src/supplemental/util/options.c
@@ -0,0 +1,124 @@
+//
+// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2018 Capitar IT Group BV <info@capitar.com>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "core/nng_impl.h"
+#include "supplemental/util/options.h"
+
+// Call with optidx set to 1 to start parsing.
+int
+nng_opts_parse(int argc, const char **argv, const nng_optspec *opts, int *val,
+ const char **optarg, int *optidx)
+{
+ const nng_optspec *opt;
+ int matches;
+ bool shortopt;
+ size_t l;
+ const char * arg = argv[*optidx];
+ int i;
+
+ if ((i = *optidx) >= argc) {
+ return (-1);
+ }
+
+ if (arg[0] != '-') {
+ return (-1);
+ }
+ if (arg[1] == '\0') {
+ *optidx = i + 1;
+ return (-1);
+ }
+
+ if ((arg[0] == '-') && (arg[1] == '-')) {
+ arg += 2;
+ shortopt = false;
+ for (l = 0; arg[l] != '\0'; l++) {
+ if ((arg[l] == '=') || (arg[l] == ':')) {
+ break;
+ }
+ }
+ } else {
+ arg++;
+ shortopt = true;
+ l = 1;
+ }
+
+ matches = 0;
+ opt = NULL;
+
+ for (int x = 0; opts[x].o_val != 0; x++) {
+
+ if (shortopt) {
+ if (arg[0] == opts[x].o_short) {
+ matches = 1;
+ opt = &opts[x];
+ break;
+ }
+ continue;
+ }
+
+ if ((opts[x].o_name == NULL) ||
+ (strncmp(arg, opts[x].o_name, l) != 0)) {
+ continue;
+ }
+ matches++;
+ opt = &opts[x];
+
+ if (strlen(opts[x].o_name) == l) {
+ // Perfect match.
+ matches = 1;
+ break;
+ }
+ }
+
+ switch (matches) {
+ case 1:
+ // Exact match
+ break;
+ case 0:
+ // No such option
+ return (NNG_EINVAL);
+ break;
+ default:
+ // Ambiguous (not match)
+ return (NNG_EINVAL);
+ break;
+ }
+
+ if (!opt->o_arg) {
+ // No option clustering for short options yet.
+ if (arg[l] != '\0') {
+ return (NNG_EINVAL);
+ }
+ *val = opt->o_val;
+ *optidx = i + 1;
+ return (0);
+ }
+
+ if (arg[l] != '\0') {
+ if (shortopt) {
+ *optarg = arg + l;
+ } else {
+ *optarg = arg + l + 1;
+ }
+ } else {
+ i++;
+ if (i >= argc) {
+ return (NNG_EINVAL);
+ }
+ *optarg = argv[i];
+ }
+ *optidx = ++i;
+ *val = opt->o_val;
+
+ return (0);
+}
diff --git a/src/supplemental/util/options.h b/src/supplemental/util/options.h
new file mode 100644
index 00000000..02b3a9d4
--- /dev/null
+++ b/src/supplemental/util/options.h
@@ -0,0 +1,48 @@
+//
+// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2018 Capitar IT Group BV <info@capitar.com>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+#ifndef NNG_SUPPLEMENTAL_UTIL_OPTIONS_H
+#define NNG_SUPPLEMENTAL_UTIL_OPTIONS_H
+
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// This is a relatively simple "options parsing" library, used to
+// parse command line options. We would use getopt(3), but there are
+// two problems with getopt(3). First, it isn't available on all
+// platforms (especially Win32), and second, it doesn't support long
+// options. We *exclusively* support long options. POSIX style
+// short option clustering is *NOT* supported.
+
+struct nng_optspec {
+ const char *o_name; // Long style name (may be NULL for short only)
+ int o_short; // Short option (no clustering!)
+ int o_val; // Value stored on a good parse (>0)
+ bool o_arg; // Option takes an argument if true
+};
+
+typedef struct nng_optspec nng_optspec;
+
+// Call with *optidx set to 1 to start parsing for a standard program.
+// The val will store the value of the matched "o_val", optarg will be
+// set to match the option string, and optidx will be increment appropriately.
+// Returns -1 when the end of options is reached, 0 on success, or
+// NNG_EINVAL if the option parse is invalid for any reason.
+NNG_DECL int nng_opts_parse(int argc, const char **argv,
+ const nng_optspec *opts, int *val, const char **optarg, int *optidx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // NNG_SUPPLEMENTAL_UTIL_OPTIONS_H \ No newline at end of file
diff --git a/src/supplemental/util/platform.h b/src/supplemental/util/platform.h
index 1383fe15..fd287713 100644
--- a/src/supplemental/util/platform.h
+++ b/src/supplemental/util/platform.h
@@ -20,13 +20,13 @@
// APIs or Windows APIs, then by all means please feel free to simply
// ignore this.
+#include <stddef.h>
+#include <stdint.h>
+
#ifdef __cplusplus
extern "C" {
#endif
-#include <stddef.h>
-#include <stdint.h>
-
// nng_time represents an absolute time since some arbitrary point in the
// past, measured in milliseconds. The values are always positive.
typedef uint64_t nng_time;
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 6ae41ffe..51903572 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -145,6 +145,7 @@ add_nng_test(ipc 5 NNG_TRANSPORT_IPC)
add_nng_test(list 5 ON)
add_nng_test(message 5 ON)
add_nng_test(multistress 60 ON)
+add_nng_test(options 5 ON)
add_nng_test(platform 5 ON)
add_nng_test(pollfd 5 ON)
add_nng_test(reconnect 5 ON)
diff --git a/tests/options.c b/tests/options.c
new file mode 100644
index 00000000..166af37c
--- /dev/null
+++ b/tests/options.c
@@ -0,0 +1,252 @@
+//
+// Copyright 2018 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2018 Capitar IT Group BV <info@capitar.com>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+#include <string.h>
+
+#include "convey.h"
+
+#include "nng.h"
+
+#include "supplemental/util/options.h"
+
+static nng_optspec case1[] = {
+ // clang-format off
+ { "flag", 'f', 1, false },
+ { "longflag", 0, 2, false },
+ { "value", 'v', 3, true },
+ { NULL, 'b', 4, false },
+ { NULL, 0, 0, false },
+ // clang-format on
+};
+
+TestMain("Option Parsing", {
+
+ Convey("Simple works", {
+
+ int opti = 1;
+ const char *av[6];
+ int ac = 5;
+ int v;
+ const char *a = NULL;
+
+ av[0] = "program";
+ av[1] = "-f";
+ av[2] = "-v";
+ av[3] = "123";
+ av[4] = "456";
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == 0);
+ So(v == 1);
+ So(a == NULL);
+ So(opti == 2);
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == 0);
+ So(opti == 4);
+ So(v == 3);
+ So(strcmp(a, "123") == 0);
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == -1);
+ So(opti == 4);
+ So(strcmp(av[opti], "456") == 0);
+ });
+
+ Convey("Long works", {
+
+ int opti = 1;
+ const char *av[6];
+ int ac = 5;
+ int v;
+ const char *a = NULL;
+
+ av[0] = "program";
+ av[1] = "--flag";
+ av[2] = "--value";
+ av[3] = "123";
+ av[4] = "456";
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == 0);
+ So(v == 1);
+ So(a == NULL);
+ So(opti == 2);
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == 0);
+ So(opti == 4);
+ So(v == 3);
+ So(strcmp(a, "123") == 0);
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == -1);
+ So(opti == 4);
+ So(strcmp(av[opti], "456") == 0);
+ });
+
+ Convey("Attached short works", {
+
+ int opti = 1;
+ const char *av[3];
+ int ac = 3;
+ int v;
+ const char *a = NULL;
+
+ av[0] = "program";
+ av[1] = "-v123";
+ av[2] = "456";
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == 0);
+ So(opti == 2);
+ So(v == 3);
+ So(strcmp(a, "123") == 0);
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == -1);
+ So(opti == 2);
+ So(strcmp(av[opti], "456") == 0);
+ });
+
+ Convey("Attached long (=) works", {
+
+ int opti = 1;
+ const char *av[3];
+ int ac = 3;
+ int v;
+ const char *a = NULL;
+
+ av[0] = "program";
+ av[1] = "--value=123";
+ av[2] = "456";
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == 0);
+ So(opti == 2);
+ So(v == 3);
+ So(strcmp(a, "123") == 0);
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == -1);
+ So(opti == 2);
+ So(strcmp(av[opti], "456") == 0);
+ });
+
+ Convey("Attached long (:) works", {
+
+ int opti = 1;
+ const char *av[3];
+ int ac = 3;
+ int v;
+ const char *a = NULL;
+
+ av[0] = "program";
+ av[1] = "--value:123";
+ av[2] = "456";
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == 0);
+ So(opti == 2);
+ So(v == 3);
+ So(strcmp(a, "123") == 0);
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == -1);
+ So(opti == 2);
+ So(strcmp(av[opti], "456") == 0);
+ });
+
+ Convey("Negative bad short works", {
+
+ int opti = 1;
+ const char *av[3];
+ int ac = 3;
+ int v;
+ const char *a = NULL;
+
+ av[0] = "program";
+ av[1] = "-Z";
+ av[2] = "456";
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == NNG_EINVAL);
+ So(opti == 1);
+ });
+
+ Convey("Negative bad long works", {
+
+ int opti = 1;
+ const char *av[3];
+ int ac = 3;
+ int v;
+ const char *a = NULL;
+
+ av[0] = "program";
+ av[1] = "--something";
+ av[2] = "456";
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == NNG_EINVAL);
+ So(opti == 1);
+ });
+
+ Convey("Separator flag works", {
+ int opti = 1;
+ const char *av[5];
+ int ac = 5;
+ int v;
+ const char *a = NULL;
+
+ av[0] = "program";
+ av[1] = "-f";
+ av[2] = "-";
+ av[3] = "-v";
+ av[4] = "456";
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == 0);
+ So(v == 1);
+ So(opti == 2);
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == -1);
+ So(opti == 3);
+ });
+
+ Convey("No options works", {
+ int opti = 1;
+ const char *av[1];
+ int ac = 1;
+ int v;
+ const char *a = NULL;
+
+ av[0] = "program";
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == -1);
+ });
+
+ Convey("No options (but arguments) works", {
+ int opti = 1;
+ const char *av[2];
+ int ac = 2;
+ int v;
+ const char *a = NULL;
+
+ av[0] = "program";
+ av[1] = "123";
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == -1);
+ So(opti == 1);
+ });
+ Convey("Mixed long and short works", {
+
+ int opti = 1;
+ const char *av[7];
+ int ac = 7;
+ int v;
+ const char *a = NULL;
+
+ av[0] = "program";
+ av[1] = "--value=123";
+ av[2] = "-f";
+ av[3] = "--longflag";
+ av[4] = "-b";
+ av[5] = "-vxyz";
+ av[6] = "456";
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == 0);
+ So(opti == 2);
+ So(v == 3);
+ So(strcmp(a, "123") == 0);
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == 0);
+ So(opti == 3);
+ So(v == 1);
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == 0);
+ So(opti == 4);
+ So(v == 2);
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == 0);
+ So(opti == 5);
+ So(v == 4);
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == 0);
+ So(opti == 6);
+ So(v == 3);
+ So(strcmp(a, "xyz") == 0);
+ So(strcmp(av[opti], "456") == 0);
+ So(nng_opts_parse(ac, av, case1, &v, &a, &opti) == -1);
+ So(opti == 6);
+ });
+
+});