view th_args.c @ 2:ecfa4e3597e3

Cleanups in th-libs.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 20 Mar 2008 01:06:03 +0000
parents 728243125263
children 707e35b03f89
line wrap: on
line source

/*
 * Simple commandline argument processing
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2002-2008 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */
/*
Description
===========
Structures and functions for simple commandline argument processing,
option argument handling (short and long forms supported).

Output function for printing out short on-line help for said options
and program commandline usage.


Format for typical commandline:

$ program -a -b -c --long-d -e argument-for-e --long-f="argument for f"

where -a, -b and -c are short options without required arguments;
--long-d is a long option without argument; -e is short option with
argument and finally --long-f is long option with argument.


Introduction
============
Handling of commandline options in th_args_process() has various
options, which effect how an option is handled. Also the error
situations (unknown option/no required argument) can be handled
in different ways.

Typically th_args_process() is called as follows:

BOOL optionHandlerCallback(int optionNumber, char *optionArgument, char *optionName)
{
  if (option is OK)
    return TRUE;
  else
    return FALSE;
}

BOOL nonoptionHandlerCallback(char *argumentString)
{
  // return value same as in optionHandlerCallback
}


int main(int argc, char *argv[])
{
  if (th_args_process(argc, argv, optList, optListN,
      optionHandlerCallback, nonoptionHandlerCallback)) {
    ... arguments OK ...
  } else {
    ... arguments invalid or required arguments missing ...
  }
}


NOTICE!
-------
The return value from handler callbacks affects the return value of
th_args_process(). Additionally, a failure in callback (returns FALSE)
effects the argument processing if bailOut argument of th_args_process()
is TRUE!

If bailOut is TRUE, any error/failure in argument processing (including
callbacks) immediately stops the argument processing and FALSE is
returned from th_args_process().

If bailOut is FALSE, most errors are "ignored", but FALSE is still returned
if any errors occured.


NOTICE #2!
----------
A small temporary buffer of N*sizeof(BOOL) (where N is number of
options in optList[]) is allocated for processing required options.
If this allocation fails, the program is immediately exited with
code 128.


Examples
========
Example of different options, in a fictional optionlist struct:

optarg_t optList[] = {
  // Option without arguments
  { 0, '?', "help",      "Show this help",                    OPT_NONE },

  // Option with a required argument
  { 1, 'o', "output",    "Output file name",                  OPT_ARGREQ },
  
  // Option with optional argument
  { 2, 'f', "foobar",    "Use foobar, with optional string",  OPT_ARGOPT },

  // This option is required to be given, though without other flags
  // it may not make much sense.
  { 4, 'S', "stupid",     "You must give this option",        OPT_REQUIRED },

  // The flags can be combined with OR operator: this option is both
  // required to be specified, and also requires argument (the filename)
  { 5, 'i', "input",     "Input file name",                   OPT_REQUIRED | OPT_ARGREQ },


  // Option with only long form
  { 0, 0,   "only-long", "Long option",                       OPT_NONE },

  // Option with only short form
  { 0, 's', NULL,        "Short option",                      OPT_NONE },

};

const DINT optListN = (sizeof(optList) / sizeof(t_opt));


*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "th_util.h"
#include "th_args.h"
#include "th_string.h"


/* Check if option requires an argument
 */
static BOOL th_args_check_arg(optarg_t *o, char *optArg)
{
	if ((o->optFlags & OPT_ARGMASK) == OPT_ARGREQ && optArg == NULL) {
		if (o->optShort != 0 && o->optLong != NULL) {
			THERR("Option '-%c ARG' (--%s=ARG) requires an argument!\n",
				o->optShort, o->optLong);
		} else if (o->optShort != 0) {
			THERR("Option '-%c ARG' requires an argument!\n", o->optShort);
		} else if (o->optLong != NULL) {
			THERR("Option --%s=ARG requires an argument!\n", o->optLong);
		}
		
		return FALSE;
	} else
		return TRUE;

}


/* Handle short options
 */
static BOOL th_args_process_short(char *currArg, int *newArgIndex,
	BOOL *wasGiven, int argc, char *argv[],
	optarg_t optList[], int optListN,
	BOOL (*handleOpt)(int, char *, char *))
{
	char *tmpArg = currArg, *optArg;
	int optN;
	BOOL isFound;
	
	/* Short options can be combined: -a -b -c == -abc */
	while (*tmpArg) {
		
		for (optN = 0, isFound = FALSE; (optN < optListN) && !isFound; optN++)
		if (*tmpArg == optList[optN].optShort) {
			/* Get possible option argument, if needed */
			if ((optList[optN].optFlags & OPT_ARGMASK) != 0 && (++(*newArgIndex) < argc))
				optArg = argv[*newArgIndex];
			else
				optArg = NULL;
			
			/* Check if option argument is required */
			if (!th_args_check_arg(&optList[optN], optArg))
				return FALSE;			
			else {
				char tmpStr[2] = { 0, 0 };

				/* Option was given succesfully, try to handle it */
				wasGiven[optN] = TRUE;
				
				tmpStr[0] = *tmpArg;
				
				if (!handleOpt(optList[optN].optID, optArg, tmpStr))
					return FALSE;
			}
							
			isFound = TRUE;
		}
		
		if (!isFound) {
			THERR("Unknown short option '%c' in argument '-%s'\n",
				*tmpArg, currArg);
			return FALSE;
		}

		tmpArg++;
	}

	return TRUE;
}


/* Handle long options
 */
static BOOL th_args_process_long(char *currArg, int *newArgIndex,
	BOOL *wasGiven, int argc, char *argv[],
	optarg_t optList[], int optListN,
	BOOL (*handleOpt)(int, char *, char *))
{
	int optN, optLen, i;
	char *optArg;
	
	(void) argc; (void) argv; (void) newArgIndex;
	
	/* Long option */
	for (optN = -1, optLen = i = 0; (i < optListN) && (optN < 0); i++)
	if (optList[i].optLong) {
		optLen = th_strlen(optList[i].optLong);
		if (th_strncmp(currArg, optList[i].optLong, optLen) == 0)
			optN = i;
	}
	
	/* Get possible option argument, if needed */
	if (optN >= 0) {
		if ((optList[optN].optFlags & OPT_ARGMASK) != 0) {
			if (currArg[optLen] == '=')
				optArg = &currArg[optLen + 1];
			else
				optArg = NULL;
		} else
			optArg = NULL;

		/* Check if option argument is required */
		if (!th_args_check_arg(&optList[optN], optArg))
			return FALSE;			
		else {
			/* Option was given succesfully, try to handle it */
			wasGiven[optN] = TRUE;
			if (!handleOpt(optList[optN].optID, optArg, currArg))
				return FALSE;
		}
	} else {
		THERR("Unknown long option '--%s'\n", currArg);
		return FALSE;
	}

	return TRUE;
}


/* Process arguments, handling short and long options by
 * calling the given callback functions.
 */
BOOL th_args_process(int argc, char *argv[],
		     optarg_t optList[], int optListN,
		     BOOL (*handleOpt) (int, char *, char *),
		     BOOL (*handleNonOption) (char *), BOOL bailOut)
{
	BOOL endOptions, optionsOK;
	int argIndex, newArgIndex, i;
	char *currArg;
	BOOL *wasGiven;

	/* Allocate wasGiven */
	wasGiven = (BOOL *) th_calloc(optListN, sizeof(BOOL));
	if (!wasGiven) {
		THERR("FATAL ERROR! Could not allocate wasGiven in th_args_process()!\n");
		exit(128);
	}

	/* Parse arguments */
	argIndex = 1;
	optionsOK = TRUE;
	endOptions = FALSE;
	while (argIndex < argc) {
		currArg = argv[argIndex];
		if ((currArg[0] == '-') && !endOptions) {
			newArgIndex = argIndex;
			currArg++;
			if (*currArg == '-') {
				/* Check for "--", which ends the options-list */
				currArg++;
				if (*currArg == 0) {
					endOptions = TRUE;
					continue;
				}
				
				/* Long options */
				if (!th_args_process_long(currArg, &newArgIndex,
					wasGiven, argc, argv, optList, optListN,
					handleOpt))
					optionsOK = FALSE;
			} else {
				/* Short options */
				if (!th_args_process_short(currArg, &newArgIndex,
					wasGiven, argc, argv, optList, optListN,
					handleOpt))
					optionsOK = FALSE;
			}

			argIndex = newArgIndex;
		} else {
			/* Was not option argument */
			if (!handleNonOption || (handleNonOption && !handleNonOption(currArg))) {
				THERR("Invalid argument '%s'\n", currArg);
				optionsOK = FALSE;
			}
		}
		
		/* Check if we bail out on invalid argument */
		if (!optionsOK && bailOut) {
			th_free(wasGiven);
			return FALSE;
		}
		
		argIndex++;
	}

	/* Check wasGiven by isRequired */
	for (i = 0; i < optListN; i++)
	if ((optList[i].optFlags & OPT_REQUIRED) != 0 && !wasGiven[i]) {
		THERR("Option -%s (--%s) is required.\n",
			optList[i].optShort, optList[i].optLong);

		optionsOK = FALSE;
		if (bailOut) break;
	}
	
	th_free(wasGiven);
	return optionsOK;
}


/* Print help for commandline arguments/options
 */
void th_args_help(FILE * outFile,
	optarg_t optList[], int optListN,
	char * progName, char * progUsage)
{
	int i, nrequired;

	fprintf(outFile,
		"\n%s v%s (%s)\n"
		"%s\n"
		"%s\n"
		"Usage: %s %s\n",
		th_prog_name, th_prog_version, th_prog_fullname,
		th_prog_author, th_prog_license, progName, progUsage);


	for (i = nrequired = 0; i < optListN; i++) {
		optarg_t *o = &optList[i];
		
		/* Print short option */
		if (o->optShort != 0)
			fprintf(outFile, "  -%c,  ", o->optShort);
		else
			fprintf(outFile, "       ");
		
		/* Print long option */
		if (o->optLong) {
			char tmpStr[64], *p;
			
			if ((o->optFlags & OPT_ARGMASK) == OPT_ARGOPT) {
				snprintf(tmpStr, sizeof(tmpStr), "%s[=ARG]", optList[i].optLong);
				p = tmpStr;
			} else if ((o->optFlags & OPT_ARGMASK) == OPT_ARGREQ) {
				snprintf(tmpStr, sizeof(tmpStr), "%s=ARG", optList[i].optLong);
				p = tmpStr;
			} else
				p = o->optLong;
			
			fprintf(outFile, "--%-15s", p);
		} else
			fprintf(outFile, "                 ");

		fprintf(outFile, "  %s.", optList[i].optDesc);
		
		if (o->optFlags & OPT_REQUIRED) {
			fprintf(outFile, " [*]\n");
			nrequired++;
		} else
			fprintf(outFile, "\n");
	}
	
	if (nrequired > 0)
		fprintf(outFile, "(Options marked with [*] are required)\n");
}