mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-12 21:18:53 +00:00
src/builtin_argparse: Add --ignore-unknown flag
This keeps all unknown options in $argv, so ```fish argparse -i a/alpha -- -a banana -o val -w ``` results in $_flag_a set to banana, and $argv set to `-o val -w`. This allows users to use multiple argparse passes, or to simply avoid specifying all options e.g. in completions - `systemctl` has 46 of them, most not having any effect on the completions. Fixes #5367.
This commit is contained in:
parent
d8ac051f89
commit
8c9359fdd4
6 changed files with 48 additions and 8 deletions
|
@ -27,6 +27,7 @@
|
||||||
- `printf` prints what it can when input hasn't been fully converted to a number, but still prints an error (#5532).
|
- `printf` prints what it can when input hasn't been fully converted to a number, but still prints an error (#5532).
|
||||||
- `complete -C foo` now works instead of erroring out and requiring `complete -Cfoo`.
|
- `complete -C foo` now works instead of erroring out and requiring `complete -Cfoo`.
|
||||||
- `argparse` now defaults to showing the current function name (instead of `argparse`) in its errors, making `--name` often superfluous (#5835).
|
- `argparse` now defaults to showing the current function name (instead of `argparse`) in its errors, making `--name` often superfluous (#5835).
|
||||||
|
- `argparse` learned a new `--ignore-unknown` flag to keep unrecognized options, allowing multiple argparse passes to parse options (#5367).
|
||||||
|
|
||||||
### Interactive improvements
|
### Interactive improvements
|
||||||
- Major improvements in performance and functionality to the 'sorin' sample prompt (#5411).
|
- Major improvements in performance and functionality to the 'sorin' sample prompt (#5411).
|
||||||
|
|
|
@ -33,6 +33,8 @@ The following ``argparse`` options are available. They must appear before all OP
|
||||||
|
|
||||||
- ``-X`` or ``--max-args`` is followed by an integer that defines the maximum number of acceptable non-option arguments. The default is infinity.
|
- ``-X`` or ``--max-args`` is followed by an integer that defines the maximum number of acceptable non-option arguments. The default is infinity.
|
||||||
|
|
||||||
|
- ``-i`` or ``--ignore-unknown`` ignores unknown options, keeping them and their arguments in $argv instead.
|
||||||
|
|
||||||
- ``-s`` or ``--stop-nonopt`` causes scanning the arguments to stop as soon as the first non-option argument is seen. Using this arg is equivalent to calling the C function ``getopt_long()`` with the short options starting with a ``+`` symbol. This is sometimes known as "POSIXLY CORRECT". If this flag is not used then arguments are reordered (i.e., permuted) so that all non-option arguments are moved after option arguments. This mode has several uses but the main one is to implement a command that has subcommands.
|
- ``-s`` or ``--stop-nonopt`` causes scanning the arguments to stop as soon as the first non-option argument is seen. Using this arg is equivalent to calling the C function ``getopt_long()`` with the short options starting with a ``+`` symbol. This is sometimes known as "POSIXLY CORRECT". If this flag is not used then arguments are reordered (i.e., permuted) so that all non-option arguments are moved after option arguments. This mode has several uses but the main one is to implement a command that has subcommands.
|
||||||
|
|
||||||
- ``-h`` or ``--help`` displays help about using this command.
|
- ``-h`` or ``--help`` displays help about using this command.
|
||||||
|
|
|
@ -44,6 +44,7 @@ struct option_spec_t {
|
||||||
using option_spec_ref_t = std::unique_ptr<option_spec_t>;
|
using option_spec_ref_t = std::unique_ptr<option_spec_t>;
|
||||||
|
|
||||||
struct argparse_cmd_opts_t {
|
struct argparse_cmd_opts_t {
|
||||||
|
bool ignore_unknown = false;
|
||||||
bool print_help = false;
|
bool print_help = false;
|
||||||
bool stop_nonopt = false;
|
bool stop_nonopt = false;
|
||||||
size_t min_args = 0;
|
size_t min_args = 0;
|
||||||
|
@ -57,8 +58,9 @@ struct argparse_cmd_opts_t {
|
||||||
std::vector<std::vector<wchar_t>> exclusive_flag_sets;
|
std::vector<std::vector<wchar_t>> exclusive_flag_sets;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const wchar_t *const short_options = L"+:hn:sx:N:X:";
|
static const wchar_t *const short_options = L"+:hn:six:N:X:";
|
||||||
static const struct woption long_options[] = {{L"stop-nonopt", no_argument, NULL, 's'},
|
static const struct woption long_options[] = {{L"stop-nonopt", no_argument, NULL, 's'},
|
||||||
|
{L"ignore-unknown", no_argument, NULL, 'i'},
|
||||||
{L"name", required_argument, NULL, 'n'},
|
{L"name", required_argument, NULL, 'n'},
|
||||||
{L"exclusive", required_argument, NULL, 'x'},
|
{L"exclusive", required_argument, NULL, 'x'},
|
||||||
{L"help", no_argument, NULL, 'h'},
|
{L"help", no_argument, NULL, 'h'},
|
||||||
|
@ -352,6 +354,10 @@ static int parse_cmd_opts(argparse_cmd_opts_t &opts, int *optind, //!OCLINT(hig
|
||||||
opts.stop_nonopt = true;
|
opts.stop_nonopt = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'i': {
|
||||||
|
opts.ignore_unknown = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'x': {
|
case 'x': {
|
||||||
// Just save the raw string here. Later, when we have all the short and long flag
|
// Just save the raw string here. Later, when we have all the short and long flag
|
||||||
// definitions we'll parse these strings into a more useful data structure.
|
// definitions we'll parse these strings into a more useful data structure.
|
||||||
|
@ -536,7 +542,7 @@ static int handle_flag(parser_t &parser, const argparse_cmd_opts_t &opts, option
|
||||||
return STATUS_CMD_OK;
|
return STATUS_CMD_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int argparse_parse_flags(parser_t &parser, const argparse_cmd_opts_t &opts,
|
static int argparse_parse_flags(parser_t &parser, argparse_cmd_opts_t &opts,
|
||||||
const wchar_t *short_options, const woption *long_options,
|
const wchar_t *short_options, const woption *long_options,
|
||||||
const wchar_t *cmd, int argc, wchar_t **argv, int *optind,
|
const wchar_t *cmd, int argc, wchar_t **argv, int *optind,
|
||||||
io_streams_t &streams) {
|
io_streams_t &streams) {
|
||||||
|
@ -547,16 +553,14 @@ static int argparse_parse_flags(parser_t &parser, const argparse_cmd_opts_t &opt
|
||||||
if (opt == ':') {
|
if (opt == ':') {
|
||||||
builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]);
|
builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]);
|
||||||
return STATUS_INVALID_ARGS;
|
return STATUS_INVALID_ARGS;
|
||||||
}
|
} else if (opt == '?') {
|
||||||
|
|
||||||
if (opt == '?') {
|
|
||||||
// It's not a recognized flag. See if it's an implicit int flag.
|
// It's not a recognized flag. See if it's an implicit int flag.
|
||||||
const wchar_t *arg_contents = argv[w.woptind - 1] + 1;
|
const wchar_t *arg_contents = argv[w.woptind - 1] + 1;
|
||||||
int retval = STATUS_CMD_OK;
|
int retval = STATUS_CMD_OK;
|
||||||
if (is_implicit_int(opts, arg_contents)) {
|
if (is_implicit_int(opts, arg_contents)) {
|
||||||
retval = validate_and_store_implicit_int(parser, opts, arg_contents, w, long_idx,
|
retval = validate_and_store_implicit_int(parser, opts, arg_contents, w, long_idx,
|
||||||
streams);
|
streams);
|
||||||
} else {
|
} else if (!opts.ignore_unknown) {
|
||||||
streams.err.append_format(BUILTIN_ERR_UNKNOWN, cmd, argv[w.woptind - 1]);
|
streams.err.append_format(BUILTIN_ERR_UNKNOWN, cmd, argv[w.woptind - 1]);
|
||||||
// We don't use builtin_print_error_trailer as that
|
// We don't use builtin_print_error_trailer as that
|
||||||
// says to use the cmd help,
|
// says to use the cmd help,
|
||||||
|
@ -565,12 +569,25 @@ static int argparse_parse_flags(parser_t &parser, const argparse_cmd_opts_t &opt
|
||||||
// Plus this particular error is not an error in argparse usage.
|
// Plus this particular error is not an error in argparse usage.
|
||||||
streams.err.append(parser.current_line());
|
streams.err.append(parser.current_line());
|
||||||
retval = STATUS_INVALID_ARGS;
|
retval = STATUS_INVALID_ARGS;
|
||||||
|
} else {
|
||||||
|
// Any unrecognized option is put back if ignore_unknown is used.
|
||||||
|
// This allows reusing the same argv in multiple argparse calls,
|
||||||
|
// or just ignoring the error (e.g. in completions).
|
||||||
|
opts.argv.push_back(arg_contents - 1);
|
||||||
}
|
}
|
||||||
if (retval != STATUS_CMD_OK) return retval;
|
if (retval != STATUS_CMD_OK) return retval;
|
||||||
long_idx = -1;
|
long_idx = -1;
|
||||||
continue;
|
continue;
|
||||||
|
} else if (opt == 1) {
|
||||||
|
// A non-option argument.
|
||||||
|
// We use `-` as the first option-string-char to disable GNU getopt's reordering,
|
||||||
|
// otherwise we'd get ignored options first and normal arguments later.
|
||||||
|
// E.g. `argparse -i -- -t tango -w` needs to keep `-t tango -w` in $argv, not `-t -w tango`.
|
||||||
|
opts.argv.push_back(argv[w.woptind - 1]);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// It's a recognized flag.
|
// It's a recognized flag.
|
||||||
auto found = opts.options.find(opt);
|
auto found = opts.options.find(opt);
|
||||||
assert(found != opts.options.end());
|
assert(found != opts.options.end());
|
||||||
|
@ -591,7 +608,8 @@ static int argparse_parse_args(argparse_cmd_opts_t &opts, const wcstring_list_t
|
||||||
parser_t &parser, io_streams_t &streams) {
|
parser_t &parser, io_streams_t &streams) {
|
||||||
if (args.empty()) return STATUS_CMD_OK;
|
if (args.empty()) return STATUS_CMD_OK;
|
||||||
|
|
||||||
wcstring short_options = opts.stop_nonopt ? L"+:" : L":";
|
// "+" means stop at nonopt, "-" means give nonoptions the option character code `1`, and don't reorder.
|
||||||
|
wcstring short_options = opts.stop_nonopt ? L"+:" : L"-";
|
||||||
std::vector<woption> long_options;
|
std::vector<woption> long_options;
|
||||||
populate_option_strings(opts, &short_options, &long_options);
|
populate_option_strings(opts, &short_options, &long_options);
|
||||||
|
|
||||||
|
@ -614,7 +632,11 @@ static int argparse_parse_args(argparse_cmd_opts_t &opts, const wcstring_list_t
|
||||||
retval = check_for_mutually_exclusive_flags(opts, streams);
|
retval = check_for_mutually_exclusive_flags(opts, streams);
|
||||||
if (retval != STATUS_CMD_OK) return retval;
|
if (retval != STATUS_CMD_OK) return retval;
|
||||||
|
|
||||||
for (int i = optind; argv[i]; i++) opts.argv.push_back(argv[i]);
|
if (opts.stop_nonopt) {
|
||||||
|
for (int i = optind; argv[i]; i++) {
|
||||||
|
opts.argv.push_back(argv[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return STATUS_CMD_OK;
|
return STATUS_CMD_OK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,3 +112,6 @@ Standard input (line 353):
|
||||||
argparse a/alpha -- --banana
|
argparse a/alpha -- --banana
|
||||||
^
|
^
|
||||||
in function 'notargparse'
|
in function 'notargparse'
|
||||||
|
|
||||||
|
####################
|
||||||
|
# Ignoring unknown options
|
||||||
|
|
|
@ -179,3 +179,10 @@ end
|
||||||
notargparse
|
notargparse
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|
||||||
|
logmsg Ignoring unknown options
|
||||||
|
argparse -i a=+ b=+ -- -a alpha -b bravo -t tango -a aaaa --wurst
|
||||||
|
or echo unexpected argparse return status $status >&2
|
||||||
|
# The unknown options are removed _entirely_.
|
||||||
|
echo $argv
|
||||||
|
echo $_flag_a
|
||||||
|
|
|
@ -132,3 +132,8 @@ expected argparse return status 57
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# Errors use function name by default
|
# Errors use function name by default
|
||||||
|
|
||||||
|
####################
|
||||||
|
# Ignoring unknown options
|
||||||
|
-t tango --wurst
|
||||||
|
alpha aaaa
|
||||||
|
|
Loading…
Reference in a new issue