tools subcmd: allow parsing distinct --opt and --no-opt

libsubcmd automatically generates for every option --opt an equivalent
negated option, --no-opt, to unset the option. Vice versa, for every
option declared as --no-opt, a shorthand --opt is declared for
convenience.

Add a flag, PARSE_OPT_NOAUTONEG, to disable this behavior. This new flag
behaves similarly to the already existing PARSE_OPT_NONEG, only it does
not reject the --no-opt variant, but leaves it undefined. That is useful
when there is a conflicting distinct --no-opt option in the syntax of
the tool.

PARSE_OPT_NOAUTONEG is enabled per-option, allowing to unset other
options that do not have this conflict.

Link: https://lore.kernel.org/r/20260528103254.2990068-4-tglozar@redhat.com
Signed-off-by: Tomas Glozar <tglozar@redhat.com>
This commit is contained in:
Tomas Glozar 2026-05-28 12:32:51 +02:00
parent 534d9a93db
commit da62fc3458
2 changed files with 9 additions and 4 deletions

View File

@ -427,7 +427,8 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
return 0;
}
if (!rest) {
if (strstarts(options->long_name, "no-")) {
if (strstarts(options->long_name, "no-") &&
!(options->flags & PARSE_OPT_NOAUTONEG)) {
/*
* The long name itself starts with "no-", so
* accept the option without "no-" so that users
@ -465,12 +466,12 @@ static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
continue;
}
/* negated and abbreviated very much? */
if (strstarts("no-", arg)) {
if (strstarts("no-", arg) && !(options->flags & PARSE_OPT_NOAUTONEG)) {
flags |= OPT_UNSET;
goto is_abbreviated;
}
/* negated? */
if (strncmp(arg, "no-", 3))
if (strncmp(arg, "no-", 3) || (options->flags & PARSE_OPT_NOAUTONEG))
continue;
flags |= OPT_UNSET;
rest = skip_prefix(arg + 3, options->long_name);
@ -1019,7 +1020,8 @@ int parse_options_usage(const char * const *usagestr,
if (strstarts(opts->long_name, optstr))
print_option_help(opts, 0);
if (strstarts("no-", optstr) &&
strstarts(opts->long_name, optstr + 3))
strstarts(opts->long_name, optstr + 3) &&
!(opts->flags & PARSE_OPT_NOAUTONEG))
print_option_help(opts, 0);
}

View File

@ -47,6 +47,7 @@ enum parse_opt_option_flags {
PARSE_OPT_NOEMPTY = 128,
PARSE_OPT_NOBUILD = 256,
PARSE_OPT_CANSKIP = 512,
PARSE_OPT_NOAUTONEG = 1024,
};
struct option;
@ -149,6 +150,8 @@ struct option {
{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = "time", .help = (h), .callback = parse_opt_approxidate_cb }
#define OPT_CALLBACK(s, l, v, a, h, f) \
{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = (a), .help = (h), .callback = (f) }
#define OPT_CALLBACK_FLAG(s, l, v, a, h, f, fl) \
{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = (a), .help = (h), .callback = (f), .flags = (fl) }
#define OPT_CALLBACK_SET(s, l, v, os, a, h, f) \
{ .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = (a), .help = (h), .callback = (f), .set = check_vtype(os, bool *)}
#define OPT_CALLBACK_NOOPT(s, l, v, a, h, f) \