feat(libcli): add CLI_OPT_F_REPEATABLE flag
This commit is contained in:
parent
17ba957ed6
commit
a1b7a36204
5 changed files with 72 additions and 30 deletions
35
README.md
35
README.md
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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])
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 */
|
||||||
|
|
|
||||||
19
src/parse.c
19
src/parse.c
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue