stdcpp/tsrc/Stdcpp_test/stdcxx/testengine/src/cmdopt.cpp
changeset 31 ce057bb09d0b
child 45 4b03adbd26ca
equal deleted inserted replaced
30:e20de85af2ee 31:ce057bb09d0b
       
     1 /***************************************************************************
       
     2  *
       
     3  * $Id: cmdopt.cpp 348342 2005-11-23 02:03:26Z sebor $
       
     4  *
       
     5  ************************************************************************
       
     6  *
       
     7  * Copyright (c) 1994-2005 Quovadx,  Inc., acting through its  Rogue Wave
       
     8  * Software division. Licensed under the Apache License, Version 2.0 (the
       
     9  * "License");  you may  not use this file except  in compliance with the
       
    10  * License.    You    may   obtain   a   copy   of    the   License    at
       
    11  * http://www.apache.org/licenses/LICENSE-2.0.    Unless   required    by
       
    12  * applicable law  or agreed to  in writing,  software  distributed under
       
    13  * the License is distributed on an "AS IS" BASIS,  WITHOUT WARRANTIES OR
       
    14  * CONDITIONS OF  ANY KIND, either  express or implied.  See  the License
       
    15  * for the specific language governing permissions  and limitations under
       
    16  * the License.
       
    17  * 
       
    18  **************************************************************************/
       
    19 
       
    20 // expand _TEST_EXPORT macros
       
    21 #define _RWSTD_TEST_SRC
       
    22 
       
    23 #include <cmdopt.h>
       
    24 
       
    25 #include <assert.h>   // for assert
       
    26 #include <errno.h>    // for errno
       
    27 #include <stdarg.h>   // for va_arg, ...
       
    28 #include <stdio.h>    // for fprintf
       
    29 #include <stdlib.h>   // for atexit, free, malloc
       
    30 #include <string.h>   // for memcpy, strcpy, strcmp, ...
       
    31 
       
    32 #ifdef __ARMCC__
       
    33 #pragma diag_suppress 61
       
    34 #pragma diag_suppress 63
       
    35 #endif
       
    36 
       
    37 #ifndef EINVAL
       
    38 #  define EINVAL   22   /* e.g., HP-UX, Linux, Solaris */
       
    39 #endif   // EINVAL
       
    40 
       
    41 /**************************************************************************/
       
    42 
       
    43 typedef int (optcallback_t)(int, char*[]);
       
    44 
       
    45 struct cmdopts_t
       
    46 {
       
    47     char           loptbuf_ [32];   // buffer for long option name
       
    48     optcallback_t *callback_;       // function to call to process option
       
    49 
       
    50     // counter to increment for each occurrence of an option
       
    51     // or to set to the numeric argument (when specified)
       
    52     int           *pcntr_;
       
    53 
       
    54     char          *lopt_;           // long option name
       
    55     char           sopt_;           // short option name
       
    56 
       
    57     size_t         maxcalls_;       // how many times option can be invoked
       
    58     size_t         ncalls_;         // how many times it has been invoked
       
    59 
       
    60     unsigned       arg_ : 1;        // option takes an argument?
       
    61     unsigned       inv_ : 1;        // callback invocation inverted
       
    62     unsigned       envseen_ : 1;    // environment option already processed
       
    63 };
       
    64 
       
    65 
       
    66 // total number of registered options
       
    67 static size_t ncmdopts;
       
    68 
       
    69 // number of default (always defined) options
       
    70 static size_t ndefopts;
       
    71 static cmdopts_t cmdoptbuf [32];
       
    72 static cmdopts_t *cmdopts = cmdoptbuf;
       
    73 static size_t optbufsize = sizeof cmdoptbuf / sizeof *cmdoptbuf;
       
    74 
       
    75 /**************************************************************************/
       
    76 
       
    77 static int
       
    78 rw_print_help (int argc, char *argv[])
       
    79 {
       
    80     if (1 == argc && argv && 0 == argv [0]) {
       
    81         static const char helpstr[] = {
       
    82             "Without an argument, prints this help to stdout and exits with\n"
       
    83             "a status of 0 without further processing.\n"
       
    84             "With the optional argument prints help on the option with that\n"
       
    85             "name, if one exists, and exits with a status of 0. If an option\n"
       
    86             "with the specified name does not exist, prints an error message\n"
       
    87             "to stderr and exits with a status of 1.\n"
       
    88             "The leading underscores in the name of an option are optional.\n"
       
    89         };
       
    90 
       
    91         argv [0] = _RWSTD_CONST_CAST (char*, helpstr);
       
    92 
       
    93         return 0;
       
    94     }
       
    95 
       
    96     // the option name to get help on, if any
       
    97     const char* opthelp = 1 < argc ? argv [1] : 0;
       
    98 
       
    99     // remove the optional one or two leading underscores
       
   100     if (opthelp && '-' == opthelp [0] && opthelp [1])
       
   101         opthelp += 1 + ('-' == opthelp [1]);
       
   102 
       
   103     if (0 == opthelp)
       
   104         printf ("OPTIONS\n");
       
   105 
       
   106     // set to a non-zero when the specified option is found
       
   107     int option_found = 0;
       
   108 
       
   109     for (size_t i = 0; i != ncmdopts; ++i) {
       
   110 
       
   111         // get a pointer to the name of the long option, if any
       
   112         const char* const lopt =
       
   113             cmdopts [i].lopt_ ? cmdopts [i].lopt_ : cmdopts [i].loptbuf_;
       
   114 
       
   115         if (opthelp && *opthelp) {
       
   116 
       
   117             if (   cmdopts [i].sopt_ == opthelp [0] && '\0' == opthelp [1]
       
   118                 || *lopt && 0 == strcmp (lopt + 1, opthelp)) {
       
   119 
       
   120                 // remember that we found the option whose (short
       
   121                 // or long) name we're to give help on; after printing
       
   122                 // the help text on the option keep looping in case
       
   123                 // there is another option and callback with the same
       
   124                 // name (unlikely but possible)
       
   125                 option_found = 1;
       
   126             }
       
   127             else {
       
   128                 // the option doesn't match, continue searching
       
   129                 continue;
       
   130             }
       
   131         }
       
   132 
       
   133         printf ("     ");
       
   134 
       
   135         if (cmdopts [i].sopt_) {
       
   136             printf ("-%c", cmdopts [i].sopt_);
       
   137 
       
   138             if (lopt)
       
   139                 printf (" | ");
       
   140         }
       
   141 
       
   142         const char *pfx = "";
       
   143         const char *sfx = pfx;
       
   144 
       
   145         if (lopt) {
       
   146             printf ("-%s", lopt);
       
   147             if (   cmdopts [i].arg_
       
   148                 && '=' != lopt [strlen (lopt) - 1]) {
       
   149                 pfx = " [ ";
       
   150                 sfx = " ]";
       
   151             }
       
   152         }
       
   153 
       
   154         printf ("%s%s%s", pfx, cmdopts [i].arg_ ? "<arg>" : "", sfx);
       
   155 
       
   156         if (_RWSTD_SIZE_MAX == cmdopts [i].maxcalls_)
       
   157             printf (" (each occurrence evaluated)\n");
       
   158         else if (1 < cmdopts [i].maxcalls_)
       
   159             printf (" (at most %u occurrences evaluated)\n",
       
   160                     unsigned (cmdopts [i].maxcalls_));
       
   161         else
       
   162             printf (" (only the first occurrence evaluated)\n");
       
   163 
       
   164         // invoke callback with the "--help" option
       
   165         if (cmdopts [i].callback_) {
       
   166 
       
   167             char* help [2] = { 0, 0 };
       
   168 
       
   169             cmdopts [i].callback_ (1, help);
       
   170 
       
   171             for (const char *line = help [0]; line; ) {
       
   172 
       
   173                 const char* const nl = strchr (line, '\n');
       
   174                 const int len = nl ? int (nl - line) : int (strlen (line));
       
   175 
       
   176                 printf ("       %.*s\n", len, line);
       
   177 
       
   178                 line = nl;
       
   179                 if (nl)
       
   180                     ++line;
       
   181             }
       
   182         }
       
   183     }
       
   184 
       
   185     if (opthelp && !option_found) {
       
   186         fprintf (stderr, "Unknown option \"%s\".\n", opthelp);
       
   187         exit (1);
       
   188     }
       
   189 
       
   190     exit (0);
       
   191 
       
   192     return 0;
       
   193 }
       
   194 
       
   195 /**************************************************************************/
       
   196 
       
   197 static int
       
   198 rw_set_ignenv (int argc, char *argv[])
       
   199 {
       
   200     if (1 == argc && argv && 0 == argv [0]) {
       
   201         static const char helpstr[] = {
       
   202             "Prevents options specified in the RWSTD_TESTOPTS environment\n"
       
   203             "variable from taking effect.\n"
       
   204             "Unless this option is specified, the RWSTD_TESTOPTS environment\n"
       
   205             "variable is processed as if its value were specified on the \n"
       
   206             "command line.\n"
       
   207             "For example, setting the value of the variable to the string\n"
       
   208             "\"--verbose --no-wchar\" and invoking this program with no\n"
       
   209             "command line arguments will have the same effect as invoking\n"
       
   210             "it with the two arguments on the command line.\n"
       
   211         };
       
   212 
       
   213         argv [0] = _RWSTD_CONST_CAST (char*, helpstr);
       
   214 
       
   215         return 0;
       
   216     }
       
   217 
       
   218     return 0;
       
   219 }
       
   220 
       
   221 extern "C" {
       
   222 
       
   223 static void
       
   224 rw_clear_opts ()
       
   225 {
       
   226     // reset all options, deallocating dynamically allocated storage
       
   227 
       
   228     for (size_t i = 0; i != ncmdopts; ++i) {
       
   229 
       
   230         // free any storage allocated for the option name
       
   231         free (cmdopts [i].lopt_);
       
   232     }
       
   233 
       
   234     if (cmdopts != cmdoptbuf) {
       
   235         // free the storage allocated for all the options
       
   236         free (cmdopts);
       
   237     }
       
   238 
       
   239     // reset the options pointer to point at the statically
       
   240     // allocated buffer and the count back to 0
       
   241     ncmdopts   = 0;
       
   242     cmdopts    = cmdoptbuf;
       
   243     optbufsize = sizeof cmdoptbuf / sizeof *cmdoptbuf;
       
   244 }
       
   245 
       
   246 }
       
   247 
       
   248 static void
       
   249 rw_set_myopts ()
       
   250 {
       
   251     static int cleanup_handler_registered;
       
   252 
       
   253     if (0 == cleanup_handler_registered) {
       
   254         atexit (rw_clear_opts);
       
   255         cleanup_handler_registered = 1;
       
   256     }
       
   257 
       
   258     if (0 != ncmdopts)
       
   259         return;
       
   260 
       
   261     static int recursive;
       
   262 
       
   263     if (recursive)
       
   264         return;
       
   265 
       
   266     ++recursive;
       
   267 
       
   268     rw_setopts ("|-help: "   // argument optional
       
   269                 "|-ignenv ",
       
   270                 rw_print_help,
       
   271                 rw_set_ignenv);
       
   272 
       
   273     ndefopts = ncmdopts;
       
   274 
       
   275     recursive = 0;
       
   276 }
       
   277 
       
   278 /**************************************************************************/
       
   279 
       
   280 //////////////////////////////////////////////////////////////////////
       
   281 // syntax of the option description string:
       
   282 //
       
   283 // opts ::= opt [ ':' | '=' | '#' ] [ @N | @* | '!' ] [ opts ]
       
   284 // opt  ::= <sopt> [ '|' <lopt>]
       
   285 //      ::= '|' <lopt>
       
   286 // sopt ::= char
       
   287 // lopt ::= char char*
       
   288 // char ::= A-Z a-z _ 0-9
       
   289 
       
   290 _TEST_EXPORT int
       
   291 rw_vsetopts (const char *opts, va_list va)
       
   292 {
       
   293     if (0 == opts) {
       
   294 
       
   295         rw_clear_opts ();
       
   296         return 0;
       
   297     }
       
   298 
       
   299     rw_set_myopts ();
       
   300 
       
   301     const char *next = opts;
       
   302 
       
   303     for ( ; ; ++ncmdopts) {
       
   304 
       
   305         while (' ' == *next)
       
   306             ++next;
       
   307 
       
   308         if ('\0' == *next) {
       
   309             break;
       
   310         }
       
   311 
       
   312         if (ncmdopts == optbufsize) {
       
   313 
       
   314             const size_t newbufsize = 2 * ncmdopts + 1;
       
   315             
       
   316             cmdopts_t* const newopts =
       
   317                 (cmdopts_t*)malloc (newbufsize * sizeof (cmdopts_t));
       
   318 
       
   319             if (0 == newopts) {
       
   320                 fprintf (stderr, "%s%d: failed to allocate memory\n",
       
   321                          __FILE__, __LINE__);
       
   322                 abort ();
       
   323             }
       
   324 
       
   325             memcpy (newopts, cmdopts, ncmdopts * sizeof (cmdopts_t));
       
   326 
       
   327             if (cmdopts != cmdoptbuf)
       
   328                 free (cmdopts);
       
   329 
       
   330             cmdopts    = newopts;
       
   331             optbufsize = newbufsize;
       
   332         }
       
   333 
       
   334         // clear the next option info
       
   335         memset (cmdopts + ncmdopts, 0, sizeof *cmdopts);
       
   336 
       
   337         if ('|' != *next)
       
   338             cmdopts [ncmdopts].sopt_ = *next++;
       
   339 
       
   340         if ('|' == *next) {
       
   341             const char* end = strpbrk (++next, "|@:=*!# ");
       
   342             if (0 == end)
       
   343                 end = next + strlen (next);
       
   344 
       
   345             // copy the option name up to but not including the delimiter
       
   346             // (except when the delimiter is the equals sign ('='), which
       
   347             // becomes the last character of the option name
       
   348             const size_t optlen = size_t (end - next) + ('=' == *end);
       
   349 
       
   350             char *lopt = 0;
       
   351 
       
   352             if (optlen < sizeof cmdopts [ncmdopts].loptbuf_)
       
   353                 lopt = cmdopts [ncmdopts].loptbuf_;
       
   354             else {
       
   355                 lopt = (char*)malloc (optlen + 1);
       
   356                 cmdopts [ncmdopts].lopt_ = lopt;
       
   357             }
       
   358 
       
   359             memcpy (lopt, next, optlen);
       
   360             lopt [optlen] = '\0';
       
   361 
       
   362             next = end;
       
   363         }
       
   364 
       
   365         // only the first occurrence of each command line option
       
   366         // causes an invocation of the callback, all subsequent
       
   367         // ones will be ignored by default
       
   368         cmdopts [ncmdopts].maxcalls_ = 1;
       
   369 
       
   370         int arg_is_callback = true;
       
   371 
       
   372         if ('#' == *next) {
       
   373             // insead of a pointer to a callback, the argument
       
   374             // is a pointer to an int counter that is to be
       
   375             // incremented for each occurrence of the option
       
   376             // during processing; when the option is immediately
       
   377             // followed by the equals sign ('=') and a numeric
       
   378             // argument the value of the argument will be stored
       
   379             arg_is_callback = false;
       
   380             ++next;
       
   381             
       
   382             // an unlimited number of occurrences of the option
       
   383             // are allowed and will be counted
       
   384             cmdopts [ncmdopts].maxcalls_ = _RWSTD_SIZE_MAX;
       
   385         }
       
   386         else if (':' == *next || '=' == *next) {
       
   387             // ':' : argument optional
       
   388             // '=' : argument required
       
   389             cmdopts [ncmdopts].arg_ = true;
       
   390             ++next;
       
   391         }
       
   392 
       
   393         if ('@' == *next) {
       
   394 
       
   395             ++next;
       
   396 
       
   397             // at most how many occurrences of an option can be processed?
       
   398             if ('*' == *next) {
       
   399                 // unlimited
       
   400                 cmdopts [ncmdopts].maxcalls_ = _RWSTD_SIZE_MAX;
       
   401                 ++next;
       
   402             }
       
   403             else {
       
   404                 // at most this many
       
   405                 char *end;
       
   406                 cmdopts [ncmdopts].maxcalls_ = strtoul (next, &end, 10);
       
   407                 next = end;
       
   408             }
       
   409         }
       
   410         else if ('!' == *next) {
       
   411             cmdopts [ncmdopts].inv_ = true;
       
   412             ++next;
       
   413         }
       
   414 
       
   415         if (arg_is_callback) {
       
   416             // retrieve the callback and verify it's not null
       
   417             // (null callback is permitted in the special case when
       
   418             // the short option is '-', i.e., when setting up or
       
   419             // resetting an "unknown option" handler)
       
   420             cmdopts [ncmdopts].callback_ = va_arg (va, optcallback_t*);
       
   421         }
       
   422         else {
       
   423             // retrieve the address of the int counter where to keep
       
   424             // track of the number of occurrences of the option, or
       
   425             // where to store the value of the numeric argument of
       
   426             // the option
       
   427             cmdopts [ncmdopts].pcntr_ = va_arg (va, int*);
       
   428         }
       
   429 
       
   430         if (   '-' != cmdopts [ncmdopts].sopt_
       
   431             && 0 == cmdopts [ncmdopts].callback_
       
   432             && 0 == cmdopts [ncmdopts].pcntr_) {
       
   433 
       
   434             // get a pointer to the long option name
       
   435             const char* const lopt = cmdopts [ncmdopts].lopt_
       
   436                 ? cmdopts [ncmdopts].lopt_ : cmdopts [ncmdopts].loptbuf_;
       
   437 
       
   438             if (*lopt)
       
   439                 fprintf (stderr, "null handler for option -%s\n", lopt);
       
   440             else
       
   441                 fprintf (stderr, "null handler for option -%c\n",
       
   442                          cmdopts [ncmdopts].sopt_);
       
   443                 
       
   444             abort ();
       
   445         }
       
   446     }
       
   447 
       
   448     return int (ncmdopts - ndefopts);
       
   449 }
       
   450 
       
   451 /**************************************************************************/
       
   452 
       
   453 _TEST_EXPORT int
       
   454 rw_setopts (const char *opts, ...)
       
   455 {
       
   456     va_list va;
       
   457     va_start (va, opts);
       
   458     const int result = rw_vsetopts (opts, va);
       
   459     va_end (va);
       
   460     return result;
       
   461 }
       
   462 
       
   463 /**************************************************************************/
       
   464 
       
   465 _TEST_EXPORT int
       
   466 rw_runopts (int argc, char *argv[])
       
   467 {
       
   468     rw_set_myopts ();
       
   469 
       
   470     static int recursive = false;
       
   471 
       
   472     // ignore options set in the environment?
       
   473     int ignenv = recursive;
       
   474 
       
   475     // return status
       
   476     int status = 0;
       
   477 
       
   478     // number of options processed
       
   479     int nopts = 0;
       
   480 
       
   481     // index of registered option whose callback should be invoked
       
   482     // for command line options that do not match any other
       
   483     size_t not_found_inx = _RWSTD_SIZE_MAX;
       
   484 
       
   485     // iterate over the command line arguments until a callback
       
   486     // returns a non-zero value or until all options have been
       
   487     // successfully processed
       
   488     for (int i = 0; i < argc && argv [i] && 0 == status; ++i) {
       
   489 
       
   490         if (0 == strcmp ("--ignore-environment", argv [i])) {
       
   491             // ignore options set in the environment
       
   492             ignenv = true;
       
   493             continue;
       
   494         }
       
   495 
       
   496         if (0 == strcmp ("--", argv [i])) {
       
   497             // "--" terminates options, everything
       
   498             // after it is treated as an argument
       
   499             break;
       
   500         }
       
   501 
       
   502         // the name of the option without the leading dash
       
   503         const char* const optname = argv [i] + 1;
       
   504 
       
   505         // look for the first equals sign
       
   506         const char* const eq = strchr (optname, '=');
       
   507 
       
   508         // compute the length of the option including the equals sign (if any)
       
   509         const size_t optlen = eq ? size_t (eq - optname + 1) : strlen (optname);
       
   510 
       
   511         int found = false;
       
   512 
       
   513         // look up each command line option (i.e., a string that starts
       
   514         // with a dash ('-')) and invoke the callback associated with it
       
   515         for (size_t j = 0; j != ncmdopts; ++j) {
       
   516 
       
   517             if ('-' == cmdopts [j].sopt_)
       
   518                 not_found_inx = j;
       
   519 
       
   520             if ('-' == argv [i][0]) {
       
   521 
       
   522                 const size_t cmplen =
       
   523                     eq && cmdopts [j].pcntr_ ? optlen - 1 : optlen;
       
   524 
       
   525                 // get a pointer to the (possibly empty) name
       
   526                 // of the long option
       
   527                 const char* const lopt = cmdopts [j].lopt_ ? 
       
   528                     cmdopts [j].lopt_ : cmdopts [j].loptbuf_;
       
   529 
       
   530                 // try to match the long option first, and only if it
       
   531                 // doesn't match try the short single-character option
       
   532                 if (   cmplen == strlen (lopt)
       
   533                     && 0 == memcmp (optname, lopt, cmplen)
       
   534                     || cmdopts [j].sopt_
       
   535                     && optname [0] == cmdopts [j].sopt_
       
   536                     && (1 == optlen || cmdopts [j].arg_)) {
       
   537 
       
   538                     // matching option has been found
       
   539                     found = true;
       
   540 
       
   541                     // ignore the option if invoked recursively (by processing
       
   542                     // options set in the environment) and the option has
       
   543                     // already been seen (this prevents duplicate processing
       
   544                     // of options that are set both on the command line and
       
   545                     // in the environment and allows option with an argument
       
   546                     // set on the command line to override those set in the
       
   547                     // environment)
       
   548 
       
   549                     if (cmdopts [j].ncalls_ && recursive)
       
   550                         continue;
       
   551 
       
   552                     // if the option has been evaluated the maximum number
       
   553                     // of times, avoid evaluating it and continue processing
       
   554                     if (cmdopts [j].maxcalls_ <= cmdopts [j].ncalls_)
       
   555                         continue;
       
   556 
       
   557                     if (cmdopts [j].callback_) {
       
   558                         if (!cmdopts [j].inv_) {
       
   559                             // when the command line argument matched
       
   560                             // the option,  invoke the callback function
       
   561                             status = cmdopts [j].callback_ (argc - i, argv + i);
       
   562                         }
       
   563                     }
       
   564                     else if (eq) {
       
   565                         assert (0 != cmdopts [j].pcntr_);
       
   566 
       
   567                         // obtain the numeric argument
       
   568                         char *end = 0;
       
   569                         const long optval = strtol (eq + 1, &end, 0);
       
   570 
       
   571                         if (end && '\0' != *end) {
       
   572                             fprintf (stderr, "expected numeric argument: %s\n",
       
   573                                      optname);
       
   574                             ignenv = true;
       
   575                             errno  = EINVAL;
       
   576                             status = 1;
       
   577                         }
       
   578 
       
   579 #if _RWSTD_INT_SIZE < _RWSTD_LONG_SIZE
       
   580 
       
   581                         else if (   optval < _RWSTD_INT_MIN
       
   582                                  || _RWSTD_INT_MAX < optval) {
       
   583                             fprintf (stderr, "numeric argument %ld out of range"
       
   584                                      " [%d, %d]: %s\n", optval,
       
   585                                      _RWSTD_INT_MIN, _RWSTD_INT_MAX, optname);
       
   586                             ignenv = true;
       
   587                             errno  = EINVAL;
       
   588                             status = 1;
       
   589                         }
       
   590 
       
   591 #endif   // _RWSTD_INT_SIZE < _RWSTD_LONG_SIZE
       
   592 
       
   593                         else {
       
   594                             *cmdopts [j].pcntr_ = optval;
       
   595                         }
       
   596                     }
       
   597                     else {
       
   598                         assert (0 != cmdopts [j].pcntr_);
       
   599                         ++*cmdopts [j].pcntr_;
       
   600                     }
       
   601 
       
   602                     ++cmdopts [j].ncalls_;
       
   603 
       
   604                     if (recursive)
       
   605                         cmdopts [j].envseen_ = true;
       
   606 
       
   607                     ++nopts;
       
   608 
       
   609                     if (status) {
       
   610                         // when the status returned from the last callback
       
   611                         // is non-0 stop further processing (including
       
   612                         // any options set in the environment) and return
       
   613                         // status to the caller
       
   614                         ignenv = true;
       
   615                         break;
       
   616                     }
       
   617                 }
       
   618             }
       
   619         }
       
   620 
       
   621         if (!found && '-' == argv [i][0]) {
       
   622 
       
   623             // invoke the appropriate error handler for an option
       
   624             // that was not found
       
   625             if (_RWSTD_SIZE_MAX != not_found_inx) {
       
   626 
       
   627                 // invoke the error handler set up through rw_setopts()
       
   628                 // and let the handler decide whether to go on processing
       
   629                 // other options or whether to abort
       
   630                 status = cmdopts [not_found_inx].callback_ (argc - i, argv + i);
       
   631                 if (status) {
       
   632                     // no further processing done
       
   633                     ignenv = true;
       
   634                     break;
       
   635                 }
       
   636             }
       
   637             else {
       
   638                 // print an error message to stderr when no error
       
   639                 // handler has been set up
       
   640                 fprintf (stderr, "unknown option: %s\n", argv [i]);
       
   641                 ignenv = true;
       
   642                 errno  = EINVAL;
       
   643                 status = 1;
       
   644                 break;
       
   645             }
       
   646         }
       
   647     }
       
   648 
       
   649     if (!ignenv) {
       
   650         // process options from the environment
       
   651         const char* const envar = getenv ("RWSTD_TESTOPTS");
       
   652         if (envar) {
       
   653             recursive = true;
       
   654             rw_runopts (envar);
       
   655             recursive = false;
       
   656         }
       
   657     }
       
   658 
       
   659     // invoke any inverted callbacks or bump their user-specified counters,
       
   660     // and reset internal counters indicating if/how many times each option
       
   661     // has been processed
       
   662     for (size_t j = 0; j != ncmdopts; ++j) {
       
   663 
       
   664         if (cmdopts [j].inv_ && 0 == cmdopts [j].ncalls_ && 0 == status) {
       
   665 
       
   666             if (cmdopts [j].callback_)
       
   667                 status = cmdopts [j].callback_ (0, 0);
       
   668             else {
       
   669                 assert (0 != cmdopts [j].pcntr_);
       
   670                 ++*cmdopts [j].pcntr_;
       
   671             }
       
   672         }
       
   673 
       
   674         cmdopts [j].ncalls_  = 0;
       
   675         cmdopts [j].envseen_ = false;
       
   676     }
       
   677 
       
   678     return status;
       
   679 }
       
   680 
       
   681 /**************************************************************************/
       
   682 
       
   683 _TEST_EXPORT int
       
   684 rw_runopts (const char *str)
       
   685 {
       
   686     assert (0 != str);
       
   687 
       
   688     rw_set_myopts ();
       
   689 
       
   690     char buf [80];      // fixed size buffer to copy `str' into
       
   691     char *pbuf = buf;   // a modifiable copy of `str'
       
   692 
       
   693     size_t len = strlen (str);
       
   694     if (len >= sizeof buf) {
       
   695         // allocate if necessary
       
   696         pbuf = (char*)malloc (len + 1);
       
   697         if (!pbuf)
       
   698             return -1;
       
   699     }
       
   700 
       
   701     // copy `str' to modifiable buffer
       
   702     memcpy (pbuf, str, len + 1);
       
   703 
       
   704     char *tmp_argv_buf [32] = { 0 };   // fixed size argv buffer
       
   705     char **argv = tmp_argv_buf;        // array of arguments
       
   706 
       
   707     // initial size of argument array (will grow as necessary)
       
   708     size_t argv_size = sizeof tmp_argv_buf / sizeof *tmp_argv_buf;
       
   709     size_t argc = 0;   // number of arguments in array
       
   710 
       
   711     int in_quotes = 0;   // quoted argument being processed
       
   712 
       
   713     for (char *s = pbuf; *s; ++s) {
       
   714         if ('"' == *s) {
       
   715             in_quotes = !in_quotes;
       
   716             continue;
       
   717         }
       
   718 
       
   719         if (in_quotes)
       
   720             continue;
       
   721 
       
   722         // split up unquoted space-separated arguments
       
   723         if (argc == 0 || ' ' == *s) {
       
   724             if (argc > 0) 
       
   725                 *s = 0;
       
   726 
       
   727             // skip over leading spaces
       
   728             if (argc > 0 || ' ' == *s)
       
   729                 while (' ' == *++s);
       
   730 
       
   731             if (*s) {
       
   732                 if (argc == argv_size) {
       
   733                     // grow `argv' as necessary
       
   734                     char **tmp = (char**)malloc (sizeof *tmp * argv_size * 2);
       
   735                     if (!tmp) {
       
   736                         if (argv != tmp_argv_buf)
       
   737                             free (argv);
       
   738                         return -1;
       
   739                     }
       
   740 
       
   741                     // copy existing elementes and zero out any new entries
       
   742                     memcpy (tmp, argv, sizeof *tmp * argv_size);
       
   743                     memset (tmp + argv_size, 0, sizeof *tmp * argv_size);
       
   744 
       
   745                     // free existing buffer if necessary
       
   746                     if (argv != tmp_argv_buf)
       
   747                         free (argv);
       
   748 
       
   749                     // reassign buffer and increase size
       
   750                     argv       = tmp;
       
   751                     argv_size *= 2;
       
   752                 }
       
   753 
       
   754                 // add argument to array
       
   755                 argv [argc++] = s;
       
   756             }
       
   757         }
       
   758     }
       
   759 
       
   760     // process `argc' options pointed to by `argv'
       
   761     const int status = rw_runopts (int (argc), argv);
       
   762 
       
   763     // free buffers if necessary
       
   764     if (argv != tmp_argv_buf)
       
   765         free (argv);
       
   766 
       
   767     if (pbuf != buf)
       
   768         free (pbuf);
       
   769 
       
   770     return status;
       
   771 }