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