408 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			408 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* Nicked from third-party https://github.com/skeeto/optparse 2018-09-24 */
 | |
| /* µWebSockets is not the origin of this software file */
 | |
| /* ------------------------------------------------------ */
 | |
| 
 | |
| /* 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 */
 |