diff options
Diffstat (limited to 'parse-options.c')
-rw-r--r-- | parse-options.c | 505 |
1 files changed, 355 insertions, 150 deletions
diff --git a/parse-options.c b/parse-options.c index edf55d3..30b9e68 100644 --- a/parse-options.c +++ b/parse-options.c @@ -1,9 +1,10 @@ #include "git-compat-util.h" #include "parse-options.h" -#include "cache.h" -#include "config.h" -#include "commit.h" -#include "color.h" +#include "abspath.h" +#include "parse.h" +#include "gettext.h" +#include "strbuf.h" +#include "string-list.h" #include "utf8.h" static int disallow_abbreviated_options; @@ -59,50 +60,18 @@ static enum parse_opt_result get_arg(struct parse_opt_ctx_t *p, return 0; } -static void fix_filename(const char *prefix, const char **file) +static void fix_filename(const char *prefix, char **file) { - if (!file || !*file || !prefix || is_absolute_path(*file) - || !strcmp("-", *file)) - return; - *file = prefix_filename(prefix, *file); -} - -static enum parse_opt_result opt_command_mode_error( - const struct option *opt, - const struct option *all_opts, - enum opt_parsed flags) -{ - const struct option *that; - struct strbuf that_name = STRBUF_INIT; - - /* - * Find the other option that was used to set the variable - * already, and report that this is not compatible with it. - */ - for (that = all_opts; that->type != OPTION_END; that++) { - if (that == opt || - !(that->flags & PARSE_OPT_CMDMODE) || - that->value != opt->value || - that->defval != *(int *)opt->value) - continue; - - if (that->long_name) - strbuf_addf(&that_name, "--%s", that->long_name); - else - strbuf_addf(&that_name, "-%c", that->short_name); - error(_("%s is incompatible with %s"), - optname(opt, flags), that_name.buf); - strbuf_release(&that_name); - return PARSE_OPT_ERROR; - } - return error(_("%s : incompatible with something else"), - optname(opt, flags)); + if (!file || !*file) + ; /* leave as NULL */ + else + *file = prefix_filename_except_for_dash(prefix, *file); } -static enum parse_opt_result get_value(struct parse_opt_ctx_t *p, - const struct option *opt, - const struct option *all_opts, - enum opt_parsed flags) +static enum parse_opt_result do_get_value(struct parse_opt_ctx_t *p, + const struct option *opt, + enum opt_parsed flags, + const char **argp) { const char *s, *arg; const int unset = flags & OPT_UNSET; @@ -115,14 +84,6 @@ static enum parse_opt_result get_value(struct parse_opt_ctx_t *p, if (!(flags & OPT_SHORT) && p->opt && (opt->flags & PARSE_OPT_NOARG)) return error(_("%s takes no value"), optname(opt, flags)); - /* - * Giving the same mode option twice, although unnecessary, - * is not a grave error, so let it pass. - */ - if ((opt->flags & PARSE_OPT_CMDMODE) && - *(int *)opt->value && *(int *)opt->value != opt->defval) - return opt_command_mode_error(opt, all_opts, flags); - switch (opt->type) { case OPTION_LOWLEVEL_CALLBACK: return opt->ll_callback(p, opt, NULL, unset); @@ -177,7 +138,7 @@ static enum parse_opt_result get_value(struct parse_opt_ctx_t *p, err = get_arg(p, opt, flags, (const char **)opt->value); if (!err) - fix_filename(p->prefix, (const char **)opt->value); + fix_filename(p->prefix, (char **)opt->value); return err; case OPTION_CALLBACK: @@ -197,6 +158,8 @@ static enum parse_opt_result get_value(struct parse_opt_ctx_t *p, p_unset = 0; p_arg = arg; } + if (opt->flags & PARSE_OPT_CMDMODE) + *argp = p_arg; if (opt->callback) return (*opt->callback)(opt, p_arg, p_unset) ? (-1) : 0; else @@ -244,16 +207,92 @@ static enum parse_opt_result get_value(struct parse_opt_ctx_t *p, } } +struct parse_opt_cmdmode_list { + int value, *value_ptr; + const struct option *opt; + const char *arg; + enum opt_parsed flags; + struct parse_opt_cmdmode_list *next; +}; + +static void build_cmdmode_list(struct parse_opt_ctx_t *ctx, + const struct option *opts) +{ + ctx->cmdmode_list = NULL; + + for (; opts->type != OPTION_END; opts++) { + struct parse_opt_cmdmode_list *elem = ctx->cmdmode_list; + int *value_ptr = opts->value; + + if (!(opts->flags & PARSE_OPT_CMDMODE) || !value_ptr) + continue; + + while (elem && elem->value_ptr != value_ptr) + elem = elem->next; + if (elem) + continue; + + CALLOC_ARRAY(elem, 1); + elem->value_ptr = value_ptr; + elem->value = *value_ptr; + elem->next = ctx->cmdmode_list; + ctx->cmdmode_list = elem; + } +} + +static char *optnamearg(const struct option *opt, const char *arg, + enum opt_parsed flags) +{ + if (flags & OPT_SHORT) + return xstrfmt("-%c%s", opt->short_name, arg ? arg : ""); + return xstrfmt("--%s%s%s%s", flags & OPT_UNSET ? "no-" : "", + opt->long_name, arg ? "=" : "", arg ? arg : ""); +} + +static enum parse_opt_result get_value(struct parse_opt_ctx_t *p, + const struct option *opt, + enum opt_parsed flags) +{ + const char *arg = NULL; + enum parse_opt_result result = do_get_value(p, opt, flags, &arg); + struct parse_opt_cmdmode_list *elem = p->cmdmode_list; + char *opt_name, *other_opt_name; + + for (; elem; elem = elem->next) { + if (*elem->value_ptr == elem->value) + continue; + + if (elem->opt && + (elem->opt->flags | opt->flags) & PARSE_OPT_CMDMODE) + break; + + elem->opt = opt; + elem->arg = arg; + elem->flags = flags; + elem->value = *elem->value_ptr; + } + + if (result || !elem) + return result; + + opt_name = optnamearg(opt, arg, flags); + other_opt_name = optnamearg(elem->opt, elem->arg, elem->flags); + error(_("options '%s' and '%s' cannot be used together"), + opt_name, other_opt_name); + free(opt_name); + free(other_opt_name); + return -1; +} + 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; for (; options->type != OPTION_END; options++) { if (options->short_name == *p->opt) { p->opt = p->opt[1] ? p->opt + 1 : NULL; - return get_value(p, options, all_opts, OPT_SHORT); + return get_value(p, options, OPT_SHORT); } /* @@ -311,96 +350,107 @@ static int is_alias(struct parse_opt_ctx_t *ctx, return 0; } +struct parsed_option { + const struct option *option; + enum opt_parsed flags; +}; + +static void register_abbrev(struct parse_opt_ctx_t *p, + const struct option *option, enum opt_parsed flags, + struct parsed_option *abbrev, + struct parsed_option *ambiguous) +{ + if (p->flags & PARSE_OPT_KEEP_UNKNOWN_OPT) + return; + if (abbrev->option && + !(abbrev->flags == flags && is_alias(p, abbrev->option, option))) { + /* + * If this is abbreviated, it is + * ambiguous. So when there is no + * exact match later, we need to + * error out. + */ + ambiguous->option = abbrev->option; + ambiguous->flags = abbrev->flags; + } + abbrev->option = option; + abbrev->flags = flags; +} + 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, '='); - const struct option *abbrev_option = NULL, *ambiguous_option = NULL; - enum opt_parsed abbrev_flags = OPT_LONG, ambiguous_flags = OPT_LONG; + const char *arg_start = arg; + enum opt_parsed flags = OPT_LONG; + int arg_starts_with_no_no = 0; + struct parsed_option abbrev = { .option = NULL, .flags = OPT_LONG }; + struct parsed_option ambiguous = { .option = NULL, .flags = OPT_LONG }; + + if (skip_prefix(arg_start, "no-", &arg_start)) { + if (skip_prefix(arg_start, "no-", &arg_start)) + arg_starts_with_no_no = 1; + else + flags |= OPT_UNSET; + } for (; options->type != OPTION_END; options++) { const char *rest, *long_name = options->long_name; - enum opt_parsed flags = OPT_LONG, opt_flags = OPT_LONG; + enum opt_parsed opt_flags = OPT_LONG; + int allow_unset = !(options->flags & PARSE_OPT_NONEG); + if (options->type == OPTION_SUBCOMMAND) + continue; if (!long_name) continue; -again: - if (!skip_prefix(arg, long_name, &rest)) - rest = NULL; - if (!rest) { - /* abbreviated? */ - if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN) && - !strncmp(long_name, arg, arg_end - arg)) { -is_abbreviated: - if (abbrev_option && - !is_alias(p, abbrev_option, options)) { - /* - * If this is abbreviated, it is - * ambiguous. So when there is no - * exact match later, we need to - * error out. - */ - ambiguous_option = abbrev_option; - ambiguous_flags = abbrev_flags; - } - if (!(flags & OPT_UNSET) && *arg_end) - p->opt = arg_end + 1; - abbrev_option = options; - abbrev_flags = flags ^ opt_flags; - continue; - } - /* negation allowed? */ - if (options->flags & PARSE_OPT_NONEG) - continue; - /* negated and abbreviated very much? */ - if (starts_with("no-", arg)) { - flags |= OPT_UNSET; - goto is_abbreviated; - } - /* negated? */ - if (!starts_with(arg, "no-")) { - if (skip_prefix(long_name, "no-", &long_name)) { - opt_flags |= OPT_UNSET; - goto again; - } - continue; - } - flags |= OPT_UNSET; - if (!skip_prefix(arg + 3, long_name, &rest)) { - /* abbreviated and negated? */ - if (starts_with(long_name, arg + 3)) - goto is_abbreviated; - else - continue; - } - } - if (*rest) { - if (*rest != '=') + if (skip_prefix(long_name, "no-", &long_name)) + opt_flags |= OPT_UNSET; + else if (arg_starts_with_no_no) + continue; + + if (((flags ^ opt_flags) & OPT_UNSET) && !allow_unset) + continue; + + if (skip_prefix(arg_start, long_name, &rest)) { + if (*rest == '=') + p->opt = rest + 1; + else if (*rest) continue; - p->opt = rest + 1; + return get_value(p, options, flags ^ opt_flags); } - return get_value(p, options, all_opts, flags ^ opt_flags); + + /* abbreviated? */ + if (!strncmp(long_name, arg_start, arg_end - arg_start)) + register_abbrev(p, options, flags ^ opt_flags, + &abbrev, &ambiguous); + + /* negated and abbreviated very much? */ + if (allow_unset && starts_with("no-", arg)) + register_abbrev(p, options, OPT_UNSET ^ opt_flags, + &abbrev, &ambiguous); } - if (disallow_abbreviated_options && (ambiguous_option || abbrev_option)) + if (disallow_abbreviated_options && (ambiguous.option || abbrev.option)) die("disallowed abbreviated or ambiguous option '%.*s'", (int)(arg_end - arg), arg); - if (ambiguous_option) { + if (ambiguous.option) { error(_("ambiguous option: %s " "(could be --%s%s or --%s%s)"), arg, - (ambiguous_flags & OPT_UNSET) ? "no-" : "", - ambiguous_option->long_name, - (abbrev_flags & OPT_UNSET) ? "no-" : "", - abbrev_option->long_name); + (ambiguous.flags & OPT_UNSET) ? "no-" : "", + ambiguous.option->long_name, + (abbrev.flags & OPT_UNSET) ? "no-" : "", + abbrev.option->long_name); return PARSE_OPT_HELP; } - if (abbrev_option) - return get_value(p, abbrev_option, all_opts, abbrev_flags); + if (abbrev.option) { + if (*arg_end) + p->opt = arg_end + 1; + return get_value(p, abbrev.option, abbrev.flags); + } return PARSE_OPT_UNKNOWN; } @@ -408,17 +458,28 @@ static enum parse_opt_result parse_nodash_opt(struct parse_opt_ctx_t *p, const char *arg, const struct option *options) { - const struct option *all_opts = options; - for (; options->type != OPTION_END; options++) { if (!(options->flags & PARSE_OPT_NODASH)) continue; if (options->short_name == arg[0] && arg[1] == '\0') - return get_value(p, options, all_opts, OPT_SHORT); + return get_value(p, options, OPT_SHORT); } 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) @@ -442,6 +503,7 @@ static void check_typos(const char *arg, const struct option *options) static void parse_options_check(const struct option *opts) { char short_opts[128]; + void *subcommand_value = NULL; memset(short_opts, '\0', sizeof(short_opts)); for (; opts->type != OPTION_END; opts++) { @@ -462,6 +524,9 @@ static void parse_options_check(const struct option *opts) opts->long_name)) optbug(opts, "uses feature " "not supported for dashless options"); + if (opts->type == OPTION_SET_INT && !opts->defval && + opts->long_name && !(opts->flags & PARSE_OPT_NONEG)) + optbug(opts, "OPTION_SET_INT 0 should not be negatable"); switch (opts->type) { case OPTION_COUNTUP: case OPTION_BIT: @@ -489,6 +554,14 @@ static void parse_options_check(const struct option *opts) "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) */ } @@ -499,6 +572,14 @@ static void parse_options_check(const struct option *opts) 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, int argc, const char **argv, const char *prefix, const struct option *options, @@ -515,7 +596,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"); @@ -523,6 +617,7 @@ static void parse_options_start_1(struct parse_opt_ctx_t *ctx, (flags & PARSE_OPT_KEEP_ARGV0)) BUG("Can't keep argv0 if you don't have it"); parse_options_check(options); + build_cmdmode_list(ctx, options); } void parse_options_start(struct parse_opt_ctx_t *ctx, @@ -589,6 +684,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) @@ -598,6 +694,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: @@ -620,7 +719,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); @@ -652,8 +752,7 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx, if (!nr_aliases) return NULL; - ALLOC_ARRAY(newopt, nr + 1); - COPY_ARRAY(newopt, options, nr + 1); + DUP_ARRAY(newopt, options, nr + 1); /* each alias has two string pointers and NULL */ CALLOC_ARRAY(ctx->alias_groups, 3 * (nr_aliases + 1)); @@ -743,10 +842,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 */ @@ -774,6 +901,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"); @@ -799,6 +927,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"); @@ -809,13 +938,18 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx, continue; } - if (!arg[2] /* "--" */ || - !strcmp(arg + 2, "end-of-options")) { + if (!arg[2] /* "--" */) { if (!(ctx->flags & PARSE_OPT_KEEP_DASHDASH)) { ctx->argc--; ctx->argv++; } break; + } else if (!strcmp(arg + 2, "end-of-options")) { + if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN_OPT)) { + ctx->argc--; + ctx->argv++; + } + break; } if (internal_help && !strcmp(arg + 2, "help-all")) @@ -830,6 +964,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: @@ -839,7 +974,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; @@ -884,7 +1031,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] == '-') { @@ -901,6 +1055,11 @@ int parse_options(int argc, const char **argv, precompose_argv_prefix(argc, argv, NULL); free_preprocessed_options(real_options); free(ctx.alias_groups); + for (struct parse_opt_cmdmode_list *elem = ctx.cmdmode_list; elem;) { + struct parse_opt_cmdmode_list *next = elem->next; + free(elem); + elem = next; + } return parse_options_end(&ctx); } @@ -919,14 +1078,37 @@ static int usage_argh(const struct option *opts, FILE *outfile) return utf8_fprintf(outfile, s, opts->argh ? _(opts->argh) : _("...")); } -#define USAGE_OPTS_WIDTH 24 -#define USAGE_GAP 2 +static int usage_indent(FILE *outfile) +{ + return fprintf(outfile, " "); +} + +#define USAGE_OPTS_WIDTH 26 + +static void usage_padding(FILE *outfile, size_t pos) +{ + if (pos < USAGE_OPTS_WIDTH) + fprintf(outfile, "%*s", USAGE_OPTS_WIDTH - (int)pos, ""); + else + fprintf(outfile, "\n%*s", USAGE_OPTS_WIDTH, ""); +} + +static const struct option *find_option_by_long_name(const struct option *opts, + const char *long_name) +{ + for (; opts->type != OPTION_END; opts++) { + if (opts->long_name && !strcmp(opts->long_name, long_name)) + return opts; + } + return NULL; +} static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t *ctx, const char * const *usagestr, const struct option *opts, int full, int err) { + const struct option *all_opts = opts; FILE *outfile = err ? stderr : stdout; int need_newline; @@ -1007,8 +1189,11 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t for (; opts->type != OPTION_END; opts++) { size_t pos; - int pad; + const char *cp, *np; + const char *positive_name = NULL; + if (opts->type == OPTION_SUBCOMMAND) + continue; if (opts->type == OPTION_GROUP) { fputc('\n', outfile); need_newline = 0; @@ -1024,7 +1209,7 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t need_newline = 0; } - pos = fprintf(outfile, " "); + pos = usage_indent(outfile); if (opts->short_name) { if (opts->flags & PARSE_OPT_NODASH) pos += fprintf(outfile, "%c", opts->short_name); @@ -1033,8 +1218,15 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t } if (opts->long_name && opts->short_name) pos += fprintf(outfile, ", "); - if (opts->long_name) - pos += fprintf(outfile, "--%s", opts->long_name); + if (opts->long_name) { + const char *long_name = opts->long_name; + if ((opts->flags & PARSE_OPT_NONEG) || + skip_prefix(long_name, "no-", &positive_name)) + pos += fprintf(outfile, "--%s", long_name); + else + pos += fprintf(outfile, "--[no-]%s", long_name); + } + if (opts->type == OPTION_NUMBER) pos += utf8_fprintf(outfile, _("-NUM")); @@ -1042,19 +1234,32 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t !(opts->flags & PARSE_OPT_NOARG)) pos += usage_argh(opts, outfile); - if (pos <= USAGE_OPTS_WIDTH) - pad = USAGE_OPTS_WIDTH - pos; - else { - fputc('\n', outfile); - pad = USAGE_OPTS_WIDTH; - } if (opts->type == OPTION_ALIAS) { - fprintf(outfile, "%*s", pad + USAGE_GAP, ""); + usage_padding(outfile, pos); fprintf_ln(outfile, _("alias of --%s"), (const char *)opts->value); continue; } - fprintf(outfile, "%*s%s\n", pad + USAGE_GAP, "", _(opts->help)); + + for (cp = opts->help ? _(opts->help) : ""; *cp; cp = np) { + np = strchrnul(cp, '\n'); + if (*np) + np++; + usage_padding(outfile, pos); + fwrite(cp, 1, np - cp, outfile); + pos = 0; + } + fputc('\n', outfile); + + if (positive_name) { + if (find_option_by_long_name(all_opts, positive_name)) + continue; + pos = usage_indent(outfile); + pos += fprintf(outfile, "--%s", positive_name); + usage_padding(outfile, pos); + fprintf_ln(outfile, _("opposite of --no-%s"), + positive_name); + } } fputc('\n', outfile); |