|
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 } |