/** * XOpt - command line parsing library * * Copyright (c) 2015-2019 Josh Junon * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifndef XOPT_NOSTANDARD # define HAVE_STDARG_H 1 # define HAVE_STDLIB_H 1 # define HAVE_ASPRINTF_H 1 # define vasprintf rpl_vasprintf # ifndef _GNU_SOURCE # define _GNU_SOURCE # endif #endif #include #include #include #include #include #include #include #include "./xopt.h" #include "./snprintf.c" #define EXTRAS_INIT 10 #define ERRBUF_SIZE 1024 * 4 static char errbuf[ERRBUF_SIZE]; struct xoptContext { const xoptOption *options; long flags; const char *name; bool doubledash; size_t options_count; bool *required; jmp_buf *jmp; }; static void _xopt_set_err(xoptContext *ctx, const char **err, const char *const fmt, ...); static int _xopt_parse_arg(xoptContext *ctx, int argc, const char **argv, int *argi, void *data, const char **err); static void _xopt_assert_increment(xoptContext *ctx, const char ***extras, int extrasCount, size_t *extrasCapac, const char **err); static int _xopt_get_size(const char *arg); static int _xopt_get_arg(const xoptContext *ctx, const char *arg, size_t len, int size, const xoptOption **option, size_t *option_index); static void _xopt_set(xoptContext *ctx, void *data, const xoptOption *option, const char *val, bool longArg, const char **err); static void _xopt_default_callback(const char *value, void *data, const xoptOption *option, bool longArg, const char **err); xoptContext* xopt_context(const char *name, const xoptOption *options, long flags, const char **err) { xoptContext* ctx; *err = 0; /* malloc context and check */ ctx = malloc(sizeof(xoptContext)); if (!ctx) { ctx = 0; _xopt_set_err(NULL, err, "could not allocate context"); } else { const xoptOption *cur; ctx->options = options; ctx->flags = flags; ctx->name = name; ctx->doubledash = false; ctx->required = NULL; ctx->jmp = NULL; ctx->options_count = 0; cur = options; for (; cur->longArg || cur->shortArg; cur++) ++ctx->options_count; } return ctx; } static int _xopt_parse_impl(xoptContext *ctx, int argc, const char **argv, void *data, const char ***inextras, const char **err, int *extrasCount, size_t *extrasCapac, const char ***extras, int *argi) { int parseResult; size_t i; *err = 0; *argi = 0; *extrasCount = 0; *extrasCapac = EXTRAS_INIT; *extras = malloc(sizeof(**extras) * EXTRAS_INIT); jmp_buf jmp; ctx->jmp = &jmp; if (setjmp(jmp)) { goto end; } /* check if extras malloc'd okay */ if (!*extras) { _xopt_set_err(ctx, err, "could not allocate extras array"); } /* increment argument counter if we aren't instructed to check argv[0] */ if (!(ctx->flags & XOPT_CTX_KEEPFIRST)) { ++(*argi); } /* set up required parameters list */ ctx->required = malloc(sizeof(*ctx->required) * ctx->options_count); for (i = 0; i < ctx->options_count; i++) { ctx->required[i] = (ctx->options[i].options & XOPT_REQUIRED) > 0; } /* iterate over passed command line arguments */ for (; *argi < argc; (*argi)++) { /* parse, breaking if there was a failure parseResult is 0 if option, 1 if extra, or 2 if double-dash was encountered */ parseResult = _xopt_parse_arg(ctx, argc, argv, argi, data, err); /* is the argument an extra? */ switch (parseResult) { case 0: /* option */ /* make sure we're super-posix'd if specified to be (check that no extras have been specified when an option is parsed, enforcing options to be specific before [extra] arguments */ if ((ctx->flags & XOPT_CTX_POSIXMEHARDER) && *extrasCount) { _xopt_set_err(ctx, err, "options cannot be specified after arguments: %s", argv[*argi]); goto end; } break; case 1: /* extra */ /* make sure we have enough room, or realloc if we don't - check that it succeeded */ _xopt_assert_increment(ctx, extras, *extrasCount, extrasCapac, err); /* add extra to list */ (*extras)[(*extrasCount)++] = argv[*argi]; break; case 2: /* "--" was encountered */ /* nothing to do here - "--" was already handled for us */ break; } } end: if (!*err) { for (i = 0; i < ctx->options_count; i++) { if (ctx->required[i]) { const xoptOption *opt = &ctx->options[i]; if (opt->longArg) { _xopt_set_err(ctx, err, "missing required option: --%s", opt->longArg); } else { _xopt_set_err(ctx, err, "missing required option: -%c", opt->shortArg); } break; } } } free(ctx->required); if (!*err) { /* append null terminator to extras */ _xopt_assert_increment(ctx, extras, *extrasCount, extrasCapac, err); if (!*err) { (*extras)[*extrasCount] = 0; } } if (*err) { free(*extras); *inextras = 0; return 0; } *inextras = *extras; return *extrasCount; } int xopt_parse(xoptContext *ctx, int argc, const char **argv, void *data, const char ***inextras, const char **err) { /* avoid longjmp clobbering */ int extrasCount; size_t extrasCapac; const char **extras; int argi; return _xopt_parse_impl(ctx, argc, argv, data, inextras, err, &extrasCount, &extrasCapac, &extras, &argi); } void xopt_autohelp(xoptContext *ctx, FILE *stream, const xoptAutohelpOptions *options, const char **err) { const xoptOption *o; size_t i, width = 0, twidth; const char *nl = ""; size_t spacer = options ? options->spacer : 2; *err = 0; /* make sure that if we ever write a call to _set_err() in the future here, that we won't accidentally cause segfaults - we have an assertion in place for ctx->jmp != NULL, so we make sure we'd trigger that assertion */ ctx->jmp = NULL; if (options && options->usage) { fprintf(stream, "%susage: %s %s\n", nl, ctx->name, options->usage); nl = "\n"; } if (options && options->prefix) { fprintf(stream, "%s%s\n\n", nl, options->prefix); nl = "\n"; } /* find max width */ for (i = 0; ctx->options[i].longArg || ctx->options[i].shortArg; i++) { o = &ctx->options[i]; twidth = 0; if (o->longArg) { twidth += 2 + strlen(o->longArg); if (o->argDescrip) { twidth += 1 + strlen(o->argDescrip); } } if (ctx->options[i].shortArg) { twidth += 2; } if (ctx->options[i].shortArg && ctx->options[i].longArg) { twidth += 2; /* `, ` */ } width = width > twidth ? width : twidth; } /* print */ for (i = 0; ctx->options[i].longArg || ctx->options[i].shortArg; i++) { o = &ctx->options[i]; twidth = 0; if (o->shortArg) { fprintf(stream, "-%c", o->shortArg); twidth += 2; } if (o->shortArg && o->longArg) { fprintf(stream, ", "); twidth += 2; } if (o->longArg) { fprintf(stream, "--%s", o->longArg); twidth += 2 + strlen(o->longArg); if (o->argDescrip) { fprintf(stream, "=%s", o->argDescrip); twidth += 1 + strlen(o->argDescrip); } } if (o->descrip) { for (; twidth < (width + spacer); twidth++) { fprintf(stream, " "); } if (o->options & XOPT_REQUIRED) { fprintf(stream, "(Required) %s\n", o->descrip); } else { fprintf(stream, "%s\n", o->descrip); } } } if (options && options->suffix) { fprintf(stream, "%s%s\n", nl, options->suffix); } } static void _xopt_set_err(xoptContext *ctx, const char **err, const char *const fmt, ...) { va_list list; va_start(list, fmt); rpl_vsnprintf(&errbuf[0], ERRBUF_SIZE, fmt, list); va_end(list); *err = &errbuf[0]; if (ctx != NULL) { assert(ctx->jmp != NULL); longjmp(*ctx->jmp, 1); } } static int _xopt_parse_arg(xoptContext *ctx, int argc, const char **argv, int *argi, void *data, const char **err) { int size; size_t length; bool isExtra = false; const xoptOption *option = NULL; size_t option_index = 0; const char* arg = argv[*argi]; /* are we in doubledash mode? */ if (ctx->doubledash) { return 1; } /* get argument 'size' (long/short/extra) */ size = _xopt_get_size(arg); /* adjust to parse from beginning of actual content */ arg += size; length = strlen(arg); if (size == 1 && length == 0) { /* it's just a singular dash - treat it as an extra arg */ return 1; } if (size == 2 && length == 0) { /* double-dash - everything after this is an extra */ ctx->doubledash = 1; return 2; } switch (size) { int argRequirement; char *valStart; case 1: /* short */ /* parse all */ while (length--) { /* get argument or error if not found and strict mode enabled. */ argRequirement = _xopt_get_arg(ctx, arg++, 1, size, &option, &option_index); if (!option) { if (ctx->flags & XOPT_CTX_STRICT) { _xopt_set_err(ctx, err, "invalid option: -%c", arg[-1]); } break; } if (argRequirement > 0 && length > 0 && !(ctx->flags & XOPT_CTX_SLOPPYSHORTS)) { _xopt_set_err(ctx, err, "short option parameters must be separated, not condensed: %s", argv[*argi]); } switch (argRequirement) { case 0: /* flag; doesn't take an argument */ if (length > 0 && (ctx->flags & XOPT_CTX_NOCONDENSE)) { _xopt_set_err(ctx, err, "short options cannot be combined: %s", argv[*argi]); } _xopt_set(ctx, data, option, 0, false, err); break; case 1: /* argument is optional */ /* is there another argument, and is it a non-option? */ if (*argi + 1 < argc && _xopt_get_size(argv[*argi + 1]) == 0) { _xopt_set(ctx, data, option, argv[++*argi], false, err); } else { _xopt_set(ctx, data, option, 0, false, err); } break; case 2: /* requires an argument */ /* is it the last in a set of condensed options? */ if (length == 0) { /* is there another argument? */ if (*argi + 1 < argc) { /* is the next argument actually an option? this indicates no value was passed */ if (_xopt_get_size(argv[*argi + 1])) { _xopt_set_err(ctx, err, "missing option value: -%c", option->shortArg); } else { _xopt_set(ctx, data, option, argv[++*argi], false, err); } } else { _xopt_set_err(ctx, err, "missing option value: -%c", option->shortArg); } } else { _xopt_set(ctx, data, option, arg, false, err); length = 0; } break; } } break; case 2: /* long */ /* find first equals sign */ valStart = strchr(arg, '='); /* is there a value? */ if (valStart) { /* we also increase valStart here in order to lop off the equals sign */ length = valStart++ - arg; /* but not really, if it's null */ if (!*valStart) { valStart = 0; } } /* get the option */ argRequirement = _xopt_get_arg(ctx, arg, length, size, &option, &option_index); if (!option) { _xopt_set_err(ctx, err, "invalid option: --%.*s", length, arg); } else { switch (argRequirement) { case 0: /* flag; doesn't take an argument */ if (valStart) { _xopt_set_err(ctx, err, "option doesn't take a value: --%s", arg); } _xopt_set(ctx, data, option, valStart, true, err); break; case 2: /* requires an argument */ if (!valStart) { _xopt_set_err(ctx, err, "missing option value: --%s", arg); } break; } _xopt_set(ctx, data, option, valStart, true, err); } break; case 0: /* extra */ isExtra = true; break; } if (option) { /* indicate that we've seen this option and thus is no longer required */ ctx->required[option_index] = false; } return isExtra ? 1 : 0; } static void _xopt_assert_increment(xoptContext *ctx, const char ***extras, int extrasCount, size_t *extrasCapac, const char **err) { /* have we hit the list size limit? */ if ((size_t) extrasCount == *extrasCapac) { /* increase capcity, realloc, and check for success */ *extrasCapac += EXTRAS_INIT; *extras = realloc(*extras, sizeof(**extras) * *extrasCapac); if (!*extras) { _xopt_set_err(ctx, err, "could not realloc arguments array"); } } } static int _xopt_get_size(const char *arg) { int size; for (size = 0; size < 2; size++) { if (arg[size] != '-') { break; } } return size; } static int _xopt_get_arg(const xoptContext *ctx, const char *arg, size_t len, int size, const xoptOption **option, size_t *option_index) { size_t i; *option = 0; /* find the argument */ for (i = 0; i < ctx->options_count; i++) { const xoptOption *opt = &ctx->options[i]; if ((size == 1 && opt->shortArg == arg[0]) || (opt->longArg && strlen(opt->longArg) == len && !strncmp(opt->longArg, arg, len))) { *option_index = i; *option = opt; break; } } /* determine the optionality of a value */ if (!*option || (*option)->options & XOPT_TYPE_BOOL) { return 0; } else if ((*option)->options & XOPT_PARAM_OPTIONAL) { return 1; } else { return 2; } } static void _xopt_set(xoptContext *ctx, void *data, const xoptOption *option, const char *val, bool longArg, const char **err) { /* determine callback */ xoptCallback callback = option->callback ? option->callback : &_xopt_default_callback; /* dispatch callback */ callback(val, data, option, longArg, err); /* we check err here instead of relying upon longjmp() since we can't call _set_err() with a context */ if (*err) { assert(ctx->jmp != NULL); longjmp(*ctx->jmp, 1); } } static void _xopt_default_callback(const char *value, void *data, const xoptOption *option, bool longArg, const char **err) { void *target; char *parsePtr = 0; /* is a value specified? */ if ((!value || !strlen(value)) && !(option->options & XOPT_TYPE_BOOL)) { /* we reach this point when they specified an optional, non-boolean option but didn't specify a custom handler (therefore, it's not optional). to fix, just remove the optional flag or specify a callback to handle it yourself. */ return; } /* get location */ target = ((char*) data) + option->offset; /* switch on the type */ switch (option->options & 0x3F) { case XOPT_TYPE_BOOL: /* booleans are special in that they won't have an argument passed into this callback */ *((_Bool*) target) = true; break; case XOPT_TYPE_STRING: /* lifetime here works out fine; argv can usually be assumed static-like in nature */ *((const char**) target) = value; break; case XOPT_TYPE_INT: *((int*) target) = (int) strtol(value, &parsePtr, 0); break; case XOPT_TYPE_LONG: *((long*) target) = strtol(value, &parsePtr, 0); break; case XOPT_TYPE_FLOAT: *((float*) target) = (float) strtod(value, &parsePtr); break; case XOPT_TYPE_DOUBLE: *((double*) target) = strtod(value, &parsePtr); break; default: /* something wonky, or the implementation specifies two types */ fprintf(stderr, "warning: XOpt argument type invalid: %ld\n", option->options & 0x2F); break; } /* check that our parsing functions worked */ if (parsePtr && *parsePtr) { if (longArg) { _xopt_set_err(NULL, err, "value isn't a valid number: --%s=%s", (void*) option->longArg, value); } else { _xopt_set_err(NULL, err, "value isn't a valid number: -%c %s", option->shortArg, value); } } }