libgit2/src/cli/opt.c
2024-05-20 11:15:36 +02:00

696 lines
16 KiB
C

/*
* 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=common.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>
#if defined(__sun) || defined(__illumos__)
# include <alloca.h>
#endif
#include "common.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 &&
spec->name && 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);
}
int cli_opt_foreach(
const cli_opt_spec specs[],
char **args,
size_t args_len,
unsigned int flags,
int (*callback)(cli_opt *, void *),
void *callback_data)
{
cli_opt_parser parser;
cli_opt opt;
int ret;
cli_opt_parser_init(&parser, specs, args, args_len, flags);
while (cli_opt_parser_next(&opt, &parser)) {
if ((ret = callback(&opt, callback_data)) != 0)
return ret;
}
return 0;
}
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;
}