summaryrefslogtreecommitdiffstats
path: root/src/cli/opt.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cli/opt.c')
-rw-r--r--src/cli/opt.c669
1 files changed, 669 insertions, 0 deletions
diff --git a/src/cli/opt.c b/src/cli/opt.c
new file mode 100644
index 0000000..62a3430
--- /dev/null
+++ b/src/cli/opt.c
@@ -0,0 +1,669 @@
+/*
+ * Copyright (c), Edward Thomson <ethomson@edwardthomson.com>
+ * All rights reserved.
+ *
+ * This file is part of adopt, distributed under the MIT license.
+ * For full terms and conditions, see the included LICENSE file.
+ *
+ * THIS FILE IS AUTOMATICALLY GENERATED; DO NOT EDIT.
+ *
+ * This file was produced by using the `rename.pl` script included with
+ * adopt. The command-line specified was:
+ *
+ * ./rename.pl cli_opt --filename=opt --include=cli.h --inline=GIT_INLINE --header-guard=CLI_opt_h__ --lowercase-status --without-usage
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <limits.h>
+#include <assert.h>
+
+#include "cli.h"
+#include "opt.h"
+
+#ifdef _WIN32
+# include <windows.h>
+#else
+# include <fcntl.h>
+# include <sys/ioctl.h>
+#endif
+
+#ifdef _MSC_VER
+# define alloca _alloca
+#endif
+
+#define spec_is_option_type(x) \
+ ((x)->type == CLI_OPT_TYPE_BOOL || \
+ (x)->type == CLI_OPT_TYPE_SWITCH || \
+ (x)->type == CLI_OPT_TYPE_VALUE)
+
+GIT_INLINE(const cli_opt_spec *) spec_for_long(
+ int *is_negated,
+ int *has_value,
+ const char **value,
+ const cli_opt_parser *parser,
+ const char *arg)
+{
+ const cli_opt_spec *spec;
+ char *eql;
+ size_t eql_pos;
+
+ eql = strchr(arg, '=');
+ eql_pos = (eql = strchr(arg, '=')) ? (size_t)(eql - arg) : strlen(arg);
+
+ for (spec = parser->specs; spec->type; ++spec) {
+ /* Handle -- (everything after this is literal) */
+ if (spec->type == CLI_OPT_TYPE_LITERAL && arg[0] == '\0')
+ return spec;
+
+ /* Handle --no-option arguments for bool types */
+ if (spec->type == CLI_OPT_TYPE_BOOL &&
+ strncmp(arg, "no-", 3) == 0 &&
+ strcmp(arg + 3, spec->name) == 0) {
+ *is_negated = 1;
+ return spec;
+ }
+
+ /* Handle the typical --option arguments */
+ if (spec_is_option_type(spec) &&
+ spec->name &&
+ strcmp(arg, spec->name) == 0)
+ return spec;
+
+ /* Handle --option=value arguments */
+ if (spec->type == CLI_OPT_TYPE_VALUE &&
+ eql &&
+ strncmp(arg, spec->name, eql_pos) == 0 &&
+ spec->name[eql_pos] == '\0') {
+ *has_value = 1;
+ *value = arg[eql_pos + 1] ? &arg[eql_pos + 1] : NULL;
+ return spec;
+ }
+ }
+
+ return NULL;
+}
+
+GIT_INLINE(const cli_opt_spec *) spec_for_short(
+ const char **value,
+ const cli_opt_parser *parser,
+ const char *arg)
+{
+ const cli_opt_spec *spec;
+
+ for (spec = parser->specs; spec->type; ++spec) {
+ /* Handle -svalue short options with a value */
+ if (spec->type == CLI_OPT_TYPE_VALUE &&
+ arg[0] == spec->alias &&
+ arg[1] != '\0') {
+ *value = &arg[1];
+ return spec;
+ }
+
+ /* Handle typical -s short options */
+ if (arg[0] == spec->alias) {
+ *value = NULL;
+ return spec;
+ }
+ }
+
+ return NULL;
+}
+
+GIT_INLINE(const cli_opt_spec *) spec_for_arg(cli_opt_parser *parser)
+{
+ const cli_opt_spec *spec;
+ size_t args = 0;
+
+ for (spec = parser->specs; spec->type; ++spec) {
+ if (spec->type == CLI_OPT_TYPE_ARG) {
+ if (args == parser->arg_idx) {
+ parser->arg_idx++;
+ return spec;
+ }
+
+ args++;
+ }
+
+ if (spec->type == CLI_OPT_TYPE_ARGS && args == parser->arg_idx)
+ return spec;
+ }
+
+ return NULL;
+}
+
+GIT_INLINE(int) spec_is_choice(const cli_opt_spec *spec)
+{
+ return ((spec + 1)->type &&
+ ((spec + 1)->usage & CLI_OPT_USAGE_CHOICE));
+}
+
+/*
+ * If we have a choice with switches and bare arguments, and we see
+ * the switch, then we no longer expect the bare argument.
+ */
+GIT_INLINE(void) consume_choices(const cli_opt_spec *spec, cli_opt_parser *parser)
+{
+ /* back up to the beginning of the choices */
+ while (spec->type && (spec->usage & CLI_OPT_USAGE_CHOICE))
+ --spec;
+
+ if (!spec_is_choice(spec))
+ return;
+
+ do {
+ if (spec->type == CLI_OPT_TYPE_ARG)
+ parser->arg_idx++;
+ ++spec;
+ } while(spec->type && (spec->usage & CLI_OPT_USAGE_CHOICE));
+}
+
+static cli_opt_status_t parse_long(cli_opt *opt, cli_opt_parser *parser)
+{
+ const cli_opt_spec *spec;
+ char *arg = parser->args[parser->idx++];
+ const char *value = NULL;
+ int is_negated = 0, has_value = 0;
+
+ opt->arg = arg;
+
+ if ((spec = spec_for_long(&is_negated, &has_value, &value, parser, &arg[2])) == NULL) {
+ opt->spec = NULL;
+ opt->status = CLI_OPT_STATUS_UNKNOWN_OPTION;
+ goto done;
+ }
+
+ opt->spec = spec;
+
+ /* Future options parsed as literal */
+ if (spec->type == CLI_OPT_TYPE_LITERAL)
+ parser->in_literal = 1;
+
+ /* --bool or --no-bool */
+ else if (spec->type == CLI_OPT_TYPE_BOOL && spec->value)
+ *((int *)spec->value) = !is_negated;
+
+ /* --accumulate */
+ else if (spec->type == CLI_OPT_TYPE_ACCUMULATOR && spec->value)
+ *((int *)spec->value) += spec->switch_value ? spec->switch_value : 1;
+
+ /* --switch */
+ else if (spec->type == CLI_OPT_TYPE_SWITCH && spec->value)
+ *((int *)spec->value) = spec->switch_value;
+
+ /* Parse values as "--foo=bar" or "--foo bar" */
+ else if (spec->type == CLI_OPT_TYPE_VALUE) {
+ if (has_value)
+ opt->value = (char *)value;
+ else if ((parser->idx + 1) <= parser->args_len)
+ opt->value = parser->args[parser->idx++];
+
+ if (spec->value)
+ *((char **)spec->value) = opt->value;
+ }
+
+ /* Required argument was not provided */
+ if (spec->type == CLI_OPT_TYPE_VALUE &&
+ !opt->value &&
+ !(spec->usage & CLI_OPT_USAGE_VALUE_OPTIONAL))
+ opt->status = CLI_OPT_STATUS_MISSING_VALUE;
+ else
+ opt->status = CLI_OPT_STATUS_OK;
+
+ consume_choices(opt->spec, parser);
+
+done:
+ return opt->status;
+}
+
+static cli_opt_status_t parse_short(cli_opt *opt, cli_opt_parser *parser)
+{
+ const cli_opt_spec *spec;
+ char *arg = parser->args[parser->idx++];
+ const char *value;
+
+ opt->arg = arg;
+
+ if ((spec = spec_for_short(&value, parser, &arg[1 + parser->in_short])) == NULL) {
+ opt->spec = NULL;
+ opt->status = CLI_OPT_STATUS_UNKNOWN_OPTION;
+ goto done;
+ }
+
+ opt->spec = spec;
+
+ if (spec->type == CLI_OPT_TYPE_BOOL && spec->value)
+ *((int *)spec->value) = 1;
+
+ else if (spec->type == CLI_OPT_TYPE_ACCUMULATOR && spec->value)
+ *((int *)spec->value) += spec->switch_value ? spec->switch_value : 1;
+
+ else if (spec->type == CLI_OPT_TYPE_SWITCH && spec->value)
+ *((int *)spec->value) = spec->switch_value;
+
+ /* Parse values as "-ifoo" or "-i foo" */
+ else if (spec->type == CLI_OPT_TYPE_VALUE) {
+ if (value)
+ opt->value = (char *)value;
+ else if ((parser->idx + 1) <= parser->args_len)
+ opt->value = parser->args[parser->idx++];
+
+ if (spec->value)
+ *((char **)spec->value) = opt->value;
+ }
+
+ /*
+ * Handle compressed short arguments, like "-fbcd"; see if there's
+ * another character after the one we processed. If not, advance
+ * the parser index.
+ */
+ if (spec->type != CLI_OPT_TYPE_VALUE && arg[2 + parser->in_short] != '\0') {
+ parser->in_short++;
+ parser->idx--;
+ } else {
+ parser->in_short = 0;
+ }
+
+ /* Required argument was not provided */
+ if (spec->type == CLI_OPT_TYPE_VALUE && !opt->value)
+ opt->status = CLI_OPT_STATUS_MISSING_VALUE;
+ else
+ opt->status = CLI_OPT_STATUS_OK;
+
+ consume_choices(opt->spec, parser);
+
+done:
+ return opt->status;
+}
+
+static cli_opt_status_t parse_arg(cli_opt *opt, cli_opt_parser *parser)
+{
+ const cli_opt_spec *spec = spec_for_arg(parser);
+
+ opt->spec = spec;
+ opt->arg = parser->args[parser->idx];
+
+ if (!spec) {
+ parser->idx++;
+ opt->status = CLI_OPT_STATUS_UNKNOWN_OPTION;
+ } else if (spec->type == CLI_OPT_TYPE_ARGS) {
+ if (spec->value)
+ *((char ***)spec->value) = &parser->args[parser->idx];
+
+ /*
+ * We have started a list of arguments; the remainder of
+ * given arguments need not be examined.
+ */
+ parser->in_args = (parser->args_len - parser->idx);
+ parser->idx = parser->args_len;
+ opt->args_len = parser->in_args;
+ opt->status = CLI_OPT_STATUS_OK;
+ } else {
+ if (spec->value)
+ *((char **)spec->value) = parser->args[parser->idx];
+
+ parser->idx++;
+ opt->status = CLI_OPT_STATUS_OK;
+ }
+
+ return opt->status;
+}
+
+static int support_gnu_style(unsigned int flags)
+{
+ if ((flags & CLI_OPT_PARSE_FORCE_GNU) != 0)
+ return 1;
+
+ if ((flags & CLI_OPT_PARSE_GNU) == 0)
+ return 0;
+
+ /* TODO: Windows */
+#if defined(_WIN32) && defined(UNICODE)
+ if (_wgetenv(L"POSIXLY_CORRECT") != NULL)
+ return 0;
+#else
+ if (getenv("POSIXLY_CORRECT") != NULL)
+ return 0;
+#endif
+
+ return 1;
+}
+
+void cli_opt_parser_init(
+ cli_opt_parser *parser,
+ const cli_opt_spec specs[],
+ char **args,
+ size_t args_len,
+ unsigned int flags)
+{
+ assert(parser);
+
+ memset(parser, 0x0, sizeof(cli_opt_parser));
+
+ parser->specs = specs;
+ parser->args = args;
+ parser->args_len = args_len;
+ parser->flags = flags;
+
+ parser->needs_sort = support_gnu_style(flags);
+}
+
+GIT_INLINE(const cli_opt_spec *) spec_for_sort(
+ int *needs_value,
+ const cli_opt_parser *parser,
+ const char *arg)
+{
+ int is_negated, has_value = 0;
+ const char *value;
+ const cli_opt_spec *spec = NULL;
+ size_t idx = 0;
+
+ *needs_value = 0;
+
+ if (strncmp(arg, "--", 2) == 0) {
+ spec = spec_for_long(&is_negated, &has_value, &value, parser, &arg[2]);
+ *needs_value = !has_value;
+ }
+
+ else if (strncmp(arg, "-", 1) == 0) {
+ spec = spec_for_short(&value, parser, &arg[1]);
+
+ /*
+ * Advance through compressed short arguments to see if
+ * the last one has a value, eg "-xvffilename".
+ */
+ while (spec && !value && arg[1 + ++idx] != '\0')
+ spec = spec_for_short(&value, parser, &arg[1 + idx]);
+
+ *needs_value = (value == NULL);
+ }
+
+ return spec;
+}
+
+/*
+ * Some parsers allow for handling arguments like "file1 --help file2";
+ * this is done by re-sorting the arguments in-place; emulate that.
+ */
+static int sort_gnu_style(cli_opt_parser *parser)
+{
+ size_t i, j, insert_idx = parser->idx, offset;
+ const cli_opt_spec *spec;
+ char *option, *value;
+ int needs_value, changed = 0;
+
+ parser->needs_sort = 0;
+
+ for (i = parser->idx; i < parser->args_len; i++) {
+ spec = spec_for_sort(&needs_value, parser, parser->args[i]);
+
+ /* Not a "-" or "--" prefixed option. No change. */
+ if (!spec)
+ continue;
+
+ /* A "--" alone means remaining args are literal. */
+ if (spec->type == CLI_OPT_TYPE_LITERAL)
+ break;
+
+ option = parser->args[i];
+
+ /*
+ * If the argument is a value type and doesn't already
+ * have a value (eg "--foo=bar" or "-fbar") then we need
+ * to copy the next argument as its value.
+ */
+ if (spec->type == CLI_OPT_TYPE_VALUE && needs_value) {
+ /*
+ * A required value is not provided; set parser
+ * index to this value so that we fail on it.
+ */
+ if (i + 1 >= parser->args_len) {
+ parser->idx = i;
+ return 1;
+ }
+
+ value = parser->args[i + 1];
+ offset = 1;
+ } else {
+ value = NULL;
+ offset = 0;
+ }
+
+ /* Caller error if args[0] is an option. */
+ if (i == 0)
+ return 0;
+
+ /* Shift args up one (or two) and insert the option */
+ for (j = i; j > insert_idx; j--)
+ parser->args[j + offset] = parser->args[j - 1];
+
+ parser->args[insert_idx] = option;
+
+ if (value)
+ parser->args[insert_idx + 1] = value;
+
+ insert_idx += (1 + offset);
+ i += offset;
+
+ changed = 1;
+ }
+
+ return changed;
+}
+
+cli_opt_status_t cli_opt_parser_next(cli_opt *opt, cli_opt_parser *parser)
+{
+ assert(opt && parser);
+
+ memset(opt, 0x0, sizeof(cli_opt));
+
+ if (parser->idx >= parser->args_len) {
+ opt->args_len = parser->in_args;
+ return CLI_OPT_STATUS_DONE;
+ }
+
+ /* Handle options in long form, those beginning with "--" */
+ if (strncmp(parser->args[parser->idx], "--", 2) == 0 &&
+ !parser->in_short &&
+ !parser->in_literal)
+ return parse_long(opt, parser);
+
+ /* Handle options in short form, those beginning with "-" */
+ else if (parser->in_short ||
+ (strncmp(parser->args[parser->idx], "-", 1) == 0 &&
+ !parser->in_literal))
+ return parse_short(opt, parser);
+
+ /*
+ * We've reached the first "bare" argument. In POSIX mode, all
+ * remaining items on the command line are arguments. In GNU
+ * mode, there may be long or short options after this. Sort any
+ * options up to this position then re-parse the current position.
+ */
+ if (parser->needs_sort && sort_gnu_style(parser))
+ return cli_opt_parser_next(opt, parser);
+
+ return parse_arg(opt, parser);
+}
+
+GIT_INLINE(int) spec_included(const cli_opt_spec **specs, const cli_opt_spec *spec)
+{
+ const cli_opt_spec **i;
+
+ for (i = specs; *i; ++i) {
+ if (spec == *i)
+ return 1;
+ }
+
+ return 0;
+}
+
+static cli_opt_status_t validate_required(
+ cli_opt *opt,
+ const cli_opt_spec specs[],
+ const cli_opt_spec **given_specs)
+{
+ const cli_opt_spec *spec, *required;
+ int given;
+
+ /*
+ * Iterate over the possible specs to identify requirements and
+ * ensure that those have been given on the command-line.
+ * Note that we can have required *choices*, where one in a
+ * list of choices must be specified.
+ */
+ for (spec = specs, required = NULL, given = 0; spec->type; ++spec) {
+ if (!required && (spec->usage & CLI_OPT_USAGE_REQUIRED)) {
+ required = spec;
+ given = 0;
+ } else if (!required) {
+ continue;
+ }
+
+ if (!given)
+ given = spec_included(given_specs, spec);
+
+ /*
+ * Validate the requirement unless we're in a required
+ * choice. In that case, keep the required state and
+ * validate at the end of the choice list.
+ */
+ if (!spec_is_choice(spec)) {
+ if (!given) {
+ opt->spec = required;
+ opt->status = CLI_OPT_STATUS_MISSING_ARGUMENT;
+ break;
+ }
+
+ required = NULL;
+ given = 0;
+ }
+ }
+
+ return opt->status;
+}
+
+cli_opt_status_t cli_opt_parse(
+ cli_opt *opt,
+ const cli_opt_spec specs[],
+ char **args,
+ size_t args_len,
+ unsigned int flags)
+{
+ cli_opt_parser parser;
+ const cli_opt_spec **given_specs;
+ size_t given_idx = 0;
+
+ cli_opt_parser_init(&parser, specs, args, args_len, flags);
+
+ given_specs = alloca(sizeof(const cli_opt_spec *) * (args_len + 1));
+
+ while (cli_opt_parser_next(opt, &parser)) {
+ if (opt->status != CLI_OPT_STATUS_OK &&
+ opt->status != CLI_OPT_STATUS_DONE)
+ return opt->status;
+
+ if ((opt->spec->usage & CLI_OPT_USAGE_STOP_PARSING))
+ return (opt->status = CLI_OPT_STATUS_DONE);
+
+ given_specs[given_idx++] = opt->spec;
+ }
+
+ given_specs[given_idx] = NULL;
+
+ return validate_required(opt, specs, given_specs);
+}
+
+static int spec_name_fprint(FILE *file, const cli_opt_spec *spec)
+{
+ int error;
+
+ if (spec->type == CLI_OPT_TYPE_ARG)
+ error = fprintf(file, "%s", spec->value_name);
+ else if (spec->type == CLI_OPT_TYPE_ARGS)
+ error = fprintf(file, "%s", spec->value_name);
+ else if (spec->alias && !(spec->usage & CLI_OPT_USAGE_SHOW_LONG))
+ error = fprintf(file, "-%c", spec->alias);
+ else
+ error = fprintf(file, "--%s", spec->name);
+
+ return error;
+}
+
+int cli_opt_status_fprint(
+ FILE *file,
+ const char *command,
+ const cli_opt *opt)
+{
+ const cli_opt_spec *choice;
+ int error;
+
+ if (command && (error = fprintf(file, "%s: ", command)) < 0)
+ return error;
+
+ switch (opt->status) {
+ case CLI_OPT_STATUS_DONE:
+ error = fprintf(file, "finished processing arguments (no error)\n");
+ break;
+ case CLI_OPT_STATUS_OK:
+ error = fprintf(file, "no error\n");
+ break;
+ case CLI_OPT_STATUS_UNKNOWN_OPTION:
+ error = fprintf(file, "unknown option: %s\n", opt->arg);
+ break;
+ case CLI_OPT_STATUS_MISSING_VALUE:
+ if ((error = fprintf(file, "argument '")) < 0 ||
+ (error = spec_name_fprint(file, opt->spec)) < 0 ||
+ (error = fprintf(file, "' requires a value.\n")) < 0)
+ break;
+ break;
+ case CLI_OPT_STATUS_MISSING_ARGUMENT:
+ if (spec_is_choice(opt->spec)) {
+ int is_choice = 1;
+
+ if (spec_is_choice((opt->spec)+1))
+ error = fprintf(file, "one of");
+ else
+ error = fprintf(file, "either");
+
+ if (error < 0)
+ break;
+
+ for (choice = opt->spec; is_choice; ++choice) {
+ is_choice = spec_is_choice(choice);
+
+ if (!is_choice)
+ error = fprintf(file, " or");
+ else if (choice != opt->spec)
+ error = fprintf(file, ",");
+
+ if ((error < 0) ||
+ (error = fprintf(file, " '")) < 0 ||
+ (error = spec_name_fprint(file, choice)) < 0 ||
+ (error = fprintf(file, "'")) < 0)
+ break;
+
+ if (!spec_is_choice(choice))
+ break;
+ }
+
+ if ((error < 0) ||
+ (error = fprintf(file, " is required.\n")) < 0)
+ break;
+ } else {
+ if ((error = fprintf(file, "argument '")) < 0 ||
+ (error = spec_name_fprint(file, opt->spec)) < 0 ||
+ (error = fprintf(file, "' is required.\n")) < 0)
+ break;
+ }
+
+ break;
+ default:
+ error = fprintf(file, "unknown status: %d\n", opt->status);
+ break;
+ }
+
+ return error;
+}
+