libcli/src/parse.c
2026-04-22 11:28:18 +02:00

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);
}