diff --git a/README.md b/README.md index c8c60ab..2f22bd1 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/configure.ac b/configure.ac index b30d376..3ea132d 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) diff --git a/example/main.c b/example/main.c index 00ab410..851e946 100644 --- a/example/main.c +++ b/example/main.c @@ -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; } diff --git a/include/cli.h b/include/cli.h index b27d22e..a01c8fc 100644 --- a/include/cli.h +++ b/include/cli.h @@ -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 */ diff --git a/src/parse.c b/src/parse.c index dd4406c..1440099 100644 --- a/src/parse.c +++ b/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, 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);