]> git.proxmox.com Git - libgit2.git/blob - src/cli/opt.c
New upstream version 1.5.0+ds
[libgit2.git] / src / cli / opt.c
1 /*
2 * Copyright (c), Edward Thomson <ethomson@edwardthomson.com>
3 * All rights reserved.
4 *
5 * This file is part of adopt, distributed under the MIT license.
6 * For full terms and conditions, see the included LICENSE file.
7 *
8 * THIS FILE IS AUTOMATICALLY GENERATED; DO NOT EDIT.
9 *
10 * This file was produced by using the `rename.pl` script included with
11 * adopt. The command-line specified was:
12 *
13 * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage
14 */
15
16 #include <stdlib.h>
17 #include <string.h>
18 #include <stdio.h>
19 #include <limits.h>
20 #include <assert.h>
21
22 #include "cli.h"
23 #include "opt.h"
24
25 #ifdef _WIN32
26 # include <Windows.h>
27 #else
28 # include <fcntl.h>
29 # include <sys/ioctl.h>
30 #endif
31
32 #ifdef _MSC_VER
33 # define alloca _alloca
34 #endif
35
36 #define spec_is_option_type(x) \
37 ((x)->type == CLI_OPT_TYPE_BOOL || \
38 (x)->type == CLI_OPT_TYPE_SWITCH || \
39 (x)->type == CLI_OPT_TYPE_VALUE)
40
41 GIT_INLINE(const cli_opt_spec *) spec_for_long(
42 int *is_negated,
43 int *has_value,
44 const char **value,
45 const cli_opt_parser *parser,
46 const char *arg)
47 {
48 const cli_opt_spec *spec;
49 char *eql;
50 size_t eql_pos;
51
52 eql = strchr(arg, '=');
53 eql_pos = (eql = strchr(arg, '=')) ? (size_t)(eql - arg) : strlen(arg);
54
55 for (spec = parser->specs; spec->type; ++spec) {
56 /* Handle -- (everything after this is literal) */
57 if (spec->type == CLI_OPT_TYPE_LITERAL && arg[0] == '\0')
58 return spec;
59
60 /* Handle --no-option arguments for bool types */
61 if (spec->type == CLI_OPT_TYPE_BOOL &&
62 strncmp(arg, "no-", 3) == 0 &&
63 strcmp(arg + 3, spec->name) == 0) {
64 *is_negated = 1;
65 return spec;
66 }
67
68 /* Handle the typical --option arguments */
69 if (spec_is_option_type(spec) &&
70 spec->name &&
71 strcmp(arg, spec->name) == 0)
72 return spec;
73
74 /* Handle --option=value arguments */
75 if (spec->type == CLI_OPT_TYPE_VALUE &&
76 eql &&
77 strncmp(arg, spec->name, eql_pos) == 0 &&
78 spec->name[eql_pos] == '\0') {
79 *has_value = 1;
80 *value = arg[eql_pos + 1] ? &arg[eql_pos + 1] : NULL;
81 return spec;
82 }
83 }
84
85 return NULL;
86 }
87
88 GIT_INLINE(const cli_opt_spec *) spec_for_short(
89 const char **value,
90 const cli_opt_parser *parser,
91 const char *arg)
92 {
93 const cli_opt_spec *spec;
94
95 for (spec = parser->specs; spec->type; ++spec) {
96 /* Handle -svalue short options with a value */
97 if (spec->type == CLI_OPT_TYPE_VALUE &&
98 arg[0] == spec->alias &&
99 arg[1] != '\0') {
100 *value = &arg[1];
101 return spec;
102 }
103
104 /* Handle typical -s short options */
105 if (arg[0] == spec->alias) {
106 *value = NULL;
107 return spec;
108 }
109 }
110
111 return NULL;
112 }
113
114 GIT_INLINE(const cli_opt_spec *) spec_for_arg(cli_opt_parser *parser)
115 {
116 const cli_opt_spec *spec;
117 size_t args = 0;
118
119 for (spec = parser->specs; spec->type; ++spec) {
120 if (spec->type == CLI_OPT_TYPE_ARG) {
121 if (args == parser->arg_idx) {
122 parser->arg_idx++;
123 return spec;
124 }
125
126 args++;
127 }
128
129 if (spec->type == CLI_OPT_TYPE_ARGS && args == parser->arg_idx)
130 return spec;
131 }
132
133 return NULL;
134 }
135
136 GIT_INLINE(int) spec_is_choice(const cli_opt_spec *spec)
137 {
138 return ((spec + 1)->type &&
139 ((spec + 1)->usage & CLI_OPT_USAGE_CHOICE));
140 }
141
142 /*
143 * If we have a choice with switches and bare arguments, and we see
144 * the switch, then we no longer expect the bare argument.
145 */
146 GIT_INLINE(void) consume_choices(const cli_opt_spec *spec, cli_opt_parser *parser)
147 {
148 /* back up to the beginning of the choices */
149 while (spec->type && (spec->usage & CLI_OPT_USAGE_CHOICE))
150 --spec;
151
152 if (!spec_is_choice(spec))
153 return;
154
155 do {
156 if (spec->type == CLI_OPT_TYPE_ARG)
157 parser->arg_idx++;
158 ++spec;
159 } while(spec->type && (spec->usage & CLI_OPT_USAGE_CHOICE));
160 }
161
162 static cli_opt_status_t parse_long(cli_opt *opt, cli_opt_parser *parser)
163 {
164 const cli_opt_spec *spec;
165 char *arg = parser->args[parser->idx++];
166 const char *value = NULL;
167 int is_negated = 0, has_value = 0;
168
169 opt->arg = arg;
170
171 if ((spec = spec_for_long(&is_negated, &has_value, &value, parser, &arg[2])) == NULL) {
172 opt->spec = NULL;
173 opt->status = CLI_OPT_STATUS_UNKNOWN_OPTION;
174 goto done;
175 }
176
177 opt->spec = spec;
178
179 /* Future options parsed as literal */
180 if (spec->type == CLI_OPT_TYPE_LITERAL)
181 parser->in_literal = 1;
182
183 /* --bool or --no-bool */
184 else if (spec->type == CLI_OPT_TYPE_BOOL && spec->value)
185 *((int *)spec->value) = !is_negated;
186
187 /* --accumulate */
188 else if (spec->type == CLI_OPT_TYPE_ACCUMULATOR && spec->value)
189 *((int *)spec->value) += spec->switch_value ? spec->switch_value : 1;
190
191 /* --switch */
192 else if (spec->type == CLI_OPT_TYPE_SWITCH && spec->value)
193 *((int *)spec->value) = spec->switch_value;
194
195 /* Parse values as "--foo=bar" or "--foo bar" */
196 else if (spec->type == CLI_OPT_TYPE_VALUE) {
197 if (has_value)
198 opt->value = (char *)value;
199 else if ((parser->idx + 1) <= parser->args_len)
200 opt->value = parser->args[parser->idx++];
201
202 if (spec->value)
203 *((char **)spec->value) = opt->value;
204 }
205
206 /* Required argument was not provided */
207 if (spec->type == CLI_OPT_TYPE_VALUE &&
208 !opt->value &&
209 !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL))
210 opt->status = CLI_OPT_STATUS_MISSING_VALUE;
211 else
212 opt->status = CLI_OPT_STATUS_OK;
213
214 consume_choices(opt->spec, parser);
215
216 done:
217 return opt->status;
218 }
219
220 static cli_opt_status_t parse_short(cli_opt *opt, cli_opt_parser *parser)
221 {
222 const cli_opt_spec *spec;
223 char *arg = parser->args[parser->idx++];
224 const char *value;
225
226 opt->arg = arg;
227
228 if ((spec = spec_for_short(&value, parser, &arg[1 + parser->in_short])) == NULL) {
229 opt->spec = NULL;
230 opt->status = CLI_OPT_STATUS_UNKNOWN_OPTION;
231 goto done;
232 }
233
234 opt->spec = spec;
235
236 if (spec->type == CLI_OPT_TYPE_BOOL && spec->value)
237 *((int *)spec->value) = 1;
238
239 else if (spec->type == CLI_OPT_TYPE_ACCUMULATOR && spec->value)
240 *((int *)spec->value) += spec->switch_value ? spec->switch_value : 1;
241
242 else if (spec->type == CLI_OPT_TYPE_SWITCH && spec->value)
243 *((int *)spec->value) = spec->switch_value;
244
245 /* Parse values as "-ifoo" or "-i foo" */
246 else if (spec->type == CLI_OPT_TYPE_VALUE) {
247 if (value)
248 opt->value = (char *)value;
249 else if ((parser->idx + 1) <= parser->args_len)
250 opt->value = parser->args[parser->idx++];
251
252 if (spec->value)
253 *((char **)spec->value) = opt->value;
254 }
255
256 /*
257 * Handle compressed short arguments, like "-fbcd"; see if there's
258 * another character after the one we processed. If not, advance
259 * the parser index.
260 */
261 if (spec->type != CLI_OPT_TYPE_VALUE && arg[2 + parser->in_short] != '\0') {
262 parser->in_short++;
263 parser->idx--;
264 } else {
265 parser->in_short = 0;
266 }
267
268 /* Required argument was not provided */
269 if (spec->type == CLI_OPT_TYPE_VALUE && !opt->value)
270 opt->status = CLI_OPT_STATUS_MISSING_VALUE;
271 else
272 opt->status = CLI_OPT_STATUS_OK;
273
274 consume_choices(opt->spec, parser);
275
276 done:
277 return opt->status;
278 }
279
280 static cli_opt_status_t parse_arg(cli_opt *opt, cli_opt_parser *parser)
281 {
282 const cli_opt_spec *spec = spec_for_arg(parser);
283
284 opt->spec = spec;
285 opt->arg = parser->args[parser->idx];
286
287 if (!spec) {
288 parser->idx++;
289 opt->status = CLI_OPT_STATUS_UNKNOWN_OPTION;
290 } else if (spec->type == CLI_OPT_TYPE_ARGS) {
291 if (spec->value)
292 *((char ***)spec->value) = &parser->args[parser->idx];
293
294 /*
295 * We have started a list of arguments; the remainder of
296 * given arguments need not be examined.
297 */
298 parser->in_args = (parser->args_len - parser->idx);
299 parser->idx = parser->args_len;
300 opt->args_len = parser->in_args;
301 opt->status = CLI_OPT_STATUS_OK;
302 } else {
303 if (spec->value)
304 *((char **)spec->value) = parser->args[parser->idx];
305
306 parser->idx++;
307 opt->status = CLI_OPT_STATUS_OK;
308 }
309
310 return opt->status;
311 }
312
313 static int support_gnu_style(unsigned int flags)
314 {
315 if ((flags & CLI_OPT_PARSE_FORCE_GNU) != 0)
316 return 1;
317
318 if ((flags & CLI_OPT_PARSE_GNU) == 0)
319 return 0;
320
321 /* TODO: Windows */
322 #if defined(_WIN32) && defined(UNICODE)
323 if (_wgetenv(L"POSIXLY_CORRECT") != NULL)
324 return 0;
325 #else
326 if (getenv("POSIXLY_CORRECT") != NULL)
327 return 0;
328 #endif
329
330 return 1;
331 }
332
333 void cli_opt_parser_init(
334 cli_opt_parser *parser,
335 const cli_opt_spec specs[],
336 char **args,
337 size_t args_len,
338 unsigned int flags)
339 {
340 assert(parser);
341
342 memset(parser, 0x0, sizeof(cli_opt_parser));
343
344 parser->specs = specs;
345 parser->args = args;
346 parser->args_len = args_len;
347 parser->flags = flags;
348
349 parser->needs_sort = support_gnu_style(flags);
350 }
351
352 GIT_INLINE(const cli_opt_spec *) spec_for_sort(
353 int *needs_value,
354 const cli_opt_parser *parser,
355 const char *arg)
356 {
357 int is_negated, has_value = 0;
358 const char *value;
359 const cli_opt_spec *spec = NULL;
360 size_t idx = 0;
361
362 *needs_value = 0;
363
364 if (strncmp(arg, "--", 2) == 0) {
365 spec = spec_for_long(&is_negated, &has_value, &value, parser, &arg[2]);
366 *needs_value = !has_value;
367 }
368
369 else if (strncmp(arg, "-", 1) == 0) {
370 spec = spec_for_short(&value, parser, &arg[1]);
371
372 /*
373 * Advance through compressed short arguments to see if
374 * the last one has a value, eg "-xvffilename".
375 */
376 while (spec && !value && arg[1 + ++idx] != '\0')
377 spec = spec_for_short(&value, parser, &arg[1 + idx]);
378
379 *needs_value = (value == NULL);
380 }
381
382 return spec;
383 }
384
385 /*
386 * Some parsers allow for handling arguments like "file1 --help file2";
387 * this is done by re-sorting the arguments in-place; emulate that.
388 */
389 static int sort_gnu_style(cli_opt_parser *parser)
390 {
391 size_t i, j, insert_idx = parser->idx, offset;
392 const cli_opt_spec *spec;
393 char *option, *value;
394 int needs_value, changed = 0;
395
396 parser->needs_sort = 0;
397
398 for (i = parser->idx; i < parser->args_len; i++) {
399 spec = spec_for_sort(&needs_value, parser, parser->args[i]);
400
401 /* Not a "-" or "--" prefixed option. No change. */
402 if (!spec)
403 continue;
404
405 /* A "--" alone means remaining args are literal. */
406 if (spec->type == CLI_OPT_TYPE_LITERAL)
407 break;
408
409 option = parser->args[i];
410
411 /*
412 * If the argument is a value type and doesn't already
413 * have a value (eg "--foo=bar" or "-fbar") then we need
414 * to copy the next argument as its value.
415 */
416 if (spec->type == CLI_OPT_TYPE_VALUE && needs_value) {
417 /*
418 * A required value is not provided; set parser
419 * index to this value so that we fail on it.
420 */
421 if (i + 1 >= parser->args_len) {
422 parser->idx = i;
423 return 1;
424 }
425
426 value = parser->args[i + 1];
427 offset = 1;
428 } else {
429 value = NULL;
430 offset = 0;
431 }
432
433 /* Caller error if args[0] is an option. */
434 if (i == 0)
435 return 0;
436
437 /* Shift args up one (or two) and insert the option */
438 for (j = i; j > insert_idx; j--)
439 parser->args[j + offset] = parser->args[j - 1];
440
441 parser->args[insert_idx] = option;
442
443 if (value)
444 parser->args[insert_idx + 1] = value;
445
446 insert_idx += (1 + offset);
447 i += offset;
448
449 changed = 1;
450 }
451
452 return changed;
453 }
454
455 cli_opt_status_t cli_opt_parser_next(cli_opt *opt, cli_opt_parser *parser)
456 {
457 assert(opt && parser);
458
459 memset(opt, 0x0, sizeof(cli_opt));
460
461 if (parser->idx >= parser->args_len) {
462 opt->args_len = parser->in_args;
463 return CLI_OPT_STATUS_DONE;
464 }
465
466 /* Handle options in long form, those beginning with "--" */
467 if (strncmp(parser->args[parser->idx], "--", 2) == 0 &&
468 !parser->in_short &&
469 !parser->in_literal)
470 return parse_long(opt, parser);
471
472 /* Handle options in short form, those beginning with "-" */
473 else if (parser->in_short ||
474 (strncmp(parser->args[parser->idx], "-", 1) == 0 &&
475 !parser->in_literal))
476 return parse_short(opt, parser);
477
478 /*
479 * We've reached the first "bare" argument. In POSIX mode, all
480 * remaining items on the command line are arguments. In GNU
481 * mode, there may be long or short options after this. Sort any
482 * options up to this position then re-parse the current position.
483 */
484 if (parser->needs_sort && sort_gnu_style(parser))
485 return cli_opt_parser_next(opt, parser);
486
487 return parse_arg(opt, parser);
488 }
489
490 GIT_INLINE(int) spec_included(const cli_opt_spec **specs, const cli_opt_spec *spec)
491 {
492 const cli_opt_spec **i;
493
494 for (i = specs; *i; ++i) {
495 if (spec == *i)
496 return 1;
497 }
498
499 return 0;
500 }
501
502 static cli_opt_status_t validate_required(
503 cli_opt *opt,
504 const cli_opt_spec specs[],
505 const cli_opt_spec **given_specs)
506 {
507 const cli_opt_spec *spec, *required;
508 int given;
509
510 /*
511 * Iterate over the possible specs to identify requirements and
512 * ensure that those have been given on the command-line.
513 * Note that we can have required *choices*, where one in a
514 * list of choices must be specified.
515 */
516 for (spec = specs, required = NULL, given = 0; spec->type; ++spec) {
517 if (!required && (spec->usage & CLI_OPT_USAGE_REQUIRED)) {
518 required = spec;
519 given = 0;
520 } else if (!required) {
521 continue;
522 }
523
524 if (!given)
525 given = spec_included(given_specs, spec);
526
527 /*
528 * Validate the requirement unless we're in a required
529 * choice. In that case, keep the required state and
530 * validate at the end of the choice list.
531 */
532 if (!spec_is_choice(spec)) {
533 if (!given) {
534 opt->spec = required;
535 opt->status = CLI_OPT_STATUS_MISSING_ARGUMENT;
536 break;
537 }
538
539 required = NULL;
540 given = 0;
541 }
542 }
543
544 return opt->status;
545 }
546
547 cli_opt_status_t cli_opt_parse(
548 cli_opt *opt,
549 const cli_opt_spec specs[],
550 char **args,
551 size_t args_len,
552 unsigned int flags)
553 {
554 cli_opt_parser parser;
555 const cli_opt_spec **given_specs;
556 size_t given_idx = 0;
557
558 cli_opt_parser_init(&parser, specs, args, args_len, flags);
559
560 given_specs = alloca(sizeof(const cli_opt_spec *) * (args_len + 1));
561
562 while (cli_opt_parser_next(opt, &parser)) {
563 if (opt->status != CLI_OPT_STATUS_OK &&
564 opt->status != CLI_OPT_STATUS_DONE)
565 return opt->status;
566
567 if ((opt->spec->usage & CLI_OPT_USAGE_STOP_PARSING))
568 return (opt->status = CLI_OPT_STATUS_DONE);
569
570 given_specs[given_idx++] = opt->spec;
571 }
572
573 given_specs[given_idx] = NULL;
574
575 return validate_required(opt, specs, given_specs);
576 }
577
578 static int spec_name_fprint(FILE *file, const cli_opt_spec *spec)
579 {
580 int error;
581
582 if (spec->type == CLI_OPT_TYPE_ARG)
583 error = fprintf(file, "%s", spec->value_name);
584 else if (spec->type == CLI_OPT_TYPE_ARGS)
585 error = fprintf(file, "%s", spec->value_name);
586 else if (spec->alias && !(spec->usage & CLI_OPT_USAGE_SHOW_LONG))
587 error = fprintf(file, "-%c", spec->alias);
588 else
589 error = fprintf(file, "--%s", spec->name);
590
591 return error;
592 }
593
594 int cli_opt_status_fprint(
595 FILE *file,
596 const char *command,
597 const cli_opt *opt)
598 {
599 const cli_opt_spec *choice;
600 int error;
601
602 if (command && (error = fprintf(file, "%s: ", command)) < 0)
603 return error;
604
605 switch (opt->status) {
606 case CLI_OPT_STATUS_DONE:
607 error = fprintf(file, "finished processing arguments (no error)\n");
608 break;
609 case CLI_OPT_STATUS_OK:
610 error = fprintf(file, "no error\n");
611 break;
612 case CLI_OPT_STATUS_UNKNOWN_OPTION:
613 error = fprintf(file, "unknown option: %s\n", opt->arg);
614 break;
615 case CLI_OPT_STATUS_MISSING_VALUE:
616 if ((error = fprintf(file, "argument '")) < 0 ||
617 (error = spec_name_fprint(file, opt->spec)) < 0 ||
618 (error = fprintf(file, "' requires a value.\n")) < 0)
619 break;
620 break;
621 case CLI_OPT_STATUS_MISSING_ARGUMENT:
622 if (spec_is_choice(opt->spec)) {
623 int is_choice = 1;
624
625 if (spec_is_choice((opt->spec)+1))
626 error = fprintf(file, "one of");
627 else
628 error = fprintf(file, "either");
629
630 if (error < 0)
631 break;
632
633 for (choice = opt->spec; is_choice; ++choice) {
634 is_choice = spec_is_choice(choice);
635
636 if (!is_choice)
637 error = fprintf(file, " or");
638 else if (choice != opt->spec)
639 error = fprintf(file, ",");
640
641 if ((error < 0) ||
642 (error = fprintf(file, " '")) < 0 ||
643 (error = spec_name_fprint(file, choice)) < 0 ||
644 (error = fprintf(file, "'")) < 0)
645 break;
646
647 if (!spec_is_choice(choice))
648 break;
649 }
650
651 if ((error < 0) ||
652 (error = fprintf(file, " is required.\n")) < 0)
653 break;
654 } else {
655 if ((error = fprintf(file, "argument '")) < 0 ||
656 (error = spec_name_fprint(file, opt->spec)) < 0 ||
657 (error = fprintf(file, "' is required.\n")) < 0)
658 break;
659 }
660
661 break;
662 default:
663 error = fprintf(file, "unknown status: %d\n", opt->status);
664 break;
665 }
666
667 return error;
668 }
669