diff --git a/optparse.h b/optparse.h index 3a577a7..bf76335 100644 --- a/optparse.h +++ b/optparse.h @@ -44,20 +44,28 @@ * arguments to the end. This can be disabled by setting the `permute` * field to 0 after initialization. */ + +#ifndef OPTPARSE_IMPLEMENTATION + #ifndef OPTPARSE_H #define OPTPARSE_H +#ifndef _WIN32 +#define TCHAR char +#define _TEXT(x) x +#endif + #ifndef OPTPARSE_API # define OPTPARSE_API #endif struct optparse { - char **argv; + TCHAR **argv; int permute; int optind; int optopt; - char *optarg; - char errmsg[64]; + TCHAR *optarg; + TCHAR errmsg[64]; int subopt; }; @@ -68,7 +76,7 @@ enum optparse_argtype { }; struct optparse_long { - const char *longname; + const TCHAR *longname; int shortname; enum optparse_argtype argtype; }; @@ -77,7 +85,7 @@ struct optparse_long { * Initializes the parser state. */ OPTPARSE_API -void optparse_init(struct optparse *options, char **argv); +void optparse_init(struct optparse *options, TCHAR **argv); /** * Read the next option in the argv array. @@ -89,7 +97,7 @@ void optparse_init(struct optparse *options, char **argv); * colons means the option takes an optional argument. */ OPTPARSE_API -int optparse(struct optparse *options, const char *optstring); +int optparse(struct optparse *options, const TCHAR *optstring); /** * Handles GNU-style long options in addition to getopt() options. @@ -102,6 +110,14 @@ int optparse_long(struct optparse *options, const struct optparse_long *longopts, int *longindex); +/** + * Works a lot like GNU's getopt_long_only(). +*/ +OPTPARSE_API +int optparse_long_only(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 @@ -112,65 +128,68 @@ int optparse_long(struct optparse *options, * ignore the value of optind. */ OPTPARSE_API -char *optparse_arg(struct optparse *options); +TCHAR *optparse_arg(struct optparse *options); + +#endif /* OPTPARSE_H */ +#else /* OPTPARSE_IMPLEMENTATION */ /* 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" +#define OPTPARSE_MSG_INVALID _TEXT("invalid option") +#define OPTPARSE_MSG_MISSING _TEXT("option requires an argument") +#define OPTPARSE_MSG_TOOMANY _TEXT("option takes no arguments") +#define OPTPARSE_MSG_AMBIGUITY _TEXT("option is ambiguous") static int -optparse_error(struct optparse *options, const char *msg, const char *data) +optparse_error(struct optparse *options, const TCHAR *msg, const TCHAR *data) { unsigned p = 0; - const char *sep = " -- '"; + const TCHAR *sep = _TEXT(" -- '"); 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'; + options->errmsg[p++] = _TEXT('\''); + options->errmsg[p++] = _TEXT('\0'); return '?'; } OPTPARSE_API void -optparse_init(struct optparse *options, char **argv) +optparse_init(struct optparse *options, TCHAR **argv) { options->argv = argv; options->permute = 1; options->optind = 1; options->subopt = 0; options->optarg = 0; - options->errmsg[0] = '\0'; + options->errmsg[0] = _TEXT('\0'); } static int -optparse_is_dashdash(const char *arg) +optparse_is_dashdash(const TCHAR *arg) { - return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0'; + return arg != 0 && arg[0] == _TEXT('-') && arg[1] == _TEXT('-') && arg[2] == _TEXT('\0') ; } static int -optparse_is_shortopt(const char *arg) +optparse_is_shortopt(const TCHAR *arg) { - return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0'; + return arg != 0 && arg[0] == _TEXT('-') && arg[1] != _TEXT('-') && arg[1] != _TEXT('\0') ; } static int -optparse_is_longopt(const char *arg) +optparse_is_longopt(const TCHAR *arg) { - return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0'; + return arg != 0 && arg[0] == _TEXT('-') && arg[1] == _TEXT('-') && arg[2] != _TEXT('\0'); } static void optparse_permute(struct optparse *options, int index) { - char *nonoption = options->argv[index]; + TCHAR *nonoption = options->argv[index]; int i; for (i = index; i < options->optind - 1; i++) options->argv[i] = options->argv[i + 1]; @@ -178,7 +197,7 @@ optparse_permute(struct optparse *options, int index) } static int -optparse_argtype(const char *optstring, char c) +optparse_argtype(const TCHAR *optstring, TCHAR c) { int count = OPTPARSE_NONE; if (c == ':') @@ -193,12 +212,12 @@ optparse_argtype(const char *optstring, char c) OPTPARSE_API int -optparse(struct optparse *options, const char *optstring) +optparse(struct optparse *options, const TCHAR *optstring) { int type; - char *next; - char *option = options->argv[options->optind]; - options->errmsg[0] = '\0'; + TCHAR *next; + TCHAR *option = options->argv[options->optind]; + options->errmsg[0] = _TEXT('\0'); options->optopt = 0; options->optarg = 0; if (option == 0) { @@ -223,7 +242,7 @@ optparse(struct optparse *options, const char *optstring) next = options->argv[options->optind + 1]; switch (type) { case -1: { - char str[2] = {0, 0}; + TCHAR str[2] = {0, 0}; str[0] = option[0]; options->optind++; return optparse_error(options, OPTPARSE_MSG_INVALID, str); @@ -245,7 +264,7 @@ optparse(struct optparse *options, const char *optstring) options->optarg = next; options->optind++; } else { - char str[2] = {0, 0}; + TCHAR str[2] = {0, 0}; str[0] = option[0]; options->optarg = 0; return optparse_error(options, OPTPARSE_MSG_MISSING, str); @@ -264,10 +283,10 @@ optparse(struct optparse *options, const char *optstring) } OPTPARSE_API -char * +TCHAR * optparse_arg(struct optparse *options) { - char *option = options->argv[options->optind]; + TCHAR *option = options->argv[options->optind]; options->subopt = 0; if (option != 0) options->optind++; @@ -281,37 +300,46 @@ optparse_longopts_end(const struct optparse_long *longopts, int i) } static void -optparse_from_long(const struct optparse_long *longopts, char *optstring) +optparse_from_long(const struct optparse_long *longopts, TCHAR *optstring) { - char *p = optstring; + TCHAR *p = optstring; int i; for (i = 0; !optparse_longopts_end(longopts, i); i++) { if (longopts[i].shortname) { int a; - *p++ = longopts[i].shortname; + *p++ = (TCHAR)longopts[i].shortname; for (a = 0; a < (int)longopts[i].argtype; a++) - *p++ = ':'; + *p++ = _TEXT(':'); } } - *p = '\0'; + *p = _TEXT('\0'); } /* Unlike strcmp(), handles options containing "=". */ +/* is_exact_match is true if LCP(longname, option) == option */ static int -optparse_longopts_match(const char *longname, const char *option) +optparse_longopts_match(const TCHAR *longname, const TCHAR *option, int *is_exact_match) { - const char *a = option, *n = longname; + *is_exact_match = 0; + const TCHAR *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 == '='); + if (*n == '\0' && (*a == '\0' || *a == '=')) { + *is_exact_match = 1; + return 1; + } if (*a == '\0' || *a == '=') { + // longname can be longer. + return 1; + } + return 0; } /* Return the part after "=", or NULL. */ -static char * -optparse_longopts_arg(char *option) +static TCHAR * +optparse_longopts_arg(TCHAR *option) { for (; *option && *option != '='; option++); if (*option == '=') @@ -326,7 +354,7 @@ optparse_long_fallback(struct optparse *options, int *longindex) { int result; - char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */ + TCHAR optstring[96 * 3 + 1]; /* 96 ASCII printable characters */ optparse_from_long(longopts, optstring); result = optparse(options, optstring); if (longindex != 0) { @@ -341,14 +369,85 @@ optparse_long_fallback(struct optparse *options, return result; } +static int +parse_long_option(TCHAR *option, + struct optparse *options, + const struct optparse_long *longopts, + int *longindex, + int is_long_only) +{ + int i; + int matched_options_count = 0; + int last_matched_longopt_index = -1; + const TCHAR *name = NULL; + const TCHAR *last_matched_name = NULL; + + options->errmsg[0] = _TEXT('\0'); + options->optopt = 0; + options->optarg = 0; + if (is_long_only && !optparse_is_longopt(option)) { + option += 1; /* skip "-" */ + } + else { + option += 2; /* skip "--" */ + } + options->optind++; + + for (i = 0; !optparse_longopts_end(longopts, i); i++) { + int is_exact_match = 0; + name = longopts[i].longname; + if (!optparse_longopts_match(name, option, &is_exact_match)) { + continue; + } + if (is_exact_match) { + // exact match + last_matched_name = name; + last_matched_longopt_index = i; + matched_options_count = 1; + break; + } + else { + // nonexact match + last_matched_name = name; + last_matched_longopt_index = i; + ++matched_options_count; + } + } + + if (matched_options_count > 1) { + return optparse_error(options, OPTPARSE_MSG_AMBIGUITY, name); + } if (matched_options_count) { + i = last_matched_longopt_index; + name = last_matched_name; + TCHAR *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); +} + OPTPARSE_API int optparse_long(struct optparse *options, const struct optparse_long *longopts, int *longindex) { - int i; - char *option = options->argv[options->optind]; + TCHAR *option = options->argv[options->optind]; if (option == 0) { return -1; } else if (optparse_is_dashdash(option)) { @@ -368,36 +467,25 @@ optparse_long(struct optparse *options, } } - /* 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 parse_long_option(option, options, longopts, longindex, 0); +} + +OPTPARSE_API +int +optparse_long_only(struct optparse *options, + const struct optparse_long *longopts, + int *longindex) +{ + TCHAR *option = options->argv[options->optind]; + if (option == 0) { + return -1; } - return optparse_error(options, OPTPARSE_MSG_INVALID, option); + else if (optparse_is_dashdash(option)) { + options->optind++; /* consume "--" */ + return -1; + } + + return parse_long_option(option, options, longopts, longindex, 1); } #endif /* OPTPARSE_IMPLEMENTATION */ -#endif /* OPTPARSE_H */