Line data Source code
1 : #include <stdio.h>
2 : #include <stdlib.h>
3 : #include <string.h>
4 : #include <strings.h>
5 : #include <ctype.h>
6 : #include <limits.h>
7 : #include <errno.h>
8 :
9 : #include <crater/opts.h>
10 : #include <crater/hash.h>
11 :
12 113 : bool cr8r_opt_missing_optional(cr8r_opt *self){
13 113 : return 1;
14 : }
15 :
16 2 : bool cr8r_opt_print_help(cr8r_opt *self, char *opt){
17 2 : cr8r_opt *opts = self->dest;
18 2 : fprintf(stderr, "%s", self->description);
19 29 : for(uint64_t i = 0; opts[i].short_name || opts[i].long_name; ++i){
20 27 : if(opts + i != self){
21 25 : if(opts[i].short_name){
22 15 : if(opts[i].long_name){
23 14 : fprintf(stderr, "\t-%s/--%s: \t%s\n", opts[i].short_name, opts[i].long_name, opts[i].description ?: "<no description>");
24 : }else{
25 1 : fprintf(stderr, "\t-%s: \t%s\n", opts[i].short_name, opts[i].description ?: "<no description>");
26 : }
27 : }else{
28 10 : fprintf(stderr, "\t--%s: \t%s\n", opts[i].long_name, opts[i].description ?: "<no description>");
29 : }
30 : }else{
31 2 : fprintf(stderr, "\t-h/--help: display this help information and exit\n");
32 : }
33 : }
34 2 : exit(0);
35 : }
36 :
37 1 : bool cr8r_opt_ignore_arg(void *data, int argc, char **argv, int i){
38 1 : return 1;
39 : }
40 :
41 0 : bool cr8r_opt_set_enum_0(cr8r_opt *self, char *opt){
42 0 : *(int*)self->dest = 0;
43 0 : return 1;
44 : }
45 :
46 : /// Implementation of on_opt that sets *(int*)self->dest to 1
47 0 : bool cr8r_opt_set_enum_1(cr8r_opt *self, char *opt){
48 0 : *(int*)self->dest = 1;
49 0 : return 1;
50 : }
51 :
52 1 : bool cr8r_opt_parse_ull(cr8r_opt *self, char *opt){
53 1 : char *end = opt;
54 1 : errno = 0;
55 1 : *(unsigned long long*)self->dest = strtoull(opt, &end, 0);
56 1 : return errno != ERANGE && end != opt && !*end;
57 : }
58 :
59 1 : bool cr8r_opt_parse_ll(cr8r_opt *self, char *opt){
60 1 : char *end = opt;
61 1 : errno = 0;
62 1 : *(long long*)self->dest = strtoll(opt, &end, 0);
63 1 : return errno != ERANGE && end != opt && !*end;
64 : }
65 :
66 1 : bool cr8r_opt_parse_ul(cr8r_opt *self, char *opt){
67 1 : char *end = opt;
68 1 : errno = 0;
69 1 : *(unsigned long*)self->dest = strtoul(opt, &end, 0);
70 1 : return errno != ERANGE && end != opt && !*end;
71 : }
72 :
73 1 : bool cr8r_opt_parse_l(cr8r_opt *self, char *opt){
74 1 : char *end = opt;
75 1 : errno = 0;
76 1 : *(unsigned long*)self->dest = strtol(opt, &end, 0);
77 1 : return errno != ERANGE && end != opt && !*end;
78 : }
79 :
80 1 : bool cr8r_opt_parse_u(cr8r_opt *self, char *opt){
81 1 : char *end = opt;
82 1 : errno = 0;
83 1 : unsigned long n = strtoul(opt, &end, 0);
84 1 : *(unsigned*)self->dest = n;
85 1 : return errno != ERANGE && end != opt && !*end &&
86 : n <= UINT_MAX;
87 : }
88 :
89 1 : bool cr8r_opt_parse_i(cr8r_opt *self, char *opt){
90 1 : char *end = opt;
91 1 : errno = 0;
92 1 : long n = strtol(opt, &end, 0);
93 1 : *(int*)self->dest = n;
94 2 : return errno != ERANGE && end != opt && !*end &&
95 2 : INT_MIN <= n && n <= INT_MAX;
96 : }
97 :
98 1 : bool cr8r_opt_parse_us(cr8r_opt *self, char *opt){
99 1 : char *end = opt;
100 1 : errno = 0;
101 1 : unsigned long n = strtoul(opt, &end, 0);
102 1 : *(unsigned short*)self->dest = n;
103 1 : return errno != ERANGE && end != opt && !*end &&
104 : n <= USHRT_MAX;
105 : }
106 :
107 1 : bool cr8r_opt_parse_s(cr8r_opt *self, char *opt){
108 1 : char *end = opt;
109 1 : errno = 0;
110 1 : long n = strtol(opt, &end, 0);
111 1 : *(short*)self->dest = n;
112 2 : return errno != ERANGE && end != opt && !*end &&
113 2 : SHRT_MIN <= n && n <= SHRT_MAX;
114 : }
115 :
116 3 : bool cr8r_opt_parse_uc(cr8r_opt *self, char *opt){
117 3 : char *end = opt;
118 3 : if(!*end){
119 : return 0;
120 : }
121 2 : if(!end[1]){
122 1 : *(unsigned char*)self->dest = end[0];
123 1 : return 1;
124 : }
125 1 : errno = 0;
126 1 : unsigned long n = strtoul(opt, &end, 0);
127 1 : *(unsigned char*)self->dest = n;
128 1 : return errno != ERANGE && end != opt && !*end &&
129 : n <= UCHAR_MAX;
130 : }
131 :
132 3 : bool cr8r_opt_parse_sc(cr8r_opt *self, char *opt){
133 3 : char *end = opt;
134 3 : if(!*end){
135 : return 0;
136 : }
137 2 : if(!end[1]){
138 1 : *(signed char*)self->dest = end[0];
139 1 : return 1;
140 : }
141 1 : errno = 0;
142 1 : long n = strtol(opt, &end, 0);
143 1 : *(signed char*)self->dest = n;
144 2 : return errno != ERANGE && end != opt && !*end &&
145 2 : SCHAR_MIN <= n && n <= SCHAR_MAX;
146 : }
147 :
148 3 : bool cr8r_opt_parse_c(cr8r_opt *self, char *opt){
149 3 : char *end = opt;
150 3 : if(!*end){
151 : return 0;
152 : }
153 2 : if(!end[1]){
154 1 : *(char*)self->dest = end[0];
155 1 : return 1;
156 : }
157 1 : errno = 0;
158 : #if CHAR_MIN
159 1 : long n = strtol(opt, &end, 0);
160 1 : *(char*)self->dest = n;
161 : #else
162 : unsigned long n = strtoul(opt, &end, 0);
163 : *(char*)self->dest = n;
164 : #endif
165 2 : return errno != ERANGE && end != opt && !*end &&
166 2 : CHAR_MIN <= n && n <= CHAR_MAX;
167 : }
168 :
169 1 : bool cr8r_opt_parse_f(cr8r_opt *self, char *opt){
170 1 : char *end = opt;
171 1 : errno = 0;
172 1 : *(float*)self->dest = strtof(opt, &end);
173 1 : return errno != ERANGE && end != opt && !*end;
174 : }
175 :
176 1 : bool cr8r_opt_parse_d(cr8r_opt *self, char *opt){
177 1 : char *end = opt;
178 1 : errno = 0;
179 1 : *(double*)self->dest = strtod(opt, &end);
180 1 : return errno != ERANGE && end != opt && !*end;
181 : }
182 :
183 1 : bool cr8r_opt_parse_ld(cr8r_opt *self, char *opt){
184 1 : char *end = opt;
185 1 : errno = 0;
186 1 : *(long double*)self->dest = strtold(opt, &end);
187 1 : return errno != ERANGE && end != opt && !*end;
188 : }
189 :
190 1 : bool cr8r_opt_parse_cstr(cr8r_opt *self, char *opt){
191 1 : *(const char**)self->dest = opt;
192 1 : return !!*opt;
193 : }
194 :
195 4 : bool cr8r_opt_parse_b(cr8r_opt *self, char *opt){
196 4 : char *end = opt;
197 4 : errno = 0;
198 4 : unsigned long n = strtoul(opt, &end, 0);
199 4 : *(bool*)self->dest = !!n;
200 4 : if(errno == ERANGE || n > 1){
201 : return 0;
202 : }
203 3 : if(end == opt){
204 3 : if(!strcasecmp(opt, "true") || !strcasecmp(opt, "yes") || !strcasecmp(opt, "t") || !strcasecmp(opt, "y")){
205 1 : *(bool*)self->dest = true;
206 2 : }else if(!strcasecmp(opt, "false") || !strcasecmp(opt, "no") || !strcasecmp(opt, "f") || !strcasecmp(opt, "n")){
207 1 : *(bool*)self->dest = false;
208 : }else{
209 : return 0;
210 : }
211 : }
212 : return 1;
213 : }
214 :
215 245 : static inline int get_digit_base(int c, int base){
216 245 : if(c < '0'){
217 : return -1;
218 239 : }else if(base <= 10){
219 235 : return c < '0' + base ? c - '0' : -1;
220 4 : }else if(c <= '9'){
221 1 : return c - '0';
222 3 : }else if(c > 'Z'){
223 2 : if(c < 'a'){
224 : return -1;
225 : }
226 1 : c -= (int)'a' - 'A';
227 : }
228 2 : return c < 'A' + base - 10 ? c - 'A' + 10 : -1;
229 : }
230 :
231 : static const __int128 i128_max = ~(((__int128)1) << 127);
232 : static const __int128 i128_min = ((__int128)1) << 127;
233 : static const unsigned __int128 u128_max = ~(unsigned __int128)0;
234 :
235 : // this function tries to emulate strtoul/strtol and friends, including most of the bugs in the api
236 : // in particular, str is a pointer to a string to parse, base is the base to use, and is_signed
237 : // indicates whether or not the string is signed.
238 : // base can be 2-36, or 0 to infer the base. if any other value is supplied, errno is set to EINVAL,
239 : // *end is set to str if end is not NULL, and 0 is returned.
240 : // the string should have the format `\s*[+-](?:0[xX]?)?\d+`.
241 : // "\s" is a whitespace character according to isspace().
242 : // "\d" is a digit in the given base, which are the first `base` digits starting from 0 if `base <= 10`,
243 : // with letters from a-z, case insensitive, if `base > 10`.
244 : // the allowed prefix `(?:0[xX]?)?` depends on the base: if base is 16, `0[xX]` is allowed, but optional,
245 : // and if base is 0, the base is inferred to be 16 if the prefix is `0[xX]`, 8 if the prefix is `0`, and 10
246 : // otherwise. notice that `0` is not considered to be part of the prefix if base is explicitly set to 8.
247 : // numbers are always allowed to start with 0's.
248 : // if matching the string to the format fails before any digit in the number is read, *end is set to str if
249 : // applicable (NOT the first position which does not match the format, since having it set to str is
250 : // faster to check and more convenient). this includes if the string is empty or NULL.
251 : // this is why it is important that leading 0's are considered to be part of the number rather than the prefix
252 : // (besides the first 0 if base is 0). if a non-digit character is encountered after at least one
253 : // digit in the number has been read, the number read up until that point is converted to a 128-bit integer
254 : // and then *end is set to point to the first non-digit character if applicable and the number read so far is
255 : // returned.
256 : // if the number read so far overflows, *end is set to pointto the first character AFTER the digit that caused
257 : // overflow, errno is set to ERANGE, and a clamped limit is returned (ie, i128_min if is_signed and the number
258 : // has a leading '-', i128_max if is_signed and the number does not have a leading '-', and u128_max otherwise).
259 : //
260 : // bugs carried over from strtoul and friends:
261 : // if !is_signed, a leading '-' is ignored rather than treated as an error.
262 : // errno is not set to 0 in the absence of any error
263 : // **end is not const
264 : CR8R_ATTR_NO_SAN("integer")
265 11 : static inline unsigned __int128 strtou128(const char *str, char **end, int base, bool is_signed){
266 11 : if(end){
267 11 : *end = (char*)str;
268 : }
269 11 : if(base < 0 || base > 36 || base == 1){
270 0 : errno = EINVAL;
271 0 : return 0;
272 : }
273 11 : if(!str){
274 : return 0;
275 : }
276 12 : while(isspace(*str)){
277 1 : ++str;
278 : }
279 11 : bool negative = *str == '-';
280 11 : if(negative || *str == '+'){
281 3 : ++str;
282 : }
283 11 : if(!base){
284 11 : if(*str == '0'){
285 5 : if(!strncasecmp("x", str + 1, 1)){
286 2 : str += 2;
287 2 : base = 16;
288 : }else{
289 3 : base = 8;
290 3 : if(!*++str){
291 2 : if(end){
292 2 : *end = (char*)str;
293 : }
294 2 : return 0;
295 : }
296 : }
297 : }else{
298 : base = 10;
299 : }
300 0 : }else if(base == 16 && !strncasecmp("0x", str, 2)){
301 0 : str += 2;
302 : }
303 9 : int cur_digit = get_digit_base(*str, base);
304 9 : if(cur_digit == -1){
305 : return 0;
306 : }
307 8 : unsigned __int128 res = cur_digit;
308 : // res*base <= -i128_min <-- res <= -i128_min/base
309 : // res*base > -i128_min <-- res >= (-i128_min + base)/base <-> res > -i128_min/base
310 8 : unsigned __int128 MAX = (is_signed ? (unsigned __int128)i128_min : u128_max);
311 8 : unsigned __int128 safe_ceil = MAX/(unsigned __int128)base;
312 464 : while(1){
313 236 : cur_digit = get_digit_base(*++str, base);
314 236 : if(cur_digit == -1){
315 : break;
316 : }
317 : // res*base + cur_digit <= MAX
318 : // cur_digit <= MAX - res*base
319 230 : if(res > safe_ceil || (unsigned __int128)cur_digit > MAX - res*(unsigned __int128)base){
320 2 : if(end){
321 2 : *end = (char*)str;
322 : }
323 2 : errno = ERANGE;
324 2 : return is_signed ? negative ? (unsigned __int128)i128_min : (unsigned __int128)i128_max : u128_max;
325 : }
326 228 : res = res*(unsigned __int128)base + (unsigned __int128)cur_digit;
327 : }
328 6 : if(is_signed && !negative && res == (unsigned __int128)i128_min){
329 1 : if(end){
330 1 : *end = (char*)str;
331 : }
332 1 : errno = ERANGE;
333 1 : return i128_max;
334 5 : }else if(end){
335 5 : *end = (char*)str;
336 : }
337 5 : return is_signed && negative ? -res : res;
338 : }
339 :
340 8 : bool cr8r_opt_parse_i128(cr8r_opt *self, char *opt){
341 8 : char *end = opt;
342 8 : errno = 0;
343 8 : *(__int128*)self->dest = (__int128)strtou128(opt, &end, 0, true);
344 8 : return errno != ERANGE && end != opt && !*end;
345 : }
346 :
347 3 : bool cr8r_opt_parse_u128(cr8r_opt *self, char *opt){
348 3 : char *end = opt;
349 3 : errno = 0;
350 3 : *(unsigned __int128*)self->dest = strtou128(opt, &end, 0, false);
351 3 : return errno != ERANGE && end != opt && !*end;
352 : }
353 :
354 3 : char *cr8r_sprint_i128(char buf[static 41], __int128 i){
355 3 : char *it = buf + 41;
356 3 : *--it = '\0';
357 3 : if(!i){
358 1 : *--it = '0';
359 2 : }else if(i < 0){
360 44 : for(;i; i /= 10){
361 42 : *--it = '0' - i%10;
362 : }
363 2 : *--it = '-';
364 : }else{
365 0 : for(;i; i /= 10){
366 0 : *--it = '0' + i%10;
367 : }
368 : }
369 3 : return it;
370 : }
371 :
372 2 : char *cr8r_sprint_u128(char buf[static 40], unsigned __int128 i){
373 2 : char *it = buf + 40;
374 2 : *--it = '\0';
375 2 : if(!i){
376 1 : *--it = '0';
377 40 : }else for(;i; i /= 10){
378 39 : *--it = '0' + i%10;
379 : }
380 2 : return it;
381 : }
382 :
383 13 : static bool init_opt_tbls(cr8r_opt *opts, cr8r_opt_cfg *cfg, cr8r_hashtbl_t *long_opts, cr8r_hashtbl_t *short_opts){
384 13 : uint64_t num_opts = 0, num_long = 0, num_short = 0;
385 194 : for(cr8r_opt *opt; opt = opts + num_opts, opt->short_name || opt->long_name; ++num_opts){
386 181 : int char_len;
387 181 : if(opt->arg_mode && !opt->on_opt){
388 0 : fprintf(stderr, "Crater opt error: invalid opt spec (argument to opt allowed but on_opt missing)\n");
389 0 : return 0;
390 181 : }else if(opt->long_name && !*opt->long_name){
391 0 : fprintf(stderr, "Crater opt error: invalid opt spec (long_name is empty string (use NULL instead))\n");
392 0 : return 0;
393 181 : }else if(opt->short_name && !*opt->short_name){
394 0 : fprintf(stderr, "Crater opt error: invalid opt spec (short_name is empty string (use NULL instead))\n");
395 0 : return 0;
396 181 : }else if(opt->short_name && ((char_len = mblen(opt->short_name, MB_CUR_MAX)) == -1 || opt->short_name[char_len])){
397 0 : fprintf(stderr, "Crater opt error: invalid opt spec (short_name contains invalid multibyte character or multiple locale characters)\n");
398 0 : return 0;
399 181 : }else if(opt->short_name && strchr("-= ", *opt->short_name)){
400 0 : fprintf(stderr, "Crater opt error: invalid opt spec (short_name is '-', '=', or ' ', which are not permitted)\n");
401 0 : return 0;
402 181 : }else if(opt->long_name){
403 782 : for(uint64_t i = 0; (char_len = mblen(opt->long_name + i, MB_CUR_MAX)) > 0; i += char_len){
404 608 : if(char_len == 1 && strchr("= ", opt->long_name[i])){
405 0 : fprintf(stderr, "Crater opt error: invalid opt spec (long_name contains '=' or ' ', which are not permitted)\n");
406 0 : return 0;
407 : }
408 : }
409 7 : }else if(opt->arg_mode < 0 || opt->arg_mode > 2){
410 0 : fprintf(stderr, "Crater opt error: unknown value for arg_mode\n");
411 0 : return 0;
412 : }
413 181 : opt->found = false;
414 181 : num_long += !!opt->long_name;
415 181 : num_short += !!opt->short_name;
416 : }
417 13 : if(!cr8r_hash_init(long_opts, &cr8r_htft_cstr_u64, num_long*3)){
418 0 : fprintf(stderr, "Crater opt error: could not allocate long_opts table!\n");
419 0 : return 0;
420 : }
421 13 : if(!cr8r_hash_init(short_opts, &cr8r_htft_cstr_u64, num_short*3)){
422 0 : cr8r_hash_destroy(long_opts, &cr8r_htft_cstr_u64);
423 0 : fprintf(stderr, "Crater opt error: could not allocate short_opts table!\n");
424 0 : return 0;
425 : }
426 194 : bool success = true;
427 194 : for(uint64_t i = 0; i < num_opts; ++i){
428 181 : int status = 0;
429 181 : if(opts[i].short_name){
430 111 : cr8r_hash_insert(short_opts, &cr8r_htft_cstr_u64, &(cr8r_htent_cstr_u64){opts[i].short_name, i}, &status);
431 111 : if(status != 1){
432 0 : fprintf(stderr, "Crater opt error: %s\n", status ? "duplicate short name" : "hash_insert failed!");
433 0 : success = false;
434 0 : break;
435 : }
436 : }
437 181 : if(opts[i].long_name){
438 174 : cr8r_hash_insert(long_opts, &cr8r_htft_cstr_u64, &(cr8r_htent_cstr_u64){opts[i].long_name, i}, &status);
439 174 : if(status != 1){
440 0 : fprintf(stderr, "Crater opt error: %s\n", status ? "duplicate long name" : "hash_insert failed!");
441 0 : success = false;
442 0 : break;
443 : }
444 : }
445 : }
446 0 : if(!success){
447 0 : cr8r_hash_destroy(long_opts, &cr8r_htft_cstr_u64);
448 0 : cr8r_hash_destroy(short_opts, &cr8r_htft_cstr_u64);
449 : }
450 : return success;
451 : }
452 :
453 29 : static const char *strmbchr(const char *str, const char *mbchr){
454 159 : for(int i = 0, char_len; (char_len = mblen(str + i, MB_CUR_MAX)) > 0; i += char_len){
455 153 : if(!memcmp(str + i, mbchr, char_len)){
456 : return str + i;
457 : }
458 : }
459 : return NULL;
460 : }
461 :
462 29 : static inline bool parse_long_opt(cr8r_hashtbl_t *long_opts, cr8r_opt *opts, cr8r_opt_cfg *cfg, int argc, char **argv, int *argi){
463 29 : char *opt = NULL;
464 29 : cr8r_htent_cstr_u64 *ent = NULL;
465 29 : char *eq_chr = (char*)strmbchr(argv[*argi] + 2, "=");
466 29 : if(eq_chr){
467 23 : *eq_chr = '\0';
468 : }
469 29 : ent = cr8r_hash_get(long_opts, &cr8r_htft_cstr_u64, &(cr8r_htent_cstr_u64){.str=argv[*argi] + 2});
470 29 : if(!ent){
471 2 : fprintf(stderr, "Unrecognized option --%s\n", argv[*argi] + 2);
472 2 : if(eq_chr){
473 1 : *eq_chr = '=';
474 : }
475 2 : ++*argi;
476 2 : return 0;
477 : }
478 27 : if(eq_chr){
479 22 : *eq_chr = '=';
480 : }
481 27 : bool success = true;
482 27 : if(eq_chr){//option argument can be passed after an '=' in the same argv entry
483 22 : opt = eq_chr + 1;
484 22 : ++*argi;
485 22 : if(opts[ent->n].arg_mode == 0){
486 0 : fprintf(stderr, "Option --%s does not take an argument\n", opts[ent->n].long_name);
487 0 : success = false;
488 : }
489 5 : }else if(opts[ent->n].arg_mode == 0){
490 2 : ++*argi;
491 3 : }else if(*argi + 1 != argc && *argv[*argi + 1] != '-'){//or in the next argv entry, if it does not start with '-'
492 3 : opt = argv[*argi + 1];
493 3 : *argi += 2;
494 0 : }else if(opts[ent->n].arg_mode == 1){//or there is no option argument
495 0 : fprintf(stderr, "Option --%s missing required argument\n", opts[ent->n].long_name);
496 0 : ++*argi;
497 0 : success = false;
498 : }
499 : // if arg_mode is 0 or 2 and on_opt is not NULL, on_opt must be able to tolerate NULL opt
500 : // if arg_mode == 1, an error will be reported and on_opt will never be called if opt is NULL
501 27 : if(success && opts[ent->n].on_opt){
502 27 : if(!opts[ent->n].on_opt(opts + ent->n, opt)){
503 6 : if(opt){
504 6 : fprintf(stderr, "Invalid argument to option --%s\n", opts[ent->n].long_name);
505 : }else{
506 0 : fprintf(stderr, "Crater opt error: option --%s has arg_mode 2 but on_opt failed on NULL. arg_mode or on_opt should be changed\n", opts[ent->n].long_name);
507 : }
508 : success = false;
509 : }
510 : }
511 26 : opts[ent->n].found = true;
512 26 : return success;
513 : }
514 :
515 20 : static inline bool parse_short_optgrp(cr8r_hashtbl_t *short_opts, cr8r_opt *opts, cr8r_opt_cfg *cfg, int argc, char **argv, int *argi){
516 20 : char *opt = NULL;
517 20 : cr8r_htent_cstr_u64 *ent = NULL;
518 20 : bool success = true, reading_group = true;
519 42 : for(int j = 1, char_len; reading_group && argv[*argi][j]; j += char_len){
520 23 : if(!success && (cfg->flags & CR8R_OPTS_FATAL_ERRS)){
521 0 : return 0;
522 : }
523 23 : char_len = mblen(argv[*argi] + j, MB_CUR_MAX);
524 23 : if(strchr("-= ", argv[*argi][j]) || char_len <= 0){
525 1 : fprintf(stderr, "Invalid short option name ('-', '=', ' ', '\\n', or a bad multibyte character)\n");
526 1 : success = false;
527 2 : continue;
528 : }
529 22 : char tmp_chr = argv[*argi][j + char_len];
530 22 : argv[*argi][j + char_len] = '\0';
531 22 : ent = cr8r_hash_get(short_opts, &cr8r_htft_cstr_u64, &(cr8r_htent_cstr_u64){.str=argv[*argi] + j});
532 22 : argv[*argi][j + char_len] = tmp_chr;
533 22 : if(!ent){
534 1 : fprintf(stderr, "Unrecognized option -%.*s\n", char_len, argv[*argi] + j);
535 1 : success = false;
536 1 : continue;
537 : }
538 21 : bool curr_success = true;
539 21 : if(tmp_chr == '='){
540 13 : opt = argv[*argi] + j + char_len + 1;
541 13 : ++*argi;
542 13 : reading_group = false;
543 13 : if(opts[ent->n].arg_mode == 0){
544 0 : fprintf(stderr, "Option -%s does not take an argument\n", opts[ent->n].short_name);
545 0 : success = curr_success = false;
546 : }
547 : // if the current option accepts an argument (arg_mode is 1 or 2),
548 : // the current option is the last one in the group, and the next command line argument does
549 : // not begin with a dash
550 8 : }else if(opts[ent->n].arg_mode && !tmp_chr && *argi + 1 != argc && *argv[*argi + 1] != '-'){
551 2 : opt = argv[*argi + 1];
552 2 : *argi += 2;
553 2 : reading_group = false;
554 6 : }else if(opts[ent->n].arg_mode == 1){
555 1 : fprintf(stderr, "Option -%s missing required argument (are you missing a ' '/'='?)\n", opts[ent->n].short_name);
556 1 : success = curr_success = false;
557 : }
558 21 : if(curr_success && opts[ent->n].on_opt){
559 20 : if(!opts[ent->n].on_opt(opts + ent->n, opt)){
560 3 : if(opts[ent->n].arg_mode == 2 && !opt){
561 0 : fprintf(stderr, "Crater opt error: option -%s has arg_mode 2 but on_opt failed on NULL. arg_mode or on_opt should be changed\n", opts[ent->n].short_name);
562 : }else{
563 3 : fprintf(stderr, "Invalid argument to option -%s\n", opts[ent->n].short_name);
564 : }
565 : success = false;
566 : }
567 : }
568 20 : opts[ent->n].found = true;
569 : }
570 19 : if(reading_group){
571 4 : ++*argi;
572 : }
573 : return success;
574 : }
575 :
576 13 : bool cr8r_opt_parse(cr8r_opt *opts, cr8r_opt_cfg *cfg, int argc, char **argv){
577 13 : cr8r_hashtbl_t long_opts, short_opts;
578 13 : if(!init_opt_tbls(opts, cfg, &long_opts, &short_opts)){
579 : return 0;
580 : }
581 13 : bool success = true;
582 13 : int i = 1;
583 61 : while(i < argc){
584 50 : if(*argv[i] != '-'){
585 1 : if(!cfg->on_arg || !cfg->on_arg(cfg->data, argc, argv, i)){
586 0 : fprintf(stderr, "Failed to parse positional argument %i \"%s\"\n", i, argv[i]);
587 0 : success = false;
588 : }
589 1 : ++i;
590 49 : }else if(argv[i][1] == '-'){
591 29 : if(!argv[i][2]){//encountered "--"; done
592 0 : if(cfg->flags & CR8R_OPTS_CB_ON_DD){
593 0 : if(!cfg->on_arg || !cfg->on_arg(cfg->data, argc, argv, i)){
594 0 : fprintf(stderr, "Failed to parse positional argument %i \"--\"\n", i);
595 0 : success = false;
596 : }
597 : }
598 : break;
599 : }//otherwise we have a "--long_name" option
600 29 : success &= parse_long_opt(&long_opts, opts, cfg, argc, argv, &i);
601 20 : }else if(!argv[i][1]){//encountered "-"
602 0 : if(!(cfg->flags & CR8R_OPTS_ALLOW_STRAY_DASH)){
603 0 : fprintf(stderr, "Stray \"-\" in argv\n");
604 0 : success = false;
605 0 : }else if(!cfg->on_arg || !cfg->on_arg(cfg->data, argc, argv, i)){
606 0 : fprintf(stderr, "Failed to parse positional argument %i \"%s\"\n", i, argv[i]);
607 0 : success = false;
608 : }
609 0 : ++i;
610 : }else{
611 20 : success &= parse_short_optgrp(&short_opts, opts, cfg, argc, argv, &i);
612 : }
613 48 : if(!success && (cfg->flags & CR8R_OPTS_FATAL_ERRS)){
614 0 : cr8r_hash_destroy(&long_opts, &cr8r_htft_cstr_u64);
615 0 : cr8r_hash_destroy(&short_opts, &cr8r_htft_cstr_u64);
616 0 : return 0;
617 : }
618 : }
619 11 : cr8r_hash_destroy(&long_opts, &cr8r_htft_cstr_u64);
620 11 : cr8r_hash_destroy(&short_opts, &cr8r_htft_cstr_u64);
621 165 : for(cr8r_opt *opt = opts; opt->short_name || opt->long_name; ++opt){
622 154 : if(!opt->found && opt->on_missing){
623 113 : if(!opt->on_missing(opt)){
624 0 : if(opt->short_name){
625 0 : if(opt->long_name){
626 0 : fprintf(stderr, "Crater opt error: Option -%s/--%s was not provided but its on_missing callback failed\n", opt->short_name, opt->long_name);
627 : }else{
628 0 : fprintf(stderr, "Crater opt error: Option -%s was not provided but its on_missing callback failed\n", opt->short_name);
629 : }
630 : }else{
631 0 : fprintf(stderr, "Crater opt error: Option --%s was not provided but its on_missing callback failed\n", opt->long_name);
632 : }
633 : success = false;
634 : }
635 : }
636 154 : if(!opt->found && !opt->on_missing){
637 0 : if(opt->short_name){
638 0 : if(opt->long_name){
639 0 : fprintf(stderr, "Missing required option -%s/--%s\n", opt->short_name, opt->long_name);
640 : }else{
641 0 : fprintf(stderr, "Missing required option -%s\n", opt->short_name);
642 : }
643 : }else{
644 0 : fprintf(stderr, "Missing required option --%s\n", opt->long_name);
645 : }
646 : success = false;
647 : }
648 154 : if(!success && (cfg->flags & CR8R_OPTS_FATAL_ERRS)){
649 : return 0;
650 : }
651 : }
652 11 : for(; i < argc; ++i){
653 0 : if(!cfg->on_arg || !cfg->on_arg(cfg->data, argc, argv, i)){
654 0 : fprintf(stderr, "Failed to parse positional argument %i \"%s\"\n", i, argv[i]);
655 0 : success = false;
656 : }
657 : }
658 : return success;
659 : }
660 :
|