From 45dff8f3f6c649bcf7af1ce2f4ae37901fe1253b Mon Sep 17 00:00:00 2001 From: a dinosaur Date: Mon, 13 May 2019 23:57:44 +1000 Subject: [PATCH] Give up on implementation specific getopt and just use a known competent one --- CMakeLists.txt | 5 +- inc/XGetopt.h | 23 -- inc/ultragetopt.h | 105 ++++++ src/tmx2gba.cpp | 20 +- src/ultragetopt.c | 826 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 934 insertions(+), 45 deletions(-) delete mode 100644 inc/XGetopt.h create mode 100644 inc/ultragetopt.h create mode 100644 src/ultragetopt.c diff --git a/CMakeLists.txt b/CMakeLists.txt index bd714db..4d0dfdd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ set(SOURCES src/tmxreader.cpp src/tmxreader.h src/tmxtileset.cpp src/tmxtileset.h + src/ultragetopt.c inc/ultragetopt.h src/base64.cpp inc/base64.h inc/miniz.h @@ -38,8 +39,4 @@ set(SOURCES inc/rapidxml/rapidxml_print.hpp inc/rapidxml/rapidxml_utils.hpp) -if (MSVC) - set(SOURCES ${SOURCES} src/XGetopt.cpp inc/XGetopt.h) -endif() - add_executable(tmx2gba ${SOURCES}) diff --git a/inc/XGetopt.h b/inc/XGetopt.h deleted file mode 100644 index 3a64486..0000000 --- a/inc/XGetopt.h +++ /dev/null @@ -1,23 +0,0 @@ -// XGetopt.h Version 1.2 -// -// Author: Hans Dietrich -// hdietrich2@hotmail.com -// -// This software is released into the public domain. -// You are free to use it in any way you like. -// -// This software is provided "as is" with no expressed -// or implied warranty. I accept no liability for any -// damage or loss of business that this software may cause. -// -/////////////////////////////////////////////////////////////////////////////// - -#ifndef XGETOPT_H -#define XGETOPT_H - -extern int optind; -extern char *optarg, *next; - -int getopt(int argc, char *argv[], char *optstring); - -#endif //XGETOPT_H diff --git a/inc/ultragetopt.h b/inc/ultragetopt.h new file mode 100644 index 0000000..2f8d359 --- /dev/null +++ b/inc/ultragetopt.h @@ -0,0 +1,105 @@ + +/* define ULTRAGETOPT_REPLACE_GETOPT for ultragetopt*() to replace getopt*() */ +/* define ULTRAGETOPT_ONLY_DOS for ultragetopt*_dos() to replace ultragetopt*() */ + +#ifndef INCLUDED_GETOPT_H +#define INCLUDED_GETOPT_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +#define ULTRAGETOPT_REPLACE_GETOPT +#define ULTRAGETOPT_LIKE_GNU +#define ULTRAGETOPT_LINKAGE extern + +/* Flag values to pass to getopt_tunable() */ +#define UGO_CASEINSENSITIVE 0x1 +#define UGO_SINGLELEADERLONG 0x2 +#define UGO_OPTIONPERMUTE 0x4 +#define UGO_NONOPTARG 0x8 +#define UGO_NOPRINTERR 0x10 +#define UGO_OPTIONALARG 0x20 +#define UGO_MISSINGCOLON 0x40 +#define UGO_SEPARATEDOPTIONAL 0x80 +#define UGO_SHORTOPTASSIGN 0x100 +#define UGO_NOEATDASHDASH 0x200 +#define UGO_LONGOPTADJACENT 0x400 +#define UGO_HYPHENARG 0x800 +#define UGO_SINGLELEADERONLY 0x1000 + +#ifndef required_argument +# define no_argument 0 +# define required_argument 1 +# define optional_argument 2 + +struct option { + const char *name; /* Name of the option */ + int has_arg; /* Does the option take an argument? */ + int *flag; /* Location to store val when option encountered */ + int val; /* Value to return (or store in flag) for option */ +}; +#endif /* required_argument */ + +ULTRAGETOPT_LINKAGE char *ultraoptarg; +ULTRAGETOPT_LINKAGE int ultraoptind, ultraopterr, ultraoptopt, ultraoptreset; + +ULTRAGETOPT_LINKAGE int ultragetopt(int argc, char *const argv[], + const char *optstring); + +ULTRAGETOPT_LINKAGE int ultragetopt_long(int argc, char *const argv[], + const char *shortopts, const struct option *longopts, int *indexptr); + +ULTRAGETOPT_LINKAGE int ultragetopt_long_only(int argc, char *const argv[], + const char *shortopts, const struct option *longopts, int *indexptr); + +ULTRAGETOPT_LINKAGE int ultragetopt_dos(int argc, char * const argv[], + const char *optstring); + +ULTRAGETOPT_LINKAGE int ultragetopt_long_dos(int argc, char *const argv[], + const char *shortopts, const struct option *longopts, int *indexptr); + +/* Getopt with modifiable (tunable) behavior - also the backend for all other + * getopt functions. + * assigners - string of characters accepted to assign to an option + * (e.g. --outfile=file.txt where '=' is the assigner) + * optleaders - string of characters that indicate an option + * (usually "-" on UNIX, "/" on DOS) + * flags - see README for list of accepted flags + */ +ULTRAGETOPT_LINKAGE int ultragetopt_tunable(int argc, char *const argv[], + const char *shortopts, const struct option *longopts, int *indexptr, + const char *assigners, const char *optleaders, int flags); + +#ifdef ULTRAGETOPT_REPLACE_GETOPT +# define optarg ultraoptarg +# define optind ultraoptind +# define opterr ultraopterr +# define optopt ultraoptopt +# define optreset ultraoptreset +# define getopt(argc, argv, optstring) \ + ultragetopt(argc, argv, optstring) +# define getopt_long(argc, argv, shortopts, longopts, indexptr) \ + ultragetopt_long(argc, argv, shortopts, longopts, indexptr) +# define getopt_long_only(argc, argv, shortopts, longopts, indexptr) \ + ultragetopt_long_only(argc, argv, shortopts, longopts, indexptr) +# define getopt_dos(argc, argv, optstring) \ + ultragetopt_dos(argc, argv, optstring) +# define getopt_long_dos(argc, argv, shortopts, longopts, indexptr) \ + ultragetopt_long_dos(argc, argv, shortopts, longopts, indexptr) +#endif /* GETOPT_NO_EXTENSIONS */ + +#ifdef ULTRAGETOPT_DOS_ONLY +# define ultragetopt(argc, argv, optstring) \ + ultragetopt_dos(argc, argv, optstring) +# define ultragetopt_long(argc, argv, shortopts, longopts, indexptr) \ + ultragetopt_long_dos(argc, argv, shortopts, longopts, indexptr) +# define ultragetopt_long_only(argc, argv, shortopts, longopts, indexptr) \ + ultragetopt_long_dos(argc, argv, shortopts, longopts, indexptr) +#endif /* ULTRAGETOPT_DOS_ONLY */ + +#ifdef __cplusplus +} +#endif + +#endif /* INCLUDED_GETOPT_H */ diff --git a/src/tmx2gba.cpp b/src/tmx2gba.cpp index 64f5cc4..7cfd86e 100644 --- a/src/tmx2gba.cpp +++ b/src/tmx2gba.cpp @@ -31,12 +31,8 @@ #include #include #include +#include -#ifdef _MSC_VER - #include -#else - #include -#endif using std::cout; using std::cerr; @@ -77,22 +73,10 @@ struct SParams bool objExport = false; }; -void GetoptClear () -{ -#ifdef _MSC_VER - optarg = nullptr; - optind = 0; - next = nullptr; -#else - optreset = 1; - optind = 1; -#endif -} - void ParseArgs ( int argc, char** argv, SParams* params ) { char cOption; - GetoptClear (); + optreset = 1; while ( ( cOption = (char)getopt ( argc, argv, "hr:l:c:p:y:m:i:o:f:" ) ) > 0 ) { switch ( cOption ) diff --git a/src/ultragetopt.c b/src/ultragetopt.c new file mode 100644 index 0000000..84bfade --- /dev/null +++ b/src/ultragetopt.c @@ -0,0 +1,826 @@ +/* Ultra-Getopt - A replacement for getopt() with support for many common + * extensions, MS-DOS formatted option strings, and much more. + * + * To use this library as a replacement for vendor-provided getopt() functions, + * define ULTRAGETOPT_REPLACE_GETOPT and include "ultragetopt.h" after the + * vendor-provided headers for getopt() functions. + * + * Copyright (C) 2007-2011, Kevin Locke + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include /* islower() isupper() tolower() toupper() */ +#include +#include /* fprintf() */ +#include /* getenv() */ +#include /* strcmp(), strncmp(), strchr() */ + +#if HAVE_STRINGS_H +# include /* strcasecmp(), strncasecmp() */ +#endif + +#undef ULTRAGETOPT_REPLACE_GETOPT /* Protect against project-wide defines */ +#include "ultragetopt.h" + +/* Define replacements for missing functions */ +#if !HAVE_STRCASECMP && HAVE__STRICMP +# define strcasecmp _stricmp +#endif + +#if !HAVE_STRNCASECMP && HAVE__STRNICMP +# define strncasecmp _strnicmp +#endif + +#if !HAVE_STRCHR && HAVE_INDEX +# define strchr index +#endif + +/* Supported defines: + * ULTRAGETOPT_LIKE_BSD Behave like BSD getopt() + * ULTRAGETOPT_LIKE_DARWIN Behave like Darwin (Mac OS) getopt() + * ULTRAGETOPT_LIKE_GNU Behave like GNU getopt() + * ULTRAGETOPT_LIKE_POSIX Behave like POSIX definition of getopt() + * + * ULTRAGETOPT_ASSIGNSPACE Parse "-o value" as "value" rather than " value" + * Note: Only applicable when argv[x] == "-o value" + * Not for argv[x] == "-o" [x+1] == "value" + * ULTRAGETOPT_DEFAULTOPTOPT Set optopt to this value by default on each + * call to getopt() + * ULTRAGETOPT_HYPHENARG Accept -option -arg as -option with argument + * "-arg" rather than -option missing argument + * Note: A single "-" is always accepted as an + * argument + * ULTRAGETOPT_LONGOPTADJACENT Accept adjacent arguments to long options + * (e.g. --optionarg) based on first longest-match + * ULTRAGETOPT_OPTIONPERMUTE Permute options, do not stop at first non-option + * Behaves like GNU getopt where leading '+' or + * $POSIXLY_CORRECT both stop this @ runtime + * ULTRAGETOPT_SHORTOPTASSIGN Support -o=file syntax for short options + * ULTRAGETOPT_SEPARATEDOPTIONAL Accept separated optional arguments + * Parse -o file as -o with argument file + * ULTRAGETOPT_DOS_DASH Support - and -- options in ultragetopt*_dos() + * ULTRAGETOPT_BSD_ERRORS Print error messages matching BSD getopt + * ULTRAGETOPT_DARWIN_ERRORS Print error messages matching Darwin getopt + * ULTRAGETOPT_GNU_ERRORS Print error messages matching GNU getopt + * ULTRAGETOPT_NO_EATDASHDASH Do not increment optind when argv[optind] is -- + * ULTRAGETOPT_NO_OPTIONALARG Do not support GNU "::" optional argument + * Always supported in *_long*() + * ULTRAGETOPT_NO_OPTIONASSIGN Do not support --option=value syntax + */ + +#ifdef ULTRAGETOPT_LIKE_POSIX +# define ULTRAGETOPT_NO_OPTIONALARG +# define ULTRAGETOPT_NO_OPTIONASSIGN +# undef ULTRAGETOPT_NO_EATDASHDASH +# undef ULTRAGETOPT_ASSIGNSPACE +# undef ULTRAGETOPT_BSD_ERRORS +# undef ULTRAGETOPT_DARWIN_ERRORS +# undef ULTRAGETOPT_GNU_ERRORS +# undef ULTRAGETOPT_OPTIONPERMUTE +# undef ULTRAGETOPT_SHORTOPTASSIGN +#elif defined(ULTRAGETOPT_LIKE_GNU) +# define ULTRAGETOPT_GNU_ERRORS +# define ULTRAGETOPT_HYPHENARG +# define ULTRAGETOPT_OPTIONPERMUTE +# undef ULTRAGETOPT_ASSIGNSPACE +# undef ULTRAGETOPT_NO_OPTIONALARG +# undef ULTRAGETOPT_NO_OPTIONASSIGN +# undef ULTRAGETOPT_NO_EATDASHDASH +# undef ULTRAGETOPT_SHORTOPTASSIGN +# undef ULTRAGETOPT_SEPARATEOPTIONAL +# undef ULTRAGETOPT_LONGOPTADJACENT +#elif defined(ULTRAGETOPT_LIKE_BSD) +# define ULTRAGETOPT_BSD_ERRORS +# define ULTRAGETOPT_OPTIONPERMUTE +# define ULTRAGETOPT_DEFAULTOPTOPT '?' +# undef ULTRAGETOPT_ASSIGNSPACE +# undef ULTRAGETOPT_NO_OPTIONALARG +# undef ULTRAGETOPT_NO_OPTIONASSIGN +# undef ULTRAGETOPT_NO_EATDASHDASH +# undef ULTRAGETOPT_SHORTOPTASSIGN +# undef ULTRAGETOPT_SEPARATEOPTIONAL +# undef ULTRAGETOPT_LONGOPTADJACENT +#elif defined(ULTRAGETOPT_LIKE_DARWIN) +# define ULTRAGETOPT_DARWIN_ERRORS +# define ULTRAGETOPT_OPTIONPERMUTE +# undef ULTRAGETOPT_ASSIGNSPACE +# undef ULTRAGETOPT_NO_OPTIONALARG +# undef ULTRAGETOPT_NO_OPTIONASSIGN +# undef ULTRAGETOPT_SHORTOPTASSIGN +# undef ULTRAGETOPT_NO_EATDASHDASH +# undef ULTRAGETOPT_SEPARATEOPTIONAL +# undef ULTRAGETOPT_LONGOPTADJACENT +#endif + +#ifdef ULTRAGETOPT_NO_OPTIONASSIGN +static const char *const unixassigners = ""; +static const char *const dosassigners = ":"; +#elif defined(ULTRAGETOPT_OPTIONSPACE) +static const char *const unixassigners = "= "; +static const char *const dosassigners = ":= "; +#else +static const char *const unixassigners = "="; +static const char *const dosassigners = ":="; +#endif + +#ifdef ULTRAGETOPT_DOS_DASH +static const char *const unixleaders = "-"; +static const char *const dosleaders = "/-"; +#else +static const char *const unixleaders = "-"; +static const char *const dosleaders = "/"; +#endif + +/* Flags for all variants of ultragetopt*() */ +static const int getoptflags = 0 +#ifdef ULTRAGETOPT_SEPARATEDOPTIONAL + | UGO_SEPARATEDOPTIONAL +#endif +#ifdef ULTRAGETOPT_SHORTOPTASSIGN + | UGO_SHORTOPTASSIGN +#endif +#ifdef ULTRAGETOPT_NO_EATDASHDASH + | UGO_NOEATDASHDASH +#endif +#ifdef ULTRAGETOPT_HYPHENARG + | UGO_HYPHENARG +#endif + ; + +#ifdef ULTRAGETOPT_GNU_ERRORS +static const char *const errorarg = + "%s: option `%.*s' doesn't allow an argument\n"; +static const char *const errornoarg = + "%s: option `%.*s' requires an argument \n"; +static const char *const erroropt = + "%s: unrecognized option `%.*s'\n"; +static const char *const errorargc = + "%s: option `-%c' does not take an argument\n"; +static const char *const errornoargc = + "%s: option requires an argument -- `-%c'\n"; +static const char *const erroroptc = + "%s: invalid option -- %c\n"; +#elif defined(ULTRAGETOPT_BSD_ERRORS) +static const char *const errorarg = + "%s: option doesn't take an argument -- %.*s\n"; +static const char *const errornoarg = + "%s: option requires an argument -- %.*s\n"; +static const char *const erroropt = + "%s: unknown option -- %.*s\n"; +static const char *const errorargc = + "%s: option doesn't take an argument -- %c\n"; +static const char *const errornoargc = + "%s: option requires an argument -- %c\n"; +static const char *const erroroptc = + "%s: unknown option -- %c\n"; +#elif defined(ULTRAGETOPT_DARWIN_ERRORS) +static const char *const errorarg = + "%s: option `%.*s' doesn't allow an argument\n"; /* with -- */ +static const char *const errornoarg = + "%s: option `%.*s' requires an argument\n"; +static const char *const erroropt = + "%s: unrecognized option `%.*s'\n"; /* with -- */ +static const char *const errorargc = + "%s: option doesn't take an argument -- %c\n"; +static const char *const errornoargc = + "%s: option requires an argument -- %c\n"; +static const char *const erroroptc = + "%s: invalid option -- %c\n"; +#else /* POSIX-like */ +static const char *const errorarg = + "%s: option does not take an argument -- %.*s\n"; +static const char *const errornoarg = + "%s: option requires an argument -- %.*s\n"; +static const char *const erroropt = + "%s: illegal option -- %.*s\n"; +static const char *const errorargc = + "%s: option does not take an argument -- %c\n"; +static const char *const errornoargc = + "%s: option requires an argument -- %c\n"; +static const char *const erroroptc = + "%s: illegal option -- %c\n"; +#endif + +/* Globals to match optarg, optind, opterr, optopt, optreset */ +char *ultraoptarg = NULL; +int ultraoptind = 1; +int ultraopterr = 1; +int ultraoptreset = 0; +#ifdef ULTRAGETOPT_DEFAULTOPTOPT +int ultraoptopt = ULTRAGETOPT_DEFAULTOPTOPT -0; +#else +int ultraoptopt = 0; +#endif + +static int ultraoptnum = 0; /* How many options of the current multi-option + argument have been processed? (e.g. -vvv) */ + +/* Add format error checking for gcc versions that support it */ +#if defined(__GNUC__) && __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR > 6) +static void print_error(int flags, const char *template, ...) + __attribute__ ((format (printf, 2, 3))); +#endif + +/* Print errors only if not suppressed */ +static void print_error(int flags, const char *template, ...) +{ + va_list ap; + + va_start(ap, template); + if (ultraopterr != 0 && !(flags & UGO_NOPRINTERR)) + vfprintf(stderr, template, ap); + va_end(ap); +} + +/* Check if an argument string looks like an option string */ +static inline int like_option(const char *arg, const char *optleaders) +{ + return arg != NULL && + arg[0] != '\0' && /* >= 2 characters long */ + arg[1] != '\0' && + strchr(optleaders, arg[0]) && /* Starts with optleader */ + (arg[2] != '\0' || arg[0] != arg[1]); /* Not -- */ +} + +/* Check if an argument string looks like the option terminator string */ +static inline int like_optterm(const char *arg, const char *optleaders) +{ + return arg != NULL && + arg[0] != '\0' && + arg[1] != '\0' && + arg[2] == '\0' && + arg[0] == arg[1] && + strchr(optleaders, arg[0]); +} + +/* Check if an argument string looks like an option argument string */ +static inline int like_optarg(const char *arg, const char *optleaders, + int allow_option) +{ + return arg != NULL && + (allow_option || + (!like_option(arg, optleaders) && !like_optterm(arg, optleaders))); +} + +/* If argv[curopt] matches a long option, return the index of that option + * Otherwise, return -1 + * If it has an adjacent argument, return pointer to it in longarg, else NULL + */ +static int match_longopt(int curopt, char *const argv[], + const struct option *longopts, const char *assigners, + const char *optleaders, int flags, char **longarg) +{ + size_t alen, optnamelen = 0; + char *optname; + char *temp; + int i; + int (*optncmp)(const char *s1, const char *s2, size_t n); + + if (longarg == NULL) + longarg = &temp; + *longarg = NULL; + + if (flags & UGO_CASEINSENSITIVE) + optncmp = strncasecmp; + else + optncmp = strncmp; + + if (longopts == NULL) + return -1; + + if (!like_option(argv[curopt], optleaders)) + return -1; + + if (flags & UGO_SINGLELEADERONLY) { + optname = argv[curopt]+1; + } else if (!strchr(optleaders, argv[curopt][1])) { + /* Possible short option */ + if (flags & UGO_SINGLELEADERLONG) + optname = argv[curopt]+1; + else + return -1; + } else { + optname = argv[curopt]+2; + } + + /* Do first longest-match search if requested */ + if (flags & UGO_LONGOPTADJACENT) { + size_t matchlen = 0; + int matchind = -1; + for (i=0; longopts[i].name != NULL; i++) { + size_t longnamelen = strlen(longopts[i].name); + if (longnamelen > matchlen + && optncmp(optname, longopts[i].name, longnamelen) == 0) { + matchind = i; + matchlen = longnamelen; + } + } + + if (matchlen > 0) { + /* See if our match has an adjacent argument */ + if (optname[matchlen] != '\0') { + /* Strip assigner character if present */ + if (strchr(assigners, optname[matchlen])) + *longarg = optname+matchlen+1; + else + *longarg = optname+matchlen; + } + + return matchind; + } + + return -1; + } + + /* Check for assigner in the option */ + alen = strlen(assigners); + for (i=0; (unsigned)i < alen; i++) { + char *asn = strchr(optname, assigners[i]); + if (asn != NULL) { + optnamelen = asn - optname; + *longarg = asn+1; + break; + } + } + + if (optnamelen == 0) + optnamelen = strlen(optname); + + for (i=0; longopts[i].name != NULL; i++) + if (optncmp(optname, longopts[i].name, optnamelen) == 0 + && strlen(longopts[i].name) == optnamelen) + return i; + + return -1; +} + +/* Check if an option has a separate argument (in the following argv[] index) */ +static int has_separate_argument(int curopt, int argc, char *const argv[], + const char *shortopts, + const struct option *longopts, + const char *assigners, const char *optleaders, + int flags) +{ + int longind; + char *longarg; + + assert(curopt < argc && like_option(argv[curopt], optleaders)); + + /* Check if we have a long option */ + longind = match_longopt(ultraoptind, argv, longopts, assigners, optleaders, + flags, &longarg); + if (longind >= 0) { + if (longopts[longind].has_arg == no_argument + || longarg != NULL + || (longopts[longind].has_arg == optional_argument + && !(flags & UGO_SEPARATEDOPTIONAL))) + return 0; + + return like_optarg(argv[curopt+1], optleaders, + (flags & UGO_HYPHENARG) && + longopts[longind].has_arg == required_argument); + } else if (!strchr(optleaders, argv[curopt][1])) { + /* Short option */ + char *optpos; + + optpos = strchr(shortopts, argv[curopt][1]); + if ((flags & UGO_CASEINSENSITIVE) && optpos == NULL) { + if (islower(argv[curopt][1])) + optpos = strchr(shortopts, toupper(argv[curopt][1])); + else + optpos = strchr(shortopts, tolower(argv[curopt][1])); + } + + + return optpos != NULL /* Option found */ + && optpos[1] == ':' /* Option takes argument */ + && (optpos[2] != ':' || (flags & UGO_SEPARATEDOPTIONAL)) + && argv[curopt][2] == '\0' /* Argument is not adjacent */ + && like_optarg(argv[curopt+1], /* Is an argument */ + optleaders, + (flags & UGO_HYPHENARG) && optpos[2] != ':'); + } + + /* No match */ + return 0; +} + +/* Bring the next option, or terminator, up to ultraoptind if there is one + * Returns number of words shifted forward + */ +static int permute_options(int argc, char *argv[], const char *shortopts, + const struct option *longopts, + const char *assigners, const char *optleaders, + int flags) +{ + int curopt = ultraoptind; + + /* If we already have an option or no more possible, give up */ + if (curopt >= argc || like_option(argv[curopt], optleaders)) + return 0; + + for ( ; curopt < argc && argv[curopt]; curopt++) { + int shiftarg = 0; + int i; + + /* Permute options and the option terminator */ + if (like_option(argv[curopt], optleaders)) { + /* Check if we need to shift argument too */ + shiftarg = has_separate_argument(curopt, argc, argv, shortopts, + longopts, assigners, optleaders, + flags); + } else if (!like_optterm(argv[curopt], optleaders)) { + continue; + } + + /* Shift option */ + for (i=curopt; i>ultraoptind; i--) { + char *temp = argv[i]; + argv[i] = argv[i-1]; + argv[i-1] = temp; + + if (shiftarg) { + temp = argv[i+1]; + argv[i+1] = argv[i]; + argv[i] = temp; + } + } + + /* All done */ + if (shiftarg) + return 2; + else + return 1; + } + + /* Couldn't find an option, bummer */ + return 0; +} + +/* Handle a longopts[longind] matches argv[ultraoptind] actions */ +static int handle_longopt(int longind, char *longarg, int noseparg, + char *const argv[], + const struct option *longopts, int *indexptr, + const char *optleaders, int flags) +{ + /* Handle assignment arguments */ + if (longarg && longopts[longind].has_arg == no_argument) { + print_error(flags, errorarg, argv[0], + longarg-argv[ultraoptind]-1, argv[ultraoptind]); + /* TODO: What is a good value to put in ultraoptopt? */ + /* Looks like GNU getopt() uses val */ + ultraoptopt = longopts[longind].val; + ultraoptind++; + return '?'; + } else if (longarg) { + ultraoptind++; + ultraoptarg = longarg; + + if (indexptr) + *indexptr = longind; + + if (longopts[longind].flag) { + *(longopts[longind].flag) = longopts[longind].val; + return 0; + } else + return longopts[longind].val; + } + + /* Handle missing required argument */ + if (longopts[longind].has_arg == required_argument + && (noseparg + || !like_optarg(argv[ultraoptind+1], + optleaders, + flags & UGO_HYPHENARG))) { + print_error(flags, errornoarg, argv[0], + strlen(argv[ultraoptind]), argv[ultraoptind]); + ultraoptind++; + if (flags & UGO_MISSINGCOLON) + return ':'; + else + return '?'; + } + + /* Handle available argument */ + if ((longopts[longind].has_arg == required_argument + || (longopts[longind].has_arg == optional_argument + && (flags & UGO_SEPARATEDOPTIONAL))) + && !noseparg + && like_optarg(argv[ultraoptind+1], + optleaders, + (flags & UGO_HYPHENARG) && + longopts[longind].has_arg == required_argument)) { + ultraoptarg = argv[ultraoptind+1]; + ultraoptind += 2; + } else + ultraoptind++; + + if (indexptr) + *indexptr = longind; + + if (longopts[longind].flag) { + *(longopts[longind].flag) = longopts[longind].val; + return 0; + } else + return longopts[longind].val; +} + +int ultragetopt_tunable(int argc, char *const argv[], const char *shortopts, + const struct option *longopts, int *indexptr, + const char *assigners, const char *optleaders, + int flags) +{ + char *opt; /* Option we are processing */ + char *optpos; /* Pointer to opt in shortopts */ + int noseparg = 0; /* Force option not to have a separate argument */ + + if (ultraoptreset) { + ultraoptind = 1; + ultraopterr = 1; + ultraoptnum = 0; + ultraoptreset = 0; + } + + ultraoptarg = NULL; +#ifdef ULTRAGETOPT_DEFAULTOPTOPT + ultraoptopt = ULTRAGETOPT_DEFAULTOPTOPT -0; +#endif + + /* Sanity check (These are specified verbatim in SUS) */ + if (ultraoptind > argc + || argv[ultraoptind] == NULL) + return -1; + + /* No permuting when $POSIXLY_CORRECT is set (to match GNU getopt) */ + if (getenv("POSIXLY_CORRECT")) + flags &= ~UGO_OPTIONPERMUTE; + + /* Get flags from shortopts */ + for ( ; shortopts && *shortopts; shortopts++) { + if (*shortopts == '+') + flags &= ~UGO_OPTIONPERMUTE; + else if (*shortopts == '-') + flags |= UGO_NONOPTARG; + else if (*shortopts == ':') { + flags |= UGO_NOPRINTERR; + flags |= UGO_MISSINGCOLON; + } else + break; + } + + /* Found non-option */ + if (!like_option(argv[ultraoptind], optleaders)) { + int shifted; + + if (like_optterm(argv[ultraoptind], optleaders)) { + if (!(flags & UGO_NOEATDASHDASH)) + ultraoptind++; + + return -1; + } + + if (flags & UGO_NONOPTARG) { + ultraoptarg = argv[ultraoptind]; + ultraoptind++; + return 1; + } + + if (!(flags & UGO_OPTIONPERMUTE)) + return -1; + + shifted = permute_options(argc, (char **)argv, shortopts, longopts, + assigners, optleaders, flags); + if (shifted == 0) + return -1; + else if (shifted == 1) + noseparg = 1; + + if (like_optterm(argv[ultraoptind], optleaders)) { + if (!(flags & UGO_NOEATDASHDASH)) + ultraoptind++; + + return -1; + } + } + + /* At this point we must have an option of some sort */ + assert(like_option(argv[ultraoptind], optleaders)); + + /* Handle --* */ + if (argv[ultraoptind][0] == argv[ultraoptind][1]) { + int longind; + char *longarg; + + /* Handle long option */ + longind = match_longopt(ultraoptind, argv, longopts, assigners, + optleaders, flags, &longarg); + if (longind < 0) { + if (longarg == NULL) + print_error(flags, erroropt, argv[0], + strlen(argv[ultraoptind]), argv[ultraoptind]); + else + print_error(flags, erroropt, argv[0], + longarg - argv[ultraoptind] - 1, argv[ultraoptind]); + + /* TODO: What is a good value for optopt in this case? */ + /* Looks like BSD uses 0 */ + ultraoptopt = 0; + ultraoptind++; + return '?'; + } + + return handle_longopt(longind, longarg, noseparg, argv, + longopts, indexptr, optleaders, flags); + } + + /* See if it matches a long-only option */ + if (longopts != NULL && + ultraoptnum == 0 && + ((flags & UGO_SINGLELEADERLONG) || + (flags & UGO_SINGLELEADERONLY))) { + int longind; + char *longarg; + + longind = match_longopt(ultraoptind, argv, longopts, assigners, + optleaders, flags, &longarg); + + if (longind >= 0) + return handle_longopt(longind, longarg, noseparg, argv, + longopts, indexptr, optleaders, flags); + } + + /* No long matches, process short option */ + opt = argv[ultraoptind] + ultraoptnum + 1; + optpos = strchr(shortopts, opt[0]); + if (optpos == NULL && (flags & UGO_CASEINSENSITIVE)) { + if (islower(opt[0])) + optpos = strchr(shortopts, toupper(opt[0])); + else + optpos = strchr(shortopts, tolower(opt[0])); + } + + /* This could indicate ultraoptnum not being reset properly */ + assert(opt[0] != '\0'); + + /* Check for invalid or unrecognized option */ + if (optpos == NULL || opt[0] == ':') { + print_error(flags, erroroptc, argv[0], opt[0]); + + ultraoptopt = opt[0]; + if (opt[1] != '\0') + ultraoptnum++; + else { + ultraoptnum = 0; + ultraoptind++; + } + + return '?'; + } + + /* Handle arguments */ + if (optpos[1] == ':') { + ultraoptnum = 0; + + /* Handle adjacent arguments -ofile.txt */ + if (opt[1] != '\0') { + /* Skip over assignment character */ + if ((flags & UGO_SHORTOPTASSIGN) && strchr(assigners, opt[1])) + ultraoptarg = opt + 2; + else + ultraoptarg = opt + 1; + + ultraoptind++; + return optpos[0]; + } + + /* Handle optional argument not present */ + if ((flags & UGO_OPTIONALARG) /* accept optionals */ + && optpos[2] == ':' /* opt takes optional */ + && (argv[ultraoptind+1] == NULL /* optional doesn't exist */ + || !(flags & UGO_SEPARATEDOPTIONAL) /* separated not accepted */ + || like_option(argv[ultraoptind+1], optleaders))) { + ultraoptind++; + return optpos[0]; + } + + /* Handle separated argument missing */ + if (ultraoptind+2 > argc + || noseparg + || !like_optarg(argv[ultraoptind+1], + optleaders, + (flags & UGO_HYPHENARG))) { + ultraoptind++; + print_error(flags, errornoargc, argv[0], opt[0]); + + ultraoptopt = opt[0]; + if (flags & UGO_MISSINGCOLON) + return ':'; + else + return '?'; + } + + ultraoptind += 2; + ultraoptarg = argv[ultraoptind-1]; + return optpos[0]; + } + + /* Handle argumentless option with assigned option */ + if ((flags & UGO_SHORTOPTASSIGN) + && opt[1] != '\0' && strchr(assigners, opt[1])) { + print_error(flags, errorargc, argv[0], opt[0]); + ultraoptnum = 0; + ultraoptopt = opt[0]; + ultraoptind++; + return '?'; + } + + if (opt[1] != '\0') { + ultraoptnum++; + } else { + ultraoptnum = 0; + ultraoptind++; + } + + return optpos[0]; +} + +/* POSIX-compliant getopt + * + * Handles optional argument '::' specifier as an extension for compatibility + * with glibc + */ +int ultragetopt(int argc, char * const argv[], const char *optstring) +{ + int flags = getoptflags; + +#ifdef ULTRAGETOPT_OPTIONPERMUTE + flags |= UGO_OPTIONPERMUTE; +#endif + +#ifndef ULTRAGETOPT_NO_OPTIONALARG + flags |= UGO_OPTIONALARG; +#endif + + return ultragetopt_tunable(argc, argv, optstring, NULL, NULL, + unixassigners, unixleaders, flags); +} + +/* GNU getopt_long workalike + * + * Argument reordering not yet implemented + * Leading + and - under consideration (behavior violates POSIX...) + */ +int ultragetopt_long(int argc, char *const argv[], const char *shortopts, + const struct option *longopts, int *indexptr) +{ + return ultragetopt_tunable(argc, argv, shortopts, longopts, indexptr, + unixassigners, unixleaders, + getoptflags | UGO_OPTIONPERMUTE | UGO_OPTIONALARG); +} + +/* GNU getopt_long_only workalike */ +int ultragetopt_long_only(int argc, char *const argv[], const char *shortopts, + const struct option *longopts, int *indexptr) +{ + return ultragetopt_tunable(argc, argv, shortopts, longopts, indexptr, + unixassigners, unixleaders, + getoptflags | UGO_SINGLELEADERLONG + | UGO_OPTIONPERMUTE | UGO_OPTIONALARG); +} + +int ultragetopt_dos(int argc, char * const argv[], const char *optstring) +{ + return ultragetopt_tunable(argc, argv, optstring, NULL, NULL, + dosassigners, dosleaders, + getoptflags | UGO_CASEINSENSITIVE); +} + +int ultragetopt_long_dos(int argc, char *const argv[], const char *shortopts, + const struct option *longopts, int *indexptr) +{ + return ultragetopt_tunable(argc, argv, shortopts, longopts, indexptr, + dosassigners, dosleaders, + getoptflags | UGO_CASEINSENSITIVE + | UGO_SINGLELEADERLONG | UGO_SINGLELEADERONLY + | UGO_OPTIONPERMUTE | UGO_OPTIONALARG); +} + +/* vim:set sts=4 sw=4: */