summaryrefslogtreecommitdiff
path: root/parse-options.c
diff options
context:
space:
mode:
Diffstat (limited to 'parse-options.c')
-rw-r--r--parse-options.c168
1 files changed, 126 insertions, 42 deletions
diff --git a/parse-options.c b/parse-options.c
index 9f84bacce6..cb24f1aa8a 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -6,6 +6,8 @@
#include "color.h"
#include "utf8.h"
+static int disallow_abbreviated_options;
+
#define OPT_SHORT 1
#define OPT_UNSET 2
@@ -20,8 +22,9 @@ int optbug(const struct option *opt, const char *reason)
return error("BUG: switch '%c' %s", opt->short_name, reason);
}
-static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt,
- int flags, const char **arg)
+static enum parse_opt_result get_arg(struct parse_opt_ctx_t *p,
+ const struct option *opt,
+ int flags, const char **arg)
{
if (p->opt) {
*arg = p->opt;
@@ -44,9 +47,10 @@ static void fix_filename(const char *prefix, const char **file)
*file = prefix_filename(prefix, *file);
}
-static int opt_command_mode_error(const struct option *opt,
- const struct option *all_opts,
- int flags)
+static enum parse_opt_result opt_command_mode_error(
+ const struct option *opt,
+ const struct option *all_opts,
+ int flags)
{
const struct option *that;
struct strbuf that_name = STRBUF_INIT;
@@ -69,16 +73,16 @@ static int opt_command_mode_error(const struct option *opt,
error(_("%s is incompatible with %s"),
optname(opt, flags), that_name.buf);
strbuf_release(&that_name);
- return -1;
+ return PARSE_OPT_ERROR;
}
return error(_("%s : incompatible with something else"),
optname(opt, flags));
}
-static int get_value(struct parse_opt_ctx_t *p,
- const struct option *opt,
- const struct option *all_opts,
- int flags)
+static enum parse_opt_result get_value(struct parse_opt_ctx_t *p,
+ const struct option *opt,
+ const struct option *all_opts,
+ int flags)
{
const char *s, *arg;
const int unset = flags & OPT_UNSET;
@@ -93,7 +97,7 @@ static int get_value(struct parse_opt_ctx_t *p,
switch (opt->type) {
case OPTION_LOWLEVEL_CALLBACK:
- return (*(parse_opt_ll_cb *)opt->callback)(p, opt, unset);
+ return opt->ll_callback(p, opt, NULL, unset);
case OPTION_BIT:
if (unset)
@@ -109,6 +113,13 @@ static int get_value(struct parse_opt_ctx_t *p,
*(int *)opt->value &= ~opt->defval;
return 0;
+ case OPTION_BITOP:
+ if (unset)
+ BUG("BITOP can't have unset form");
+ *(int *)opt->value &= ~opt->extra;
+ *(int *)opt->value |= opt->defval;
+ return 0;
+
case OPTION_COUNTUP:
if (*(int *)opt->value < 0)
*(int *)opt->value = 0;
@@ -152,16 +163,27 @@ static int get_value(struct parse_opt_ctx_t *p,
return err;
case OPTION_CALLBACK:
+ {
+ const char *p_arg = NULL;
+ int p_unset;
+
if (unset)
- return (*opt->callback)(opt, NULL, 1) ? (-1) : 0;
- if (opt->flags & PARSE_OPT_NOARG)
- return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
- if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
- return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
- if (get_arg(p, opt, flags, &arg))
+ p_unset = 1;
+ else if (opt->flags & PARSE_OPT_NOARG)
+ p_unset = 0;
+ else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
+ p_unset = 0;
+ else if (get_arg(p, opt, flags, &arg))
return -1;
- return (*opt->callback)(opt, arg, 0) ? (-1) : 0;
-
+ else {
+ p_unset = 0;
+ p_arg = arg;
+ }
+ if (opt->callback)
+ return (*opt->callback)(opt, p_arg, p_unset) ? (-1) : 0;
+ else
+ return (*opt->ll_callback)(p, opt, p_arg, p_unset);
+ }
case OPTION_INTEGER:
if (unset) {
*(int *)opt->value = 0;
@@ -201,7 +223,8 @@ static int get_value(struct parse_opt_ctx_t *p,
}
}
-static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *options)
+static enum parse_opt_result parse_short_opt(struct parse_opt_ctx_t *p,
+ const struct option *options)
{
const struct option *all_opts = options;
const struct option *numopt = NULL;
@@ -228,15 +251,19 @@ static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *optio
len++;
arg = xmemdupz(p->opt, len);
p->opt = p->opt[len] ? p->opt + len : NULL;
- rc = (*numopt->callback)(numopt, arg, 0) ? (-1) : 0;
+ if (numopt->callback)
+ rc = (*numopt->callback)(numopt, arg, 0) ? (-1) : 0;
+ else
+ rc = (*numopt->ll_callback)(p, numopt, arg, 0);
free(arg);
return rc;
}
- return -2;
+ return PARSE_OPT_UNKNOWN;
}
-static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
- const struct option *options)
+static enum parse_opt_result parse_long_opt(
+ struct parse_opt_ctx_t *p, const char *arg,
+ const struct option *options)
{
const struct option *all_opts = options;
const char *arg_end = strchrnul(arg, '=');
@@ -261,12 +288,15 @@ again:
optname(options, flags));
if (*rest)
continue;
+ if (options->value)
+ *(int *)options->value = options->defval;
p->out[p->cpidx++] = arg - 2;
- return 0;
+ return PARSE_OPT_DONE;
}
if (!rest) {
/* abbreviated? */
- if (!strncmp(long_name, arg, arg_end - arg)) {
+ if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN) &&
+ !strncmp(long_name, arg, arg_end - arg)) {
is_abbreviated:
if (abbrev_option) {
/*
@@ -318,6 +348,10 @@ is_abbreviated:
return get_value(p, options, all_opts, flags ^ opt_flags);
}
+ if (disallow_abbreviated_options && (ambiguous_option || abbrev_option))
+ die("disallowed abbreviated or ambiguous option '%.*s'",
+ (int)(arg_end - arg), arg);
+
if (ambiguous_option) {
error(_("ambiguous option: %s "
"(could be --%s%s or --%s%s)"),
@@ -326,11 +360,11 @@ is_abbreviated:
ambiguous_option->long_name,
(abbrev_flags & OPT_UNSET) ? "no-" : "",
abbrev_option->long_name);
- return -3;
+ return PARSE_OPT_HELP;
}
if (abbrev_option)
return get_value(p, abbrev_option, all_opts, abbrev_flags);
- return -2;
+ return PARSE_OPT_UNKNOWN;
}
static int parse_nodash_opt(struct parse_opt_ctx_t *p, const char *arg,
@@ -400,6 +434,19 @@ static void parse_options_check(const struct option *opts)
if ((opts->flags & PARSE_OPT_OPTARG) ||
!(opts->flags & PARSE_OPT_NOARG))
err |= optbug(opts, "should not accept an argument");
+ break;
+ case OPTION_CALLBACK:
+ if (!opts->callback && !opts->ll_callback)
+ BUG("OPTION_CALLBACK needs one callback");
+ if (opts->callback && opts->ll_callback)
+ BUG("OPTION_CALLBACK can't have two callbacks");
+ break;
+ case OPTION_LOWLEVEL_CALLBACK:
+ if (!opts->ll_callback)
+ BUG("OPTION_LOWLEVEL_CALLBACK needs a callback");
+ if (opts->callback)
+ BUG("OPTION_LOWLEVEL_CALLBACK needs no high level callback");
+ break;
default:
; /* ok. (usually accepts an argument) */
}
@@ -416,15 +463,24 @@ void parse_options_start(struct parse_opt_ctx_t *ctx,
const struct option *options, int flags)
{
memset(ctx, 0, sizeof(*ctx));
- ctx->argc = ctx->total = argc - 1;
- ctx->argv = argv + 1;
- ctx->out = argv;
+ ctx->argc = argc;
+ ctx->argv = argv;
+ if (!(flags & PARSE_OPT_ONE_SHOT)) {
+ ctx->argc--;
+ ctx->argv++;
+ }
+ ctx->total = ctx->argc;
+ ctx->out = argv;
ctx->prefix = prefix;
ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
ctx->flags = flags;
if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
- (flags & PARSE_OPT_STOP_AT_NON_OPTION))
+ (flags & PARSE_OPT_STOP_AT_NON_OPTION) &&
+ !(flags & PARSE_OPT_ONE_SHOT))
BUG("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
+ if ((flags & PARSE_OPT_ONE_SHOT) &&
+ (flags & PARSE_OPT_KEEP_ARGV0))
+ BUG("Can't keep argv0 if you don't have it");
parse_options_check(options);
}
@@ -475,8 +531,7 @@ static void show_negated_gitcomp(const struct option *opts, int nr_noopts)
}
}
-static int show_gitcomp(struct parse_opt_ctx_t *ctx,
- const struct option *opts)
+static int show_gitcomp(const struct option *opts)
{
const struct option *original_opts = opts;
int nr_noopts = 0;
@@ -536,6 +591,10 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
for (; ctx->argc; ctx->argc--, ctx->argv++) {
const char *arg = ctx->argv[0];
+ if (ctx->flags & PARSE_OPT_ONE_SHOT &&
+ ctx->argc != ctx->total)
+ break;
+
if (*arg != '-' || !arg[1]) {
if (parse_nodash_opt(ctx, arg, options) == 0)
continue;
@@ -551,27 +610,33 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
/* lone --git-completion-helper is asked by git-completion.bash */
if (ctx->total == 1 && !strcmp(arg + 1, "-git-completion-helper"))
- return show_gitcomp(ctx, options);
+ return show_gitcomp(options);
if (arg[1] != '-') {
ctx->opt = arg + 1;
switch (parse_short_opt(ctx, options)) {
- case -1:
+ case PARSE_OPT_ERROR:
return PARSE_OPT_ERROR;
- case -2:
+ case PARSE_OPT_UNKNOWN:
if (ctx->opt)
check_typos(arg + 1, options);
if (internal_help && *ctx->opt == 'h')
goto show_usage;
goto unknown;
+ case PARSE_OPT_NON_OPTION:
+ case PARSE_OPT_HELP:
+ case PARSE_OPT_COMPLETE:
+ BUG("parse_short_opt() cannot return these");
+ case PARSE_OPT_DONE:
+ break;
}
if (ctx->opt)
check_typos(arg + 1, options);
while (ctx->opt) {
switch (parse_short_opt(ctx, options)) {
- case -1:
+ case PARSE_OPT_ERROR:
return PARSE_OPT_ERROR;
- case -2:
+ case PARSE_OPT_UNKNOWN:
if (internal_help && *ctx->opt == 'h')
goto show_usage;
@@ -583,6 +648,12 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
ctx->argv[0] = xstrdup(ctx->opt - 1);
*(char *)ctx->argv[0] = '-';
goto unknown;
+ case PARSE_OPT_NON_OPTION:
+ case PARSE_OPT_COMPLETE:
+ case PARSE_OPT_HELP:
+ BUG("parse_short_opt() cannot return these");
+ case PARSE_OPT_DONE:
+ break;
}
}
continue;
@@ -601,15 +672,22 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
if (internal_help && !strcmp(arg + 2, "help"))
goto show_usage;
switch (parse_long_opt(ctx, arg + 2, options)) {
- case -1:
+ case PARSE_OPT_ERROR:
return PARSE_OPT_ERROR;
- case -2:
+ case PARSE_OPT_UNKNOWN:
goto unknown;
- case -3:
+ case PARSE_OPT_HELP:
goto show_usage;
+ case PARSE_OPT_NON_OPTION:
+ case PARSE_OPT_COMPLETE:
+ BUG("parse_long_opt() cannot return these");
+ case PARSE_OPT_DONE:
+ break;
}
continue;
unknown:
+ if (ctx->flags & PARSE_OPT_ONE_SHOT)
+ break;
if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN))
return PARSE_OPT_UNKNOWN;
ctx->out[ctx->cpidx++] = ctx->argv[0];
@@ -623,6 +701,9 @@ unknown:
int parse_options_end(struct parse_opt_ctx_t *ctx)
{
+ if (ctx->flags & PARSE_OPT_ONE_SHOT)
+ return ctx->total - ctx->argc;
+
MOVE_ARRAY(ctx->out + ctx->cpidx, ctx->argv, ctx->argc);
ctx->out[ctx->cpidx + ctx->argc] = NULL;
return ctx->cpidx + ctx->argc;
@@ -634,6 +715,9 @@ int parse_options(int argc, const char **argv, const char *prefix,
{
struct parse_opt_ctx_t ctx;
+ disallow_abbreviated_options =
+ git_env_bool("GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS", 0);
+
parse_options_start(&ctx, argc, argv, prefix, options, flags);
switch (parse_options_step(&ctx, options, usagestr)) {
case PARSE_OPT_HELP: