title: src/crelude/argparse.c


src/crelude/argparse.c

Source code

#include "argparse.h"
#include "common.h"
#include "io.h"

#ifndef IMPLEMENTATION

u0 arginit(ArgParser *ctx)
{
    ctx->magic = ARGP_MAGIC_INIT_CONST;
    ctx->templates = ANEW(ctx->templates, 15);
    ctx->index_of_short_form = MNEW(ctx->index_of_short_form, 15);
    ctx->index_of_long_form  = MNEW(ctx->index_of_long_form, 15);
    ctx->awaiting_value = false;
    ctx->string_count = 0;
    ctx->error_message = EMPTY(string);
}

ArgID argreg(ArgParser *ctx, char *short_form, char *long_form, bool takes_value, char *help)
{
    if (nil == ctx || ctx->magic != ARGP_MAGIC_INIT_CONST)
        panic("you did not init (`arginit`) the ArgParse structure.");

    usize index = ctx->templates.len;
    ArgID id = { (u16)index };
    string short_form_str = nil == short_form ? EMPTY(string) : from_cstring(short_form);
    string  long_form_str = nil ==  long_form ? EMPTY(string) : from_cstring(long_form);
    ArgTemplate template = {
        .id = id,
        .short_form = short_form_str,
        .long_form = long_form_str,
        .takes_value = takes_value,
        .help = from_cstring(help),
    };

    PUSH(ctx->templates, template);
    unless (nil == short_form) {
        if ('-' == short_form[0]) short_form += 1;
        ASSOCIATE(ctx->index_of_short_form, short_form[0], index);
    }
    unless (nil == long_form) {
        if (0 == strncmp(long_form, "--", 2)) long_form += 2;
        ASSOCIATE(ctx->index_of_long_form, from_cstring(long_form), index);
    }

    return id;
}

ierr argparse_c(ArgParser *ctx, u0 *map_id_to_arg, char *arg)
{
    return argparse(ctx, map_id_to_arg, from_cstring(arg));
}

ierr argparse(ArgParser *ctx, u0 *map_id_to_arg, string arg)
{
    ArgTemplate template;
    Arg argument;
    ArgID arg_id;
    usize *id = nil;
    char *equal_sign = nil;
    bool disabled = '+' == GET(arg, 0);

    if (ctx->awaiting_value) {
        // set value of argument awaiting a value.
        argument = (Arg){ .value = arg, .is_on = true };
        associate(map_id_to_arg, &ctx->awaiting_value_id, &argument);
        ctx->awaiting_value = false;
    } else if (0 == string_ncmp(arg, STR("--"), 2)) {
        // parsing long-form argument.
        equal_sign = strchr(UNWRAP(arg), '=');
        if (nil != equal_sign)
            arg = SLICE(string, arg, 0, equal_sign - UNWRAP(arg));
        arg = SLICE(string, arg, 2, -1);  // skip leading '--'

        id = LOOKUP(ctx->index_of_long_form, arg);
        if (nil == id) {
            ctx->error_message = sprint("no such argument `--%S' exists.", arg);
            return ARGPARSE_UNKNOWN;
        }
        arg_id = (ArgID){ *id };
        // get template for argument to determine behaviour.
        template = GET(ctx->templates, *id);
        if (template.takes_value) {
            // takes an argument.
            if (nil != equal_sign) {
                // value comes after equal sign.
                argument = (Arg){ .value = from_cstring(equal_sign + 1), .is_on = true };
                associate(map_id_to_arg, &arg_id, &argument);
            } else {
                // value comes in the next argument.
                ctx->awaiting_value = true;
                ctx->awaiting_value_id = arg_id;
            }
        } else {
            // is a toggle argument.
            argument = (Arg){ .value = EMPTY(string), .is_on = true };
            associate(map_id_to_arg, &arg_id, &argument);
        }
    } else if ('-' == GET(arg, 0) || '+' == GET(arg, 0)) {
        // parsing short-form argument(s).
        arg = SLICE(string, arg, 1, -1);  // skip the dash (-)
        foreach (opt, arg) {
            id = LOOKUP(ctx->index_of_short_form, opt);
            if (nil == id) {
                ctx->error_message = sprint("no such argument `-%c' exists.", opt);
                return ARGPARSE_UNKNOWN;
            }
            arg_id = (ArgID){ *id };
            // get template for argument to determine behaviour.
            template = GET(ctx->templates, *id);
            if (template.takes_value) {
                // takes an argument.
                if (arg.len != 1 && !it.first) {
                    // option with argument mixed in with other options: not allowed (ambiguous).
                    ctx->error_message = sprint("argument `-%c' takes a value, and must stand alone.", opt);
                    return ARGPARSE_INVALID;
                } else if (arg.len != 1) {
                    // the argument is glued onto the end (e.g. `-n13` <-> `-n 13`)
                    argument = (Arg){ .value = SLICE(string, arg, 1, -1), .is_on = true };
                    associate(map_id_to_arg, &arg_id, &argument);
                    break;  // the argument has been consumed, no further options.
                } else {
                    // argument value comes in the next argument.
                    ctx->awaiting_value = true;
                    ctx->awaiting_value_id = arg_id;
                }
            } else {
                // is a toggle argument.
                argument = (Arg){ .value = EMPTY(string), .is_on = !disabled };
                associate(map_id_to_arg, &arg_id, &argument);
            }
        }
    } else {
        ctx->error_message = sprint("unexpected string `%S' when parsing arguments.", arg);
        return ARGPARSE_UNEXPECTED;
    }

    ++ctx->string_count;
    return ARGPARSE_OK;
}

ierr argparseall_c(ArgParser *ctx, u0 *map_id_to_arg, usize argc, char **argv)
{
    ierr err = OK;

    for (usize i = 0; i < argc; ++i)
        if (OK != (err = argparse_c(ctx, map_id_to_arg, argv[i]))) break;

    return err;
}

#ifdef ENTRY_FUNCTION
ierr argparseall(ArgParser *ctx, u0 *map_id_to_arg, Arguments args)
{
    ierr err = OK;

    foreach (arg, args)
        unless (OK == (err = argparse(ctx, map_id_to_arg, arg))) break;

    return err;
}
#endif

#endif

Updated on 23 August 2022 at 00:54:19 UTC