| /* Optparse --- portable, reentrant, embeddable, getopt-like option parser |
| * |
| * This is free and unencumbered software released into the public domain. |
| * |
| * To get the implementation, define OPTPARSE_IMPLEMENTATION. |
| * Optionally define OPTPARSE_API to control the API's visibility |
| * and/or linkage (static, __attribute__, __declspec). |
| * |
| * The POSIX getopt() option parser has three fatal flaws. These flaws |
| * are solved by Optparse. |
| * |
| * 1) Parser state is stored entirely in global variables, some of |
| * which are static and inaccessible. This means only one thread can |
| * use getopt(). It also means it's not possible to recursively parse |
| * nested sub-arguments while in the middle of argument parsing. |
| * Optparse fixes this by storing all state on a local struct. |
| * |
| * 2) The POSIX standard provides no way to properly reset the parser. |
| * This means for portable code that getopt() is only good for one |
| * run, over one argv with one option string. It also means subcommand |
| * options cannot be processed with getopt(). Most implementations |
| * provide a method to reset the parser, but it's not portable. |
| * Optparse provides an optparse_arg() function for stepping over |
| * subcommands and continuing parsing of options with another option |
| * string. The Optparse struct itself can be passed around to |
| * subcommand handlers for additional subcommand option parsing. A |
| * full reset can be achieved by with an additional optparse_init(). |
| * |
| * 3) Error messages are printed to stderr. This can be disabled with |
| * opterr, but the messages themselves are still inaccessible. |
| * Optparse solves this by writing an error message in its errmsg |
| * field. The downside to Optparse is that this error message will |
| * always be in English rather than the current locale. |
| * |
| * Optparse should be familiar with anyone accustomed to getopt(), and |
| * it could be a nearly drop-in replacement. The option string is the |
| * same and the fields have the same names as the getopt() global |
| * variables (optarg, optind, optopt). |
| * |
| * Optparse also supports GNU-style long options with optparse_long(). |
| * The interface is slightly different and simpler than getopt_long(). |
| * |
| * By default, argv is permuted as it is parsed, moving non-option |
| * arguments to the end. This can be disabled by setting the `permute` |
| * field to 0 after initialization. |
| */ |
| #ifndef OPTPARSE_H |
| #define OPTPARSE_H |
| |
| #ifndef OPTPARSE_API |
| # define OPTPARSE_API |
| #endif |
| |
| struct optparse { |
| char **argv; |
| int permute; |
| int optind; |
| int optopt; |
| char *optarg; |
| char errmsg[64]; |
| int subopt; |
| }; |
| |
| enum optparse_argtype { |
| OPTPARSE_NONE, |
| OPTPARSE_REQUIRED, |
| OPTPARSE_OPTIONAL |
| }; |
| |
| struct optparse_long { |
| const char *longname; |
| int shortname; |
| enum optparse_argtype argtype; |
| }; |
| |
| /** |
| * Initializes the parser state. |
| */ |
| OPTPARSE_API |
| void optparse_init(struct optparse *options, char **argv); |
| |
| /** |
| * Read the next option in the argv array. |
| * @param optstring a getopt()-formatted option string. |
| * @return the next option character, -1 for done, or '?' for error |
| * |
| * Just like getopt(), a character followed by no colons means no |
| * argument. One colon means the option has a required argument. Two |
| * colons means the option takes an optional argument. |
| */ |
| OPTPARSE_API |
| int optparse(struct optparse *options, const char *optstring); |
| |
| /** |
| * Handles GNU-style long options in addition to getopt() options. |
| * This works a lot like GNU's getopt_long(). The last option in |
| * longopts must be all zeros, marking the end of the array. The |
| * longindex argument may be NULL. |
| */ |
| OPTPARSE_API |
| int optparse_long(struct optparse *options, |
| const struct optparse_long *longopts, |
| int *longindex); |
| |
| /** |
| * Used for stepping over non-option arguments. |
| * @return the next non-option argument, or NULL for no more arguments |
| * |
| * Argument parsing can continue with optparse() after using this |
| * function. That would be used to parse the options for the |
| * subcommand returned by optparse_arg(). This function allows you to |
| * ignore the value of optind. |
| */ |
| OPTPARSE_API |
| char *optparse_arg(struct optparse *options); |
| |
| /* Implementation */ |
| #ifdef OPTPARSE_IMPLEMENTATION |
| |
| #define OPTPARSE_MSG_INVALID "invalid option" |
| #define OPTPARSE_MSG_MISSING "option requires an argument" |
| #define OPTPARSE_MSG_TOOMANY "option takes no arguments" |
| |
| static int |
| optparse_error(struct optparse *options, const char *msg, const char *data) |
| { |
| unsigned p = 0; |
| const char *sep = " -- '"; |
| while (*msg) |
| options->errmsg[p++] = *msg++; |
| while (*sep) |
| options->errmsg[p++] = *sep++; |
| while (p < sizeof(options->errmsg) - 2 && *data) |
| options->errmsg[p++] = *data++; |
| options->errmsg[p++] = '\''; |
| options->errmsg[p++] = '\0'; |
| return '?'; |
| } |
| |
| OPTPARSE_API |
| void |
| optparse_init(struct optparse *options, char **argv) |
| { |
| options->argv = argv; |
| options->permute = 1; |
| options->optind = 1; |
| options->subopt = 0; |
| options->optarg = 0; |
| options->errmsg[0] = '\0'; |
| } |
| |
| static int |
| optparse_is_dashdash(const char *arg) |
| { |
| return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0'; |
| } |
| |
| static int |
| optparse_is_shortopt(const char *arg) |
| { |
| return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0'; |
| } |
| |
| static int |
| optparse_is_longopt(const char *arg) |
| { |
| return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0'; |
| } |
| |
| static void |
| optparse_permute(struct optparse *options, int index) |
| { |
| char *nonoption = options->argv[index]; |
| int i; |
| for (i = index; i < options->optind - 1; i++) |
| options->argv[i] = options->argv[i + 1]; |
| options->argv[options->optind - 1] = nonoption; |
| } |
| |
| static int |
| optparse_argtype(const char *optstring, char c) |
| { |
| int count = OPTPARSE_NONE; |
| if (c == ':') |
| return -1; |
| for (; *optstring && c != *optstring; optstring++); |
| if (!*optstring) |
| return -1; |
| if (optstring[1] == ':') |
| count += optstring[2] == ':' ? 2 : 1; |
| return count; |
| } |
| |
| OPTPARSE_API |
| int |
| optparse(struct optparse *options, const char *optstring) |
| { |
| int type; |
| char *next; |
| char *option = options->argv[options->optind]; |
| options->errmsg[0] = '\0'; |
| options->optopt = 0; |
| options->optarg = 0; |
| if (option == 0) { |
| return -1; |
| } else if (optparse_is_dashdash(option)) { |
| options->optind++; /* consume "--" */ |
| return -1; |
| } else if (!optparse_is_shortopt(option)) { |
| if (options->permute) { |
| int index = options->optind++; |
| int r = optparse(options, optstring); |
| optparse_permute(options, index); |
| options->optind--; |
| return r; |
| } else { |
| return -1; |
| } |
| } |
| option += options->subopt + 1; |
| options->optopt = option[0]; |
| type = optparse_argtype(optstring, option[0]); |
| next = options->argv[options->optind + 1]; |
| switch (type) { |
| case -1: { |
| char str[2] = {0, 0}; |
| str[0] = option[0]; |
| options->optind++; |
| return optparse_error(options, OPTPARSE_MSG_INVALID, str); |
| } |
| case OPTPARSE_NONE: |
| if (option[1]) { |
| options->subopt++; |
| } else { |
| options->subopt = 0; |
| options->optind++; |
| } |
| return option[0]; |
| case OPTPARSE_REQUIRED: |
| options->subopt = 0; |
| options->optind++; |
| if (option[1]) { |
| options->optarg = option + 1; |
| } else if (next != 0) { |
| options->optarg = next; |
| options->optind++; |
| } else { |
| char str[2] = {0, 0}; |
| str[0] = option[0]; |
| options->optarg = 0; |
| return optparse_error(options, OPTPARSE_MSG_MISSING, str); |
| } |
| return option[0]; |
| case OPTPARSE_OPTIONAL: |
| options->subopt = 0; |
| options->optind++; |
| if (option[1]) |
| options->optarg = option + 1; |
| else |
| options->optarg = 0; |
| return option[0]; |
| } |
| return 0; |
| } |
| |
| OPTPARSE_API |
| char * |
| optparse_arg(struct optparse *options) |
| { |
| char *option = options->argv[options->optind]; |
| options->subopt = 0; |
| if (option != 0) |
| options->optind++; |
| return option; |
| } |
| |
| static int |
| optparse_longopts_end(const struct optparse_long *longopts, int i) |
| { |
| return !longopts[i].longname && !longopts[i].shortname; |
| } |
| |
| static void |
| optparse_from_long(const struct optparse_long *longopts, char *optstring) |
| { |
| char *p = optstring; |
| int i; |
| for (i = 0; !optparse_longopts_end(longopts, i); i++) { |
| if (longopts[i].shortname) { |
| int a; |
| *p++ = longopts[i].shortname; |
| for (a = 0; a < (int)longopts[i].argtype; a++) |
| *p++ = ':'; |
| } |
| } |
| *p = '\0'; |
| } |
| |
| /* Unlike strcmp(), handles options containing "=". */ |
| static int |
| optparse_longopts_match(const char *longname, const char *option) |
| { |
| const char *a = option, *n = longname; |
| if (longname == 0) |
| return 0; |
| for (; *a && *n && *a != '='; a++, n++) |
| if (*a != *n) |
| return 0; |
| return *n == '\0' && (*a == '\0' || *a == '='); |
| } |
| |
| /* Return the part after "=", or NULL. */ |
| static char * |
| optparse_longopts_arg(char *option) |
| { |
| for (; *option && *option != '='; option++); |
| if (*option == '=') |
| return option + 1; |
| else |
| return 0; |
| } |
| |
| static int |
| optparse_long_fallback(struct optparse *options, |
| const struct optparse_long *longopts, |
| int *longindex) |
| { |
| int result; |
| char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */ |
| optparse_from_long(longopts, optstring); |
| result = optparse(options, optstring); |
| if (longindex != 0) { |
| *longindex = -1; |
| if (result != -1) { |
| int i; |
| for (i = 0; !optparse_longopts_end(longopts, i); i++) |
| if (longopts[i].shortname == options->optopt) |
| *longindex = i; |
| } |
| } |
| return result; |
| } |
| |
| OPTPARSE_API |
| int |
| optparse_long(struct optparse *options, |
| const struct optparse_long *longopts, |
| int *longindex) |
| { |
| int i; |
| char *option = options->argv[options->optind]; |
| if (option == 0) { |
| return -1; |
| } else if (optparse_is_dashdash(option)) { |
| options->optind++; /* consume "--" */ |
| return -1; |
| } else if (optparse_is_shortopt(option)) { |
| return optparse_long_fallback(options, longopts, longindex); |
| } else if (!optparse_is_longopt(option)) { |
| if (options->permute) { |
| int index = options->optind++; |
| int r = optparse_long(options, longopts, longindex); |
| optparse_permute(options, index); |
| options->optind--; |
| return r; |
| } else { |
| return -1; |
| } |
| } |
| |
| /* Parse as long option. */ |
| options->errmsg[0] = '\0'; |
| options->optopt = 0; |
| options->optarg = 0; |
| option += 2; /* skip "--" */ |
| options->optind++; |
| for (i = 0; !optparse_longopts_end(longopts, i); i++) { |
| const char *name = longopts[i].longname; |
| if (optparse_longopts_match(name, option)) { |
| char *arg; |
| if (longindex) |
| *longindex = i; |
| options->optopt = longopts[i].shortname; |
| arg = optparse_longopts_arg(option); |
| if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) { |
| return optparse_error(options, OPTPARSE_MSG_TOOMANY, name); |
| } if (arg != 0) { |
| options->optarg = arg; |
| } else if (longopts[i].argtype == OPTPARSE_REQUIRED) { |
| options->optarg = options->argv[options->optind]; |
| if (options->optarg == 0) |
| return optparse_error(options, OPTPARSE_MSG_MISSING, name); |
| else |
| options->optind++; |
| } |
| return options->optopt; |
| } |
| } |
| return optparse_error(options, OPTPARSE_MSG_INVALID, option); |
| } |
| |
| #endif /* OPTPARSE_IMPLEMENTATION */ |
| #endif /* OPTPARSE_H */ |