view th_args.c @ 349:8662c07455be

Added tag dev-0_9_6 for changeset f3572f4f73d1
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 23 Jun 2011 01:37:43 +0300
parents 69aed051f84d
children 9ad157feb99a
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 int optListN = (sizeof(optList) / sizeof(optarg_t));


*/
#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 = strlen(optList[i].optLong);
        if (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 == NULL || (handleNonOption != NULL && !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");
}