view th_args.c @ 138:dab546dfb9b4

Oops, fix the new option handling options.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 25 Sep 2014 03:47:48 +0300
parents 0f43a94516f4
children 4ca0af6dbcf8
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 OPTH_BAILOUT flag for th_args_process()
is set.

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

If OPTH_BAILOUT is NOT set, 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 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,
    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
                    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,
    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++)
    {
        optarg_t *opt = &optList[i];
        if (opt->optLong)
        {
            optLen = strlen(opt->optLong);
            if (strncmp(currArg, opt->optLong, optLen) == 0)
                optN = i;
        }
    }

    // Get possible option argument, if needed
    if (optN >= 0)
    {
        if ((optList[optN].flags & OPT_ARGMASK) != 0)
            optArg = (currArg[optLen] == '=') ? &currArg[optLen + 1] : 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
        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 *), int flags)
{
    BOOL endOptions, optionsOK;
    int argIndex, newArgIndex;
    int handle = flags & OPTH_ONLY_MASK;

    // Parse arguments
    argIndex = 1;
    optionsOK = TRUE;
    endOptions = FALSE;
    while (argIndex < argc)
    {
        char *currArg = argv[argIndex];
        if (*currArg == '-' && !endOptions)
        {
            if (handle == OPTH_ONLY_OPTS || handle == 0)
            {
                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,
                                              argc, argv, optList,
                                              optListN, handleOpt))
                        optionsOK = FALSE;
                }
                else
                {
                    // Short options
                    if (!th_args_process_short(currArg, &newArgIndex,
                                               argc, argv, optList,
                                               optListN, handleOpt))
                        optionsOK = FALSE;
                }

                argIndex = newArgIndex;
            }
        }
        else
        if (handle == OPTH_ONLY_OTHER || handle == 0)
        {
            // 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 && (flags & OPTH_BAILOUT))
            return FALSE;

        argIndex++;
    }

    return optionsOK;
}


/* Print help for commandline arguments/options
 */
void th_args_help(FILE *outFile, optarg_t optList[], int optListN)
{
    int index;

    for (index = 0; index < optListN; index++)
    {
        optarg_t *opt = &optList[index];

        // Print short option
        if (opt->optShort != 0)
            fprintf(outFile, "  -%c,  ", opt->optShort);
        else
            fprintf(outFile, "       ");

        // Print long option
        if (opt->optLong)
        {
            char tmpStr[64], *p;

            if ((opt->flags & OPT_ARGMASK) == OPT_ARGREQ)
            {
                snprintf(tmpStr, sizeof(tmpStr), "%s=ARG",
                    opt->optLong);
                p = tmpStr;
            }
            else
                p = opt->optLong;

            fprintf(outFile, "--%-15s", p);
        }
        else
            fprintf(outFile, "                 ");

        fprintf(outFile, "  %s.\n", opt->desc);
    }
}