aboutsummaryrefslogtreecommitdiffstats
path: root/parse-options.c
diff options
context:
space:
mode:
Diffstat (limited to 'parse-options.c')
-rw-r--r--parse-options.c171
1 files changed, 136 insertions, 35 deletions
diff --git a/parse-options.c b/parse-options.c
index 6e57744fd2..a1ec932f0f 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -14,15 +14,15 @@ enum opt_parsed {
OPT_UNSET = 1<<1,
};
-static int optbug(const struct option *opt, const char *reason)
+static void optbug(const struct option *opt, const char *reason)
{
- if (opt->long_name) {
- if (opt->short_name)
- return error("BUG: switch '%c' (--%s) %s",
- opt->short_name, opt->long_name, reason);
- return error("BUG: option '%s' %s", opt->long_name, reason);
- }
- return error("BUG: switch '%c' %s", opt->short_name, reason);
+ if (opt->long_name && opt->short_name)
+ bug("switch '%c' (--%s) %s", opt->short_name,
+ opt->long_name, reason);
+ else if (opt->long_name)
+ bug("option '%s' %s", opt->long_name, reason);
+ else
+ bug("switch '%c' %s", opt->short_name, reason);
}
static const char *optname(const struct option *opt, enum opt_parsed flags)
@@ -324,6 +324,8 @@ static enum parse_opt_result parse_long_opt(
const char *rest, *long_name = options->long_name;
enum opt_parsed flags = OPT_LONG, opt_flags = OPT_LONG;
+ if (options->type == OPTION_SUBCOMMAND)
+ continue;
if (!long_name)
continue;
@@ -332,7 +334,7 @@ again:
rest = NULL;
if (!rest) {
/* abbreviated? */
- if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN) &&
+ if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN_OPT) &&
!strncmp(long_name, arg, arg_end - arg)) {
is_abbreviated:
if (abbrev_option &&
@@ -419,6 +421,19 @@ static enum parse_opt_result parse_nodash_opt(struct parse_opt_ctx_t *p,
return PARSE_OPT_ERROR;
}
+static enum parse_opt_result parse_subcommand(const char *arg,
+ const struct option *options)
+{
+ for (; options->type != OPTION_END; options++)
+ if (options->type == OPTION_SUBCOMMAND &&
+ !strcmp(options->long_name, arg)) {
+ *(parse_opt_subcommand_fn **)options->value = options->subcommand_fn;
+ return PARSE_OPT_SUBCOMMAND;
+ }
+
+ return PARSE_OPT_UNKNOWN;
+}
+
static void check_typos(const char *arg, const struct option *options)
{
if (strlen(arg) < 3)
@@ -441,28 +456,28 @@ static void check_typos(const char *arg, const struct option *options)
static void parse_options_check(const struct option *opts)
{
- int err = 0;
char short_opts[128];
+ void *subcommand_value = NULL;
memset(short_opts, '\0', sizeof(short_opts));
for (; opts->type != OPTION_END; opts++) {
if ((opts->flags & PARSE_OPT_LASTARG_DEFAULT) &&
(opts->flags & PARSE_OPT_OPTARG))
- err |= optbug(opts, "uses incompatible flags "
- "LASTARG_DEFAULT and OPTARG");
+ optbug(opts, "uses incompatible flags "
+ "LASTARG_DEFAULT and OPTARG");
if (opts->short_name) {
if (0x7F <= opts->short_name)
- err |= optbug(opts, "invalid short name");
+ optbug(opts, "invalid short name");
else if (short_opts[opts->short_name]++)
- err |= optbug(opts, "short name already used");
+ optbug(opts, "short name already used");
}
if (opts->flags & PARSE_OPT_NODASH &&
((opts->flags & PARSE_OPT_OPTARG) ||
!(opts->flags & PARSE_OPT_NOARG) ||
!(opts->flags & PARSE_OPT_NONEG) ||
opts->long_name))
- err |= optbug(opts, "uses feature "
- "not supported for dashless options");
+ optbug(opts, "uses feature "
+ "not supported for dashless options");
switch (opts->type) {
case OPTION_COUNTUP:
case OPTION_BIT:
@@ -471,33 +486,49 @@ static void parse_options_check(const struct option *opts)
case OPTION_NUMBER:
if ((opts->flags & PARSE_OPT_OPTARG) ||
!(opts->flags & PARSE_OPT_NOARG))
- err |= optbug(opts, "should not accept an argument");
+ 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");
+ optbug(opts, "OPTION_CALLBACK needs one callback");
+ else if (opts->callback && opts->ll_callback)
+ optbug(opts, "OPTION_CALLBACK can't have two callbacks");
break;
case OPTION_LOWLEVEL_CALLBACK:
if (!opts->ll_callback)
- BUG("OPTION_LOWLEVEL_CALLBACK needs a callback");
+ optbug(opts, "OPTION_LOWLEVEL_CALLBACK needs a callback");
if (opts->callback)
- BUG("OPTION_LOWLEVEL_CALLBACK needs no high level callback");
+ optbug(opts, "OPTION_LOWLEVEL_CALLBACK needs no high level callback");
break;
case OPTION_ALIAS:
- BUG("OPT_ALIAS() should not remain at this point. "
- "Are you using parse_options_step() directly?\n"
- "That case is not supported yet.");
+ optbug(opts, "OPT_ALIAS() should not remain at this point. "
+ "Are you using parse_options_step() directly?\n"
+ "That case is not supported yet.");
+ break;
+ case OPTION_SUBCOMMAND:
+ if (!opts->value || !opts->subcommand_fn)
+ optbug(opts, "OPTION_SUBCOMMAND needs a value and a subcommand function");
+ if (!subcommand_value)
+ subcommand_value = opts->value;
+ else if (subcommand_value != opts->value)
+ optbug(opts, "all OPTION_SUBCOMMANDs need the same value");
+ break;
default:
; /* ok. (usually accepts an argument) */
}
if (opts->argh &&
strcspn(opts->argh, " _") != strlen(opts->argh))
- err |= optbug(opts, "multi-word argh should use dash to separate words");
+ optbug(opts, "multi-word argh should use dash to separate words");
}
- if (err)
- exit(128);
+ BUG_if_bug("invalid 'struct option'");
+}
+
+static int has_subcommands(const struct option *options)
+{
+ for (; options->type != OPTION_END; options++)
+ if (options->type == OPTION_SUBCOMMAND)
+ return 1;
+ return 0;
}
static void parse_options_start_1(struct parse_opt_ctx_t *ctx,
@@ -516,7 +547,20 @@ static void parse_options_start_1(struct parse_opt_ctx_t *ctx,
ctx->prefix = prefix;
ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
ctx->flags = flags;
- if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
+ ctx->has_subcommands = has_subcommands(options);
+ if (!ctx->has_subcommands && (flags & PARSE_OPT_SUBCOMMAND_OPTIONAL))
+ BUG("Using PARSE_OPT_SUBCOMMAND_OPTIONAL without subcommands");
+ if (ctx->has_subcommands) {
+ if (flags & PARSE_OPT_STOP_AT_NON_OPTION)
+ BUG("subcommands are incompatible with PARSE_OPT_STOP_AT_NON_OPTION");
+ if (!(flags & PARSE_OPT_SUBCOMMAND_OPTIONAL)) {
+ if (flags & PARSE_OPT_KEEP_UNKNOWN_OPT)
+ BUG("subcommands are incompatible with PARSE_OPT_KEEP_UNKNOWN_OPT unless in combination with PARSE_OPT_SUBCOMMAND_OPTIONAL");
+ if (flags & PARSE_OPT_KEEP_DASHDASH)
+ BUG("subcommands are incompatible with PARSE_OPT_KEEP_DASHDASH unless in combination with PARSE_OPT_SUBCOMMAND_OPTIONAL");
+ }
+ }
+ if ((flags & PARSE_OPT_KEEP_UNKNOWN_OPT) &&
(flags & PARSE_OPT_STOP_AT_NON_OPTION) &&
!(flags & PARSE_OPT_ONE_SHOT))
BUG("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
@@ -590,6 +634,7 @@ static int show_gitcomp(const struct option *opts, int show_all)
int nr_noopts = 0;
for (; opts->type != OPTION_END; opts++) {
+ const char *prefix = "--";
const char *suffix = "";
if (!opts->long_name)
@@ -599,6 +644,9 @@ static int show_gitcomp(const struct option *opts, int show_all)
continue;
switch (opts->type) {
+ case OPTION_SUBCOMMAND:
+ prefix = "";
+ break;
case OPTION_GROUP:
continue;
case OPTION_STRING:
@@ -621,7 +669,8 @@ static int show_gitcomp(const struct option *opts, int show_all)
suffix = "=";
if (starts_with(opts->long_name, "no-"))
nr_noopts++;
- printf(" --%s%s", opts->long_name, suffix);
+ printf("%s%s%s%s", opts == original_opts ? "" : " ",
+ prefix, opts->long_name, suffix);
}
show_negated_gitcomp(original_opts, show_all, -1);
show_negated_gitcomp(original_opts, show_all, nr_noopts);
@@ -744,10 +793,38 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx,
if (*arg != '-' || !arg[1]) {
if (parse_nodash_opt(ctx, arg, options) == 0)
continue;
- if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION)
- return PARSE_OPT_NON_OPTION;
- ctx->out[ctx->cpidx++] = ctx->argv[0];
- continue;
+ if (!ctx->has_subcommands) {
+ if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION)
+ return PARSE_OPT_NON_OPTION;
+ ctx->out[ctx->cpidx++] = ctx->argv[0];
+ continue;
+ }
+ switch (parse_subcommand(arg, options)) {
+ case PARSE_OPT_SUBCOMMAND:
+ return PARSE_OPT_SUBCOMMAND;
+ case PARSE_OPT_UNKNOWN:
+ if (ctx->flags & PARSE_OPT_SUBCOMMAND_OPTIONAL)
+ /*
+ * arg is neither a short or long
+ * option nor a subcommand. Since
+ * this command has a default
+ * operation mode, we have to treat
+ * this arg and all remaining args
+ * as args meant to that default
+ * operation mode.
+ * So we are done parsing.
+ */
+ return PARSE_OPT_DONE;
+ error(_("unknown subcommand: `%s'"), arg);
+ usage_with_options(usagestr, options);
+ case PARSE_OPT_COMPLETE:
+ case PARSE_OPT_HELP:
+ case PARSE_OPT_ERROR:
+ case PARSE_OPT_DONE:
+ case PARSE_OPT_NON_OPTION:
+ /* Impossible. */
+ BUG("parse_subcommand() cannot return these");
+ }
}
/* lone -h asks for help */
@@ -775,6 +852,7 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx,
goto show_usage;
goto unknown;
case PARSE_OPT_NON_OPTION:
+ case PARSE_OPT_SUBCOMMAND:
case PARSE_OPT_HELP:
case PARSE_OPT_COMPLETE:
BUG("parse_short_opt() cannot return these");
@@ -800,6 +878,7 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx,
*(char *)ctx->argv[0] = '-';
goto unknown;
case PARSE_OPT_NON_OPTION:
+ case PARSE_OPT_SUBCOMMAND:
case PARSE_OPT_COMPLETE:
case PARSE_OPT_HELP:
BUG("parse_short_opt() cannot return these");
@@ -831,6 +910,7 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx,
case PARSE_OPT_HELP:
goto show_usage;
case PARSE_OPT_NON_OPTION:
+ case PARSE_OPT_SUBCOMMAND:
case PARSE_OPT_COMPLETE:
BUG("parse_long_opt() cannot return these");
case PARSE_OPT_DONE:
@@ -840,7 +920,19 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx,
unknown:
if (ctx->flags & PARSE_OPT_ONE_SHOT)
break;
- if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN))
+ if (ctx->has_subcommands &&
+ (ctx->flags & PARSE_OPT_SUBCOMMAND_OPTIONAL) &&
+ (ctx->flags & PARSE_OPT_KEEP_UNKNOWN_OPT)) {
+ /*
+ * Found an unknown option given to a command with
+ * subcommands that has a default operation mode:
+ * we treat this option and all remaining args as
+ * arguments meant to that default operation mode.
+ * So we are done parsing.
+ */
+ return PARSE_OPT_DONE;
+ }
+ if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN_OPT))
return PARSE_OPT_UNKNOWN;
ctx->out[ctx->cpidx++] = ctx->argv[0];
ctx->opt = NULL;
@@ -885,7 +977,14 @@ int parse_options(int argc, const char **argv,
case PARSE_OPT_COMPLETE:
exit(0);
case PARSE_OPT_NON_OPTION:
+ case PARSE_OPT_SUBCOMMAND:
+ break;
case PARSE_OPT_DONE:
+ if (ctx.has_subcommands &&
+ !(flags & PARSE_OPT_SUBCOMMAND_OPTIONAL)) {
+ error(_("need a subcommand"));
+ usage_with_options(usagestr, options);
+ }
break;
case PARSE_OPT_UNKNOWN:
if (ctx.argv[0][1] == '-') {
@@ -1010,6 +1109,8 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t
size_t pos;
int pad;
+ if (opts->type == OPTION_SUBCOMMAND)
+ continue;
if (opts->type == OPTION_GROUP) {
fputc('\n', outfile);
need_newline = 0;