Convert Path to list in main and preserve case (#14764)

# Description

Fixes multiple issues related to `ENV_CONVERSION` and
path-conversion-to-list.

* #14681 removed some calls to `convert_env_values()`, but we found that
this caused `nu -n` to no longer convert the path properly.
* `ENV_CONVERSIONS` have apparently never preserved case, meaning a
conversion with a key of `foo` would not update `$env.FOO` but rather
create a new environment variable with a different case.
* There was a partial code-path that attempted to solve this for `PATH`,
but it only worked for `PATH` and `Path`.
* `convert_env_values()`, which handled `ENV_CONVERSIONS` was called in
multiple places in the startup depending on flags.

This PR:

* Refactors the startup to handle the conversion in `main()` rather than
in each potential startup path
* Updates `get_env_var_insensitive()` functions added in #14390 to
return the name of the environment variable with its original case. This
allows code that updates environment variables to preserve the case.
* Makes use of the updated function in `ENV_CONVERSIONS` to preserve the
case of any updated environment variables. The `ENV_CONVERSION` key
itself is still case **insensitive**.
* Makes use of the updated function to preserve the case of the `PATH`
environment variable (normally handled separately, regardless of whether
or not there was an `ENV_CONVERSION` for it).

## Before

`env_convert_values` was run:

* Before the user `env.nu` ran, which included `nu -c <commandstring>`
and `nu <script.nu>`
* Before the REPL loaded, which included `nu -n`

## After

`env_convert_values` always runs once in `main()` before any config file
is processed or the REPL is started

# User-Facing Changes

Bug fixes

# Tests + Formatting

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

Added additional tests to prevent future regression.

# After Submitting

There is additional cleanup that should probably be done in
`convert_env_values()`. This function previously handled
`ENV_CONVERSIONS`, but there is no longer any need for this since
`convert_env_vars()` runs whenever `$env.ENV_CONVERSIONS` changes now.

This means that the only relevant task in the old `convert_env_values()`
is to convert the `PATH` to a list, and ensure that it is a list of
strings. It's still calling the `from_string` conversion on every
variable (just once) even though there are no `ENV_CONVERSIONS` at this
point.

Leaving that to another PR though, while we get the core issue fixed
with this one.
This commit is contained in:
Douglas
2025-01-10 11:18:44 -05:00
committed by GitHub
parent 3a1601de8e
commit 72d50cf8b7
12 changed files with 99 additions and 54 deletions

View File

@ -2,14 +2,13 @@ use log::warn;
#[cfg(feature = "plugin")]
use nu_cli::read_plugin_file;
use nu_cli::{eval_config_contents, eval_source};
use nu_engine::convert_env_values;
use nu_path::canonicalize_with;
use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet},
eval_const::{get_user_autoload_dirs, get_vendor_autoload_dirs},
report_parse_error, report_shell_error, Config, ParseError, PipelineData, Spanned,
};
use nu_utils::{get_default_config, get_default_env, get_scaffold_config, get_scaffold_env, perf};
use nu_utils::{get_default_config, get_default_env, get_scaffold_config, get_scaffold_env};
use std::{
fs,
fs::File,
@ -37,20 +36,6 @@ pub(crate) fn read_config_file(
if is_env_config {
eval_default_config(engine_state, stack, get_default_env(), is_env_config);
let start_time = std::time::Instant::now();
let config = engine_state.get_config();
let use_color = config.use_ansi_coloring.get(engine_state);
// Translate environment variables from Strings to Values
if let Err(e) = convert_env_values(engine_state, stack) {
report_shell_error(engine_state, &e);
}
perf!(
"translate env vars after default_env.nu",
start_time,
use_color
);
} else {
eval_default_config(engine_state, stack, get_default_config(), is_env_config);
};

View File

@ -21,11 +21,13 @@ use command::gather_commandline_args;
use log::{trace, Level};
use miette::Result;
use nu_cli::gather_parent_env_vars;
use nu_engine::convert_env_values;
use nu_lsp::LanguageServer;
use nu_path::canonicalize_with;
use nu_protocol::{
engine::EngineState, record, report_shell_error, ByteStream, Config, IntoValue, PipelineData,
ShellError, Span, Spanned, Type, Value,
engine::{EngineState, Stack},
record, report_shell_error, ByteStream, Config, IntoValue, PipelineData, ShellError, Span,
Spanned, Type, Value,
};
use nu_std::load_standard_library;
use nu_utils::perf;
@ -319,6 +321,16 @@ fn main() -> Result<()> {
gather_parent_env_vars(&mut engine_state, init_cwd.as_ref());
perf!("gather env vars", start_time, use_color);
let mut stack = Stack::new();
start_time = std::time::Instant::now();
let config = engine_state.get_config();
let use_color = config.use_ansi_coloring.get(&engine_state);
// Translate environment variables from Strings to Values
if let Err(e) = convert_env_values(&mut engine_state, &mut stack) {
report_shell_error(&engine_state, &e);
}
perf!("Convert path to list", start_time, use_color);
engine_state.add_env_var(
"NU_VERSION".to_string(),
Value::string(env!("CARGO_PKG_VERSION"), Span::unknown()),
@ -464,6 +476,7 @@ fn main() -> Result<()> {
} else if let Some(commands) = parsed_nu_cli_args.commands.clone() {
run_commands(
&mut engine_state,
stack,
parsed_nu_cli_args,
use_color,
&commands,
@ -473,6 +486,7 @@ fn main() -> Result<()> {
} else if !script_name.is_empty() {
run_file(
&mut engine_state,
stack,
parsed_nu_cli_args,
use_color,
script_name,
@ -509,7 +523,12 @@ fn main() -> Result<()> {
shlvl += 1;
engine_state.add_env_var("SHLVL".to_string(), Value::int(shlvl, Span::unknown()));
run_repl(&mut engine_state, parsed_nu_cli_args, entire_start_time)?
run_repl(
&mut engine_state,
stack,
parsed_nu_cli_args,
entire_start_time,
)?
}
Ok(())

View File

@ -14,6 +14,7 @@ use nu_utils::perf;
pub(crate) fn run_commands(
engine_state: &mut EngineState,
mut stack: Stack,
parsed_nu_cli_args: command::NushellCliArgs,
use_color: bool,
commands: &Spanned<String>,
@ -25,8 +26,6 @@ pub(crate) fn run_commands(
let start_time = std::time::Instant::now();
let create_scaffold = nu_path::nu_config_dir().map_or(false, |p| !p.exists());
let mut stack = Stack::new();
// if the --no-config-file(-n) option is NOT passed, load the plugin file,
// load the default env file or custom (depending on parsed_nu_cli_args.env_file),
// and maybe a custom config file (depending on parsed_nu_cli_args.config_file)
@ -107,6 +106,7 @@ pub(crate) fn run_commands(
pub(crate) fn run_file(
engine_state: &mut EngineState,
mut stack: Stack,
parsed_nu_cli_args: command::NushellCliArgs,
use_color: bool,
script_name: String,
@ -114,7 +114,6 @@ pub(crate) fn run_file(
input: PipelineData,
) {
trace!("run_file");
let mut stack = Stack::new();
// if the --no-config-file(-n) option is NOT passed, load the plugin file,
// load the default env file or custom (depending on parsed_nu_cli_args.env_file),
@ -177,11 +176,11 @@ pub(crate) fn run_file(
pub(crate) fn run_repl(
engine_state: &mut EngineState,
mut stack: Stack,
parsed_nu_cli_args: command::NushellCliArgs,
entire_start_time: std::time::Instant,
) -> Result<(), miette::ErrReport> {
trace!("run_repl");
let mut stack = Stack::new();
let start_time = std::time::Instant::now();
if parsed_nu_cli_args.no_config_file.is_none() {