mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +00:00
Support running batch files without typing their extension (#6278)
* Support running batch files without typing their extension * suppress warning
This commit is contained in:
parent
fc8512be39
commit
dcab255d59
2 changed files with 122 additions and 17 deletions
|
@ -101,6 +101,7 @@ impl Command for External {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExternalCommand {
|
||||
pub name: Spanned<String>,
|
||||
pub args: Vec<Spanned<String>>,
|
||||
|
@ -121,28 +122,75 @@ impl ExternalCommand {
|
|||
let ctrlc = engine_state.ctrlc.clone();
|
||||
|
||||
let mut process = self.create_process(&input, false, head)?;
|
||||
let child;
|
||||
// mut is used in the windows branch only, suppress warning on other platforms
|
||||
#[allow(unused_mut)]
|
||||
let mut child;
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// Some common Windows commands are actually built in to cmd.exe, not executables in their own right.
|
||||
// To support those commands, we "shell out" to cmd.exe.
|
||||
// Running external commands on Windows has 2 points of complication:
|
||||
// 1. Some common Windows commands are actually built in to cmd.exe, not executables in their own right.
|
||||
// 2. We need to let users run batch scripts etc. (.bat, .cmd) without typing their extension
|
||||
|
||||
// This has the full list of cmd.exe "internal" commands: https://ss64.com/nt/syntax-internal.html
|
||||
// I (Reilly) went through the full list and whittled it down to ones that are potentially useful:
|
||||
const CMD_INTERNAL_COMMANDS: [&str; 8] = [
|
||||
"ASSOC", "DIR", "ECHO", "FTYPE", "MKLINK", "START", "VER", "VOL",
|
||||
];
|
||||
|
||||
let command_name_upper = self.name.item.to_uppercase();
|
||||
let use_cmd = CMD_INTERNAL_COMMANDS
|
||||
.iter()
|
||||
.any(|&cmd| command_name_upper == cmd);
|
||||
// To support these situations, we have a fallback path that gets run if a command
|
||||
// fails to be run as a normal executable:
|
||||
// 1. "shell out" to cmd.exe if the command is a known cmd.exe internal command
|
||||
// 2. Otherwise, use `which-rs` to look for batch files etc. then run those in cmd.exe
|
||||
|
||||
match process.spawn() {
|
||||
Err(_) => {
|
||||
let mut fg_process = self.create_process(&input, use_cmd, head)?;
|
||||
child = fg_process.spawn();
|
||||
Err(err) => {
|
||||
// set the default value, maybe we'll override it later
|
||||
child = Err(err);
|
||||
|
||||
// This has the full list of cmd.exe "internal" commands: https://ss64.com/nt/syntax-internal.html
|
||||
// I (Reilly) went through the full list and whittled it down to ones that are potentially useful:
|
||||
const CMD_INTERNAL_COMMANDS: [&str; 8] = [
|
||||
"ASSOC", "DIR", "ECHO", "FTYPE", "MKLINK", "START", "VER", "VOL",
|
||||
];
|
||||
let command_name_upper = self.name.item.to_uppercase();
|
||||
let looks_like_cmd_internal = CMD_INTERNAL_COMMANDS
|
||||
.iter()
|
||||
.any(|&cmd| command_name_upper == cmd);
|
||||
|
||||
if looks_like_cmd_internal {
|
||||
let mut cmd_process = self.create_process(&input, true, head)?;
|
||||
child = cmd_process.spawn();
|
||||
} else {
|
||||
#[cfg(feature = "which-support")]
|
||||
{
|
||||
// maybe it's a batch file (foo.cmd) and the user typed `foo`. Try to find it with `which-rs`
|
||||
// TODO: clean this up with an if-let chain once those are stable
|
||||
if let Ok(path) =
|
||||
nu_engine::env::path_str(engine_state, stack, self.name.span)
|
||||
{
|
||||
if let Some(cwd) = self.env_vars.get("PWD") {
|
||||
// append cwd to PATH so `which-rs` looks in the cwd too.
|
||||
// this approximates what cmd.exe does.
|
||||
let path_with_cwd = format!("{};{}", cwd, path);
|
||||
if let Ok(which_path) =
|
||||
which::which_in(&self.name.item, Some(path_with_cwd), cwd)
|
||||
{
|
||||
if let Some(file_name) = which_path.file_name() {
|
||||
let file_name_upper =
|
||||
file_name.to_string_lossy().to_uppercase();
|
||||
if file_name_upper != command_name_upper {
|
||||
// which-rs found an executable file with a slightly different name
|
||||
// than the one the user tried. Let's try running it
|
||||
let mut new_command = self.clone();
|
||||
new_command.name = Spanned {
|
||||
item: file_name.to_string_lossy().to_string(),
|
||||
span: self.name.span,
|
||||
};
|
||||
let mut cmd_process = new_command
|
||||
.create_process(&input, true, head)?;
|
||||
child = cmd_process.spawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(process) => {
|
||||
child = Ok(process);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use nu_test_support::fs::Stub::EmptyFile;
|
||||
use nu_test_support::fs::Stub::{EmptyFile, FileWithContent};
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
|
@ -259,3 +259,60 @@ fn single_quote_does_not_expand_path_glob_windows() {
|
|||
assert!(actual.out.contains("D&D_volume_2.txt"));
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn can_run_batch_files() {
|
||||
Playground::setup("run a Windows batch file", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
"foo.cmd",
|
||||
r#"
|
||||
@echo off
|
||||
echo Hello World
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(cwd: dirs.test(), pipeline("foo.cmd"));
|
||||
assert!(actual.out.contains("Hello World"));
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn can_run_batch_files_without_cmd_extension() {
|
||||
Playground::setup(
|
||||
"run a Windows batch file without specifying the extension",
|
||||
|dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
"foo.cmd",
|
||||
r#"
|
||||
@echo off
|
||||
echo Hello World
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(cwd: dirs.test(), pipeline("foo"));
|
||||
assert!(actual.out.contains("Hello World"));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn can_run_batch_files_without_bat_extension() {
|
||||
Playground::setup(
|
||||
"run a Windows batch file without specifying the extension",
|
||||
|dirs, sandbox| {
|
||||
sandbox.with_files(vec![FileWithContent(
|
||||
"foo.bat",
|
||||
r#"
|
||||
@echo off
|
||||
echo Hello World
|
||||
"#,
|
||||
)]);
|
||||
|
||||
let actual = nu!(cwd: dirs.test(), pipeline("foo"));
|
||||
assert!(actual.out.contains("Hello World"));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue