mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-03 06:41:14 -03:00
Make argparse save parsed options in $argv_opts. (Fixes #6466)
Specifically, every argument (other than the first --, if any) that argparse doesn't add to $argv is now added to a new local variable $argv_opts. This allows you to make wrapper commands that modify non-option arguments, and then forwards all arguments to another command. See the new example at the end of doc_src/cmds/argparse.rst for a use case for this new variable.
This commit is contained in:
@@ -42,6 +42,7 @@ Deprecations and removed features
|
||||
Scripting improvements
|
||||
----------------------
|
||||
- The ``psub`` command now allows combining ``--suffix`` with ``--fifo`` (:issue:`11729`).
|
||||
- ``argparse`` now saves recognised options and values in ``$argv_opts``, allowing them to be forwarded to other commands (:issue:`6466`).
|
||||
|
||||
Interactive improvements
|
||||
------------------------
|
||||
|
||||
@@ -14,7 +14,7 @@ Synopsis
|
||||
Description
|
||||
-----------
|
||||
|
||||
This command makes it easy for fish scripts and functions to handle arguments. You pass arguments that define the known options, followed by a literal **--**, then the arguments to be parsed (which might also include a literal **--**). ``argparse`` then sets variables to indicate the passed options with their values, and sets ``$argv`` to the remaining arguments. See the :ref:`usage <cmd-argparse-usage>` section below.
|
||||
This command makes it easy for fish scripts and functions to handle arguments. You pass arguments that define the known options, followed by a literal **--**, then the arguments to be parsed (which might also include a literal **--**). ``argparse`` then sets variables to indicate the passed options with their values, sets ``$argv_opts`` to the options and their values, and sets ``$argv`` to the remaining arguments. See the :ref:`usage <cmd-argparse-usage>` section below.
|
||||
|
||||
Each option specification (``OPTION_SPEC``) is written in the :ref:`domain specific language <cmd-argparse-option-specification>` described below. All OPTION_SPECs must appear after any argparse flags and before the ``--`` that separates them from the arguments to be parsed.
|
||||
|
||||
@@ -217,7 +217,7 @@ Some *OPTION_SPEC* examples:
|
||||
|
||||
- ``#longonly`` causes the last integer option to be stored in ``_flag_longonly``.
|
||||
|
||||
After parsing the arguments the ``argv`` variable is set with local scope to any values not already consumed during flag processing. If there are no unbound values the variable is set but ``count $argv`` will be zero.
|
||||
After parsing the arguments the ``argv`` variable is set with local scope to any values not already consumed during flag processing. If there are no unbound values the variable is set but ``count $argv`` will be zero. Similarly, the ``argv_opts`` variable is set with local scope to the arguments that *were* consumed during flag processing. This allows forwarding ``$argv_opts`` to another command, together with additional arguments.
|
||||
|
||||
If an error occurs during argparse processing it will exit with a non-zero status and print error messages to stderr.
|
||||
|
||||
@@ -259,6 +259,48 @@ After this it figures out which variable it should operate on according to the `
|
||||
and set $var $result
|
||||
|
||||
|
||||
An example of using ``$argv_opts`` to forward known options to another command, whilst adding new options::
|
||||
|
||||
function my-head
|
||||
# The following options are existing ones to head that we will forward verbatim
|
||||
set -l opt_spec n/lines= q/quiet silent v/verbose z/zero-terminated help version
|
||||
argparse --ignore-unknown $opt_spec -- $argv || return
|
||||
set -l opts $argv_opts # Save it so it isn't overridden by the next argparse call
|
||||
|
||||
# --qwords is a new option, but --bytes is an existing one which we will modify below
|
||||
argparse qwords= c/bytes= -- $argv || return
|
||||
|
||||
if set -q _flag_qwords
|
||||
# --qwords allows specifying the size in multiples of 8 bytes
|
||||
set -a opts --bytes=(math -- $_flag_qwords \* 8 || return)
|
||||
else if set -q _flag_bytes
|
||||
# Allows using a 'q' suffix, e.g. --bytes=4q to mean 4*8 bytes.
|
||||
if string match -qr 'q$' -- $_flag_bytes
|
||||
set -a opts --bytes=(math -- (string replace -r 'q$' '*8' -- $_flag_bytes) || return)
|
||||
else
|
||||
# Keep the users setting
|
||||
set -a opts --bytes=$_flag_bytes
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if test (count $argv) -eq 0
|
||||
# Default to heading /dev/kmsg (whereas head defaults to stdin)
|
||||
set -l argv /dev/kmsg
|
||||
end
|
||||
|
||||
# Call the real head with our modified options and arguments.
|
||||
head $opts -- $argv
|
||||
end
|
||||
|
||||
|
||||
The first argparse call above saves all the options we do *not* want to process in ``$argv_opts``, which we then copy in to ``$opts``.
|
||||
The second `argparse` call then parses the options we *do* want to process. The new
|
||||
value of ``$argv_opts`` is ignored, and instead the ``$_flag_OPTION`` variables are used to transform each of these additional options and
|
||||
add them back to ``$opts``.
|
||||
|
||||
Note that the first ``argparse`` call is *needed* for the code that inspects ``$argv`` to work, as it checks for the absence of any *non*-option arguments (i.e. no file was specified). We'd similarly need the first call if we wanted to modify the given filenames.
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ struct ArgParseCmdOpts<'args> {
|
||||
name: WString,
|
||||
raw_exclusive_flags: Vec<&'args wstr>,
|
||||
args: Vec<&'args wstr>,
|
||||
args_opts: Vec<&'args wstr>,
|
||||
options: HashMap<char, OptionSpec<'args>>,
|
||||
long_to_short_flag: HashMap<WString, char>,
|
||||
exclusive_flag_sets: Vec<Vec<char>>,
|
||||
@@ -792,6 +793,7 @@ fn argparse_parse_flags<'args>(
|
||||
// This allows reusing the same argv in multiple argparse calls,
|
||||
// or just ignoring the error (e.g. in completions).
|
||||
opts.args.push(args_read[w.wopt_index - 1]);
|
||||
w.argv_opts.pop();
|
||||
// Work around weirdness with wgetopt, which crashes if we `continue` here.
|
||||
if w.wopt_index == argc {
|
||||
break;
|
||||
@@ -816,6 +818,7 @@ fn argparse_parse_flags<'args>(
|
||||
};
|
||||
retval?;
|
||||
}
|
||||
opts.args_opts = w.argv_opts;
|
||||
|
||||
*optind = w.wopt_index;
|
||||
return Ok(SUCCESS);
|
||||
@@ -901,6 +904,8 @@ fn set_argparse_result_vars(vars: &EnvStack, opts: &ArgParseCmdOpts) {
|
||||
|
||||
let args = opts.args.iter().map(|&s| s.to_owned()).collect();
|
||||
vars.set(L!("argv"), EnvMode::LOCAL, args);
|
||||
let args_opts = opts.args_opts.iter().map(|&s| s.to_owned()).collect();
|
||||
vars.set(L!("argv_opts"), EnvMode::LOCAL, args_opts);
|
||||
}
|
||||
|
||||
/// The argparse builtin. This is explicitly not compatible with the BSD or GNU version of this
|
||||
|
||||
@@ -139,6 +139,9 @@ pub struct WGetopter<'opts, 'args, 'argarray> {
|
||||
return_colon: bool,
|
||||
/// Prevents redundant initialization.
|
||||
initialized: bool,
|
||||
/// This will be populated with the elements of the original args that were interpreted
|
||||
/// as options and arguments to options
|
||||
pub argv_opts: Vec<&'args wstr>,
|
||||
}
|
||||
|
||||
impl<'opts, 'args, 'argarray> WGetopter<'opts, 'args, 'argarray> {
|
||||
@@ -160,6 +163,7 @@ pub fn new(
|
||||
last_nonopt: 0,
|
||||
return_colon: false,
|
||||
initialized: false,
|
||||
argv_opts: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,14 +306,17 @@ fn next_argv(&mut self) -> NextArgv {
|
||||
return NextArgv::UnpermutedNonOption;
|
||||
}
|
||||
|
||||
let opt = self.argv[self.wopt_index];
|
||||
self.argv_opts.push(opt);
|
||||
|
||||
// We've found an option, so we need to skip the initial punctuation.
|
||||
let skip = if !self.longopts.is_empty() && self.argv[self.wopt_index].char_at(1) == '-' {
|
||||
let skip = if !self.longopts.is_empty() && opt.char_at(1) == '-' {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
self.remaining_text = self.argv[self.wopt_index][skip..].into();
|
||||
self.remaining_text = opt[skip..].into();
|
||||
NextArgv::FoundOption
|
||||
}
|
||||
|
||||
@@ -365,7 +372,9 @@ fn handle_short_opt(&mut self) -> char {
|
||||
c = if self.return_colon { ':' } else { '?' };
|
||||
} else {
|
||||
// Consume the next element.
|
||||
self.woptarg = Some(self.argv[self.wopt_index]);
|
||||
let val = self.argv[self.wopt_index];
|
||||
self.argv_opts.push(val);
|
||||
self.woptarg = Some(val);
|
||||
self.wopt_index += 1;
|
||||
}
|
||||
}
|
||||
@@ -393,7 +402,9 @@ fn update_long_opt(
|
||||
}
|
||||
} else if opt_found.arg_type == ArgType::RequiredArgument {
|
||||
if self.wopt_index < self.argv.len() {
|
||||
self.woptarg = Some(self.argv[self.wopt_index]);
|
||||
let val = self.argv[self.wopt_index];
|
||||
self.argv_opts.push(val);
|
||||
self.woptarg = Some(val);
|
||||
self.wopt_index += 1;
|
||||
} else {
|
||||
self.remaining_text = empty_wstr();
|
||||
|
||||
@@ -31,6 +31,7 @@ begin
|
||||
# CHECK: 0
|
||||
set -l
|
||||
# CHECK: argv hello
|
||||
# CHECK: argv_opts
|
||||
end
|
||||
|
||||
# Invalid option specs
|
||||
@@ -154,6 +155,7 @@ begin
|
||||
argparse h/help -- help
|
||||
set -l
|
||||
# CHECK: argv help
|
||||
# CHECK: argv_opts
|
||||
end
|
||||
|
||||
# Five args with two matching a flag
|
||||
@@ -163,12 +165,13 @@ begin
|
||||
# CHECK: _flag_h '--help' '-h'
|
||||
# CHECK: _flag_help '--help' '-h'
|
||||
# CHECK: argv 'help' 'me' 'a lot more'
|
||||
# CHECK: argv_opts '--help' '-h'
|
||||
end
|
||||
|
||||
# Required, optional, and multiple flags
|
||||
begin
|
||||
argparse h/help 'a/abc=' 'd/def=?' 'g/ghk=+' -- help --help me --ghk=g1 --abc=ABC --ghk g2 -d -g g3
|
||||
set -l
|
||||
set -lL
|
||||
# CHECK: _flag_a ABC
|
||||
# CHECK: _flag_abc ABC
|
||||
# CHECK: _flag_d
|
||||
@@ -178,6 +181,7 @@ begin
|
||||
# CHECK: _flag_h --help
|
||||
# CHECK: _flag_help --help
|
||||
# CHECK: argv 'help' 'me'
|
||||
# CHECK: argv_opts '--help' '--ghk=g1' '--abc=ABC' '--ghk' 'g2' '-d' '-g' 'g3'
|
||||
end
|
||||
|
||||
# --stop-nonopt works
|
||||
@@ -189,6 +193,7 @@ begin
|
||||
# CHECK: _flag_h -h
|
||||
# CHECK: _flag_help -h
|
||||
# CHECK: argv 'non-opt' 'second non-opt' '--help'
|
||||
# CHECK: argv_opts '-a' 'A1' '-h' '--abc' 'A2'
|
||||
end
|
||||
|
||||
# Implicit int flags work
|
||||
@@ -197,6 +202,7 @@ begin
|
||||
set -l
|
||||
# CHECK: _flag_val 123
|
||||
# CHECK: argv 'abc' 'def'
|
||||
# CHECK: argv_opts -123
|
||||
end
|
||||
begin
|
||||
argparse v/verbose '#-val' 't/token=' -- -123 a1 --token woohoo --234 -v a2 --verbose
|
||||
@@ -207,6 +213,7 @@ begin
|
||||
# CHECK: _flag_val -234
|
||||
# CHECK: _flag_verbose '-v' '--verbose'
|
||||
# CHECK: argv 'a1' 'a2'
|
||||
# CHECK: argv_opts '-123' '--token' 'woohoo' '--234' '-v' '--verbose'
|
||||
end
|
||||
|
||||
# Should be set to 987
|
||||
@@ -216,6 +223,7 @@ begin
|
||||
# CHECK: _flag_m 987
|
||||
# CHECK: _flag_max 987
|
||||
# CHECK: argv 'argle' 'bargle'
|
||||
# CHECK: argv_opts -987
|
||||
end
|
||||
|
||||
# Should be set to 765
|
||||
@@ -225,6 +233,7 @@ begin
|
||||
# CHECK: _flag_m 765
|
||||
# CHECK: _flag_max 765
|
||||
# CHECK: argv 'argle' 'bargle'
|
||||
# CHECK: argv_opts '-987' '--max' '765'
|
||||
end
|
||||
|
||||
# Bool short flag only
|
||||
@@ -234,6 +243,7 @@ begin
|
||||
# CHECK: _flag_C -C
|
||||
# CHECK: _flag_v '-v' '-v'
|
||||
# CHECK: argv 'arg1' 'arg2'
|
||||
# CHECK: argv_opts '-C' '-v' '-v'
|
||||
end
|
||||
|
||||
# Value taking short flag only
|
||||
@@ -244,6 +254,7 @@ begin
|
||||
# CHECK: _flag_verbose '--verbose' '-v'
|
||||
# CHECK: _flag_x arg2
|
||||
# CHECK: argv arg1
|
||||
# CHECK: argv_opts '--verbose' '-v' '-x' 'arg2'
|
||||
end
|
||||
|
||||
# Implicit int short flag only
|
||||
@@ -254,6 +265,7 @@ begin
|
||||
# CHECK: _flag_verbose '-v' '-v' '-v'
|
||||
# CHECK: _flag_x 321
|
||||
# CHECK: argv 'argle' 'bargle'
|
||||
# CHECK: argv_opts '-v' '-v' '-v' '-x' '321'
|
||||
end
|
||||
|
||||
# Implicit int short flag only with custom validation passes
|
||||
@@ -264,6 +276,7 @@ begin
|
||||
# CHECK: _flag_verbose '-v' '-v' '-v'
|
||||
# CHECK: _flag_x 499
|
||||
# CHECK: argv
|
||||
# CHECK: argv_opts '-v' '-v' '-x' '499' '-v'
|
||||
end
|
||||
|
||||
# Implicit int short flag only with custom validation fails
|
||||
@@ -326,8 +339,10 @@ begin
|
||||
or echo unexpected argparse return status $status >&2
|
||||
# The unknown options are removed _entirely_.
|
||||
echo $argv
|
||||
echo $argv_opts
|
||||
echo $_flag_a
|
||||
# CHECK: -t tango --wurst
|
||||
# CHECK: -a alpha -b bravo -a aaaa
|
||||
# CHECK: alpha aaaa
|
||||
end
|
||||
|
||||
@@ -338,6 +353,7 @@ begin
|
||||
# CHECK: _flag_b -b
|
||||
# CHECK: _flag_break -b
|
||||
# CHECK: argv '-b kubectl get pods -l name=foo'
|
||||
# CHECK: argv_opts
|
||||
end
|
||||
|
||||
begin
|
||||
@@ -346,7 +362,9 @@ begin
|
||||
printf '%s\n' $argv
|
||||
# CHECK: a
|
||||
# CHECK: b
|
||||
printf '%s\n' $argv_opts
|
||||
# CHECK: -a
|
||||
# CHECK: --alpha
|
||||
end
|
||||
|
||||
begin
|
||||
@@ -361,6 +379,7 @@ begin
|
||||
# CHECK: _flag_i 2
|
||||
# CHECK: _flag_o 3
|
||||
# CHECK: argv
|
||||
# CHECK: argv_opts '-i' '2' '-o' '3'
|
||||
end
|
||||
|
||||
# long-only flags
|
||||
@@ -370,6 +389,7 @@ begin
|
||||
# CHECK: _flag_foo --foo
|
||||
# CHECK: _flag_installed no
|
||||
# CHECK: argv
|
||||
# CHECK: argv_opts '--installed=no' '--foo'
|
||||
end
|
||||
|
||||
begin
|
||||
@@ -378,6 +398,7 @@ begin
|
||||
# CHECK: _flag_foo --foo
|
||||
# CHECK: _flag_installed 5
|
||||
# CHECK: argv
|
||||
# CHECK: argv_opts '--installed=5' '--foo'
|
||||
end
|
||||
|
||||
begin
|
||||
@@ -386,6 +407,7 @@ begin
|
||||
# CHECK: _flag_installed 5
|
||||
# CHECK: _flag_num 5
|
||||
# CHECK: argv
|
||||
# CHECK: argv_opts '--installed=5' '-5'
|
||||
end
|
||||
|
||||
begin
|
||||
@@ -498,6 +520,7 @@ begin
|
||||
argparse --ignore-unknown h i -- -hoa -oia
|
||||
echo -- $argv
|
||||
#CHECK: -hoa -oia
|
||||
echo -- $argv_opts
|
||||
echo $_flag_h
|
||||
#CHECK: -h
|
||||
set -q _flag_i
|
||||
|
||||
Reference in New Issue
Block a user