view th_args.c @ 322:b9c15c57dc8f

Clean up message functions, add new printMsgQ() helper function for messages that should not go into the log file. Add skeleton help function, accessible via F1 key. And other cleanups.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 11 Jun 2011 09:48:26 +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");
}