cli: Use latest specified flag value when repeated (#15919)

# Description

This PR makes the last specified CLI arguments take precedence over the
earlier ones.

Existing command line tools that align with the new behaviour include:
- `neovim`: `nvim -u a.lua -u b.lua` will use `b.lua`
- `ripgrep`: you can have `--smart-case` in your user config but
override it later with `--case-sensitive` or `--ignore-case` (not
exactly the same flag override as the one I'm talking about but I think
it's still a valid example of latter flags taking precedence over the
first ones)

I think a flag defined last can be considered an override. This allows
having a `nu` alias that includes some default config (`alias nu="nu
--config something.nu"`) but being able to override that default config
as if using `nu` normally.
 
## Example

```sh
nu --config config1.nu --config config2.nu -c '$nu.config-path'
```
The current behavior would print `config1.nu`, and the new one would
print `config2.nu`

## Implementation

Just `.rev()` the iterator to search for arguments starting from the end
of the list. To support that I had to modify the return type of
`named_iter` (I couldn't find a more generic way than
`DoubleEndedIterator`).

# User-Facing Changes

- Users passing repeated flags and relying in nushell using the first
value will experience breakage. Given that right now there's no point in
passing a flag multiple times I guess not many users will be affected

# Tests + Formatting

I added a test that checks the new behavior with `--config` and
`--env-config`. I'm happy to add more cases if needed

# After Submitting
This commit is contained in:
Joaquín Triñanes 2025-06-13 01:23:38 +02:00 committed by GitHub
parent bd3930d00d
commit 12465193a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 27 additions and 3 deletions

View File

@ -128,7 +128,8 @@ impl Call {
pub fn named_iter( pub fn named_iter(
&self, &self,
) -> impl Iterator<Item = &(Spanned<String>, Option<Spanned<String>>, Option<Expression>)> { ) -> impl DoubleEndedIterator<Item = &(Spanned<String>, Option<Spanned<String>>, Option<Expression>)>
{
self.arguments.iter().filter_map(|arg| match arg { self.arguments.iter().filter_map(|arg| match arg {
Argument::Named(named) => Some(named), Argument::Named(named) => Some(named),
Argument::Positional(_) => None, Argument::Positional(_) => None,
@ -222,7 +223,7 @@ impl Call {
} }
pub fn get_flag_expr(&self, flag_name: &str) -> Option<&Expression> { pub fn get_flag_expr(&self, flag_name: &str) -> Option<&Expression> {
for name in self.named_iter() { for name in self.named_iter().rev() {
if flag_name == name.0.item { if flag_name == name.0.item {
return name.2.as_ref(); return name.2.as_ref();
} }
@ -232,7 +233,7 @@ impl Call {
} }
pub fn get_named_arg(&self, flag_name: &str) -> Option<Spanned<String>> { pub fn get_named_arg(&self, flag_name: &str) -> Option<Spanned<String>> {
for name in self.named_iter() { for name in self.named_iter().rev() {
if flag_name == name.0.item { if flag_name == name.0.item {
return Some(name.0.clone()); return Some(name.0.clone());
} }

View File

@ -242,6 +242,29 @@ fn test_alternate_config_path() {
assert_eq!(actual.out, env_path.to_string_lossy().to_string()); assert_eq!(actual.out, env_path.to_string_lossy().to_string());
} }
#[test]
fn use_last_config_path() {
let config_file = "crates/nu-utils/src/default_files/scaffold_config.nu";
let env_file = "crates/nu-utils/src/default_files/scaffold_env.nu";
let cwd = std::env::current_dir().expect("Could not get current working directory");
let config_path =
nu_path::canonicalize_with(config_file, &cwd).expect("Could not get config path");
let actual = nu!(
cwd: &cwd,
format!("nu --config non-existing-path --config another-random-path.nu --config {config_path:?} -c '$nu.config-path'")
);
assert_eq!(actual.out, config_path.to_string_lossy().to_string());
let env_path = nu_path::canonicalize_with(env_file, &cwd).expect("Could not get env path");
let actual = nu!(
cwd: &cwd,
format!("nu --env-config non-existing-path --env-config {env_path:?} -c '$nu.env-path'")
);
assert_eq!(actual.out, env_path.to_string_lossy().to_string());
}
#[test] #[test]
fn test_xdg_config_empty() { fn test_xdg_config_empty() {
Playground::setup("xdg_config_empty", |_, playground| { Playground::setup("xdg_config_empty", |_, playground| {