diff options
| author | Oskari Timperi <oskari.timperi@iki.fi> | 2017-02-16 22:35:25 +0200 |
|---|---|---|
| committer | Oskari Timperi <oskari.timperi@iki.fi> | 2017-02-16 22:35:25 +0200 |
| commit | a605b6570fc6de9d7467f6fef5cc3561976c90f9 (patch) | |
| tree | 7f31530de8dbbe989dec8fc1eedeea4b66d3e811 /tools | |
| parent | 9a3f03af52c18517f87ab74395552cc9f7f27284 (diff) | |
| download | mqtt-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.txt | 6 | ||||
| -rw-r--r-- | tools/getopt.c | 358 | ||||
| -rw-r--r-- | tools/getopt.h | 175 | ||||
| -rw-r--r-- | tools/optparse.c | 264 | ||||
| -rw-r--r-- | tools/optparse.h | 102 | ||||
| -rw-r--r-- | tools/pub.c | 81 | ||||
| -rw-r--r-- | tools/sub.c | 72 |
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); |
