/*
 * egetopt.c -- Extended 'getopt'.
 *
 * A while back, a public-domain version of getopt() was posted to the
 * net.  A bit later, a gentleman by the name of Keith Bostic made some
 * enhancements and reposted it.
 *
 * In recent weeks (i.e., early-to-mid 1988) there's been some
 * heated discussion in comp.lang.c about the merits and drawbacks
 * of getopt(), especially with regard to its handling of '?'.
 *
 * In light of this, I have taken Mr. Bostic's public-domain getopt()
 * and have made some changes that I hope will be considered to be
 * improvements.  I call this routine 'egetopt' ("Extended getopt").
 * The default behavior of this routine is the same as that of getopt(),
 * but it has some optional features that make it more useful.  These
 * options are controlled by the settings of some global variables.
 * By not setting any of these extra global variables, you will have
 * the same functionality as getopt(), which should satisfy those
 * purists who believe getopt() is perfect and can never be improved.
 * If, on the other hand, you are someone who isn't satisfied with the
 * status quo, egetopt() may very well give you the added capabilities
 * you want.
 *
 * Look at the enclosed README file for a description of egetopt()'s
 * new features.
 *
 * The code was originally posted to the net as getopt.c by ...
 *
 *	Keith Bostic
 *	ARPA: keith@seismo 
 *	UUCP: seismo!keith
 *
 * Current version: added enhancements and comments, reformatted code.
 *
 *	Lloyd Zusman
 *	Master Byte Software
 *	Los Gatos, California
 *	Internet:	ljz@fx.com
 *	UUCP:		...!ames!fxgrp!ljz
 *
 *    	May, 1988
 *
 * Modified for use in free JPEG code:
 *
 *	Ed Hanway
 *	UUCP:	uunet!sisd!jeh
 *
 *	October, 1991
 */

/* The original egetopt.c was written not to need stdio.h.
 * For the JPEG code this is an unnecessary and unportable assumption.
 * Also, we make all the variables and routines "static" to avoid
 * possible conflicts with a system-library version of getopt.
 *
 * In the JPEG code, this file is compiled by #including it in jcmain.c
 * or jdmain.c.  Since ANSI2KNR does not process include files, we can't
 * rely on it to convert function definitions to K&R style.  Hence we
 * provide both styles of function header with an explicit #ifdef PROTO (ick).
 */

#define GVAR static		/* make empty to export these variables */

/*
 * None of these constants are referenced in the executable portion of
 * the code ... their sole purpose is to initialize global variables.
 */
#define BADCH		(int)'?'
#define NEEDSEP		(int)':'
#define MAYBESEP	(int)'\0'
#define EMSG		""
#define START		"-"

/*
 * Here are all the pertinent global variables.
 */
GVAR int opterr = 1;		/* if true, output error message */
GVAR int optind = 1;		/* index into parent argv vector */
GVAR int optopt;		/* character checked for validity */
GVAR int optbad = BADCH;	/* character returned on error */
GVAR int optchar = 0;		/* character that begins returned option */
GVAR int optneed = NEEDSEP;	/* flag for mandatory argument */
GVAR int optmaybe = MAYBESEP;	/* flag for optional argument */
#if defined(__GNUC__) && defined(MSDOS)
GVAR char *optarg;        /* argument associated with option */
#else /* defined(__GNUC__) && defined(MSDOS) */
GVAR const char *optarg;	/* argument associated with option */
#endif /* defined(__GNUC__) && defined(MSDOS) */
GVAR const char *optstart = START; /* list of characters that start options */


/*
 * Macros.
 */

/*
 * Conditionally print out an error message and return (depends on the
 * setting of 'opterr').
 */
#define TELL(S)	{ \
	if (opterr) \
		fprintf(stderr, "%s%s%c\n", *nargv, (S), optopt); \
	return (optbad); \
}

/*
 * This works similarly to index() and strchr().  I include it so that you
 * don't need to be concerned as to which one your system has.
 */

#ifdef PROTO
LOCAL const char *
_sindex (const char *string, int ch)
#else
LOCAL const char *
_sindex (string, ch)
     const char *string;
     int ch;
#endif
{
	if (string != NULL) {
		for (; *string != '\0'; ++string) {
			if (*string == (char)ch) {
				return (string);
			}
		}
	}

	return (NULL);
}

/*
 * Here it is:
 */

#ifdef PROTO
LOCAL int
egetopt (int nargc, char **nargv, const char *ostr)
#else
LOCAL int
egetopt (nargc, nargv, ostr)
     int nargc;
     char **nargv;
     const char *ostr;
#endif
{
	static const char *place = EMSG; /* option letter processing */
	register const char *oli;	 /* option letter list index */
	register const char *osi = NULL; /* option start list index */

	if (nargv == (char **)NULL) {
		return (EOF);
	}

	if (nargc <= optind || nargv[optind] == NULL) {
		return (EOF);
	}

	if (place == NULL) {
		place = EMSG;
	}

	/*
	 * Update scanning pointer.
	 */
	if (*place == '\0') {
		place = nargv[optind];
		if (place == NULL) {
			return (EOF);
		}
		osi = _sindex(optstart, *place);
		if (osi != NULL) {
			optchar = (int)*osi;
		}
		if (optind >= nargc || osi == NULL || *++place == '\0') {
		    	return (EOF);
		}

		/*
		 * Two adjacent, identical flag characters were found.
		 * This takes care of "--", for example.
		 */
		if (*place == place[-1]) {
			++optind;
			return (EOF);
		}
	}

	/*
	 * If the option is a separator or the option isn't in the list,
	 * we've got an error.
	 */
	optopt = (int)*place++;
	oli = _sindex(ostr, optopt);
	if (optopt == optneed || optopt == optmaybe || oli == NULL) {
		/*
		 * If we're at the end of the current argument, bump the
		 * argument index.
		 */
		if (*place == '\0') {
			++optind;
		}
		TELL(": illegal option -- ");	/* byebye */
	}

	/*
	 * If there is no argument indicator, then we don't even try to
	 * return an argument.
	 */
	++oli;
	if (*oli == '\0' || (*oli != optneed && *oli != optmaybe)) {
		/*
		 * If we're at the end of the current argument, bump the
		 * argument index.
		 */
		if (*place == '\0') {
			++optind;
		}
		optarg = NULL;
	}
	/*
	 * If we're here, there's an argument indicator.  It's handled
	 * differently depending on whether it's a mandatory or an
	 * optional argument.
	 */
	else {
		/*
		 * If there's no white space, use the rest of the
		 * string as the argument.  In this case, it doesn't
		 * matter if the argument is mandatory or optional.
		 */
		if (*place != '\0') {
			optarg = place;
		}
		/*
		 * If we're here, there's whitespace after the option.
		 *
		 * Is it a mandatory argument?  If so, return the
		 * next command-line argument if there is one.
		 */
		else if (*oli == optneed) {
			/*
			 * If we're at the end of the argument list, there
			 * isn't an argument and hence we have an error.
			 * Otherwise, make 'optarg' point to the argument.
			 */
			if (nargc <= ++optind) {
				place = EMSG;
				TELL(": option requires an argument -- ");
			}
			else {
				optarg = nargv[optind];
			}
		}
		/*
		 * If we're here it must have been an optional argument.
		 */
		else {
			if (nargc <= ++optind) {
				place = EMSG;
				optarg = NULL;
			}
			else {
				optarg = nargv[optind];
				if (optarg == NULL) {
					place = EMSG;
				}
				/*
				 * If the next item begins with a flag
				 * character, we treat it like a new
				 * argument.  This is accomplished by
				 * decrementing 'optind' and returning
				 * a null argument.
				 */
				else if (_sindex(optstart, *optarg) != NULL) {
					--optind;
					optarg = NULL;
				}
			}
		}
		place = EMSG;
		++optind;
	}

	/*
	 * Return option letter.
	 */
	return (optopt);
}
