feat(libcli): add CLI_OPT_F_REPEATABLE flag

This commit is contained in:
lohhiiccc 2026-06-10 02:02:00 +02:00
parent 17ba957ed6
commit a1b7a36204
5 changed files with 72 additions and 30 deletions

View file

@ -49,18 +49,18 @@ static int handle_help(const char *arg, void *cfg) {
}
#define OPTIONS_LIST \
X('h', "help", no_argument, handle_help, OPT_ARG_NONE, "Display this help") \
X('n', "name", required_argument, handle_name, OPT_ARG_STRING, "Your name")
X('h', "help", no_argument, handle_help, OPT_ARG_NONE, "Display this help", 0) \
X('n', "name", required_argument, handle_name, OPT_ARG_STRING, "Your name", 0)
#define X(s, l, a, h, t, d) + 1
#define X(s, l, a, h, t, d, f) + 1
enum { NB_OPTS = 0 OPTIONS_LIST };
#undef X
#define X(s, l, a, h, t, d) + (1 + (a != no_argument ? 1 : 0))
#define X(s, l, a, h, t, d, f) + (1 + (a != no_argument ? 1 : 0))
enum { OPTSTR_LEN = 1 OPTIONS_LIST };
#undef X
#define X(s, l, a, h, t, d) { s, l, a, h, t, d },
#define X(s, l, a, h, t, d, f) { s, l, a, h, t, d, f },
static const struct option_descriptor g_opts[] = { OPTIONS_LIST };
#undef X
@ -112,10 +112,35 @@ struct option_descriptor {
int has_arg; // no_argument / required_argument / optional_argument
t_option_handler handler; // callback: int f(const char *arg, void *config)
int arg_type; // display hint for cli_print_options
const char *description; // displayed by cli_print_options
const char *description; // displayed by cli_print_options
int flags; // CLI_OPT_F_REPEATABLE or 0
};
```
### Option flags
| Flag | Meaning |
|---|---|
| `CLI_OPT_F_REPEATABLE` | Allow the option to appear more than once on the command line |
When `CLI_OPT_F_REPEATABLE` is set, the library calls the handler on every
occurrence without error. Enforcing a count limit or accumulating values is
the handler's responsibility.
```c
// Declare the option as repeatable:
X('n', "name", required_argument, handle_name, OPT_ARG_STRING, "Name to greet", CLI_OPT_F_REPEATABLE)
// Handler accumulates each occurrence:
static int handle_name(const char *arg, void *cfg) {
struct my_config *c = cfg;
if (c->nb_names >= MAX_NAMES)
return CLI_ERROR;
c->names[c->nb_names++] = arg;
return CLI_SUCCESS;
}
```
### Help
```c

View file

@ -1,5 +1,5 @@
AC_PREREQ([2.69])
AC_INIT([libcli], [0.1.0], [])
AC_INIT([libcli], [0.2.0], [])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIRS([m4])
AM_INIT_AUTOMAKE([foreign -Wall -Werror subdir-objects])

View file

@ -10,8 +10,11 @@
#define FLAG_VERBOSE (1 << 0)
#define GREET_MAX_NAMES 16
struct greet_config {
const char *name;
const char *names[GREET_MAX_NAMES];
size_t nb_names;
uint64_t count;
uint8_t flags;
};
@ -27,21 +30,21 @@ static int handle_count(const char *arg, void *cfg);
/* Option table */
#define GREET_OPTIONS_LIST \
X('h', "help", no_argument, handle_help, OPT_ARG_NONE, "Display this help") \
X('V', "version", no_argument, handle_version, OPT_ARG_NONE, "Display version") \
X('v', "verbose", no_argument, handle_verbose, OPT_ARG_NONE, "Verbose output") \
X('n', "name", required_argument, handle_name, OPT_ARG_STRING, "Name to greet") \
X('c', "count", required_argument, handle_count, OPT_ARG_UINT, "Number of greetings")
X('h', "help", no_argument, handle_help, OPT_ARG_NONE, "Display this help", 0) \
X('V', "version", no_argument, handle_version, OPT_ARG_NONE, "Display version", 0) \
X('v', "verbose", no_argument, handle_verbose, OPT_ARG_NONE, "Verbose output", 0) \
X('n', "name", required_argument, handle_name, OPT_ARG_STRING, "Name to greet", CLI_OPT_F_REPEATABLE) \
X('c', "count", required_argument, handle_count, OPT_ARG_UINT, "Number of greetings", 0)
#define X(s, l, a, h, t, d) + 1
#define X(s, l, a, h, t, d, f) + 1
enum { GREET_NB_OPTS = 0 GREET_OPTIONS_LIST };
#undef X
#define X(s, l, a, h, t, d) + (1 + (a != no_argument ? 1 : 0))
#define X(s, l, a, h, t, d, f) + (1 + (a != no_argument ? 1 : 0))
enum { GREET_OPTSTR_LEN = 1 GREET_OPTIONS_LIST };
#undef X
#define X(s, l, a, h, t, d) { s, l, a, h, t, d },
#define X(s, l, a, h, t, d, f) { s, l, a, h, t, d, f },
static const struct option_descriptor g_opts[] = { GREET_OPTIONS_LIST };
#undef X
@ -74,7 +77,9 @@ static int
handle_name(const char *arg, void *cfg)
{
struct greet_config *config = (struct greet_config *)cfg;
config->name = arg;
if (config->nb_names >= GREET_MAX_NAMES)
return CLI_ERROR;
config->names[config->nb_names++] = arg;
return CLI_SUCCESS;
}
@ -92,11 +97,12 @@ handle_count(const char *arg, void *cfg)
int
main(int argc, char **argv)
{
struct greet_config config = { .name = "world", .count = 1 };
struct greet_config config = { .names = {"world"}, .nb_names = 1, .count = 1 };
char opt_str[GREET_OPTSTR_LEN + 1];
struct option long_opts[GREET_NB_OPTS + 1];
enum cli_code ret;
uint64_t i;
size_t j;
cli_set_prog_name(argv[0]);
@ -110,11 +116,14 @@ main(int argc, char **argv)
for (i = 0; i < config.count; i++)
{
if (HAS_FLAG(config.flags, FLAG_VERBOSE))
printf("[%llu] Hello, %s!\n", (unsigned long long)(i + 1),
config.name);
else
printf("Hello, %s!\n", config.name);
for (j = 0; j < config.nb_names; j++)
{
if (HAS_FLAG(config.flags, FLAG_VERBOSE))
printf("[%llu] Hello, %s!\n", (unsigned long long)(i + 1),
config.names[j]);
else
printf("Hello, %s!\n", config.names[j]);
}
}
return EXIT_SUCCESS;
}

View file

@ -13,6 +13,8 @@ enum cli_code {
typedef int (*t_option_handler)(const char *arg, void *config);
typedef const char *(*t_arg_type_to_str)(int type);
#define CLI_OPT_F_REPEATABLE (1 << 0)
struct option_descriptor
{
char short_opt;
@ -21,6 +23,7 @@ struct option_descriptor
t_option_handler handler;
int arg_type;
const char *description;
int flags;
};
/* Default arg types — clients may define their own */

View file

@ -23,7 +23,7 @@ static void build_optstr(char *dest, const struct option_descriptor *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,
uint64_t *tracker, uint64_t repeatable_mask,
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);
@ -46,15 +46,20 @@ cli_parse(int argc, char **argv, void *config,
char *opt_str, struct option *long_opts)
{
uint64_t tracker = 0;
uint64_t repeatable_mask = 0;
size_t i;
int opt;
int res;
for (i = 0; i < nb_opts; i++)
if (opts[i].flags & CLI_OPT_F_REPEATABLE)
repeatable_mask |= (1ULL << i);
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);
res = handle_one_option(opt, argv, config, &tracker, repeatable_mask, opts, nb_opts);
if (CLI_SUCCESS != res)
return (enum cli_code)res;
}
@ -84,12 +89,12 @@ build_optstr(char *dest, const struct option_descriptor *opts, size_t nb_opts)
static int
handle_one_option(int opt, char **argv, void *config,
uint64_t *opt_tracker,
uint64_t *tracker, uint64_t repeatable_mask,
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;
uint64_t bit;
int res;
if ('?' == opt)
@ -100,11 +105,11 @@ handle_one_option(int opt, char **argv, void *config,
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)))
bit = (1ULL << (size_t)(desc - opts));
if (HAS_FLAG(*tracker, bit) && !HAS_FLAG(repeatable_mask, bit))
return error_duplicate_opt(current_opt);
SET_FLAG(*opt_tracker, (1ULL << bitmask_index));
SET_FLAG(*tracker, bit);
res = desc->handler(optarg, config);
if (CLI_ERROR == res)
return error_invalid_opt(current_opt);