view src/dmargs.c @ 2426:bb7264ddaefd

Fix the check for --long option argument.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 21 Jan 2020 07:54:54 +0200
parents 85b6d7ce8ca5
children b7f622d39efc
line wrap: on
line source

/*
 * Simple commandline argument processing
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2002-2020 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */
/// @file
/// @brief Simple commandline argument processing functions
#include "dmargs.h"


/**
 * Parse and optionally handle the given long or short option argument.
 * @param currArg current argument string
 * @param argIndex pointer to index of current argument in argv[]
 * @param argc number of arguments
 * @param argv argument string array
 * @param opts options list array
 * @param nopts number of elements in options list array
 * @param handle_option function pointer to callback that handles option arguments
 * @param process if TRUE, actually handle the argument, aka call the handle_option() function. if FALSE, only validity of options are checked.
 * @param isLong TRUE if the option is a --long-format one
 */
static BOOL dmArgsProcessOpt(
    char *currArg, int *argIndex,
    int argc, char *argv[],
    const DMOptArg opts[], int nopts,
    BOOL (*handle_option)(int id, char *, char *),
    BOOL process, BOOL isLong)
{
    const DMOptArg *opt = NULL;
    char *optArg = NULL;

    for (int optIndex = 0; optIndex < nopts; optIndex++)
    {
        const DMOptArg *node = &opts[optIndex];
        if (isLong && node->o_long != NULL)
        {
            if (strcmp(currArg, node->o_long) == 0)
            {
                opt = node;
                optArg = NULL;
                break;
            }

            size_t len = strlen(node->o_long);
            if (strncmp(currArg, node->o_long, len) == 0 &&
                currArg[len] == '=')
            {
                opt = node;
                optArg = (currArg[len+1] != 0) ? &currArg[len+1] : NULL;
                break;
            }
        }
        else
        if (!isLong && node->o_short != 0)
        {
            if (*currArg == node->o_short)
            {
                opt = node;
                optArg = (currArg[1] != 0) ? &currArg[1] : NULL;
            }
        }
    }

    if (opt != NULL)
    {
        // Check for the possible option argument
        if ((opt->flags & OPT_ARGMASK) == OPT_ARGREQ && optArg == NULL)
        {
            if (*argIndex < argc)
            {
                (*argIndex)++;
                optArg = argv[*argIndex];
            }

            if (optArg == NULL)
            {
                dmErrorMsg("Option '%s%s' requires an argument.\n",
                    isLong ? "--" : "-",
                    currArg);
                return FALSE;
            }
        }

        // Option was given succesfully, try to process it
        if (process && !handle_option(opt->id, optArg, currArg))
            return FALSE;
    }
    else
    {
        dmErrorMsg("Unknown %s option '%s%s'\n",
            isLong ? "long" : "short",
            isLong ? "--" : "-",
            currArg);

        return FALSE;
    }

    return TRUE;
}


/**
 * Process given array of commandline arguments, handling short
 * and long options by calling the respective callback functions.
 *
 * @param argc number of arguments
 * @param argv argument list
 * @param opts supported option list array
 * @param nopts number of elements in the option list array
 * @param handle_option callback function
 * @param handle_other callback function
 * @param flags processing flags
 * @return return TRUE if all is well
 */
BOOL dmArgsProcess(int argc, char *argv[],
     const DMOptArg *opts, const int nopts,
     BOOL(*handle_option)(int id, char *, char *),
     BOOL(*handle_other)(char *), const int flags)
{
    int handleFlags = flags & OPTH_ONLY_MASK;
    BOOL optionsOK = TRUE, endOfOptions = FALSE;

    for (int argIndex = 1; argIndex < argc; argIndex++)
    {
        char *str = argv[argIndex];
        if (*str == '-' && !endOfOptions)
        {
            // Should we process options?
            BOOL process = (handleFlags & OPTH_ONLY_OPTS) || handleFlags == 0;
            BOOL isLong;

            str++;
            if (*str == '-')
            {
                // Check for "--", which ends the options-list
                str++;
                if (*str == 0)
                {
                    endOfOptions = TRUE;
                    continue;
                }

                // We have a long option
                isLong = TRUE;
            }
            else
                isLong = FALSE;

            if (!dmArgsProcessOpt(str, &argIndex, argc, argv,
                opts, nopts, handle_option, process, isLong))
                optionsOK = FALSE;
        }
        else
        if (handleFlags == OPTH_ONLY_OTHER || handleFlags == 0)
        {
            // Was not option argument
            if (handle_other == NULL ||
                (handle_other != NULL && !handle_other(str)))
            {
                dmErrorMsg("Invalid argument '%s'\n", str);
                optionsOK = FALSE;
            }
        }

        // Check if we bail out on invalid argument
        if (!optionsOK && (flags & OPTH_BAILOUT))
            return FALSE;
    }

    return optionsOK;
}


void dmPrintPad(FILE *fh, int count, const char och)
{
    while (count--)
        fputc(och, fh);
}


/**
 * Print given string indented in such a way that it is automatically
 * line-wrapped as necessary, taking hard linefeeds into account as well.
 * @param fh stdio file handle to output to
 * @param spad starting pad/indent of the first line
 * @param rpad how much to pad the other lines
 * @param width total line width to wrap at
 * @param str string to output
 */
void dmPrintWrap(FILE *fh, const int spad, int const rpad,
    const int width, const char *str)
{
    size_t pos = 0;
    BOOL first = TRUE;

    while (str[pos])
    {
        // Pre-pad line
        int linelen = first ? spad : rpad;
        dmPrintPad(fh, first ? 0 : rpad, ' ');
        first = FALSE;

        // Skip whitespace at line start
        while (isspace(str[pos]) || str[pos] == '\n')
            pos++;

        // Handle each word
        while (str[pos] && str[pos] != '\n')
        {
            size_t next;
            int wlen;

            // Find word length and next break
            for (wlen = 0, next = pos;
                str[next] && !isspace(str[next]) &&
                str[next] != '\n';
                next++, wlen++);

            // Check if we have too much of text?
            if (linelen + wlen > width)
                break;

            // Print what we have
            for (;pos < next; pos++, linelen++)
                fputc(str[pos], fh);

            // Check if we are at end of input or hard linefeed
            if (str[next] == '\n' || str[next] == 0)
                break;
            else
            {
                fputc(str[pos], fh);
                pos++;
                linelen++;
            }
        }
        fprintf(fh, "\n");
    }
}


static inline const char *dmArgsGetOptArg(const DMOptArg *opt)
{
#ifdef DM_USE_OPT_ARG
    return opt->o_arg != NULL ? opt->o_arg : "ARG";
#else
    (void) opt;
    return "ARG";
#endif
}


static void dmArgsPrintHelpPrintItem(FILE *fh, const DMOptArg *opt,
    int *optWidth, const int maxOptWidth, const int termWidth,
    const BOOL doPrint)
{
    const char *arg = dmArgsGetOptArg(opt);
    char fmtBuf[32];
    int padWidth;
    BOOL hasLongOpt = opt->o_long != NULL;

    if (opt->o_short != 0)
    {
        if (!hasLongOpt && (opt->flags & OPT_ARGREQ))
        {
            snprintf(fmtBuf, sizeof(fmtBuf), " -%c <%s>",
                opt->o_short, arg);
        }
        else
        {
            snprintf(fmtBuf, sizeof(fmtBuf), " -%c,",
                opt->o_short);
        }

        *optWidth = strlen(fmtBuf);
        if (doPrint)
            padWidth = hasLongOpt ? 2 : maxOptWidth - *optWidth;
        else
            padWidth = 2;
    }
    else
    {
        fmtBuf[0] = 0;
        *optWidth = 0;
        padWidth = 4 + 2;
    }

    if (doPrint)
    {
        fputs(fmtBuf, fh);
        dmPrintPad(fh, padWidth, ' ');
    }
    *optWidth += padWidth;

    if (hasLongOpt)
    {
        if (opt->flags & OPT_ARGREQ)
        {
            snprintf(fmtBuf, sizeof(fmtBuf), "--%s=<%s>",
                opt->o_long, arg);
        }
        else
        {
            snprintf(fmtBuf, sizeof(fmtBuf), "--%s",
                opt->o_long);
        }

        *optWidth += strlen(fmtBuf);
    }
    else
        fmtBuf[0] = 0;

    if (doPrint)
    {
        padWidth = hasLongOpt ? maxOptWidth - *optWidth : 0;
        *optWidth += padWidth;

        fputs(fmtBuf, fh);
        dmPrintPad(fh, padWidth, ' ');
        dmPrintWrap(fh, *optWidth, *optWidth, termWidth, opt->desc);
    }
}


/**
 * Print help for commandline arguments/options
 * @param fh stdio file handle to output to
 * @param opts options list array
 * @param nopts number of elements in options list array
 * @param flags flags (currently unused)
 * @param width width of the terminal or desired width of the print out
 */
void dmArgsPrintHelp(FILE *fh, const DMOptArg *opts,
    const int nopts, const int flags, const int width)
{
    int index, maxOptWidth;
    (void) flags;

    // Determine width of the options and arguments
    maxOptWidth = 0;
    for (index = 0; index < nopts; index++)
    {
        int optWidth = 0;
        dmArgsPrintHelpPrintItem(NULL, &opts[index], &optWidth, 0, width, FALSE);
        if (optWidth > maxOptWidth)
            maxOptWidth = optWidth;
    }

    maxOptWidth += 2;

    // Print out the formatted option list
    for (index = 0; index < nopts; index++)
    {
        int optWidth;
        dmArgsPrintHelpPrintItem(fh, &opts[index], &optWidth, maxOptWidth, width, TRUE);
    }
}