From f7832c0e822a3db7546b2ddb9b6ebb952954338b Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Fri, 15 Nov 2024 06:39:42 -0600 Subject: [PATCH] allow nuscripts to be run again on windows with assoc/ftype (#14318) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This PR tries to correct the problem of nushell scripts being made executable on Windows systems. In order to do this, these steps need to take place. 1. `assoc .nu=nuscript` 2. `ftype nuscript=C:\path\to\nu.exe '%1' %*` 3. modify the env var PATHEXT by appending `;.NU` at the end   Once those steps are done and this PR is landed, one should be able to create a script such as this. ```nushell ❯ open im_exe.nu def main [arg] { print $"Hello ($arg)!" } ``` Then they should be able to do this to run the nushell script. ```nushell ❯ im_exe Nushell Hello Nushell! ``` Under-the-hood, nushell is shelling out to cmd.exe in order to run the nushell script. # User-Facing Changes closes #13020 # Tests + Formatting # After Submitting --- crates/nu-command/src/system/run_external.rs | 31 ++++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 32543716f1..67b848915d 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -51,7 +51,6 @@ impl Command for External { input: PipelineData, ) -> Result { let cwd = engine_state.cwd(Some(stack))?; - let name: Value = call.req(engine_state, stack, 0)?; let name_str: Cow = match &name { @@ -68,10 +67,36 @@ impl Command for External { _ => Path::new(&*name_str).to_owned(), }; + // On Windows, the user could have run the cmd.exe built-in "assoc" command + // Example: "assoc .nu=nuscript" and then run the cmd.exe built-in "ftype" command + // Example: "ftype nuscript=C:\path\to\nu.exe '%1' %*" and then added the nushell + // script extension ".NU" to the PATHEXT environment variable. In this case, we use + // the which command, which will find the executable with or without the extension. + // If it "which" returns true, that means that we've found the nushell script and we + // believe the user wants to use the windows association to run the script. The only + // easy way to do this is to run cmd.exe with the script as an argument. + let potential_nuscript_in_windows = if cfg!(windows) { + // let's make sure it's a .nu scrtipt + if let Some(executable) = which(&expanded_name, "", cwd.as_ref()) { + let ext = executable + .extension() + .unwrap_or_default() + .to_string_lossy() + .to_uppercase(); + ext == "NU" + } else { + false + } + } else { + false + }; + // Find the absolute path to the executable. On Windows, set the // executable to "cmd.exe" if it's a CMD internal command. If the // command is not found, display a helpful error message. - let executable = if cfg!(windows) && is_cmd_internal_command(&name_str) { + let executable = if cfg!(windows) + && (is_cmd_internal_command(&name_str) || potential_nuscript_in_windows) + { PathBuf::from("cmd.exe") } else { // Determine the PATH to be used and then use `which` to find it - though this has no @@ -97,7 +122,7 @@ impl Command for External { // Configure args. let args = eval_arguments_from_call(engine_state, stack, call)?; #[cfg(windows)] - if is_cmd_internal_command(&name_str) { + if is_cmd_internal_command(&name_str) || potential_nuscript_in_windows { use std::os::windows::process::CommandExt; // The /D flag disables execution of AutoRun commands from registry.