aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorOskari Timperi <oskari.timperi@iki.fi>2017-02-16 22:35:25 +0200
committerOskari Timperi <oskari.timperi@iki.fi>2017-02-16 22:35:25 +0200
commita605b6570fc6de9d7467f6fef5cc3561976c90f9 (patch)
tree7f31530de8dbbe989dec8fc1eedeea4b66d3e811 /tools
parent9a3f03af52c18517f87ab74395552cc9f7f27284 (diff)
downloadmqtt-a605b6570fc6de9d7467f6fef5cc3561976c90f9.tar.gz
mqtt-a605b6570fc6de9d7467f6fef5cc3561976c90f9.zip
Replace getopt.c with optparse.c for tools
The magic getopt.c didn't work out with MSVC for some reason.
Diffstat (limited to 'tools')
-rw-r--r--tools/CMakeLists.txt6
-rw-r--r--tools/getopt.c358
-rw-r--r--tools/getopt.h175
-rw-r--r--tools/optparse.c264
-rw-r--r--tools/optparse.h102
-rw-r--r--tools/pub.c81
-rw-r--r--tools/sub.c72
7 files changed, 471 insertions, 587 deletions
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index 1a736ab..ed14e8c 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -1,7 +1,7 @@
-ADD_LIBRARY(getopt OBJECT getopt.c)
+ADD_LIBRARY(optparse OBJECT optparse.c optparse.h)
-ADD_EXECUTABLE(pub pub.c $<TARGET_OBJECTS:getopt>)
+ADD_EXECUTABLE(pub pub.c $<TARGET_OBJECTS:optparse>)
TARGET_LINK_LIBRARIES(pub mqtt)
-ADD_EXECUTABLE(sub sub.c $<TARGET_OBJECTS:getopt>)
+ADD_EXECUTABLE(sub sub.c $<TARGET_OBJECTS:optparse>)
TARGET_LINK_LIBRARIES(sub mqtt)
diff --git a/tools/getopt.c b/tools/getopt.c
deleted file mode 100644
index 5277ed0..0000000
--- a/tools/getopt.c
+++ /dev/null
@@ -1,358 +0,0 @@
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "getopt.h"
-
-/*
- * Standard getopt global variables. optreset starts as non-zero in order to
- * trigger initialization behaviour.
- */
-const char * optarg = NULL;
-int optind = 1;
-int opterr = 1;
-int optreset = 1;
-
-/*
- * Quasi-internal global variables -- these are used via GETOPT macros.
- */
-const char * getopt_dummy = "(dummy)";
-int getopt_initialized = 0;
-
-/*
- * Internal variables.
- */
-static const char * cmdname = NULL;
-static struct opt {
- const char * os;
- size_t olen;
- int hasarg;
-} * opts = NULL;
-static size_t nopts;
-static size_t opt_missing;
-static size_t opt_default;
-static size_t opt_found;
-static const char * packedopts;
-static char popt[3];
-static int atexit_registered = 0;
-
-/* Print a message. */
-#define PRINTMSG(...) do { \
- if (cmdname != NULL) \
- fprintf(stderr, "%s: ", cmdname); \
- fprintf(stderr, __VA_ARGS__); \
- fprintf(stderr, "\n"); \
-} while (0)
-
-/* Print an error message and die. */
-#define DIE(...) do { \
- PRINTMSG(__VA_ARGS__); \
- abort(); \
-} while (0)
-
-/* Print a warning, if warnings are enabled. */
-#define WARN(...) do { \
- if (opterr == 0) \
- break; \
- if (opt_missing != opt_default) \
- break; \
- PRINTMSG(__VA_ARGS__); \
-} while (0)
-
-/* Free allocated options array. */
-static void
-atexit_handler(void)
-{
-
- free(opts);
- opts = NULL;
-}
-
-/* Reset internal state. */
-static void
-reset(int argc, char * const argv[])
-{
- const char * p;
-
- /* If we have arguments, stash argv[0] for error messages. */
- if (argc > 0) {
- /* Find the basename, without leading directories. */
- for (p = cmdname = argv[0]; *p != '\0'; p++) {
- if (*p == '/')
- cmdname = p + 1;
- }
- }
-
- /* Discard any registered command-line options. */
- free(opts);
- opts = NULL;
-
- /* Register atexit handler if we haven't done so already. */
- if (!atexit_registered) {
- atexit(atexit_handler);
- atexit_registered = 1;
- }
-
- /* We will start scanning from the first option. */
- optind = 1;
-
- /* We're not in the middle of any packed options. */
- packedopts = NULL;
-
- /* We haven't found any option yet. */
- opt_found = (size_t)(-1);
-
- /* We're not initialized yet. */
- getopt_initialized = 0;
-
- /* Finished resetting state. */
- optreset = 0;
-}
-
-/* Search for an option string. */
-static size_t
-searchopt(const char * os)
-{
- size_t i;
-
- /* Scan the array of options. */
- for (i = 0; i < nopts; i++) {
- /* Is there an option in this slot? */
- if (opts[i].os == NULL)
- continue;
-
- /* Does this match up to the length of the option string? */
- if (strncmp(opts[i].os, os, opts[i].olen))
- continue;
-
- /* Do we have <option>\0 or <option>= ? */
- if ((os[opts[i].olen] == '\0') || (os[opts[i].olen] == '='))
- return (i);
- }
-
- /* Not found. */
- return (opt_default);
-}
-
-const char *
-getopt(int argc, char * const argv[])
-{
- const char * os = NULL;
- const char * canonical_os = NULL;
-
- /* No argument yet. */
- optarg = NULL;
-
- /* Reset the getopt state if needed. */
- if (optreset)
- reset(argc, argv);
-
- /* If not initialized, return dummy option. */
- if (!getopt_initialized)
- return (GETOPT_DUMMY);
-
- /* If we've run out of arguments, we're done. */
- if (optind >= argc)
- return (NULL);
-
- /*
- * If we're not already in the middle of a packed single-character
- * options, see if we should start.
- */
- if ((packedopts == NULL) && (argv[optind][0] == '-') &&
- (argv[optind][1] != '-') && (argv[optind][1] != '\0')) {
- /* We have one or more single-character options. */
- packedopts = &argv[optind][1];
- }
-
- /* If we're processing single-character options, fish one out. */
- if (packedopts != NULL) {
- /* Construct the option string. */
- popt[0] = '-';
- popt[1] = *packedopts;
- popt[2] = '\0';
- os = popt;
-
- /* We've done this character. */
- packedopts++;
-
- /* Are we done with this string? */
- if (*packedopts == '\0') {
- packedopts = NULL;
- optind++;
- }
- }
-
- /* If we don't have an option yet, do we have dash-dash? */
- if ((os == NULL) && (argv[optind][0] == '-') &&
- (argv[optind][1] == '-')) {
- /* If this is not "--\0", it's an option. */
- if (argv[optind][2] != '\0')
- os = argv[optind];
-
- /* Either way, we want to eat the string. */
- optind++;
- }
-
- /* If we have found nothing which looks like an option, we're done. */
- if (os == NULL)
- return (NULL);
-
- /* Search for the potential option. */
- opt_found = searchopt(os);
-
- /* If the option is not registered, give up now. */
- if (opt_found == opt_default) {
- WARN("unknown option: %s", os);
- return (os);
- }
-
- /* The canonical option string is the one registered. */
- canonical_os = opts[opt_found].os;
-
- /* Does the option take an argument? */
- if (opts[opt_found].hasarg) {
- /*
- * If we're processing packed single-character options, the
- * rest of the string is the argument to this option.
- */
- if (packedopts != NULL) {
- optarg = packedopts;
- packedopts = NULL;
- optind++;
- }
-
- /*
- * If the option string is <option>=<value>, extract that
- * value as the option argument.
- */
- if (os[opts[opt_found].olen] == '=')
- optarg = &os[opts[opt_found].olen + 1];
-
- /*
- * If we don't have an argument yet, take one from the
- * remaining command line.
- */
- if ((optarg == NULL) && (optind < argc))
- optarg = argv[optind++];
-
- /* If we still have no option, declare it MIA. */
- if (optarg == NULL) {
- WARN("option requires an argument: %s",
- opts[opt_found].os);
- opt_found = opt_missing;
- }
- } else {
- /* If we have --foo=bar, something went wrong. */
- if (os[opts[opt_found].olen] == '=') {
- WARN("option doesn't take an argument: %s",
- opts[opt_found].os);
- opt_found = opt_default;
- }
- }
-
- /* Return the canonical option string. */
- return (canonical_os);
-}
-
-size_t
-getopt_lookup(const char * os)
-{
-
- /* Can't reset here. */
- if (optreset)
- DIE("Can't reset in the middle of getopt loop");
-
- /* We should only be called after initialization is complete. */
- assert(getopt_initialized);
-
- /* GETOPT_DUMMY should never get passed back to us. */
- assert(os != GETOPT_DUMMY);
-
- /*
- * Make sure the option passed back to us corresponds to the one we
- * found earlier.
- */
- assert((opt_found == opt_missing) || (opt_found == opt_default) ||
- ((opt_found < nopts) && (strcmp(os, opts[opt_found].os) == 0)));
-
- /* Return the option number we identified earlier. */
- return (opt_found);
-}
-
-void
-getopt_register_opt(const char * os, size_t ln, int hasarg)
-{
-
- /* Can't reset here. */
- if (optreset)
- DIE("Can't reset in the middle of getopt loop");
-
- /* We should only be called during initialization. */
- assert(!getopt_initialized);
-
- /* We should have space allocated for registering options. */
- assert(opts != NULL);
-
- /* We should not have registered an option here yet. */
- assert(opts[ln].os == NULL);
-
- /* Options should be "-X" or "--foo". */
- if ((os[0] != '-') || (os[1] == '\0') ||
- ((os[1] == '-') && (os[2] == '\0')) ||
- ((os[1] != '-') && (os[2] != '\0')))
- DIE("Not a valid command-line option: %s", os);
-
- /* Make sure we haven't already registered this option. */
- if (searchopt(os) != opt_default)
- DIE("Command-line option registered twice: %s", os);
-
- /* Record option. */
- opts[ln].os = os;
- opts[ln].olen = strlen(os);
- opts[ln].hasarg = hasarg;
-}
-
-void
-getopt_register_missing(size_t ln)
-{
-
- /* Can't reset here. */
- if (optreset)
- DIE("Can't reset in the middle of getopt loop");
-
- /* We should only be called during initialization. */
- assert(!getopt_initialized);
-
- /* Record missing-argument value. */
- opt_missing = ln;
-}
-
-void
-getopt_setrange(size_t ln)
-{
- size_t i;
-
- /* Can't reset here. */
- if (optreset)
- DIE("Can't reset in the middle of getopt loop");
-
- /* We should only be called during initialization. */
- assert(!getopt_initialized);
-
- /* Allocate space for options. */
- opts = malloc(ln * sizeof(struct opt));
- if ((ln > 0) && (opts == NULL))
- DIE("Failed to allocate memory in getopt");
-
- /* Initialize options. */
- for (i = 0; i < ln; i++)
- opts[i].os = NULL;
-
- /* Record the number of (potential) options. */
- nopts = ln;
-
- /* Record default missing-argument and no-such-option values. */
- opt_missing = opt_default = ln + 1;
-}
diff --git a/tools/getopt.h b/tools/getopt.h
deleted file mode 100644
index 5ed3145..0000000
--- a/tools/getopt.h
+++ /dev/null
@@ -1,175 +0,0 @@
-#ifndef _GETOPT_H_
-#define _GETOPT_H_
-
-#include <setjmp.h>
-#include <stddef.h>
-
-/**
- * This getopt implementation parses options of the following forms:
- * -a -b -c foo (single-character options)
- * -abc foo (packed single-character options)
- * -abcfoo (packed single-character options and an argument)
- * --foo bar (long option)
- * --foo=bar (long option and argument separated by '=')
- *
- * It does not support abbreviated options (e.g., interpreting --foo as
- * --foobar when there are no other --foo* options) since that misfeature
- * results in breakage when new options are added. It also does not support
- * options appearing after non-options (e.g., "cp foo bar -R") since that is
- * a horrible GNU perversion.
- */
-
-/* Work around LLVM bug. */
-#ifdef __clang__
-#warning Working around bug in LLVM optimizer
-#warning For more details see https://llvm.org/bugs/show_bug.cgi?id=27190
-#define DO_SETJMP _DO_SETJMP(__LINE__)
-#define _DO_SETJMP(x) __DO_SETJMP(x)
-#define __DO_SETJMP(x) \
- void * getopt_initloop = && getopt_initloop_ ## x; \
- getopt_initloop_ ## x:
-#define DO_LONGJMP \
- goto *getopt_initloop
-#else
-#define DO_SETJMP \
- sigjmp_buf getopt_initloop; \
- if (!getopt_initialized) \
- sigsetjmp(getopt_initloop, 0)
-#define DO_LONGJMP \
- siglongjmp(getopt_initloop, 1)
-#endif
-
-/* Avoid namespace collisions with libc getopt. */
-#define getopt libcperciva_getopt
-#define optarg libcperciva_optarg
-#define optind libcperciva_optind
-#define opterr libcperciva_opterr
-#define optreset libcperciva_optreset
-
-/* Standard getopt global variables. */
-extern const char * optarg;
-extern int optind, opterr, optreset;
-
-/* Dummy option string, equal to "(dummy)". */
-#define GETOPT_DUMMY getopt_dummy
-
-/**
- * GETOPT(argc, argv):
- * When called for the first time (or the first time after optreset is set to
- * a nonzero value), return GETOPT_DUMMY, aka. "(dummy)". Thereafter, return
- * the next option string and set optarg / optind appropriately; abort if not
- * properly initialized when not being called for the first time.
- */
-#define GETOPT(argc, argv) getopt(argc, argv)
-
-/**
- * GETOPT_SWITCH(ch):
- * Jump to the appropriate GETOPT_OPT, GETOPT_OPTARG, GETOPT_MISSING_ARG, or
- * GETOPT_DEFAULT based on the option string ${ch}. When called for the first
- * time, perform magic to index the options.
- *
- * GETOPT_SWITCH(ch) is equivalent to "switch (ch)" in a standard getopt loop.
- */
-#define GETOPT_SWITCH(ch) \
- volatile size_t getopt_ln_min = __LINE__; \
- volatile size_t getopt_ln = getopt_ln_min - 1; \
- volatile int getopt_default_missing = 0; \
- DO_SETJMP; \
- switch (getopt_initialized ? getopt_lookup(ch) + getopt_ln_min : getopt_ln++)
-
-/**
- * GETOPT_OPT(os):
- * Jump to this point when the option string ${os} is passed to GETOPT_SWITCH.
- *
- * GETOPT_OPT("-x") is equivalent to "case 'x'" in a standard getopt loop
- * which has an optstring containing "x".
- */
-#define GETOPT_OPT(os) _GETOPT_OPT(os, __LINE__)
-#define _GETOPT_OPT(os, ln) __GETOPT_OPT(os, ln)
-#define __GETOPT_OPT(os, ln) \
- case ln: \
- if (getopt_initialized) \
- goto getopt_skip_ ## ln; \
- getopt_register_opt(os, ln - getopt_ln_min, 0); \
- DO_LONGJMP; \
- getopt_skip_ ## ln
-
-/**
- * GETOPT_OPTARG(os):
- * Jump to this point when the option string ${os} is passed to GETOPT_SWITCH,
- * unless no argument is available, in which case jump to GETOPT_MISSING_ARG
- * (if present) or GETOPT_DEFAULT (if not).
- *
- * GETOPT_OPTARG("-x") is equivalent to "case 'x'" in a standard getopt loop
- * which has an optstring containing "x:".
- */
-#define GETOPT_OPTARG(os) _GETOPT_OPTARG(os, __LINE__)
-#define _GETOPT_OPTARG(os, ln) __GETOPT_OPTARG(os, ln)
-#define __GETOPT_OPTARG(os, ln) \
- case ln: \
- if (getopt_initialized) \
- goto getopt_skip_ ## ln; \
- getopt_register_opt(os, ln - getopt_ln_min, 1); \
- DO_LONGJMP; \
- getopt_skip_ ## ln
-
-/**
- * GETOPT_MISSING_ARG:
- * Jump to this point if an option string specified in GETOPT_OPTARG is seen
- * but no argument is available.
- *
- * GETOPT_MISSING_ARG is equivalent to "case ':'" in a standard getopt loop
- * which has an optstring starting with ":". As such, it also has the effect
- * of disabling warnings about invalid options, as if opterr had been zeroed.
- */
-#define GETOPT_MISSING_ARG _GETOPT_MISSING_ARG(__LINE__)
-#define _GETOPT_MISSING_ARG(ln) __GETOPT_MISSING_ARG(ln)
-#define __GETOPT_MISSING_ARG(ln) \
- case ln: \
- if (getopt_initialized) \
- goto getopt_skip_ ## ln; \
- getopt_register_missing(ln - getopt_ln_min); \
- DO_LONGJMP; \
- getopt_skip_ ## ln
-
-/**
- * GETOPT_DEFAULT:
- * Jump to this point if an unrecognized option is seen or if an option
- * specified in GETOPT_OPTARG is seen, no argument is available, and there is
- * no GETOPT_MISSING_ARG label.
- *
- * GETOPT_DEFAULT is equivalent to "case '?'" in a standard getopt loop.
- *
- * NOTE: This MUST be present in the GETOPT_SWITCH statement, and MUST occur
- * after all other GETOPT_* labels.
- */
-#define GETOPT_DEFAULT _GETOPT_DEFAULT(__LINE__)
-#define _GETOPT_DEFAULT(ln) __GETOPT_DEFAULT(ln)
-#define __GETOPT_DEFAULT(ln) \
- goto getopt_skip_ ## ln; \
- case ln: \
- getopt_initialized = 1; \
- break; \
- default: \
- if (getopt_initialized) \
- goto getopt_skip_ ## ln; \
- if (!getopt_default_missing) { \
- getopt_setrange(ln - getopt_ln_min); \
- getopt_default_missing = 1; \
- } \
- DO_LONGJMP; \
- getopt_skip_ ## ln
-
-/*
- * The back-end implementation. These should be considered internal
- * interfaces and not used directly.
- */
-const char * getopt(int, char * const []);
-size_t getopt_lookup(const char *);
-void getopt_register_opt(const char *, size_t, int);
-void getopt_register_missing(size_t);
-void getopt_setrange(size_t);
-extern const char * getopt_dummy;
-extern int getopt_initialized;
-
-#endif /* !_GETOPT_H_ */
diff --git a/tools/optparse.c b/tools/optparse.c
new file mode 100644
index 0000000..4242bff
--- /dev/null
+++ b/tools/optparse.c
@@ -0,0 +1,264 @@
+#include "optparse.h"
+
+#define MSG_INVALID "invalid option"
+#define MSG_MISSING "option requires an argument"
+#define MSG_TOOMANY "option takes no arguments"
+
+static int
+opterror(struct optparse *options, const char *message, const char *data)
+{
+ unsigned p = 0;
+ while (*message)
+ options->errmsg[p++] = *message++;
+ const char *sep = " -- '";
+ while (*sep)
+ options->errmsg[p++] = *sep++;
+ while (p < sizeof(options->errmsg) - 2 && *data)
+ options->errmsg[p++] = *data++;
+ options->errmsg[p++] = '\'';
+ options->errmsg[p++] = '\0';
+ return '?';
+}
+
+void optparse_init(struct optparse *options, char **argv)
+{
+ options->argv = argv;
+ options->permute = 1;
+ options->optind = 1;
+ options->subopt = 0;
+ options->optarg = 0;
+ options->errmsg[0] = '\0';
+}
+
+static inline int
+is_dashdash(const char *arg)
+{
+ return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0';
+}
+
+static inline int
+is_shortopt(const char *arg)
+{
+ return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0';
+}
+
+static inline int
+is_longopt(const char *arg)
+{
+ return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0';
+}
+
+static void
+permute(struct optparse *options, int index)
+{
+ char *nonoption = options->argv[index];
+ for (int i = index; i < options->optind - 1; i++)
+ options->argv[i] = options->argv[i + 1];
+ options->argv[options->optind - 1] = nonoption;
+}
+
+static int
+argtype(const char *optstring, char c)
+{
+ if (c == ':')
+ return -1;
+ for (; *optstring && c != *optstring; optstring++);
+ if (!*optstring)
+ return -1;
+ int count = OPTPARSE_NONE;
+ if (optstring[1] == ':')
+ count += optstring[2] == ':' ? 2 : 1;
+ return count;
+}
+
+int optparse(struct optparse *options, const char *optstring)
+{
+ options->errmsg[0] = '\0';
+ options->optopt = 0;
+ options->optarg = 0;
+ char *option = options->argv[options->optind];
+ if (option == 0) {
+ return -1;
+ } else if (is_dashdash(option)) {
+ options->optind++; /* consume "--" */
+ return -1;
+ } else if (!is_shortopt(option)) {
+ if (options->permute) {
+ int index = options->optind;
+ options->optind++;
+ int r = optparse(options, optstring);
+ permute(options, index);
+ options->optind--;
+ return r;
+ } else {
+ return -1;
+ }
+ }
+ option += options->subopt + 1;
+ options->optopt = option[0];
+ int type = argtype(optstring, option[0]);
+ char *next = options->argv[options->optind + 1];
+ switch (type) {
+ case -1: {
+ options->optind++;
+ char str[2] = {option[0]};
+ return opterror(options, MSG_INVALID, str);
+ }
+ case OPTPARSE_NONE:
+ if (option[1]) {
+ options->subopt++;
+ } else {
+ options->subopt = 0;
+ options->optind++;
+ }
+ return option[0];
+ case OPTPARSE_REQUIRED:
+ options->subopt = 0;
+ options->optind++;
+ if (option[1]) {
+ options->optarg = option + 1;
+ } else if (next != 0) {
+ options->optarg = next;
+ options->optind++;
+ } else {
+ options->optarg = 0;
+ char str[2] = {option[0]};
+ return opterror(options, MSG_MISSING, str);
+ }
+ return option[0];
+ case OPTPARSE_OPTIONAL:
+ options->subopt = 0;
+ options->optind++;
+ if (option[1])
+ options->optarg = option + 1;
+ else
+ options->optarg = 0;
+ return option[0];
+ }
+ return 0;
+}
+
+char *optparse_arg(struct optparse *options)
+{
+ options->subopt = 0;
+ char *option = options->argv[options->optind];
+ if (option != 0)
+ options->optind++;
+ return option;
+}
+
+static inline int
+longopts_end(const struct optparse_long *longopts, int i)
+{
+ return !longopts[i].longname && !longopts[i].shortname;
+}
+
+static void
+optstring_from_long(const struct optparse_long *longopts, char *optstring)
+{
+ char *p = optstring;
+ for (int i = 0; !longopts_end(longopts, i); i++) {
+ if (longopts[i].shortname) {
+ *p++ = longopts[i].shortname;
+ for (int a = 0; a < (int)longopts[i].argtype; a++)
+ *p++ = ':';
+ }
+ }
+ *p = '\0';
+}
+
+/* Unlike strcmp(), handles options containing "=". */
+static int
+longopts_match(const char *longname, const char *option)
+{
+ if (longname == 0)
+ return 0;
+ const char *a = option, *n = longname;
+ for (; *a && *n && *a != '='; a++, n++)
+ if (*a != *n)
+ return 0;
+ return *n == '\0' && (*a == '\0' || *a == '=');
+}
+
+/* Return the part after "=", or NULL. */
+static char *
+longopts_arg(char *option)
+{
+ for (; *option && *option != '='; option++);
+ if (*option == '=')
+ return option + 1;
+ else
+ return 0;
+}
+
+static int
+long_fallback(struct optparse *options,
+ const struct optparse_long *longopts,
+ int *longindex)
+{
+ char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */
+ optstring_from_long(longopts, optstring);
+ int result = optparse(options, optstring);
+ if (longindex != 0) {
+ *longindex = -1;
+ if (result != -1)
+ for (int i = 0; !longopts_end(longopts, i); i++)
+ if (longopts[i].shortname == options->optopt)
+ *longindex = i;
+ }
+ return result;
+}
+
+int
+optparse_long(struct optparse *options,
+ const struct optparse_long *longopts,
+ int *longindex)
+{
+ char *option = options->argv[options->optind];
+ if (option == 0) {
+ return -1;
+ } else if (is_dashdash(option)) {
+ options->optind++; /* consume "--" */
+ return -1;
+ } else if (is_shortopt(option)) {
+ return long_fallback(options, longopts, longindex);
+ } else if (!is_longopt(option)) {
+ if (options->permute) {
+ int index = options->optind;
+ options->optind++;
+ int r = optparse_long(options, longopts, longindex);
+ permute(options, index);
+ options->optind--;
+ return r;
+ } else {
+ return -1;
+ }
+ }
+
+ /* Parse as long option. */
+ options->errmsg[0] = '\0';
+ options->optopt = 0;
+ options->optarg = 0;
+ option += 2; /* skip "--" */
+ options->optind++;
+ for (int i = 0; !longopts_end(longopts, i); i++) {
+ const char *name = longopts[i].longname;
+ if (longopts_match(name, option)) {
+ if (longindex)
+ *longindex = i;
+ options->optopt = longopts[i].shortname;
+ char *arg = longopts_arg(option);
+ if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) {
+ return opterror(options, MSG_TOOMANY, name);
+ } if (arg != 0) {
+ options->optarg = arg;
+ } else if (longopts[i].argtype == OPTPARSE_REQUIRED) {
+ options->optarg = options->argv[options->optind++];
+ if (options->optarg == 0)
+ return opterror(options, MSG_MISSING, name);
+ }
+ return options->optopt;
+ }
+ }
+ return opterror(options, MSG_INVALID, option);
+}
diff --git a/tools/optparse.h b/tools/optparse.h
new file mode 100644
index 0000000..f124b75
--- /dev/null
+++ b/tools/optparse.h
@@ -0,0 +1,102 @@
+#ifndef OPTPARSE_H
+#define OPTPARSE_H
+
+/**
+ * Optparse -- portable, reentrant, embeddable, getopt-like option parser
+ *
+ * The POSIX getopt() option parser has three fatal flaws. These flaws
+ * are solved by Optparse.
+ *
+ * 1) Parser state is stored entirely in global variables, some of
+ * which are static and inaccessible. This means only one thread can
+ * use getopt(). It also means it's not possible to recursively parse
+ * nested sub-arguments while in the middle of argument parsing.
+ * Optparse fixes this by storing all state on a local struct.
+ *
+ * 2) The POSIX standard provides no way to properly reset the parser.
+ * This means for portable code that getopt() is only good for one
+ * run, over one argv with one optstring. It also means subcommand
+ * options cannot be processed with getopt(). Most implementations
+ * provide a method to reset the parser, but it's not portable.
+ * Optparse provides an optparse_arg() function for stepping over
+ * subcommands and continuing parsing of options with another
+ * optstring. The Optparse struct itself can be passed around to
+ * subcommand handlers for additional subcommand option parsing. A
+ * full reset can be achieved by with an additional optparse_init().
+ *
+ * 3) Error messages are printed to stderr. This can be disabled with
+ * opterr, but the messages themselves are still inaccessible.
+ * Optparse solves this by writing an error message in its errmsg
+ * field. The downside to Optparse is that this error message will
+ * always be in English rather than the current locale.
+ *
+ * Optparse should be familiar with anyone accustomed to getopt(), and
+ * it could be a nearly drop-in replacement. The optstring is the same
+ * and the fields have the same names as the getopt() global variables
+ * (optarg, optind, optopt).
+ *
+ * Optparse also supports GNU-style long options with optparse_long().
+ * The interface is slightly different and simpler than getopt_long().
+ *
+ * By default, argv is permuted as it is parsed, moving non-option
+ * arguments to the end. This can be disabled by setting the `permute`
+ * field to 0 after initialization.
+ */
+
+struct optparse {
+ char **argv;
+ int permute;
+ int optind;
+ int optopt;
+ char *optarg;
+ char errmsg[64];
+ int subopt;
+};
+
+enum optparse_argtype { OPTPARSE_NONE, OPTPARSE_REQUIRED, OPTPARSE_OPTIONAL };
+
+struct optparse_long {
+ const char *longname;
+ int shortname;
+ enum optparse_argtype argtype;
+};
+
+/**
+ * Initializes the parser state.
+ */
+void optparse_init(struct optparse *options, char **argv);
+
+/**
+ * Read the next option in the argv array.
+ * @param optstring a getopt()-formatted option string.
+ * @return the next option character, -1 for done, or '?' for error
+ *
+ * Just like getopt(), a character followed by no colons means no
+ * argument. One colon means the option has a required argument. Two
+ * colons means the option takes an optional argument.
+ */
+int optparse(struct optparse *options, const char *optstring);
+
+/**
+ * Handles GNU-style long options in addition to getopt() options.
+ * This works a lot like GNU's getopt_long(). The last option in
+ * longopts must be all zeros, marking the end of the array. The
+ * longindex argument may be NULL.
+ */
+int
+optparse_long(struct optparse *options,
+ const struct optparse_long *longopts,
+ int *longindex);
+
+/**
+ * Used for stepping over non-option arguments.
+ * @return the next non-option argument, or NULL for no more arguments
+ *
+ * Argument parsing can continue with optparse() after using this
+ * function. That would be used to parse the options for the
+ * subcommand returned by optparse_arg(). This function allows you to
+ * ignore the value of optind.
+ */
+char *optparse_arg(struct optparse *options);
+
+#endif
diff --git a/tools/pub.c b/tools/pub.c
index 781509c..f2107ae 100644
--- a/tools/pub.c
+++ b/tools/pub.c
@@ -2,7 +2,7 @@
#include <stdio.h>
#include "mqtt.h"
-#include "getopt.h"
+#include "optparse.h"
struct options
{
@@ -10,6 +10,7 @@ struct options
int retain;
const char *topic;
const char *message;
+ const char *client_id;
};
void onConnect(MqttClient *client, MqttConnectionStatus status,
@@ -37,52 +38,80 @@ void usage(const char *prog)
exit(1);
}
-int main(int argc, char **argv)
+static void parse_args(struct options *options, int argc, char **argv)
{
- MqttClient *client;
- const char *opt;
- struct options options;
+ int option;
- options.qos = 0;
- options.retain = 0;
- options.topic = "my/topic";
- options.message = "hello, world!";
+ struct optparse_long longopts[] =
+ {
+ { "qos", 'q', OPTPARSE_REQUIRED },
+ { "topic", 't', OPTPARSE_REQUIRED },
+ { "message", 'm', OPTPARSE_REQUIRED },
+ { "id", 'i', OPTPARSE_REQUIRED },
+ { "retain", 'r', OPTPARSE_NONE },
+ { "help", 'h', OPTPARSE_NONE },
+ { NULL }
+ };
+
+ struct optparse parser;
+
+ optparse_init(&parser, argv);
- while ((opt = GETOPT(argc, argv)) != NULL)
+ while ((option = optparse_long(&parser, longopts, NULL)) != -1)
{
- GETOPT_SWITCH(opt)
+ switch (option)
{
- GETOPT_OPTARG("--qos"):
- options.qos = strtol(optarg, NULL, 10);
- if (options.qos < 0 || options.qos > 2)
+ case 'q':
+ options->qos = strtol(parser.optarg, NULL, 10);
+ if (options->qos < 0 || options->qos > 2)
{
- fprintf(stderr, "invalid qos: %s\n", optarg);
- return 1;
+ fprintf(stderr, "invalid qos: %s\n", parser.optarg);
+ exit(1);
}
break;
- GETOPT_OPT("--retain"):
- options.retain = 1;
+ case 't':
+ options->topic = parser.optarg;
break;
- GETOPT_OPTARG("--topic"):
- options.topic = optarg;
+ case 'm':
+ options->message = parser.optarg;
break;
- GETOPT_OPTARG("--message"):
- options.message = optarg;
+ case 'r':
+ options->retain = 1;
break;
- GETOPT_MISSING_ARG:
- fprintf(stderr, "missing argument to: %s\n", opt);
+ case 'i':
+ options->client_id = parser.optarg;
+ break;
+
+ case 'h':
+ usage(argv[0]);
+ break;
- GETOPT_DEFAULT:
+ case '?':
+ fprintf(stderr, "%s: %s\n", argv[0], parser.errmsg);
usage(argv[0]);
break;
}
}
+}
+
+int main(int argc, char **argv)
+{
+ MqttClient *client;
+ struct options options;
+
+ options.qos = 0;
+ options.retain = 0;
+ options.topic = "my/topic";
+ options.message = "hello, world!";
+ options.client_id = NULL;
+
+ parse_args(&options, argc, argv);
- client = MqttClientNew(NULL, 1);
+ client = MqttClientNew(options.client_id, 1);
MqttClientSetOnConnect(client, onConnect);
MqttClientSetOnPublish(client, onPublish);
diff --git a/tools/sub.c b/tools/sub.c
index 4489b5c..4937584 100644
--- a/tools/sub.c
+++ b/tools/sub.c
@@ -2,7 +2,7 @@
#include <stdio.h>
#include "mqtt.h"
-#include "getopt.h"
+#include "optparse.h"
struct options
{
@@ -43,50 +43,72 @@ void usage(const char *prog)
exit(1);
}
-int main(int argc, char **argv)
+static void parse_args(struct options *options, int argc, char **argv)
{
- MqttClient *client;
- const char *opt;
- struct options options;
+ int option;
- options.qos = 0;
- options.topic = "$SYS/broker/load/messages/#";
- options.clean = 1;
- options.client_id = NULL;
+ struct optparse_long longopts[] =
+ {
+ { "qos", 'q', OPTPARSE_REQUIRED },
+ { "topic", 't', OPTPARSE_REQUIRED },
+ { "no-clean", 'n', OPTPARSE_NONE },
+ { "id", 'i', OPTPARSE_REQUIRED },
+ { "help", 'h', OPTPARSE_NONE },
+ { NULL }
+ };
+
+ struct optparse parser;
- while ((opt = GETOPT(argc, argv)) != NULL)
+ optparse_init(&parser, argv);
+
+ while ((option = optparse_long(&parser, longopts, NULL)) != -1)
{
- GETOPT_SWITCH(opt)
+ switch (option)
{
- GETOPT_OPTARG("--qos"):
- options.qos = strtol(optarg, NULL, 10);
- if (options.qos < 0 || options.qos > 2)
+ case 'q':
+ options->qos = strtol(parser.optarg, NULL, 10);
+ if (options->qos < 0 || options->qos > 2)
{
- fprintf(stderr, "invalid qos: %s\n", optarg);
- return 1;
+ fprintf(stderr, "invalid qos: %s\n", parser.optarg);
+ exit(1);
}
break;
- GETOPT_OPTARG("--topic"):
- options.topic = optarg;
+ case 't':
+ options->topic = parser.optarg;
break;
- GETOPT_OPT("--no-clean"):
- options.clean = 0;
+ case 'n':
+ options->clean = 0;
break;
- GETOPT_OPTARG("--id"):
- options.client_id = optarg;
+ case 'i':
+ options->client_id = parser.optarg;
break;
- GETOPT_MISSING_ARG:
- fprintf(stderr, "missing argument to: %s\n", opt);
+ case 'h':
+ usage(argv[0]);
+ break;
- GETOPT_DEFAULT:
+ case '?':
+ fprintf(stderr, "%s: %s\n", argv[0], parser.errmsg);
usage(argv[0]);
break;
}
}
+}
+
+int main(int argc, char **argv)
+{
+ MqttClient *client;
+ struct options options;
+
+ options.qos = 0;
+ options.topic = "$SYS/broker/load/messages/#";
+ options.clean = 1;
+ options.client_id = NULL;
+
+ parse_args(&options, argc, argv);
client = MqttClientNew(options.client_id, options.clean);