Add environment variables for sourced files (#14486)

# Description

I always wondered why the module env vars `CURRENT_FILE`, `FILE_PWD`,
`PROCESS_PATH` weren't available in the source command. I tried to add
them here. I think it could be helpful but I'm not sure. I'm also not
sure this hack is what we should do but I thought I'd put it out there
for fun.

Thoughts?

### Run Module (works as it did before)

```nushell
❯ open test_module.nu
def main [] {
  print $"$env.CURRENT_FILE = ($env.CURRENT_FILE?)"
  print $"$env.FILE_PWD = ($env.FILE_PWD?)"
  print $"$env.PROCESS_PATH = ($env.PROCESS_PATH?)"
}
❯ nu test_module.nu
$env.CURRENT_FILE = /Users/fdncred/src/nushell/test_module.nu
$env.FILE_PWD = /Users/fdncred/src/nushell
$env.PROCESS_PATH = test_module.nu
```
### Use Module (works as it did before)
```nushell
❯ open test_module2.nu
export-env {
  print $"$env.CURRENT_FILE = ($env.CURRENT_FILE?)"
  print $"$env.FILE_PWD = ($env.FILE_PWD?)"
  print $"$env.PROCESS_PATH = ($env.PROCESS_PATH?)"
}
❯ use test_module2.nu
$env.CURRENT_FILE = /Users/fdncred/src/nushell/test_module.nu
$env.FILE_PWD = /Users/fdncred/src/nushell
$env.PROCESS_PATH =
```
### Sourced non-module script (this is the new part)

> [!NOTE] 
> Note: We intentionally left out PROCESS_PATH since it's supposed to
> to work like argv[0] in C, which is the name of the program being
executed.
> Since we're not executing a program, we don't need to set it.


```nushell
❯ open test_source.nu
print $"$env.CURRENT_FILE = ($env.CURRENT_FILE?)"
print $"$env.FILE_PWD = ($env.FILE_PWD?)"
print $"$env.PROCESS_PATH = ($env.PROCESS_PATH?)"
❯ source test_source.nu
$env.CURRENT_FILE = /Users/fdncred/src/nushell/test_source.nu
$env.FILE_PWD = /Users/fdncred/src/nushell
$env.PROCESS_PATH = 
```

Also, what is PROCESS_PATH even supposed to be?

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the
tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
This commit is contained in:
Darren Schroeder 2024-12-02 06:19:20 -06:00 committed by GitHub
parent dfec687a46
commit 1940b36e07
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 57 additions and 3 deletions

View file

@ -1,4 +1,5 @@
use nu_engine::{command_prelude::*, get_eval_block_with_early_return};
use nu_path::canonicalize_with;
use nu_protocol::{engine::CommandType, BlockId};
/// Source a file for environment variables.
@ -41,15 +42,57 @@ impl Command for Source {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
// Note: this hidden positional is the block_id that corresponded to the 0th position
// it is put here by the parser
// Note: two hidden positionals are used here that are injected by the parser:
// 1. The block_id that corresponded to the 0th position
// 2. The block_id_name that corresponded to the file name at the 0th position
let block_id: i64 = call.req_parser_info(engine_state, stack, "block_id")?;
let block_id_name: String = call.req_parser_info(engine_state, stack, "block_id_name")?;
let block_id = BlockId::new(block_id as usize);
let block = engine_state.get_block(block_id).clone();
let cwd = engine_state.cwd_as_string(Some(stack))?;
let pb = std::path::PathBuf::from(block_id_name);
let parent = pb.parent().unwrap_or(std::path::Path::new(""));
let file_path =
canonicalize_with(pb.as_path(), cwd).map_err(|err| ShellError::FileNotFoundCustom {
msg: format!("Could not access file '{}': {err}", pb.as_path().display()),
span: Span::unknown(),
})?;
// Note: We intentionally left out PROCESS_PATH since it's supposed to
// to work like argv[0] in C, which is the name of the program being executed.
// Since we're not executing a program, we don't need to set it.
// Save the old env vars so we can restore them after the script has ran
let old_file_pwd = stack.get_env_var(engine_state, "FILE_PWD").cloned();
let old_current_file = stack.get_env_var(engine_state, "CURRENT_FILE").cloned();
// Add env vars so they are available to the script
stack.add_env_var(
"FILE_PWD".to_string(),
Value::string(parent.to_string_lossy(), Span::unknown()),
);
stack.add_env_var(
"CURRENT_FILE".to_string(),
Value::string(file_path.to_string_lossy(), Span::unknown()),
);
let eval_block_with_early_return = get_eval_block_with_early_return(engine_state);
let return_result = eval_block_with_early_return(engine_state, stack, &block, input);
eval_block_with_early_return(engine_state, stack, &block, input)
// After the script has ran, restore the old values unless they didn't exist.
// If they didn't exist prior, remove the env vars
if let Some(old_file_pwd) = old_file_pwd {
stack.add_env_var("FILE_PWD".to_string(), old_file_pwd.clone());
} else {
stack.remove_env_var(engine_state, "FILE_PWD");
}
if let Some(old_current_file) = old_current_file {
stack.add_env_var("CURRENT_FILE".to_string(), old_current_file.clone());
} else {
stack.remove_env_var(engine_state, "CURRENT_FILE");
}
return_result
}
fn examples(&self) -> Vec<Example> {

View file

@ -3544,6 +3544,17 @@ pub fn parse_source(working_set: &mut StateWorkingSet, lite_command: &LiteComman
),
);
// store the file path as a string to be gathered later
call_with_block.set_parser_info(
"block_id_name".to_string(),
Expression::new(
working_set,
Expr::Filepath(path.path_buf().display().to_string(), false),
spans[1],
Type::String,
),
);
return Pipeline::from_vec(vec![Expression::new(
working_set,
Expr::Call(call_with_block),