From 1940b36e07083f745d8a427ba84ec55988972e54 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Mon, 2 Dec 2024 06:19:20 -0600 Subject: [PATCH] Add environment variables for sourced files (#14486) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 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 # Tests + Formatting # After Submitting --- crates/nu-command/src/misc/source.rs | 49 ++++++++++++++++++++++++-- crates/nu-parser/src/parse_keywords.rs | 11 ++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/crates/nu-command/src/misc/source.rs b/crates/nu-command/src/misc/source.rs index 01f971a103..a12a57ebef 100644 --- a/crates/nu-command/src/misc/source.rs +++ b/crates/nu-command/src/misc/source.rs @@ -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 { - // 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 { diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 3292405cab..e01743321c 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -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),