/* This file is part of limb https://lila.oss/limb
* Copyright (C) 2023 Olivier Brunel jjk@jjacky.com */
/* SPDX-License-Identifier: GPL-2.0-only */
#include <errno.h>
#include <stddef.h> /* offsetof() */
#include <limb/bytestr.h>
#include <limb/parseopt.h>
#include "parseopt.h"
static int
do_parseopt(int argc, const char **argv, const struct option *options,
unsigned int flags, struct parseopt *ctx)
{
const char *arg;
int is_long, arg_long;
if (!ctx->cur)
ctx->cur = 1;
ctx->idx = -1;
again:
if (ctx->cur == argc)
return 0;
arg = argv[ctx->cur] + ctx->off;
if (ctx->off == 0) {
if (*arg != '-')
/* not an option, so no more options */
return 0;
/* move on to the option */
++ctx->off;
++arg;
}
if (!*arg) {
if (ctx->off == 1)
return (errno = EINVAL, -1);
/* next argument */
++ctx->cur;
ctx->off = 0;
goto again;
}
is_long = arg_long = (ctx->off == 1 && *arg == '-');
if (is_long) {
++ctx->off;
++arg;
}
if (is_long) {
size_t l = strlen(arg);
size_t end = byte_in(arg, l, " =\t", 3);
if (!l) {
/* marker "--" for end of options */
++ctx->cur;
return 0;
}
if (flags & PARSEOPT_STRICT) {
for (ctx->idx = 0; options[ctx->idx].longopt; ++ctx->idx)
if (end == strlen(options[ctx->idx].longopt)
&& !strncmp(options[ctx->idx].longopt, arg, end))
break;
if (!options[ctx->idx].longopt) {
ctx->idx = -1;
return (errno = ENOENT, -1);
}
} else if (byte_get_match_full(&ctx->idx, arg, end, options,
offsetof(struct option, longopt), sizeof(*options)) < 0) {
return (errno = ENOENT, -1);
}
/* --option-name=value : don't look for optarg on the next arg */
if (end < l) {
arg_long = 0;
arg += end;
}
} else {
for (ctx->idx = 0; options[ctx->idx].longopt; ++ctx->idx)
if (*arg == options[ctx->idx].shortopt)
break;
if (!options[ctx->idx].longopt) {
ctx->idx = -1;
return (errno = ENOENT, -1);
}
}
ctx->arg = NULL;
if (options[ctx->idx].arg == ARG_REQ) {
if (arg_long || !arg[1]) {
++ctx->cur;
if (ctx->cur == argc)
return (errno = ENOMSG, -1);
ctx->arg = argv[ctx->cur];
} else {
ctx->arg = arg + 1;
}
} else if (options[ctx->idx].arg == ARG_OPT) {
if (!arg_long && arg[1])
ctx->arg = arg + 1;
}
if (!is_long && !ctx->arg) {
++ctx->off;
} else {
++ctx->cur;
ctx->off = 0;
}
return (options[ctx->idx].id) ? options[ctx->idx].id : options[ctx->idx].shortopt;
}
int
parseopt(int argc, const char **argv, const struct option *options,
unsigned int flags, struct parseopt *ctx)
{
int r = do_parseopt(argc, argv, options, flags, ctx);
if (r < 0 && !(flags & PARSEOPT_SILENT))
parseopt_warn(argv, options, ctx);
return r;
}