Welcome to little lamb

Code » ssp » master » tree

[master] / src / ssp / ssp.c

/* This file is part of ssp                             https://lila.oss/ssp
 * Copyright (C) 2023 Olivier Brunel                          jjk@jjacky.com */
/* SPDX-License-Identifier: GPL-2.0-only */
#include <errno.h>
#include <locale.h>
#include <skalibs/env.h>
#include <limb/autoopt.h>
#include <limb/bytestr.h>
#include <limb/command.h>
#include <limb/esc.h>
#include <limb/exitcode.h>
#include <limb/loadopt.h>
#include <limb/output.h>
#include <limb/u32.h>
#include "ssp.h"
#include "config.h"

const char *PROG = "ssp";

enum {
    OPTID_VERSION = OPTID_FIRST,
    OPTID_DEBUG,
};

int
exitcode_from_errno(int e)
{
    switch (e) {
        case ENOMEM: return EX_TEMPFAIL;
        case EINVAL:
        case ENOMSG:
        case EBADE:
                     return EX_DATA_ERR;
        case ENOENT:
                     return EX_NOINPUT;
        default:
                     return EX_IOERR;
    }
}

int
run_command(const char *name, int argc, const char *argv[], const char *env[], void *ctx)
{
    dbg("running command ", name);
    for (struct command **c = commands; *c; ++c)
        if (!strcmp((*c)->name, name))
            return (*c)->main(argc, argv, env, "", ctx);
    return -1;
}

static struct command *
parse_cmdline(int *argc, const char **argv[], struct ssp *ctx)
{
    const char usage[] = "[-h] [OPTION..] command [...]";
    const struct option options[] = {
        OPTION_ARG_OPT ( 0 , "debug",                   0,          OPTID_DEBUG),
        OPTION_ARG_REQ ('D', "database",                OPT_PATH,   OPTID_SHORTOPT),
        OPTION_ARG_NONE('h', "help",                    0,          OPTID_SHORTOPT),
        OPTION_ARG_REQ ('I', "iter",                    0,          OPTID_SHORTOPT),
        OPTION_ARG_NONE('q', "quiet",                   0,          OPTID_SHORTOPT),
        OPTION_ARG_NONE( 0 , "version",                 0,          OPTID_VERSION),
        LOADOPT_STOP
    };
    struct loadopt lo = LOADOPT_ZERO;

    int c;
    while ((c = loadopt(&ctx->sa, *argc, *argv, options, 0, NULL, 0, &lo))) switch (c) {
        case 'D':
            break;
        case 'h':
            ctx->options |= OPT_HELP;
            break;
        case 'I':
            {
                u32 u;
                if (!u32_scan0(&u, LO_ARG(&lo))
                        || (errno = ERANGE, u < ITER_MIN)) {
                    warnsys("invalid iteration number: ", LO_ARG(&lo));
                    dieusage(EX_USAGE, usage);
                }
                ctx->iter = u;
                dbg("set iteration to ", PMUINT(ctx->iter));
            }
            break;
        case 'q':
            autoopt_quiet(&options[LO_IDX(&lo)], LO_ARG(&lo));
            break;
        case OPTID_DEBUG:
            if (!autoopt_debug(&options[LO_IDX(&lo)], LO_ARG(&lo)))
                dieusage(EX_USAGE, usage);
            break;
        case OPTID_VERSION:
            liladieversion(SSP_VERSION, "2023", SSP_CURYEAR, SSP_AUTHOR, SSP_URL, NULL);
        case -1:
            dieusage(EX_USAGE, usage);
        default:
            die(EX_SOFTWARE, "unexpected return value ", PMINT(c), " from loadopt");
    };

    /* no command specified */
    if (LO_CUR(&lo) == *argc)
        dienocommand(EX_USAGE, usage, (ctx->options & OPT_HELP) ?
" -D, --database FILE                   Use FILE as database [$HOME/ssp.db]\n"
" -I, --iter ITER                       Use ITER iterations (when writing database) [500000]\n"
"\n"
" -q, --quiet                           Enable quiet mode\n"
"     --debug[=[@[level]:]+FD|FILE]     Enable debug output (to FD|FILE)\n"
"\n"
" -h, --help                            Show (command's) help screen and exit\n"
"     --version                         Show version information and exit\n"
: NULL);

    *argc -= LO_CUR(&lo);
    *argv += LO_CUR(&lo);

    return getcommandordie(EX_USAGE, usage, **argv);
}

int
main(int argc, const char *argv[], const char *env[])
{
    const char usage[] = "[-h] [-D database] [-q] ";
    struct ssp ctx = { 0 };

    setlocale(LC_ALL, "");

    struct command *command = parse_cmdline(&argc, &argv, &ctx);

    if (!ctx.sa.len) {
        const char *home = env_get2(env, "HOME");
        dbg("no database set, using default. $HOME=", ESC, home, ESC);
        if (!home || !*home) {
            warn("$HOME not defined, using current directory instead");
            home = ".";
        }
        if (!stralloc_cats(&ctx.sa, home) || !stralloc_cats0(&ctx.sa, "/ssp.db"))
            diefusys(EX_TEMPFAIL, "set database path");
    }
    dbg("database: ", ESC, db_file(&ctx), ESC);

    if (ctx.options & OPT_HELP)
        diecmdhelp(0, usage, command);

    dbg("running command ", command->name);
    int r = command->main(argc, argv, env, usage, &ctx);
    stralloc_free(&ctx.sa);
    return r;
}