/*
 * parseopt.c  -  Parse command line options
 *
 * Copyright (C) 1998-2003 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: parseopt.c,v 1.5 2003/01/25 23:29:44 gkminix Exp $
 */

#include <common.h>
#include <nblib.h>
#include "privlib.h"
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif



/*
 * Default environment variable names
 */
#ifndef ENV_NETBOOT
#define ENV_NETBOOT	"NETBOOT"
#endif
#ifndef ENV_CONFIG
#define ENV_CONFIG	"NETBOOT_CONFIG"
#endif
#ifndef ENV_DATABASE
#define ENV_DATABASE    "NETBOOT_DATABASE"
#endif



/*
 * Layout of help screen. The option names start at FIRST_COLUMN and
 * the option help text starts at SECOND_COLUMN.
 */
#define FIRST_COLUMN	4
#define SECOND_COLUMN	35



/*
 * Layout of long option value
 */
#define LONG_FLAG	0x0100
#define LONG_MASK	0x00ff



/*
 * Global variables exported by this module
 */
char *progname = NULL;			/* name of program */
int verbose = FALSE;			/* verbosity flag */
int quiet = FALSE;			/* quiet flag */



/*
 * Forward declarations
 */
static void print_version();
static void print_help();



/*
 * Definition of command line options common to all programs
 */
static struct cmdopt common_opts[] = {
	{ "config-file", 'C', strval, {&configname}, ENV_CONFIG,
	  "name of configuration file", "FILE"				},
	{ "database-file", 'D', strval, {&dbname}, ENV_DATABASE,
	  "name of database file", "FILE"				},
	{ "netboot-dir", 'N', strval, {&netbootdir}, ENV_NETBOOT,
	  "netboot base directory", "DIR"				},
	{ "quiet", 'q', boolval, {(char **)&quiet}, NULL,
	  "suppress error messages", NULL				},
	{ "verbose", 'x', boolval, {(char **)&verbose}, NULL,
	  "increase verbosity level", NULL				},
	{ "version", 'v', procval, {(char **)&print_version}, NULL,
	  "print version number", NULL					},
	{ "help", 'h', procval, {(char **)&print_help}, NULL,
	  "print this help text", NULL					},
	{ NULL, 0, noval, {NULL}, NULL, NULL, NULL			}
};



/*
 * Print version information and quit
 */
static void print_version()
{
  fprintf(stderr, "%s " VERSION "\n", progname);
  exit(EXIT_SUCCESS);
}



/*
 * Print help text for one item
 */
static void print_item_help(pos, helptext)
int pos;
char *helptext;
{
  char *buf = NULL;
  char *cp;
  int i = pos;

  copystr(&buf, helptext);
  cp = strtok(buf, "\n");
  while (cp) {
	for ( ; i < SECOND_COLUMN; i++)
		putchar(' ');
	printf("%s\n", cp);
	cp = strtok(NULL, "\n");
	i = 0;
  }
}



/*
 * Print help screen and exit
 */
static void print_help(opts)
struct cmdopt *opts;
{
  struct cmdopt *curopt;
  int i, prarg = 0;

  /* First print the usage line with all non-option arguments */
  printf("\nUsage: %s [options]", progname);
  for (i = 0, curopt = opts; curopt->valtype != noval; curopt++)
	if (curopt->valtype == nonopt) {
		printf(" [<%s>", curopt->longopt);
		i++;
	}
  for (; i > 0; i--)
	printf("]");
  printf("\n\n");

  /* Now print all non-option argument help strings */
  for (curopt = opts; curopt->valtype != noval; curopt++)
	if (curopt->valtype == nonopt) {
		for (i = 0; i < FIRST_COLUMN; i++)
			putchar(' ');
		printf("<%s>", curopt->longopt);
		i += strlen(curopt->longopt) + 2;
		print_item_help(i, curopt->helptext);
		prarg++;
	}
  if (prarg > 0)
	printf("\n");

  /* Finally print all options */
  printf("Options:\n");
  for (curopt = opts; curopt->valtype != noval; curopt++) {
	if (curopt->valtype != nonopt) {
		for (i = 0; i < FIRST_COLUMN; i++)
			putchar(' ');
#ifdef HAVE_GETOPT_LONG
		if (curopt->shortopt == 0)
			printf("    --%s", curopt->longopt);
		else
			printf("-%c, --%s", curopt->shortopt, curopt->longopt);
		i += strlen(curopt->longopt) + 6;
		if (curopt->helparg != NULL) {
			printf("=%s", curopt->helparg);
			i += strlen(curopt->helparg) + 1;
		}
#else
		if (curopt->shortopt == 0)
			printf("  ");
		else
			printf("-%c", curopt->shortopt);
		i += 2;
		if (curopt->helparg != NULL) {
			printf(" %s", curopt->helparg);
			i += strlen(curopt->helparg) + 1;
		}
#endif
		print_item_help(i, curopt->helptext);
	}
  }
  printf("\n");
  exit(EXIT_USAGE);
}



/*
 * Compare two option descriptions for sorting
 */
static int cmpopt(entry1, entry2)
const voidstar entry1;
const voidstar entry2;
{
  struct cmdopt *opt1 = (struct cmdopt *)entry1;
  struct cmdopt *opt2 = (struct cmdopt *)entry2;

  if (toupper(opt1->shortopt) < toupper(opt2->shortopt))
	return(-1);
  else if (toupper(opt1->shortopt) > toupper(opt2->shortopt))
	return(1);
  else if (opt1->shortopt < opt2->shortopt)
	return(-1);
  else if (opt1->shortopt > opt2->shortopt)
	return(1);
  else
	return(0);
}



/*
 * Handle option parsing
 */
void parseopt(argc, argv, opts)
int argc;
char **argv;
struct cmdopt *opts;
{
  struct cmdopt *optbuf = NULL;
  struct cmdopt *curopt, *tmpopt;
#ifdef HAVE_GETOPT_LONG
  struct option *longbuf = NULL;
  struct option *curlong;
  int islong = FALSE;
#endif
  char *cp, *valenv, *shortbuf = NULL;
  int optnum, optchar;
  long intarg;

  /* Determine my own program name for error output */
  if ((cp = strrchr(argv[0], '/')) == NULL)
	progname = argv[0];
  else
	progname = ++cp;

  /* Determine the total number of options */
  optnum = sizeof(common_opts) / sizeof(struct cmdopt) - 1;
  if (opts != NULL)
	for (curopt = opts; curopt->valtype != noval; curopt++)
		optnum++;

  /*
   * Copy all options into one big buffer to make further handling easier.
   * First copy all named options into the buffer, sort it, and then copy
   * all non-option arguments. We have to preserve their order. Note that
   * there are no non-option arguments in the common option list.
   */
  tmpopt = optbuf = nbmalloc(sizeof(struct cmdopt) * (optnum + 1));
  optnum = 0;
  if (opts != NULL)
	for (curopt = opts; curopt->valtype != noval; ) {
		if (curopt->valtype != nonopt) {
			*tmpopt = *curopt;
			tmpopt++;
			optnum++;
		}
		curopt++;
	}
  for (curopt = common_opts; curopt->valtype != noval; ) {
	if (curopt->valtype != nonopt) {
		*tmpopt = *curopt;
		tmpopt++;
		optnum++;
	}
	curopt++;
  }
  qsort(optbuf, optnum, sizeof(struct cmdopt), &cmpopt);
  if (opts != NULL)
	for (curopt = opts; curopt->valtype != noval; ) {
		if (curopt->valtype == nonopt) {
			*tmpopt = *curopt;
			tmpopt++;
			optnum++;
		}
		curopt++;
	}
  *tmpopt = *curopt;		/* Copy end marker */

  /* Setup buffer for single letter options */
  shortbuf = nbmalloc((optnum + 1) * 2);
  for (cp = shortbuf, curopt = optbuf; curopt->valtype != noval; curopt++)
	if (curopt->valtype == intval || curopt->valtype == strval) {
		*cp++ = curopt->shortopt;
		*cp++ = ':';
	} else if (curopt->valtype == boolval || curopt->valtype == procval)
		*cp++ = curopt->shortopt;
  *cp = '\0';

  /* Setup buffer for long option names */
#ifdef HAVE_GETOPT_LONG
  longbuf = (struct option *)nbmalloc(sizeof(struct option) * (optnum + 1));
  for (curlong = longbuf, curopt = optbuf; curopt->valtype != noval; curopt++)
	if (curopt->valtype != nonopt) {
		curlong->name = curopt->longopt;
		if (curopt->valtype == intval || curopt->valtype == strval)
			curlong->has_arg = required_argument;
		else
			curlong->has_arg = no_argument;
		curlong->flag = NULL;
		curlong->val = (curopt->shortopt & LONG_MASK) | LONG_FLAG;
		curlong++;
	}
  curlong->name = NULL;
#endif

  /* Preset all string options with environment strings if required */
  for (curopt = optbuf; curopt->valtype != noval; curopt++)
	if (curopt->valtype == strval || curopt->valtype == nonopt) {
		valenv = NULL;
		if (curopt->envdefault != NULL)
			valenv = getenv(curopt->envdefault);
		cp = *(curopt->valptr.strptr);
		if (valenv != NULL)
			cp = valenv;
		if (cp != NULL) {
			*(curopt->valptr.strptr) = NULL;
			copystr(curopt->valptr.strptr, cp);
		}
	}

  /* Now parse all options using getopt */
  opterr = 0;
  while (
#ifdef HAVE_GETOPT_LONG
         (optchar = getopt_long(argc, argv, shortbuf, longbuf, NULL))
#else
         (optchar = getopt(argc, argv, shortbuf))
#endif
                                                  != EOF) {
#ifdef HAVE_GETOPT_LONG
	/* Check if we have a long option */
	islong = (optchar & LONG_FLAG) != 0;
	optchar &= LONG_MASK;
#endif
	/* Find option value in table */
	for (curopt = optbuf; curopt->valtype != noval; curopt++)
		if (optchar == curopt->shortopt)
			break;
	switch (curopt->valtype) {
		case boolval:
			(*(curopt->valptr.intptr))++;
			break;
		case intval:
			intarg = strtol(optarg, &cp, 0);
			if (*cp || intarg < INT_MIN || intarg > INT_MAX) {
				fprintf(stderr, "%s: invalid numerical argument to option ",
								progname);
#ifdef HAVE_GETOPT_LONG
				if (islong)
					fprintf(stderr, "\"--%s\"\n",
							curopt->longopt);
				else
#endif
					fprintf(stderr, "\"-%c\"\n",
							curopt->shortopt);
				exit(EXIT_USAGE);
			}
			*(curopt->valptr.intptr) = intarg;
			break;
		case longval:
			intarg = strtol(optarg, &cp, 0);
			if (*cp) {
				fprintf(stderr, "%s: invalid numerical argument to option ",
								progname);
#ifdef HAVE_GETOPT_LONG
				if (islong)
					fprintf(stderr, "\"--%s\"\n",
							curopt->longopt);
				else
#endif
					fprintf(stderr, "\"-%c\"\n",
							curopt->shortopt);
				exit(EXIT_USAGE);
			}
			*(curopt->valptr.longptr) = intarg;
			break;
		case strval:
			copystr(curopt->valptr.strptr, optarg);
			break;
		case procval:
			(curopt->valptr.procptr)(optbuf);
			break;
		case nonopt:
			break;
		default:
			fprintf(stderr, "%s: invalid option ", progname);
#ifdef HAVE_GETOPT_LONG
			if (!optopt) {
				if ((cp = strchr(argv[optind - 1], '=')) != NULL)
					*cp = '\0';
				fprintf(stderr, "\"%s\"\n", argv[optind - 1]);
			} else
#endif
				fprintf(stderr, "\"-%c\"\n", optopt);
			exit(EXIT_USAGE);
	}
  }

  /* Finally parse all non-option arguments */
  curopt = NULL;
  while (optind < argc) {
	/* Find next non-option argument in option buffer */
	if (curopt == NULL)
		curopt = optbuf;
	else
		curopt++;
	for (; curopt->valtype != noval; curopt++)
		if (curopt->valtype == nonopt)
			break;
	if (curopt->valtype != nonopt) {
		fprintf(stderr, "%s: invalid argument \"%s\"\n",
						progname, argv[optind]);
		exit(EXIT_USAGE);
	}
	copystr(curopt->valptr.strptr, argv[optind]);
	optind++;
  }

  /* Free all occupied memory space */
  if (optbuf != NULL)
	free(optbuf);
  if (shortbuf != NULL)
	free(shortbuf);
#ifdef HAVE_GETOPT_LONG
  if (longbuf != NULL)
	free(longbuf);
#endif
}



syntax highlighted by Code2HTML, v. 0.9.1