diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 2f4e4d8634..166146aec5 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -115,6 +115,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { // System bind_command! { Benchmark, + Exec, External, Ps, Sys, diff --git a/crates/nu-command/src/system/exec.rs b/crates/nu-command/src/system/exec.rs new file mode 100644 index 0000000000..56b0a71f55 --- /dev/null +++ b/crates/nu-command/src/system/exec.rs @@ -0,0 +1,114 @@ +use nu_protocol::{ + ast::Call, + engine::{Command, EngineState, Stack}, + Category, Example, PipelineData, ShellError, Signature, SyntaxShape, +}; + +#[derive(Clone)] +pub struct Exec; + +impl Command for Exec { + fn name(&self) -> &str { + "exec" + } + + fn signature(&self) -> Signature { + Signature::build("exec") + .required("command", SyntaxShape::String, "the command to execute") + .rest( + "rest", + SyntaxShape::String, + "any additional arguments for the command", + ) + .category(Category::System) + } + + fn usage(&self) -> &str { + "Execute a command, replacing the current process." + } + + fn extra_usage(&self) -> &str { + "Currently supported only on Unix-based systems." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + exec(engine_state, stack, call) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Execute external 'ps aux' tool", + example: "exec ps aux", + result: None, + }, + Example { + description: "Execute 'nautilus'", + example: "exec nautilus", + result: None, + }, + ] + } +} + +#[cfg(unix)] +fn exec( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, +) -> Result { + use std::os::unix::process::CommandExt; + + use nu_engine::{current_dir, env_to_strings, CallExt}; + use nu_protocol::Spanned; + + use super::run_external::ExternalCommand; + + let name: Spanned = call.req(engine_state, stack, 0)?; + let name_span = name.span; + + let args: Vec> = call.rest(engine_state, stack, 1)?; + + let cwd = current_dir(engine_state, stack)?; + let config = stack.get_config()?; + let env_vars = env_to_strings(engine_state, stack, &config)?; + let current_dir = current_dir(engine_state, stack)?; + + let external_command = ExternalCommand { + name, + args, + env_vars, + last_expression: true, + }; + + let mut command = external_command.spawn_simple_command(&cwd.to_string_lossy().to_string())?; + command.current_dir(current_dir); + + println!("{:#?}", command); + let err = command.exec(); // this replaces our process, should not return + + Err(ShellError::SpannedLabeledError( + "Error on exec".to_string(), + err.to_string(), + name_span, + )) +} + +#[cfg(not(unix))] +fn exec( + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, +) -> Result { + Err(ShellError::SpannedLabeledError( + "Error on exec".to_string(), + "exec is not supported on your platform".to_string(), + call.head, + )) +} diff --git a/crates/nu-command/src/system/mod.rs b/crates/nu-command/src/system/mod.rs index 3225ddde7b..76572089fb 100644 --- a/crates/nu-command/src/system/mod.rs +++ b/crates/nu-command/src/system/mod.rs @@ -1,10 +1,12 @@ mod benchmark; +mod exec; mod ps; mod run_external; mod sys; mod which_; pub use benchmark::Benchmark; +pub use exec::Exec; pub use ps::Ps; pub use run_external::{External, ExternalCommand}; pub use sys::Sys; diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index acbc3d3f8e..9a62895712 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -95,21 +95,19 @@ impl Command for External { args: args_strs, last_expression, env_vars: env_vars_str, - call, }; command.run_with_input(engine_state, input, config) } } -pub struct ExternalCommand<'call> { +pub struct ExternalCommand { pub name: Spanned, pub args: Vec>, pub last_expression: bool, pub env_vars: HashMap, - pub call: &'call Call, } -impl<'call> ExternalCommand<'call> { +impl ExternalCommand { pub fn run_with_input( &self, engine_state: &EngineState, @@ -275,7 +273,7 @@ impl<'call> ExternalCommand<'call> { } /// Spawn a command without shelling out to an external shell - fn spawn_simple_command(&self, cwd: &str) -> Result { + pub fn spawn_simple_command(&self, cwd: &str) -> Result { let head = trim_enclosing_quotes(&self.name.item); let head = if head.starts_with('~') || head.starts_with("..") { nu_path::expand_path_with(head, cwd) @@ -397,7 +395,7 @@ impl<'call> ExternalCommand<'call> { } /// Spawn a cmd command with `cmd /c args...` - fn spawn_cmd_command(&self) -> std::process::Command { + pub fn spawn_cmd_command(&self) -> std::process::Command { let mut process = std::process::Command::new("cmd"); process.arg("/c"); process.arg(&self.name.item); @@ -412,7 +410,7 @@ impl<'call> ExternalCommand<'call> { } /// Spawn a sh command with `sh -c args...` - fn spawn_sh_command(&self) -> std::process::Command { + pub fn spawn_sh_command(&self) -> std::process::Command { let joined_and_escaped_arguments = self .args .iter()