Add support for erasing in multiple scopes.

Allow erasing in multiple scopes in one go. Closes #7711.
This commit is contained in:
Mahmoud Al-Qudsi 2022-10-20 11:27:22 -05:00 committed by GitHub
commit a3ad5d6131
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 74 additions and 43 deletions

View File

@ -25,6 +25,9 @@ Notable improvements and fixes
for a,b in y 1 z 3 for a,b in y 1 z 3
^~^ ^~^
- A new helper function ``fish_delta`` can be used to show the difference to fish's stock configuration (:issue:`9255`). - A new helper function ``fish_delta`` can be used to show the difference to fish's stock configuration (:issue:`9255`).
- It is now possible to specify multiple scopes for ``set -e`` and all of the named variables present in any of the specified scopes will be erased. This makes it possible to remove all instances of a variable in all scopes (``set -efglU foo``) in one go (:issue:`7711`).
=======
Deprecations and removed features Deprecations and removed features
--------------------------------- ---------------------------------

View File

@ -68,7 +68,7 @@ The following other options are available:
Causes the values to be prepended to the current set of values for the variable. This can be used with **--append** to both append and prepend at the same time. This cannot be used when assigning to a variable slice. Causes the values to be prepended to the current set of values for the variable. This can be used with **--append** to both append and prepend at the same time. This cannot be used when assigning to a variable slice.
**-e** or **--erase** **-e** or **--erase**
Causes the specified shell variables to be erased Causes the specified shell variables to be erased. Supports erasing from multiple scopes at once.
**-q** or **--query** **-q** or **--query**
Test if the specified variable names are defined. Does not output anything, but the builtins exit status is the number of variables specified that were not defined, up to a maximum of 255. If no variable was given, it also returns 255. Test if the specified variable names are defined. Does not output anything, but the builtins exit status is the number of variables specified that were not defined, up to a maximum of 255. If no variable was given, it also returns 255.

View File

@ -204,12 +204,15 @@ static int validate_cmd_opts(const wchar_t *cmd, const set_cmd_opts_t &opts, int
return STATUS_INVALID_ARGS; return STATUS_INVALID_ARGS;
} }
// Variables can only have one scope. // Variables can only have one scope...
if (opts.local + opts.function + opts.global + opts.universal > 1) { if (opts.local + opts.function + opts.global + opts.universal > 1) {
// ..unless we are erasing a variable, in which case we can erase from several in one go.
if (!opts.erase) {
streams.err.append_format(BUILTIN_ERR_GLOCAL, cmd); streams.err.append_format(BUILTIN_ERR_GLOCAL, cmd);
builtin_print_error_trailer(parser, streams.err, cmd); builtin_print_error_trailer(parser, streams.err, cmd);
return STATUS_INVALID_ARGS; return STATUS_INVALID_ARGS;
} }
}
// Variables can only have one export status. // Variables can only have one export status.
if (opts.exportv && opts.unexport) { if (opts.exportv && opts.unexport) {
@ -391,7 +394,7 @@ static maybe_t<split_var_t> split_var_and_indexes(const wchar_t *arg, env_mode_f
return res; return res;
} }
/// Given a list of values and 1-based indexes, return a new list, with those elements removed. /// Given a list of values and 1-based indexes, return a new list with those elements removed.
/// Note this deliberately accepts both args by value, as it modifies them both. /// Note this deliberately accepts both args by value, as it modifies them both.
static wcstring_list_t erased_at_indexes(wcstring_list_t input, std::vector<long> indexes) { static wcstring_list_t erased_at_indexes(wcstring_list_t input, std::vector<long> indexes) {
// Sort our indexes into *descending* order. // Sort our indexes into *descending* order.
@ -620,7 +623,11 @@ static int builtin_set_show(const wchar_t *cmd, const set_cmd_opts_t &opts, int
static int builtin_set_erase(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, static int builtin_set_erase(const wchar_t *cmd, set_cmd_opts_t &opts, int argc,
const wchar_t **argv, parser_t &parser, io_streams_t &streams) { const wchar_t **argv, parser_t &parser, io_streams_t &streams) {
int ret = STATUS_CMD_OK; int ret = STATUS_CMD_OK;
env_mode_flags_t scope = compute_scope(opts); env_mode_flags_t scopes = compute_scope(opts);
// `set -e` is allowed to be called with multiple scopes.
for (int bit = 0; 1<<bit <= ENV_USER; ++bit) {
int scope = scopes & 1<<bit;
if (scope == 0 || (scope == ENV_USER && scopes != ENV_USER)) continue;
for (int i = 0; i < argc; i++) { for (int i = 0; i < argc; i++) {
auto split = split_var_and_indexes(argv[i], scope, parser.vars(), streams); auto split = split_var_and_indexes(argv[i], scope, parser.vars(), streams);
if (!split) { if (!split) {
@ -661,6 +668,7 @@ static int builtin_set_erase(const wchar_t *cmd, set_cmd_opts_t &opts, int argc,
warn_if_uvar_shadows_global(cmd, opts, split->varname, streams, parser); warn_if_uvar_shadows_global(cmd, opts, split->varname, streams, parser);
} }
} }
}
return ret; return ret;
} }

View File

@ -921,3 +921,23 @@ foo=bar $FISH -c 'set foo 1 2 3; set --show foo'
# CHECK: $foo[2]: |2| # CHECK: $foo[2]: |2|
# CHECK: $foo[3]: |3| # CHECK: $foo[3]: |3|
# CHECK: $foo: originally inherited as |bar| # CHECK: $foo: originally inherited as |bar|
# Verify behavior of erasing in multiple scopes simultaneously
set -U marbles global
set -g marbles global
set -l marbles local
set -eUg marbles
set -ql marbles || echo "erased in more scopes than it should!"
set -qg marbles && echo "didn't erase from global scope!"
set -qU marbles && echo "didn't erase from universal scope!"
begin
set -l secret local 4 8 15 16 23 42
set -g secret global 4 8 15 16 23 42
set -egl secret[3..5]
echo $secret
# CHECK: local 4 23 42
end
echo $secret
# CHECK: global 4 23 42