view dmargs_int.c @ 96:6bf5220fa47e

Urgh .. use memset to silence some bogus GCC warnings about using potentially uninitialized values, while that will not actually be possible. In any case, it is annoying.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 02 Oct 2012 18:52:28 +0300
parents 32250b436bca
children 4d6769bd8cfb
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));


*/
#ifndef TH_EXTERNAL
#include "th_util.h"
#include "th_args.h"
#include "th_string.h"
#endif


/* Check if option requires an argument
 */
static BOOL th_args_check_arg(const optarg_t *o, const char *optArg)
{
    if ((o->flags & 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].flags & 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].id, 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].flags & 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].id, 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].flags & 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)
{
    int i, nrequired;

    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->flags & OPT_ARGMASK) == OPT_ARGOPT)
            {
                snprintf(tmpStr, sizeof(tmpStr), "%s[=ARG]",
                         optList[i].optLong);
                p = tmpStr;
            }
            else if ((o->flags & 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].desc);

        if (o->flags & OPT_REQUIRED)
        {
            fprintf(outFile, " [*]\n");
            nrequired++;
        }
        else
            fprintf(outFile, "\n");
    }

    if (nrequired > 0)
        fprintf(outFile, "(Options marked with [*] are required)\n");
}