Welcome to little lamb

Code » limb » master » tree

[master] / src / liblimb / parseopt.h / parseopt.c

/* 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;
}