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