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 \ #define OPTIONS_LIST \
X('h', "help", no_argument, handle_help, OPT_ARG_NONE, "Display this help") \ 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") 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 }; enum { NB_OPTS = 0 OPTIONS_LIST };
#undef X #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 }; enum { OPTSTR_LEN = 1 OPTIONS_LIST };
#undef X #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 }; static const struct option_descriptor g_opts[] = { OPTIONS_LIST };
#undef X #undef X
@ -113,9 +113,34 @@ struct option_descriptor {
t_option_handler handler; // callback: int f(const char *arg, void *config) t_option_handler handler; // callback: int f(const char *arg, void *config)
int arg_type; // display hint for cli_print_options 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 ### Help
```c ```c

View file

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

View file

@ -10,8 +10,11 @@
#define FLAG_VERBOSE (1 << 0) #define FLAG_VERBOSE (1 << 0)
#define GREET_MAX_NAMES 16
struct greet_config { struct greet_config {
const char *name; const char *names[GREET_MAX_NAMES];
size_t nb_names;
uint64_t count; uint64_t count;
uint8_t flags; uint8_t flags;
}; };
@ -27,21 +30,21 @@ static int handle_count(const char *arg, void *cfg);
/* Option table */ /* Option table */
#define GREET_OPTIONS_LIST \ #define GREET_OPTIONS_LIST \
X('h', "help", no_argument, handle_help, OPT_ARG_NONE, "Display this help") \ 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") \ 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") \ 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") \ 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") 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 }; enum { GREET_NB_OPTS = 0 GREET_OPTIONS_LIST };
#undef X #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 }; enum { GREET_OPTSTR_LEN = 1 GREET_OPTIONS_LIST };
#undef X #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 }; static const struct option_descriptor g_opts[] = { GREET_OPTIONS_LIST };
#undef X #undef X
@ -74,7 +77,9 @@ static int
handle_name(const char *arg, void *cfg) handle_name(const char *arg, void *cfg)
{ {
struct greet_config *config = (struct greet_config *)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; return CLI_SUCCESS;
} }
@ -92,11 +97,12 @@ handle_count(const char *arg, void *cfg)
int int
main(int argc, char **argv) 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]; char opt_str[GREET_OPTSTR_LEN + 1];
struct option long_opts[GREET_NB_OPTS + 1]; struct option long_opts[GREET_NB_OPTS + 1];
enum cli_code ret; enum cli_code ret;
uint64_t i; uint64_t i;
size_t j;
cli_set_prog_name(argv[0]); cli_set_prog_name(argv[0]);
@ -109,12 +115,15 @@ main(int argc, char **argv)
return EXIT_FAILURE; return EXIT_FAILURE;
for (i = 0; i < config.count; i++) for (i = 0; i < config.count; i++)
{
for (j = 0; j < config.nb_names; j++)
{ {
if (HAS_FLAG(config.flags, FLAG_VERBOSE)) if (HAS_FLAG(config.flags, FLAG_VERBOSE))
printf("[%llu] Hello, %s!\n", (unsigned long long)(i + 1), printf("[%llu] Hello, %s!\n", (unsigned long long)(i + 1),
config.name); config.names[j]);
else else
printf("Hello, %s!\n", config.name); printf("Hello, %s!\n", config.names[j]);
}
} }
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View file

@ -13,6 +13,8 @@ enum cli_code {
typedef int (*t_option_handler)(const char *arg, void *config); typedef int (*t_option_handler)(const char *arg, void *config);
typedef const char *(*t_arg_type_to_str)(int type); typedef const char *(*t_arg_type_to_str)(int type);
#define CLI_OPT_F_REPEATABLE (1 << 0)
struct option_descriptor struct option_descriptor
{ {
char short_opt; char short_opt;
@ -21,6 +23,7 @@ struct option_descriptor
t_option_handler handler; t_option_handler handler;
int arg_type; int arg_type;
const char *description; const char *description;
int flags;
}; };
/* Default arg types — clients may define their own */ /* 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, static const struct option_descriptor *find_option_handler(int opt,
const struct option_descriptor *opts, size_t nb_opts); const struct option_descriptor *opts, size_t nb_opts);
static int handle_one_option(int opt, char **argv, void *config, 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 struct option_descriptor *opts, size_t nb_opts);
static enum cli_code error_unknown_opt(const char *current_opt); 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_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) char *opt_str, struct option *long_opts)
{ {
uint64_t tracker = 0; uint64_t tracker = 0;
uint64_t repeatable_mask = 0;
size_t i;
int opt; int opt;
int res; 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_optstr(opt_str, opts, nb_opts);
build_long_options(long_opts, nb_opts, opts); build_long_options(long_opts, nb_opts, opts);
while (-1 != (opt = getopt_long(argc, argv, opt_str, long_opts, NULL))) while (-1 != (opt = getopt_long(argc, argv, opt_str, long_opts, NULL)))
{ {
HANDLE_QUESTION_MARK(opt); 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) if (CLI_SUCCESS != res)
return (enum cli_code)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 static int
handle_one_option(int opt, char **argv, void *config, 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 struct option_descriptor *opts, size_t nb_opts)
{ {
const char *current_opt = argv[optind - 1]; const char *current_opt = argv[optind - 1];
const struct option_descriptor *desc; const struct option_descriptor *desc;
size_t bitmask_index; uint64_t bit;
int res; int res;
if ('?' == opt) if ('?' == opt)
@ -100,11 +105,11 @@ handle_one_option(int opt, char **argv, void *config,
desc = find_option_handler(opt, opts, nb_opts); desc = find_option_handler(opt, opts, nb_opts);
if (NULL == desc) if (NULL == desc)
return CLI_ERROR; return CLI_ERROR;
bitmask_index = (size_t)(desc - opts); bit = (1ULL << (size_t)(desc - opts));
if (HAS_FLAG(*opt_tracker, (1ULL << bitmask_index))) if (HAS_FLAG(*tracker, bit) && !HAS_FLAG(repeatable_mask, bit))
return error_duplicate_opt(current_opt); return error_duplicate_opt(current_opt);
SET_FLAG(*opt_tracker, (1ULL << bitmask_index)); SET_FLAG(*tracker, bit);
res = desc->handler(optarg, config); res = desc->handler(optarg, config);
if (CLI_ERROR == res) if (CLI_ERROR == res)
return error_invalid_opt(current_opt); return error_invalid_opt(current_opt);