mirror of
https://github.com/nushell/nushell
synced 2024-12-28 14:03:09 +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 struct ExternalCommand {
|
||||||
pub name: Spanned<String>,
|
pub name: Spanned<String>,
|
||||||
pub args: Vec<Spanned<String>>,
|
pub args: Vec<Spanned<String>>,
|
||||||
|
@ -121,28 +122,75 @@ impl ExternalCommand {
|
||||||
let ctrlc = engine_state.ctrlc.clone();
|
let ctrlc = engine_state.ctrlc.clone();
|
||||||
|
|
||||||
let mut process = self.create_process(&input, false, head)?;
|
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)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
// Some common Windows commands are actually built in to cmd.exe, not executables in their own right.
|
// Running external commands on Windows has 2 points of complication:
|
||||||
// To support those commands, we "shell out" to cmd.exe.
|
// 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
|
||||||
|
|
||||||
|
// 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(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
|
// 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:
|
// I (Reilly) went through the full list and whittled it down to ones that are potentially useful:
|
||||||
const CMD_INTERNAL_COMMANDS: [&str; 8] = [
|
const CMD_INTERNAL_COMMANDS: [&str; 8] = [
|
||||||
"ASSOC", "DIR", "ECHO", "FTYPE", "MKLINK", "START", "VER", "VOL",
|
"ASSOC", "DIR", "ECHO", "FTYPE", "MKLINK", "START", "VER", "VOL",
|
||||||
];
|
];
|
||||||
|
|
||||||
let command_name_upper = self.name.item.to_uppercase();
|
let command_name_upper = self.name.item.to_uppercase();
|
||||||
let use_cmd = CMD_INTERNAL_COMMANDS
|
let looks_like_cmd_internal = CMD_INTERNAL_COMMANDS
|
||||||
.iter()
|
.iter()
|
||||||
.any(|&cmd| command_name_upper == cmd);
|
.any(|&cmd| command_name_upper == cmd);
|
||||||
|
|
||||||
match process.spawn() {
|
if looks_like_cmd_internal {
|
||||||
Err(_) => {
|
let mut cmd_process = self.create_process(&input, true, head)?;
|
||||||
let mut fg_process = self.create_process(&input, use_cmd, head)?;
|
child = cmd_process.spawn();
|
||||||
child = fg_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) => {
|
Ok(process) => {
|
||||||
child = 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::playground::Playground;
|
||||||
use nu_test_support::{nu, pipeline};
|
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"));
|
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