168 lines
4.2 KiB
C
168 lines
4.2 KiB
C
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <getopt.h>
|
|
|
|
#include "cli.h"
|
|
|
|
/* Internal bitmask helpers */
|
|
#define HAS_FLAG(flags, flag) ((flags) & (flag))
|
|
#define SET_FLAG(flags, flag) ((flags) |= (flag))
|
|
|
|
/* Map -? to -h to support help shortcut */
|
|
#define HANDLE_QUESTION_MARK(opt) \
|
|
do { \
|
|
if (opt == '?' && (optopt) == '?') \
|
|
opt = 'h'; \
|
|
} while (0)
|
|
|
|
/* Forward declarations */
|
|
static void build_long_options(struct option *long_opts, size_t nb_opts,
|
|
const struct option_descriptor *src);
|
|
static void build_optstr(char *dest, const struct option_descriptor *opts,
|
|
size_t nb_opts);
|
|
static const struct option_descriptor *find_option_handler(int opt,
|
|
const struct option_descriptor *opts, size_t nb_opts);
|
|
static int handle_one_option(int opt, char **argv, void *config,
|
|
uint64_t *opt_tracker,
|
|
const struct option_descriptor *opts, size_t nb_opts);
|
|
static enum cli_code error_unknown_opt(const char *current_opt);
|
|
static enum cli_code error_invalid_opt(const char *current_opt);
|
|
static enum cli_code error_duplicate_opt(const char *current_opt);
|
|
static void print_suggest_help(void);
|
|
/* ------------------- */
|
|
|
|
static const char *s_prog_name = "";
|
|
|
|
void
|
|
cli_set_prog_name(const char *name)
|
|
{
|
|
if (NULL != name)
|
|
s_prog_name = name;
|
|
}
|
|
|
|
enum cli_code
|
|
cli_parse(int argc, char **argv, void *config,
|
|
const struct option_descriptor *opts, size_t nb_opts,
|
|
char *opt_str, struct option *long_opts)
|
|
{
|
|
uint64_t tracker = 0;
|
|
int opt;
|
|
int res;
|
|
|
|
build_optstr(opt_str, opts, nb_opts);
|
|
build_long_options(long_opts, nb_opts, opts);
|
|
while (-1 != (opt = getopt_long(argc, argv, opt_str, long_opts, NULL)))
|
|
{
|
|
HANDLE_QUESTION_MARK(opt);
|
|
res = handle_one_option(opt, argv, config, &tracker, opts, nb_opts);
|
|
if (CLI_SUCCESS != res)
|
|
return (enum cli_code)res;
|
|
}
|
|
return CLI_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
build_optstr(char *dest, const struct option_descriptor *opts, size_t nb_opts)
|
|
{
|
|
size_t str_i = 1;
|
|
|
|
/* Mute default error messages */
|
|
dest[0] = ':';
|
|
|
|
for (size_t opt_i = 0; opt_i < nb_opts; ++opt_i)
|
|
{
|
|
dest[str_i++] = opts[opt_i].short_opt;
|
|
switch (opts[opt_i].has_arg)
|
|
{
|
|
case optional_argument: dest[str_i++] = ':'; /* fall through */
|
|
case required_argument: dest[str_i++] = ':'; break;
|
|
default: break;
|
|
}
|
|
}
|
|
dest[str_i] = '\0';
|
|
}
|
|
|
|
static int
|
|
handle_one_option(int opt, char **argv, void *config,
|
|
uint64_t *opt_tracker,
|
|
const struct option_descriptor *opts, size_t nb_opts)
|
|
{
|
|
const char *current_opt = argv[optind - 1];
|
|
const struct option_descriptor *desc;
|
|
size_t bitmask_index;
|
|
int res;
|
|
|
|
if ('?' == opt)
|
|
return error_unknown_opt(current_opt);
|
|
else if (':' == opt)
|
|
return error_invalid_opt(current_opt);
|
|
|
|
desc = find_option_handler(opt, opts, nb_opts);
|
|
if (NULL == desc)
|
|
return CLI_ERROR;
|
|
bitmask_index = (size_t)(desc - opts);
|
|
if (HAS_FLAG(*opt_tracker, (1ULL << bitmask_index)))
|
|
return error_duplicate_opt(current_opt);
|
|
|
|
SET_FLAG(*opt_tracker, (1ULL << bitmask_index));
|
|
res = desc->handler(optarg, config);
|
|
if (CLI_ERROR == res)
|
|
return error_invalid_opt(current_opt);
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
build_long_options(struct option *long_opts, size_t nb_opts,
|
|
const struct option_descriptor *src)
|
|
{
|
|
for (size_t i = 0; i < nb_opts; ++i)
|
|
{
|
|
long_opts[i].name = src[i].long_opt;
|
|
long_opts[i].has_arg = src[i].has_arg;
|
|
long_opts[i].flag = NULL;
|
|
long_opts[i].val = src[i].short_opt;
|
|
}
|
|
long_opts[nb_opts] = (struct option){0};
|
|
}
|
|
|
|
static const struct option_descriptor *
|
|
find_option_handler(int opt, const struct option_descriptor *opts,
|
|
size_t nb_opts)
|
|
{
|
|
for (size_t i = 0; i < nb_opts; ++i)
|
|
{
|
|
if (opts[i].short_opt == opt)
|
|
return &opts[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static enum cli_code
|
|
error_unknown_opt(const char *current_opt)
|
|
{
|
|
fprintf(stderr, "%s: unknown option -- '%s'\n", s_prog_name, current_opt);
|
|
print_suggest_help();
|
|
return CLI_ERROR;
|
|
}
|
|
|
|
static enum cli_code
|
|
error_invalid_opt(const char *current_opt)
|
|
{
|
|
fprintf(stderr, "%s: invalid option '%s'\n", s_prog_name, current_opt);
|
|
print_suggest_help();
|
|
return CLI_ERROR;
|
|
}
|
|
|
|
static enum cli_code
|
|
error_duplicate_opt(const char *current_opt)
|
|
{
|
|
fprintf(stderr, "%s: duplicate option '%s'\n", s_prog_name, current_opt);
|
|
print_suggest_help();
|
|
return CLI_ERROR;
|
|
}
|
|
|
|
static void
|
|
print_suggest_help(void)
|
|
{
|
|
fprintf(stderr, "Try '%s --help' for more information.\n", s_prog_name);
|
|
}
|