/* mgetopt.c */

/* This is a reimplementation of the getopt() function, and related variables.
 * This version supports a putopt() function which allows an argument string
 * to be parsed in the middle of the real args; when the string has been
 * completely parsed, parsing of the real args is resumed.  It also has a
 * readopt() function for parsing a line from a file as an argument list.
 * Together, these can be used to implement macros of command-line options.
 */
#include <stdio.h>
#include <string.h>

char		*optarg;	/* pointer to arg string of current option */
int		optind;		/* public copy of stackind[0] */
static struct
{
	char	**argv;		/* array of arguments */
	int	ind;		/* index of current arg in this argv */
}		stack[20];	/* stack of opt index counters */
static int	sp;		/* stack pointer (index into stack[]) */


/* insert arguments into the argument list. */
void putopt(int firstopt, char **argv)
{
	stack[++sp].ind = firstopt - 1;
	stack[sp].argv = argv;
}


/* get the next option letter */
int getopt(int argc, char **argv, char *valid)
{
 static	char	*scan;
	char	*flag;

	stack[0].argv = argv;

	/* if at end of an argv string, then look at next string */
	if (!scan || !*scan)
	{
		/* find next arg. */
		stack[sp].ind++;
		optind = stack[0].ind;

		/* If we hit the end of a macro, pop back to previous level. */
		while (stack[sp].argv[stack[sp].ind] == NULL && sp > 0)
		{
			sp--;
			stack[sp].ind++;
			optind = stack[0].ind;
		}

		/* if no next arg, or next arg doesn't start with '-', we're done */
		if ((sp==0 ? optind==argc : stack[sp].argv==NULL)
		  || *stack[sp].argv[stack[sp].ind] != '-'
		  || !stack[sp].argv[stack[sp].ind][1])
		{
			if (sp > 0) goto MacroArg;
			return EOF;
		}

		/* if next arg is "--", we need to skip it and then we're done */
		if (!strcmp(stack[sp].argv[stack[sp].ind], "--"))
		{
			if (sp > 0) goto MacroArg;
			stack[sp].ind++;
			return EOF;
		}

		/* start scanning letters right after the leading '-' */
		scan = &stack[sp].argv[stack[sp].ind][1];
	}

	/* see whether this flag is valid */
	flag = strchr(valid, *scan++);
	if (!flag || *flag == ':')
	{
		fprintf(stderr, "%s: invalid option -%c\n", argv[0], scan[-1]);
		return '?';
	}

	/* if it requires an argument, then make sure there is one */
	if (flag[1] == ':')
	{
		if (*scan)
		{
			optarg = scan;
		}
		else if (sp == 0 ? optind + 1 < argc : stack[sp].argv[stack[sp].ind + 1] != NULL)
		{
			stack[sp].ind++;
			optarg = stack[sp].argv[stack[sp].ind];
			optind = stack[0].ind;
		}
		else
		{
			fprintf(stderr, "%s: -%c requires a parameter\n", argv[0], *flag);
			return '?';
		}
		scan = NULL;
	}

	return *flag;

MacroArg:
	fprintf(stderr, "%s: macro contains non-option \"%s\"\n", argv[0], stack[sp].argv[stack[sp].ind]);
	return '?';
}


/* This function reads a line from a given file pointer, and parses it into
 * words.  If it hits EOF before a newline, then it returns NULL; otherwise
 * it returns the address of a static argv[] array which is NULL terminated.
 */
char **readopt(FILE *fp)
{
	static char	*argv[20];  /* pointers to the starts of words */
	static char	args[500];  /* storage space for all words */
	char		*nextarg;   /* used for allocating space from args[] */
	int		argc;	    /* used for allocating from argv[] */
	int		ch;	    /* next character from the file */
	char		quote;	    /* quote state: nul, backslash, or quote */

	for (argv[0] = nextarg = args, argc = 0, quote = '\0';
	     (ch = getc(fp)) != EOF && ch != '\n';
	     )
	{
		/* most characters are added to the current word, but some are
		 * treated specially.
		 */
		if (quote == '\\')
		{
			*nextarg++ = ch;
			quote = '\0';
		}
		else if ((ch == '"' || ch == '\\') && quote == '\0')
		{
			quote = ch;
		}
		else if (ch == '"')
		{
			quote = '\0';
		}
		else if ((ch == ' ' || ch == '\t') && quote == '\0')
		{
			*nextarg++ = '\0';
			while ((ch = getc(fp)) == ' ' || ch == '\t')
			{
			}
			if (ch == EOF || ch == '\n')
				break;
			else
				ungetc(ch, fp);
			argv[++argc] = nextarg;
		}
		else
		{
			*nextarg++ = ch;
		}
	}

	/* if we hit EOF before a newline, then return NULL */
	if (ch == EOF)
	{
		return NULL;
	}

	/* mark the end of the last arg */
	*nextarg = '\0';

	/* mark the end of the argv list */
	argv[++argc] = NULL;

	/* return the argv list */
	return argv;
}


#ifdef TRY
int main(int argc, char **argv)
{
	char	*a = NULL;
	int	b = 0;
	int	opt;
	char	**macroargv;
	FILE	*fp;

	while ((opt = getopt(argc, argv, "a:bm:")) != EOF)
	{
		switch (opt)
		{
		  case 'a':
			a = optarg;
			break;

		  case 'b':
			b = 1;
			break;

		  case 'm':
			fp = fopen("fractile.mac", "r");
			if (!fp)
			{
				perror("factile.mac");
				break;
			}
			while ((macroargv = readopt(fp)) != NULL &&
				(!macroargv[0] || strcmp(macroargv[0], optarg)))
			{
			}
			if (macroargv == NULL)
			{
				fprintf(stderr, "%s: macro \"%s\" not found\n", argv[0], optarg);
			}
			else
			{
				putopt(1, macroargv);
			}
			fclose(fp);
			break;

		  default:
			fprintf(stderr, "usage: %s [-m macro] [-a value] [-b] [args]\n", argv[0]);
			exit(0);
		}
	}
	printf("a=%s, b=%d\n", a ? a : "NULL", b);
	for (; optind < argc; optind++)
	{
		printf("argv[%d]=%s\n", optind, argv[optind]);
	}
}
#endif /* TRY */
