/* 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 && longopts[i].shortname < 127) { 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 */