changeset 142:0a4fd9cfb929

Revise the argument handling API. Breaks compatibility.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 22 Nov 2014 23:37:44 +0200
parents 3d555015ada4
children c878cdcfea9d 51eec969b07a
files th_args.c th_args.h
diffstat 2 files changed, 174 insertions(+), 276 deletions(-) [+]
line wrap: on
line diff
--- a/th_args.c	Mon Oct 13 06:36:15 2014 +0300
+++ b/th_args.c	Sat Nov 22 23:37:44 2014 +0200
@@ -1,109 +1,10 @@
 /*
  * Simple commandline argument processing
  * Programmed and designed by Matti 'ccr' Hamalainen
- * (C) Copyright 2002-2008 Tecnic Software productions (TNSP)
+ * (C) Copyright 2002-2014 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"
@@ -111,134 +12,81 @@
 #endif
 
 
-/* Check if option requires an argument
+/* Parse long and short options
  */
-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,
+static BOOL th_args_process_opt(
+    char *currArg, int *argIndex,
     int argc, char *argv[],
-    optarg_t optList[], int optListN,
-    BOOL (*handleOpt)(int, char *, char *), BOOL process)
+    const th_optarg_t opts[], int numOpts,
+    BOOL (*handleOptionCB)(int, char *, char *),
+    BOOL doProcess, BOOL isLong)
 {
-    char *tmpArg = currArg, *optArg;
-    int optN;
-    BOOL found;
-
-    // Short options can be combined: -a -b -c == -abc
-    while (*tmpArg)
-    {
-        for (optN = 0, found = FALSE; optN < optListN && !found; 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;
+    const th_optarg_t *opt = NULL;
+    char *optArg = NULL;
+    int optIndex;
 
-                // Check if option argument is required
-                if (!th_args_check_arg(&optList[optN], optArg))
-                    return FALSE;
-                else
-                if (process)
-                {
-                    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;
-                }
-
-                found = TRUE;
+    for (optIndex = 0; optIndex < numOpts; optIndex++)
+    {
+        const th_optarg_t *node = &opts[optIndex];
+        if (isLong && node->optLong != NULL)
+        {
+            if (strcmp(currArg, node->optLong) == 0)
+            {
+                opt = node;
+                optArg = NULL;
+                break;
             }
 
-        if (!found)
-        {
-            THERR("Unknown short option '%c' in argument '-%s'\n",
-                  *tmpArg, currArg);
-            return FALSE;
+            size_t len = strlen(node->optLong);
+            if (strncmp(currArg, node->optLong, len) == 0 &&
+                currArg[len] == '=')
+            {
+                opt = node;
+                optArg = (&currArg[len+1] != 0) ? &currArg[len+1] : NULL;
+                break;
+            }
         }
-
-        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 *), BOOL process)
-{
-    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)
+        else
+        if (!isLong && node->optShort != 0)
         {
-            optLen = strlen(opt->optLong);
-            if (strncmp(currArg, opt->optLong, optLen) == 0)
-                optN = i;
+            if (*currArg == node->optShort)
+            {
+                opt = node;
+                optArg = (currArg[1] != 0) ? &currArg[1] : NULL;
+            }
         }
     }
 
-    // Get possible option argument, if needed
-    if (optN >= 0)
+    if (opt != NULL)
     {
-        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 (process && !handleOpt(optList[optN].id, optArg, currArg))
+        // Check for the possible option argument
+        if ((opt->flags & OPT_ARGMASK) == OPT_ARGREQ && optArg == NULL)
+        {
+            if (*argIndex < argc)
+            {
+                (*argIndex)++;
+                optArg = argv[*argIndex];
+            }
+            else
+            {
+                THERR("Option '%s%s' requires an argument.\n",
+                    isLong ? "--" : "-",
+                    currArg);
+                return FALSE;
+            }
+        }
+        
+        // Option was given succesfully, try to process it
+        if (doProcess && !handleOptionCB(opt->id, optArg, currArg))
             return FALSE;
     }
     else
     {
-        THERR("Unknown long option '--%s'\n", currArg);
+        THERR("Unknown %s option '%s%s'\n",
+            isLong ? "long" : "short",
+            isLong ? "--" : "-",
+            currArg);
+
         return FALSE;
     }
 
@@ -250,58 +98,51 @@
  * 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)
+     const th_optarg_t *opts, const int numOpts,
+     BOOL(*handleOptionCB) (int, char *, char *),
+     BOOL(*handleOther) (char *), const int flags)
 {
-    int handle = flags & OPTH_ONLY_MASK,
-        argIndex = 1;
-    BOOL optionsOK = TRUE,
-        endOptions = FALSE;
+    int argIndex, handleFlags = flags & OPTH_ONLY_MASK;
+    BOOL optionsOK = TRUE, endOfOptions = FALSE;
 
-    while (argIndex < argc)
+    for (argIndex = 1; argIndex < argc; argIndex++)
     {
-        char *currArg = argv[argIndex];
-        if (*currArg == '-' && !endOptions)
+        char *str = argv[argIndex];
+        if (*str == '-' && !endOfOptions)
         {
-            BOOL process = (handle & OPTH_ONLY_OPTS) || handle == 0;
-            int newArgIndex = argIndex;
-            currArg++;
-            if (*currArg == '-')
+            // Should we process options?
+            BOOL doProcess = (handleFlags & OPTH_ONLY_OPTS) || handleFlags == 0;
+            BOOL isLong;
+
+            str++;
+            if (*str == '-')
             {
                 // Check for "--", which ends the options-list
-                currArg++;
-                if (*currArg == 0)
+                str++;
+                if (*str == 0)
                 {
-                    endOptions = TRUE;
+                    endOfOptions = TRUE;
                     continue;
                 }
 
-                // Long options
-                if (!th_args_process_long(currArg, &newArgIndex,
-                                          argc, argv, optList,
-                                          optListN, handleOpt, process))
-                    optionsOK = FALSE;
+                // We have a long option
+                isLong = TRUE;
             }
             else
-            {
-                // Short options
-                if (!th_args_process_short(currArg, &newArgIndex,
-                                           argc, argv, optList,
-                                           optListN, handleOpt, process))
-                    optionsOK = FALSE;
-            }
+                isLong = FALSE;
 
-            argIndex = newArgIndex;
+            if (!th_args_process_opt(str, &argIndex, argc, argv,
+                opts, numOpts, handleOptionCB, doProcess, isLong))
+                optionsOK = FALSE;
         }
         else
-        if (handle == OPTH_ONLY_OTHER || handle == 0)
+        if (handleFlags == OPTH_ONLY_OTHER || handleFlags == 0)
         {
             // Was not option argument
-            if (handleNonOption == NULL
-                || (handleNonOption != NULL && !handleNonOption(currArg)))
+            if (handleOther == NULL ||
+                (handleOther != NULL && !handleOther(str)))
             {
-                THERR("Invalid argument '%s'\n", currArg);
+                THERR("Invalid argument '%s'\n", str);
                 optionsOK = FALSE;
             }
         }
@@ -309,8 +150,6 @@
         // Check if we bail out on invalid argument
         if (!optionsOK && (flags & OPTH_BAILOUT))
             return FALSE;
-
-        argIndex++;
     }
 
     return optionsOK;
@@ -319,39 +158,98 @@
 
 /* Print help for commandline arguments/options
  */
-void th_args_help(FILE *outFile, optarg_t optList[], int optListN)
+static void th_pad(FILE *outFile, int count)
+{
+    while (count--)
+        fputc(' ', outFile);
+}
+
+
+static void th_print_wrap(FILE *fh, const char *str, int spad, int rpad, int width)
+{
+    size_t pos = 0;
+    BOOL first = TRUE;
+
+    while (str[pos])
+    {
+        // Pre-pad line
+        int linelen = first ? spad : rpad;
+        th_pad(fh, first ? 0 : rpad);
+        first = FALSE;
+
+        // Skip whitespace at line start
+        while (th_isspace(str[pos]) || str[pos] == '\n') pos++;
+        
+        // Handle each word
+        while (str[pos] && str[pos] != '\n')
+        {
+            size_t next;
+            int wlen;
+            for (wlen = 0, next = pos; str[next] && !th_isspace(str[next]) && str[next] != '\n'; next++, wlen++);
+//            fprintf(stdout, "X '%c', %d .. linelen=%d/%d, wlen=%d\n", str[pos], pos, linelen, width, wlen);
+            if (linelen + wlen < width)
+            {
+                for (;pos < next; pos++, linelen++)
+                    fputc(str[pos], fh);
+
+                if (str[next] == '\n' || str[next] == 0)
+                {
+                    fprintf(fh, "\n");
+                    break;
+                }
+                else
+                {
+                    fputc(str[pos], fh);
+                    pos++;
+                    linelen++;
+                }
+            }
+            else
+            {
+                fprintf(fh, "\n");
+                break;
+            }
+        }
+    }
+}
+
+
+void th_args_help(FILE *fh,
+    const th_optarg_t *opts, const int numOpts,
+    const int flags)
 {
     int index;
+    (void) flags;
 
-    for (index = 0; index < optListN; index++)
+    // Print out option list
+    for (index = 0; index < numOpts; index++)
     {
-        optarg_t *opt = &optList[index];
+        const th_optarg_t *opt = &opts[index];
+        char tmpStr[128];
 
         // Print short option
         if (opt->optShort != 0)
-            fprintf(outFile, "  -%c,  ", opt->optShort);
+        {
+            snprintf(tmpStr, sizeof(tmpStr),
+                "-%c,", opt->optShort);
+        }
         else
-            fprintf(outFile, "       ");
+            tmpStr[0] = 0;
+
+        fprintf(fh, " %-5s", tmpStr);
 
         // Print long option
-        if (opt->optLong)
+        if (opt->optLong != NULL)
         {
-            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);
+            snprintf(tmpStr, sizeof(tmpStr), "--%s%s",
+                opt->optLong,
+                (opt->flags & OPT_ARGREQ) ? "=ARG" : "");
         }
         else
-            fprintf(outFile, "                 ");
+            tmpStr[0] = 0;
 
-        fprintf(outFile, "  %s.\n", opt->desc);
+        fprintf(fh, "%-20s", tmpStr);
+
+        th_print_wrap(fh, opt->desc, 26, 26, 73);
     }
 }
--- a/th_args.h	Mon Oct 13 06:36:15 2014 +0300
+++ b/th_args.h	Sat Nov 22 23:37:44 2014 +0200
@@ -1,7 +1,7 @@
 /*
  * Simple commandline argument processing function
  * Programmed and designed by Matti 'ccr' Hamalainen
- * (C) Copyright 2002-2012 Tecnic Software productions (TNSP)
+ * (C) Copyright 2002-2014 Tecnic Software productions (TNSP)
  *
  * Please read file 'COPYING' for information on license and distribution.
  */
@@ -19,10 +19,9 @@
  */
 #define OPT_NONE             (0)    // Simple option with no arguments
 #define OPT_ARGREQ           (1)    // Option requires an argument
-#define OPT_ARGMASK          (3)    // Mask for option argument flags
+#define OPT_ARGMASK          (1)    // Mask for option argument flags
 
 #define OPTH_BAILOUT         0x0001 // Bail out on errors
-
 #define OPTH_ONLY_OPTS       0x0010 // Handle only options
 #define OPTH_ONLY_OTHER      0x0020 // Handle only "non-options"
 #define OPTH_ONLY_MASK       0x00f0 // Mask
@@ -37,15 +36,16 @@
     char *optLong;
     char *desc;
     int flags;
-} optarg_t;
+} th_optarg_t;
 
 
 BOOL th_args_process(int argc, char *argv[],
-     optarg_t argList[], int argListN,
-     BOOL (*handleOpt)(int, char *, char *),
-     BOOL (*handleFile)(char *), int flags);
+     const th_optarg_t *opts, const int nopts,
+     BOOL (*handleOption)(int, char *, char *),
+     BOOL (*handleOther)(char *), const int flags);
 
-void th_args_help(FILE *, optarg_t optList[], int optListN);
+void th_args_help(FILE *, const th_optarg_t *opts,
+     const int nopts, const int flags);
 
 #ifdef __cplusplus
 }