tildefriends/deps/xopt/xopt.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

585 lines
16 KiB
C
Raw Normal View History

/**
* 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 <assert.h>
#include <setjmp.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#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);
}
}
}