Add menu based arg parsgin

This commit is contained in:
x3 2023-08-08 19:15:19 +02:00
parent 43b2249d91
commit 9cdf2df3b3
Signed by: x3
GPG Key ID: 7E9961E8AD0E240E
8 changed files with 306 additions and 91 deletions

View File

@ -14,6 +14,7 @@ struct cmd_entry {
bool need_cache : 1; /* Does this cmd needs the file cache? */
const char *arg_name; /* If this argument is present, execute this cmd */
enum error (*argcheck)(void); /* Function to check argument correctness before calling fn */
enum error (*fn)(void *data); /* The function for the command */
};
@ -22,7 +23,7 @@ static const struct cmd_entry ents[] = {
{ .arg_name = "server-version", .fn = cmd_server_version, .need_api = true },
{ .arg_name = "uptime", .fn = cmd_server_uptime, .need_auth = true },
{ .arg_name = "ed2k", .fn = cmd_ed2k, },
{ .arg_name = "add", .fn = cmd_add, .need_auth = true, .need_cache = true, },
{ .arg_name = "add", .fn = cmd_add, .argcheck = cmd_add_argcheck, .need_auth = true, .need_cache = true, },
{ .arg_name = "modify", .fn = cmd_modify, .need_auth = true, .need_cache = false, },
};
static const int32_t ents_len = sizeof(ents)/sizeof(*ents);
@ -31,6 +32,11 @@ static enum error cmd_run_one(const struct cmd_entry *ent)
{
enum error err = NOERR;
if (ent->argcheck) {
err = ent->argcheck();
if (err != NOERR)
goto end;
}
if (ent->need_cache) {
err = cache_init();
if (err != NOERR)

View File

@ -13,6 +13,7 @@ enum error cmd_main();
* Add files to the AniDB list
*/
enum error cmd_add(void *);
enum error cmd_add_argcheck();
/*
* Take in a file/folder and print out

View File

@ -175,3 +175,11 @@ enum error cmd_add(void *data)
return err;
}
enum error cmd_add_argcheck()
{
if (config_get_nonopt_count() == 0) {
uio_error("No files specified");
return ERR_CMD_ARG;
}
return NOERR;
}

View File

@ -49,7 +49,7 @@ enum error cmd_ed2k(void *data)
return ERR_CMD_ARG;
}
if (config_get("link", (void**)&link) == NOERR)
if (config_get_subopt("ed2k", "link", (void**)&link) == NOERR)
opts.link = *link;
for (int i = 0; i < fcount; i++) {

View File

@ -73,11 +73,11 @@ enum error cmd_modify(void *data)
return ERR_CMD_ARG;
}
if (config_get("watched", (void**)&watched) == NOERR && *watched) {
if (config_get_subopt("modify", "watched", (void**)&watched) == NOERR && *watched) {
mopt.watched = *watched;
mopt.watched_set = true;
if (config_get("wdate", (void**)&wdate_str) == NOERR) {
if (config_get_subopt("modify", "wdate", (void**)&wdate_str) == NOERR) {
uint64_t wdate = util_iso2unix(*wdate_str);
if (wdate == 0) {

View File

@ -19,8 +19,10 @@
#include "config.h"
#include "error.h"
#include "util.h"
#include "uio.h"
static int show_help(struct conf_entry *ce);
static int show_subcomm_help(struct conf_entry *ce);
static int config_parse_file();
static enum error config_required_check();
@ -35,35 +37,44 @@ static int config_set_bool(struct conf_entry *ce, char *arg);
/* Everything not explicitly defined, is 0 */
/* If an option only has a long name, the short name also has to be
* defined. For example, a number larger than UCHAR_MAX */
#define SUBCOMM_HELP { \
.l_name = "help", .s_name = 'h', .has_arg = no_argument, \
.action_func = show_subcomm_help, .in_args = true, \
.type = OTYPE_ACTION, .handle_order = 0, \
.h_desc = "Display the help for the subcommand and exit", }
static struct conf_entry subcomm_def_help_opt = SUBCOMM_HELP;
static struct conf_entry ed2k_subopts[] = {
SUBCOMM_HELP,
{ .l_name = "link", .s_name = 'l', .has_arg = no_argument,
.set_func = config_set_bool, .in_args = true,
.type = OTYPE_B, .handle_order = 0, .value_is_set = true,
.h_desc = "Print an ed2k link for the files", },
};
static struct conf_entry modify_add_subopts[] = {
SUBCOMM_HELP,
{ .l_name = "watched", .s_name = 'w', .has_arg = no_argument,
.set_func = config_set_bool, .in_args = true,
.type = OTYPE_B, .handle_order = 0, .value_is_set = true,
.h_desc = "Mark the episode as watched when adding files", },
{ .l_name = "wdate", .s_name = UCHAR_MAX + 4, .has_arg = required_argument,
.set_func = config_set_str, .in_args = true,
.type = OTYPE_S, .handle_order = 0,
.h_desc = "Set the watched date when adding files", },
};
static struct conf_entry options[] = {
{ .l_name = "help", .s_name = 'h', .has_arg = no_argument,
.action_func = show_help, .in_args = true,
.type = OTYPE_ACTION, .handle_order = 0,
.h_desc = "Display the help and exit", },
/*
{ .l_name = "config-dir", .s_name = 'b', .has_arg = required_argument,
.default_func = config_def_config_dir, .set_func = config_set_str,
.in_args = true, .type = OTYPE_S, .handle_order = 0 },
{ .l_name = "default-download-dir", .s_name = 'd', .has_arg = required_argument,
.default_func = config_def_default_download_dir, .set_func = config_set_str,
.in_file = true, .in_args = true, .type = OTYPE_S, .handle_order = 1 },
{ .l_name = "port", .s_name = 'p', .has_arg = required_argument,
.set_func = config_set_port, .value.hu = 21729, .value_is_set = true,
.in_file = true, .in_args = true, .type = OTYPE_HU, .handle_order = 1 },
{ .l_name = "foreground", .s_name = 'f', .has_arg = no_argument,
.set_func = config_set_bool, .value.b = false, .value_is_set = true,
.in_args = true, .type = OTYPE_B, .handle_order = 1 },
{ .l_name = "write-config", .s_name = UCHAR_MAX + 1, .has_arg = no_argument,
.action_func = config_action_write_config, .value_is_set = true,
.in_args = true, .type = OTYPE_ACTION, .handle_order = 2 },
{ .l_name = "peer-id", .s_name = UCHAR_MAX + 2, .has_arg = required_argument,
.default_func = config_def_peer_id, .type = OTYPE_S, .handle_order = 1 },
*/
{ .l_name = "username", .s_name = 'u', .has_arg = required_argument,
.set_func = config_set_str, .in_args = true, .in_file = true,
@ -102,16 +113,6 @@ static struct conf_entry options[] = {
.type = OTYPE_B, .handle_order = 1, .value_is_set = true,
.h_desc = "not implemented", },
{ .l_name = "watched", .s_name = 'w', .has_arg = no_argument,
.set_func = config_set_bool, .in_args = true,
.type = OTYPE_B, .handle_order = 1, .value_is_set = true,
.h_desc = "Mark the episode as watched when adding files", },
{ .l_name = "link", .s_name = 'l', .has_arg = no_argument,
.set_func = config_set_bool, .in_args = true,
.type = OTYPE_B, .handle_order = 1, .value_is_set = true,
.h_desc = "Print an ed2k link when running the ed2k command", },
{ .l_name = "cachedb", .s_name = 'd', .has_arg = required_argument,
.set_func = config_set_str, .in_args = true, .in_file = true,
.type = OTYPE_S, .handle_order = 1, /*.default_func = config_def_cachedb*/
@ -122,45 +123,50 @@ static struct conf_entry options[] = {
.type = OTYPE_B, .handle_order = 1, .value_is_set = true,
.h_desc = "Enable debug output", },
{ .l_name = "wdate", .s_name = UCHAR_MAX + 4, .has_arg = required_argument,
.set_func = config_set_str, .in_args = true,
.type = OTYPE_S, .handle_order = 1,
.h_desc = "Set the watched date when adding files", },
/*### cmd ###*/
/*### cmds ###*/
{ .l_name = "server-version", .s_name = UCHAR_MAX + 2,
.has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
.type = OTYPE_B, .handle_order = 1, .value_is_set = true,
.h_desc = "CMD: Request the server version", },
.type = OTYPE_SUBCOMMAND, .handle_order = 1, .value_is_set = true,
.h_desc = "Request the server version",
},
{ .l_name = "version", .s_name = 'v',
.has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
.type = OTYPE_B, .handle_order = 1, .value_is_set = true,
.h_desc = "CMD: Print the caniadd version", },
.type = OTYPE_SUBCOMMAND, .handle_order = 1, .value_is_set = true,
.h_desc = "Print the caniadd version", },
{ .l_name = "uptime", .s_name = UCHAR_MAX + 3,
.has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
.type = OTYPE_B, .handle_order = 1, .value_is_set = true,
.h_desc = "CMD: Request the uptime of the api servers", },
.type = OTYPE_SUBCOMMAND, .handle_order = 1, .value_is_set = true,
.h_desc = "Request the uptime of the api servers", },
{ .l_name = "ed2k", .s_name = 'e',
.has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
.type = OTYPE_B, .handle_order = 1,
.h_desc = "CMD: Run an ed2k hash on the file arguments", },
.type = OTYPE_SUBCOMMAND, .handle_order = 1,
.h_desc = "Run an ed2k hash on the file arguments",
.subopts = ed2k_subopts,
.subopts_count = sizeof(ed2k_subopts) / sizeof(ed2k_subopts[0]),
},
{ .l_name = "add", .s_name = 'a',
.has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
.type = OTYPE_B, .handle_order = 1,
.h_desc = "CMD: Add files to your anidb list", },
.type = OTYPE_SUBCOMMAND, .handle_order = 1,
.h_desc = "Add files to your anidb list",
.subopts = modify_add_subopts,
.subopts_count = sizeof(modify_add_subopts) / sizeof(modify_add_subopts[0]),
},
/* Arguments are either mylist id's, or file sizes and names
* in the format '[watch_date/]<size>/<filename>'. The filename can't contain
* '/' characters. */
{ .l_name = "modify", .s_name = 'W',
.has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
.type = OTYPE_B, .handle_order = 1,
.h_desc = "CMD: Modify files in your anidb list", },
.type = OTYPE_SUBCOMMAND, .handle_order = 1,
.h_desc = "Modify files in your anidb list",
.subopts = modify_add_subopts,
.subopts_count = sizeof(modify_add_subopts) / sizeof(modify_add_subopts[0]),
},
/*{ .l_name = "stats", .s_name = UCHAR_MAX + 5,
.has_arg = no_argument, .set_func = config_set_bool, .in_args = true,
@ -169,33 +175,42 @@ static struct conf_entry options[] = {
};
static const size_t options_count = sizeof(options) / sizeof(options[0]);
static const char **opt_argv = NULL;
/* Used in show_subcomm_help to output info about the subcommand */
static struct conf_entry *current_subcommand = NULL;
static char **opt_argv = NULL;
static int opt_argc = 0;
static void config_build_getopt_args(char out_sopt[options_count * 2 + 1],
struct option out_lopt[options_count + 1])
static void config_build_getopt_args(int opts_count, const struct conf_entry opts[opts_count],
char out_sopt[opts_count * 2 + 1], struct option out_lopt[opts_count + 1],
bool stop_at_1st_nonopt)
{
int i_sopt = 0, i_lopt = 0;
if (stop_at_1st_nonopt) {
/* Tell getopts to stop at the 1st non-option argument */
out_sopt[i_sopt++] = '+';
}
for (int i = 0; i < options_count; i++) {
for (int i = 0; i < opts_count; i++) {
if (opts[i].type == OTYPE_SUBCOMMAND)
continue;
/* Short options */
if (options[i].s_name && options[i].s_name <= UCHAR_MAX) {
out_sopt[i_sopt++] = options[i].s_name;
if (options[i].has_arg == required_argument)
if (opts[i].s_name && opts[i].s_name <= UCHAR_MAX) {
out_sopt[i_sopt++] = opts[i].s_name;
if (opts[i].has_arg == required_argument)
out_sopt[i_sopt++] = ':';
assert(options[i].has_arg == required_argument ||
options[i].has_arg == no_argument);
assert(opts[i].has_arg == required_argument ||
opts[i].has_arg == no_argument);
}
/* Long options */
if (options[i].l_name) {
assert(options[i].s_name);
/* Long opts */
if (opts[i].l_name) {
assert(opts[i].s_name);
out_lopt[i_lopt].name = options[i].l_name;
out_lopt[i_lopt].has_arg = options[i].has_arg;
out_lopt[i_lopt].name = opts[i].l_name;
out_lopt[i_lopt].has_arg = opts[i].has_arg;
out_lopt[i_lopt].flag = NULL;
out_lopt[i_lopt].val = options[i].s_name;
out_lopt[i_lopt].val = opts[i].s_name;
i_lopt++;
}
@ -205,33 +220,36 @@ static void config_build_getopt_args(char out_sopt[options_count * 2 + 1],
memset(&out_lopt[i_lopt], 0, sizeof(struct option));
}
static int config_read_args(int argc, char **argv, char sopt[options_count * 2 + 1],
struct option lopt[options_count + 1], int level)
static int config_read_args(int argc, char **argv,
int opts_count, struct conf_entry opts[opts_count],
char sopt[opts_count * 2 + 1], struct option lopt[opts_count + 1], int level)
{
int optc, err = NOERR;
optind = 1;
//uio_debug("Before %d %s", argc, argv[0]);
while ((optc = getopt_long(argc, argv, sopt,
lopt, NULL)) >= 0) {
bool handled = false;
//uio_debug("Optc: %c", optc);
for (int i = 0; i < options_count; i++) {
if (options[i].handle_order != level) {
for (int i = 0; i < opts_count; i++) {
if (opts[i].handle_order != level) {
/* Lie a lil :x */
handled = true;
continue;
}
if (optc == options[i].s_name) {
if (options[i].type != OTYPE_ACTION)
err = options[i].set_func(&options[i], optarg);
if (optc == opts[i].s_name) {
if (opts[i].type != OTYPE_ACTION)
err = opts[i].set_func(&opts[i], optarg);
else
err = options[i].action_func(&options[i]);
err = opts[i].action_func(&opts[i]);
if (err != NOERR)
goto end;
options[i].value_is_set = true;
opts[i].value_is_set = true;
handled = true;
break;
@ -269,16 +287,72 @@ static enum error config_required_check()
return err;
}
static enum error config_parse_subcomm_subopts(struct conf_entry *subcomm)
{
char sopt[64];
struct option lopt[32];
enum error err;
/* Set the global current subcommand pointer */
current_subcommand = subcomm;
config_build_getopt_args(subcomm->subopts_count, subcomm->subopts, sopt, lopt, false);
//uio_debug("Parsing subconn %s", subcomm->l_name);
//uio_debug("sopt: %s", sopt);
/* Update args for next parsing and nonopts parsing for later */
opt_argv = &opt_argv[optind];
opt_argc = config_get_nonopt_count();
/* argv[0] (which is the subcommand string) will be treated as the filename here, and skipped */
err = config_read_args(opt_argc, opt_argv,
subcomm->subopts_count, subcomm->subopts, sopt, lopt, 0);
if (err == NOERR) {
/* Mark subcommand as set */
subcomm->value_is_set = true;
subcomm->value.b = true;
}
current_subcommand = NULL;
return err;
}
static enum error config_parse_subcommands()
{
const char *subcomm_str;
enum error err = ERR_OPT_NOTFOUND;
if (config_get_nonopt_count() <= 0) {
return ERR_OPT_NO_SUBCOMMAND;
}
subcomm_str = config_get_nonopt(0);
for (int i = 0; i < options_count; i++) {
if (options[i].type != OTYPE_SUBCOMMAND)
continue;
if (strcmp(options[i].l_name, subcomm_str) != 0)
continue;
if (options[i].subopts == NULL) {
/* If no suboptions is defined, define the default
* help one here */
options[i].subopts = &subcomm_def_help_opt;
options[i].subopts_count = 1;
}
/* Found, parse subopts */
err = config_parse_subcomm_subopts(&options[i]);
}
return err;
}
enum error config_parse(int argc, char **argv)
{
enum error err = NOERR;
char sopt[options_count * 2 + 1];
struct option lopt[options_count + 1];
opt_argv = (const char**)argv;
opt_argv = argv;
opt_argc = argc;
config_build_getopt_args(sopt, lopt);
config_build_getopt_args(options_count, options, sopt, lopt, true);
err = config_read_args(argc, argv, sopt, lopt, 0);
err = config_read_args(argc, argv, options_count, options, sopt, lopt, 0);
if (err != NOERR)
goto end;
@ -286,7 +360,7 @@ enum error config_parse(int argc, char **argv)
if (err != NOERR)
goto end;
err = config_read_args(argc, argv, sopt, lopt, 1);
err = config_read_args(argc, argv, options_count, options, sopt, lopt, 1);
if (err != NOERR)
goto end;
@ -301,11 +375,24 @@ enum error config_parse(int argc, char **argv)
}
}
err = config_read_args(argc, argv, sopt, lopt, 2);
err = config_read_args(argc, argv, options_count, options, sopt, lopt, 2);
if (err != NOERR)
goto end;
err = config_required_check();
if (err != NOERR)
goto end;
/* Now that all of the global arguments are parsed, do the subcommands */
err = config_parse_subcommands();
if (err != NOERR) {
if (err == ERR_OPT_NO_SUBCOMMAND)
printf("No subcommand given!\n\n");
else if (err == ERR_OPT_NOTFOUND)
printf("The given subcommand doesn't exist\n\n");
if (err != ERR_OPT_EXIT) /* If err is this, then help() was already called */
show_help(NULL);
}
end:
if (err != NOERR)
@ -384,15 +471,63 @@ static int config_set_bool(struct conf_entry *ce, char *arg)
return NOERR;
}
static int show_subcomm_help(struct conf_entry *ce)
{
assert(current_subcommand);
const struct conf_entry *subopts = current_subcommand->subopts;
printf(
"Usage: caniadd [OPTIONS] %s [SUBCOMMAND_OPTIONS]\n"
"%s\n"
"\n"
"SUBCOMMAND_OPTIONS:\n",
current_subcommand->l_name,
current_subcommand->h_desc
);
for (size_t i = 0; i < current_subcommand->subopts_count; i++) {
int printed = 0, pad;
printed += printf(" ");
if (subopts[i].l_name)
printed += printf("--%s", subopts[i].l_name);
if (subopts[i].s_name < UCHAR_MAX)
printed += printf(", -%c", subopts[i].s_name);
if (subopts[i].has_arg != no_argument)
printed += printf(" arg");
pad = 25 - printed;
if (pad <= 0)
pad = 1;
printf("%*s%s", pad, "", subopts[i].h_desc);
if (subopts[i].value_is_set) {
printf(" Val: ");
if (subopts[i].type == OTYPE_S)
printed += printf("%s", subopts[i].value.s);
else if (subopts[i].type == OTYPE_HU)
printed += printf("%hu", subopts[i].value.hu);
else if (subopts[i].type == OTYPE_B)
printed += printf("%s", subopts[i].value.b ? "true" : "false");
}
printf("\n");
}
return ERR_OPT_EXIT;
}
static int show_help(struct conf_entry *ce)
{
printf(
"Usage: caniadd [OPTION]...\n"
"Usage: caniadd [OPTIONS] SUBCOMMAND [SUBCOMMAND_OPTIONS]\n"
"Caniadd will add files to an AniDB list, and possibly more.\n"
"\n"
"OPTIONS:\n"
);
for (size_t i = 0; i < options_count; i++) {
if (options[i].type == OTYPE_SUBCOMMAND)
continue;
int printed = 0, pad;
printed += printf(" ");
@ -410,7 +545,7 @@ static int show_help(struct conf_entry *ce)
printf("%*s%s", pad, "", options[i].h_desc);
if (options[i].value_is_set) {
printf(" Def: ");
printf(" Val: ");
if (options[i].type == OTYPE_S)
printed += printf("%s", options[i].value.s);
else if (options[i].type == OTYPE_HU)
@ -420,6 +555,19 @@ static int show_help(struct conf_entry *ce)
}
printf("\n");
}
printf("\nSUBCOMMANDS:\n");
for (size_t i = 0; i < options_count; i++) {
if (options[i].type != OTYPE_SUBCOMMAND)
continue;
int printed = 0, pad;
printed += printf(" %s", options[i].l_name);
pad = 25 - printed;
printf("%*s%s\n", pad, "", options[i].h_desc);
}
return ERR_OPT_EXIT;
}
@ -537,17 +685,18 @@ end:
}
#endif
enum error config_get(const char *key, void **out)
enum error config_get_inter(int cenf_count, const struct conf_entry cenf[cenf_count],
const char *key, void **out)
{
enum error err = ERR_OPT_NOTFOUND;
for (int i = 0; i < options_count; i++) {
struct conf_entry *cc = &options[i];
for (int i = 0; i < cenf_count; i++) {
const struct conf_entry *cc = &cenf[i];
if (strcmp(cc->l_name, key) == 0) {
if (cc->value_is_set) {
if (out)
*out = &cc->value.s;
*out = (void**)&cc->value.s;
err = NOERR;
} else {
err = ERR_OPT_UNSET;
@ -559,6 +708,23 @@ enum error config_get(const char *key, void **out)
return err;
}
enum error config_get(const char *key, void **out)
{
return config_get_inter(options_count, options, key, out);
}
enum error config_get_subopt(const char *subcomm, const char *key, void **out)
{
for (int i = 0; i < options_count; i++) {
struct conf_entry *cc = &options[i];
if (strcmp(cc->l_name, subcomm) == 0) {
return config_get_inter(cc->subopts_count, cc->subopts, key, out);
}
}
return ERR_OPT_NOTFOUND;
}
const char *config_get_nonopt(int index)
{
if (index >= config_get_nonopt_count())

View File

@ -16,10 +16,36 @@ enum option_type {
/* Does not store anything, does an action. Handled after every
* other option are parsed, and defaults set */
OTYPE_ACTION,
OTYPE_SUBCOMMAND,
_OTYPE_COUNT
};
#if 0
/* This should be used to make loopups O(1), but w/e for now */
enum config_global_options {
OPT_HELP = 0,
OPT_USERNAME,
OPT_PASSWORD,
OPT_PORT,
OPT_API_SERVER,
OPT_API_KEY,
OPT_SAVE_SESSION,
OPT_DESTROY_SESSION,
OPT_CACHEDB,
OPT_DEBUG,
};
enum config_subcommands {
SUBCOMM_SERVER_VERSION = 0,
SUBCOMM_VERSION,
SUBCOMM_UPTIME,
SUBCOMM_ED2k,
SUBCOMM_ADD,
SUBCOMM_MODIFY,
};
#endif
struct conf_entry {
const char *l_name; /* The long name for the option, or for the config file */
int s_name; /* Short option name */
@ -56,6 +82,12 @@ struct conf_entry {
* 4. Execute action arguments
*/
int handle_order : 4;
/* If type is a subcommand, this is the arguments for that subcommand */
/* '-h' option is available for all of the subcommands if this is not set */
int subopts_count;
struct conf_entry *subopts;
/* The subcommand parent of this entry, if it is inside of a subcommand */
//struct conf_entry *subcomm_parent;
};
/*
@ -84,6 +116,7 @@ void config_dump();
* It the options is not found, it returns ERR_OPT_NOTFOUND, and out is unchanged
*/
enum error config_get(const char *key, void **out);
enum error config_get_subopt(const char *subcomm, const char *key, void **out);
/*
* Return an cmd line that is not an option

View File

@ -13,6 +13,7 @@
E(ERR_OPT_EXIT) /* We should exit in main, if config_parse returns this */ \
E(ERR_OPT_UNSET) /* In config_get, if the value isn't set */ \
E(ERR_OPT_NOTFOUND) /* In config_get, if the options is not found */ \
E(ERR_OPT_NO_SUBCOMMAND) \
\
E(ERR_NET_APIADDR) /* If there are problems with the api servers address */ \
E(ERR_NET_SOCKET) /* If there are problems with the udp socket */ \