blob: e64508978de9b4f0b4354190f7761bfc778a31df [file] [log] [blame]
Austin Schuh3333ec72022-12-29 16:21:06 -08001/* Copyright (C) 2013-2016, The Regents of The University of Michigan.
2All rights reserved.
3This software was developed in the APRIL Robotics Lab under the
4direction of Edwin Olson, ebolson@umich.edu. This software may be
5available under alternative licensing terms; contact the address above.
6Redistribution and use in source and binary forms, with or without
7modification, are permitted provided that the following conditions are met:
81. Redistributions of source code must retain the above copyright notice, this
9 list of conditions and the following disclaimer.
102. Redistributions in binary form must reproduce the above copyright notice,
11 this list of conditions and the following disclaimer in the documentation
12 and/or other materials provided with the distribution.
13THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23The views and conclusions contained in the software and documentation are those
24of the authors and should not be interpreted as representing official policies,
25either expressed or implied, of the Regents of The University of Michigan.
26*/
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <assert.h>
31#include <string.h>
32#include <ctype.h>
33#include <errno.h>
34
35#include "zhash.h"
36#include "zarray.h"
37#include "getopt.h"
38#include "common/math_util.h"
39
40#define GOO_BOOL_TYPE 1
41#define GOO_STRING_TYPE 2
42
43typedef struct getopt_option getopt_option_t;
44
45struct getopt_option
46{
47 char *sname;
48 char *lname;
49 char *svalue;
50
51 char *help;
52 int type;
53
54 int spacer;
55
56 int was_specified;
57};
58
59struct getopt
60{
61 zhash_t *lopts;
62 zhash_t *sopts;
63 zarray_t *extraargs;
64 zarray_t *options;
65};
66
67getopt_t *getopt_create()
68{
69 getopt_t *gopt = (getopt_t*) calloc(1, sizeof(getopt_t));
70
71 gopt->lopts = zhash_create(sizeof(char*), sizeof(getopt_option_t*), zhash_str_hash, zhash_str_equals);
72 gopt->sopts = zhash_create(sizeof(char*), sizeof(getopt_option_t*), zhash_str_hash, zhash_str_equals);
73 gopt->options = zarray_create(sizeof(getopt_option_t*));
74 gopt->extraargs = zarray_create(sizeof(char*));
75
76 return gopt;
77}
78
79void getopt_option_destroy(getopt_option_t *goo)
80{
81 free(goo->sname);
82 free(goo->lname);
83 free(goo->svalue);
84 free(goo->help);
85 memset(goo, 0, sizeof(getopt_option_t));
86 free(goo);
87}
88
89void getopt_destroy(getopt_t *gopt)
90{
91 // free the extra arguments and container
92 zarray_vmap(gopt->extraargs, free);
93 zarray_destroy(gopt->extraargs);
94
95 // deep free of the getopt_option structs. Also frees key/values, so
96 // after this loop, hash tables will no longer work
97 zarray_vmap(gopt->options, getopt_option_destroy);
98 zarray_destroy(gopt->options);
99
100 // free tables
101 zhash_destroy(gopt->lopts);
102 zhash_destroy(gopt->sopts);
103
104 memset(gopt, 0, sizeof(getopt_t));
105 free(gopt);
106}
107
108static void getopt_modify_string(char **str, char *newvalue)
109{
110 char *old = *str;
111 *str = newvalue;
112 if (old != NULL)
113 free(old);
114}
115
116static char *get_arg_assignment(char *arg)
117{
118 // not an arg starting with "--"?
119 if (!str_starts_with(arg, "--")) {
120 return NULL;
121 }
122
123 int eq_index = str_indexof(arg, "=");
124
125 // no assignment?
126 if (eq_index == -1) {
127 return NULL;
128 }
129
130 // no quotes allowed before '=' in "--key=value" option specification.
131 // quotes can be used in value string, or by extra arguments
132 for (int i = 0; i < eq_index; i++) {
133 if (arg[i] == '\'' || arg[i] == '"') {
134 return NULL;
135 }
136 }
137
138 return &arg[eq_index];
139}
140
141// returns 1 if no error
142int getopt_parse(getopt_t *gopt, int argc, char *argv[], int showErrors)
143{
144 int okay = 1;
145 zarray_t *toks = zarray_create(sizeof(char*));
146
147 // take the input stream and chop it up into tokens
148 for (int i = 1; i < argc; i++) {
149
150 char *arg = strdup(argv[i]);
151 char *eq = get_arg_assignment(arg);
152
153 // no equal sign? Push the whole thing.
154 if (eq == NULL) {
155 zarray_add(toks, &arg);
156 } else {
157 // there was an equal sign. Push the part
158 // before and after the equal sign
159 char *val = strdup(&eq[1]);
160 eq[0] = 0;
161 zarray_add(toks, &arg);
162
163 // if the part after the equal sign is
164 // enclosed by quotation marks, strip them.
165 if (val[0]=='\"') {
166 size_t last = strlen(val) - 1;
167 if (val[last]=='\"')
168 val[last] = 0;
169 char *valclean = strdup(&val[1]);
170 zarray_add(toks, &valclean);
171 free(val);
172 } else {
173 zarray_add(toks, &val);
174 }
175 }
176 }
177
178 // now loop over the elements and evaluate the arguments
179 unsigned int i = 0;
180
181 char *tok = NULL;
182
183 while (i < zarray_size(toks)) {
184
185 // rather than free statement throughout this while loop
186 if (tok != NULL)
187 free(tok);
188
189 zarray_get(toks, i, &tok);
190
191 if (!strncmp(tok,"--", 2)) {
192 char *optname = &tok[2];
193 getopt_option_t *goo = NULL;
194 zhash_get(gopt->lopts, &optname, &goo);
195 if (goo == NULL) {
196 okay = 0;
197 if (showErrors)
198 printf("Unknown option --%s\n", optname);
199 i++;
200 continue;
201 }
202
203 goo->was_specified = 1;
204
205 if (goo->type == GOO_BOOL_TYPE) {
206 if ((i+1) < zarray_size(toks)) {
207 char *val = NULL;
208 zarray_get(toks, i+1, &val);
209
210 if (!strcmp(val,"true")) {
211 i+=2;
212 getopt_modify_string(&goo->svalue, val);
213 continue;
214 }
215 if (!strcmp(val,"false")) {
216 i+=2;
217 getopt_modify_string(&goo->svalue, val);
218 continue;
219 }
220 }
221 getopt_modify_string(&goo->svalue, strdup("true"));
222 i++;
223 continue;
224 }
225
226 if (goo->type == GOO_STRING_TYPE) {
227 // TODO: check whether next argument is an option, denoting missing argument
228 if ((i+1) < zarray_size(toks)) {
229 char *val = NULL;
230 zarray_get(toks, i+1, &val);
231 i+=2;
232 getopt_modify_string(&goo->svalue, val);
233 continue;
234 }
235
236 okay = 0;
237 if (showErrors)
238 printf("Option %s requires a string argument.\n",optname);
239 }
240 }
241
242 if (!strncmp(tok,"-",1) && strncmp(tok,"--",2)) {
243 size_t len = strlen(tok);
244 int pos;
245 for (pos = 1; pos < len; pos++) {
246 char sopt[2];
247 sopt[0] = tok[pos];
248 sopt[1] = 0;
249 char *sopt_ptr = (char*) &sopt;
250 getopt_option_t *goo = NULL;
251 zhash_get(gopt->sopts, &sopt_ptr, &goo);
252
253 if (goo==NULL) {
254 // is the argument a numerical literal that happens to be negative?
255 if (pos==1 && isdigit(tok[pos])) {
256 zarray_add(gopt->extraargs, &tok);
257 tok = NULL;
258 break;
259 } else {
260 okay = 0;
261 if (showErrors)
262 printf("Unknown option -%c\n", tok[pos]);
263 i++;
264 continue;
265 }
266 }
267
268 goo->was_specified = 1;
269
270 if (goo->type == GOO_BOOL_TYPE) {
271 getopt_modify_string(&goo->svalue, strdup("true"));
272 continue;
273 }
274
275 if (goo->type == GOO_STRING_TYPE) {
276 if ((i+1) < zarray_size(toks)) {
277 char *val = NULL;
278 zarray_get(toks, i+1, &val);
279 // TODO: allow negative numerical values for short-name options ?
280 if (val[0]=='-')
281 {
282 okay = 0;
283 if (showErrors)
284 printf("Ran out of arguments for option block %s\n", tok);
285 }
286 i++;
287 getopt_modify_string(&goo->svalue, val);
288 continue;
289 }
290
291 okay = 0;
292 if (showErrors)
293 printf("Option -%c requires a string argument.\n", tok[pos]);
294 }
295 }
296 i++;
297 continue;
298 }
299
300 // it's not an option-- it's an argument.
301 zarray_add(gopt->extraargs, &tok);
302 tok = NULL;
303 i++;
304 }
305 if (tok != NULL)
306 free(tok);
307
308 zarray_destroy(toks);
309
310 return okay;
311}
312
313void getopt_add_spacer(getopt_t *gopt, const char *s)
314{
315 getopt_option_t *goo = (getopt_option_t*) calloc(1, sizeof(getopt_option_t));
316 goo->spacer = 1;
317 goo->help = strdup(s);
318 zarray_add(gopt->options, &goo);
319}
320
321void getopt_add_bool(getopt_t *gopt, char sopt, const char *lname, int def, const char *help)
322{
323 char sname[2];
324 sname[0] = sopt;
325 sname[1] = 0;
326 char *sname_ptr = (char*) &sname;
327
328 if (strlen(lname) < 1) { // must have long name
329 fprintf (stderr, "getopt_add_bool(): must supply option name\n");
330 exit (EXIT_FAILURE);
331 }
332
333 if (sopt == '-') { // short name cannot be '-' (no way to reference)
334 fprintf (stderr, "getopt_add_bool(): invalid option character: '%c'\n", sopt);
335 exit (EXIT_FAILURE);
336 }
337
338 if (zhash_contains(gopt->lopts, &lname)) {
339 fprintf (stderr, "getopt_add_bool(): duplicate option name: --%s\n", lname);
340 exit (EXIT_FAILURE);
341 }
342
343 if (sopt != '\0' && zhash_contains(gopt->sopts, &sname_ptr)) {
344 fprintf (stderr, "getopt_add_bool(): duplicate option: -%s ('%s')\n", sname, lname);
345 exit (EXIT_FAILURE);
346 }
347
348 getopt_option_t *goo = (getopt_option_t*) calloc(1, sizeof(getopt_option_t));
349 goo->sname=strdup(sname);
350 goo->lname=strdup(lname);
351 goo->svalue=strdup(def ? "true" : "false");
352 goo->type=GOO_BOOL_TYPE;
353 goo->help=strdup(help);
354
355 zhash_put(gopt->lopts, &goo->lname, &goo, NULL, NULL);
356 zhash_put(gopt->sopts, &goo->sname, &goo, NULL, NULL);
357 zarray_add(gopt->options, &goo);
358}
359
360void getopt_add_int(getopt_t *gopt, char sopt, const char *lname, const char *def, const char *help)
361{
362 getopt_add_string(gopt, sopt, lname, def, help);
363}
364
365void
366getopt_add_double (getopt_t *gopt, char sopt, const char *lname, const char *def, const char *help)
367{
368 getopt_add_string (gopt, sopt, lname, def, help);
369}
370
371void getopt_add_string(getopt_t *gopt, char sopt, const char *lname, const char *def, const char *help)
372{
373 char sname[2];
374 sname[0] = sopt;
375 sname[1] = 0;
376 char *sname_ptr = (char*) &sname;
377
378 if (strlen(lname) < 1) { // must have long name
379 fprintf (stderr, "getopt_add_string(): must supply option name\n");
380 exit (EXIT_FAILURE);
381 }
382
383 if (sopt == '-') { // short name cannot be '-' (no way to reference)
384 fprintf (stderr, "getopt_add_string(): invalid option character: '%c'\n", sopt);
385 exit (EXIT_FAILURE);
386 }
387
388 if (zhash_contains(gopt->lopts, &lname)) {
389 fprintf (stderr, "getopt_add_string(): duplicate option name: --%s\n", lname);
390 exit (EXIT_FAILURE);
391 }
392
393 if (sopt != '\0' && zhash_contains(gopt->sopts, &sname_ptr)) {
394 fprintf (stderr, "getopt_add_string(): duplicate option: -%s ('%s')\n", sname, lname);
395 exit (EXIT_FAILURE);
396 }
397
398 getopt_option_t *goo = (getopt_option_t*) calloc(1, sizeof(getopt_option_t));
399 goo->sname=strdup(sname);
400 goo->lname=strdup(lname);
401 goo->svalue=strdup(def);
402 goo->type=GOO_STRING_TYPE;
403 goo->help=strdup(help);
404
405 zhash_put(gopt->lopts, &goo->lname, &goo, NULL, NULL);
406 zhash_put(gopt->sopts, &goo->sname, &goo, NULL, NULL);
407 zarray_add(gopt->options, &goo);
408}
409
410const char *getopt_get_string(getopt_t *gopt, const char *lname)
411{
412 getopt_option_t *goo = NULL;
413 zhash_get(gopt->lopts, &lname, &goo);
414 // could return null, but this would be the only
415 // method that doesn't assert on a missing key
416 assert (goo != NULL);
417 return goo->svalue;
418}
419
420int getopt_get_int(getopt_t *getopt, const char *lname)
421{
422 const char *v = getopt_get_string(getopt, lname);
423 assert(v != NULL);
424
425 errno = 0;
426 char *endptr = (char *) v;
427 long val = strtol(v, &endptr, 10);
428
429 if (errno != 0) {
430 fprintf (stderr, "--%s argument: strtol failed: %s\n", lname, strerror(errno));
431 exit (EXIT_FAILURE);
432 }
433
434 if (endptr == v) {
435 fprintf (stderr, "--%s argument cannot be parsed as an int\n", lname);
436 exit (EXIT_FAILURE);
437 }
438
439 return (int) val;
440}
441
442int getopt_get_bool(getopt_t *getopt, const char *lname)
443{
444 const char *v = getopt_get_string(getopt, lname);
445 assert (v!=NULL);
446 int val = !strcmp(v, "true");
447 return val;
448}
449
450double getopt_get_double (getopt_t *getopt, const char *lname)
451{
452 const char *v = getopt_get_string (getopt, lname);
453 assert (v!=NULL);
454
455 errno = 0;
456 char *endptr = (char *) v;
457 double d = strtod (v, &endptr);
458
459 if (errno != 0) {
460 fprintf (stderr, "--%s argument: strtod failed: %s\n", lname, strerror(errno));
461 exit (EXIT_FAILURE);
462 }
463
464 if (endptr == v) {
465 fprintf (stderr, "--%s argument cannot be parsed as a double\n", lname);
466 exit (EXIT_FAILURE);
467 }
468
469 return d;
470}
471
472int getopt_was_specified(getopt_t *getopt, const char *lname)
473{
474 getopt_option_t *goo = NULL;
475 zhash_get(getopt->lopts, &lname, &goo);
476 if (goo == NULL)
477 return 0;
478
479 return goo->was_specified;
480}
481
482const zarray_t *getopt_get_extra_args(getopt_t *gopt)
483{
484 return gopt->extraargs;
485}
486
487void getopt_do_usage(getopt_t * gopt)
488{
489 char * usage = getopt_get_usage(gopt);
490 printf("%s", usage);
491 free(usage);
492}
493
494char * getopt_get_usage(getopt_t *gopt)
495{
496 string_buffer_t * sb = string_buffer_create();
497
498 int leftmargin=2;
499 int longwidth=12;
500 int valuewidth=10;
501
502 for (unsigned int i = 0; i < zarray_size(gopt->options); i++) {
503 getopt_option_t *goo = NULL;
504 zarray_get(gopt->options, i, &goo);
505
506 if (goo->spacer)
507 continue;
508
509 longwidth = max(longwidth, (int) strlen(goo->lname));
510
511 if (goo->type == GOO_STRING_TYPE)
512 valuewidth = max(valuewidth, (int) strlen(goo->svalue));
513 }
514
515 for (unsigned int i = 0; i < zarray_size(gopt->options); i++) {
516 getopt_option_t *goo = NULL;
517 zarray_get(gopt->options, i, &goo);
518
519 if (goo->spacer)
520 {
521 if (goo->help==NULL || strlen(goo->help)==0)
522 string_buffer_appendf(sb,"\n");
523 else
524 string_buffer_appendf(sb,"\n%*s%s\n\n", leftmargin, "", goo->help);
525 continue;
526 }
527
528 string_buffer_appendf(sb,"%*s", leftmargin, "");
529
530 if (goo->sname[0]==0)
531 string_buffer_appendf(sb," ");
532 else
533 string_buffer_appendf(sb,"-%c | ", goo->sname[0]);
534
535 string_buffer_appendf(sb,"--%*s ", -longwidth, goo->lname);
536
537 string_buffer_appendf(sb," [ %s ]", goo->svalue); // XXX: displays current value rather than default value
538
539 string_buffer_appendf(sb,"%*s", (int) (valuewidth-strlen(goo->svalue)), "");
540
541 string_buffer_appendf(sb," %s ", goo->help);
542 string_buffer_appendf(sb,"\n");
543 }
544
545 char * usage = string_buffer_to_string(sb);
546 string_buffer_destroy(sb);
547 return usage;
548}