mirror of
https://github.com/nushell/nushell.git
synced 2025-04-03 06:01:11 +02:00
124 lines
3.7 KiB
Rust
124 lines
3.7 KiB
Rust
use std::env;
|
|
use std::process::Command as CommandSys;
|
|
|
|
use nu_protocol::{
|
|
ast::{Call, Expression},
|
|
engine::{Command, EvaluationContext},
|
|
ShellError, Signature, SyntaxShape, Value,
|
|
};
|
|
|
|
use nu_engine::eval_expression;
|
|
|
|
pub struct External;
|
|
|
|
impl Command for External {
|
|
fn name(&self) -> &str {
|
|
"run_external"
|
|
}
|
|
|
|
fn usage(&self) -> &str {
|
|
"Runs external command"
|
|
}
|
|
|
|
fn signature(&self) -> nu_protocol::Signature {
|
|
Signature::build("run_external").rest("rest", SyntaxShape::Any, "external command to run")
|
|
}
|
|
|
|
fn run(
|
|
&self,
|
|
context: &EvaluationContext,
|
|
call: &Call,
|
|
input: Value,
|
|
) -> Result<Value, ShellError> {
|
|
let command = ExternalCommand::try_new(call, context)?;
|
|
command.run_with_input(input)
|
|
}
|
|
}
|
|
|
|
pub struct ExternalCommand<'call, 'contex> {
|
|
pub name: &'call Expression,
|
|
pub args: &'call [Expression],
|
|
pub context: &'contex EvaluationContext,
|
|
}
|
|
|
|
impl<'call, 'contex> ExternalCommand<'call, 'contex> {
|
|
pub fn try_new(
|
|
call: &'call Call,
|
|
context: &'contex EvaluationContext,
|
|
) -> Result<Self, ShellError> {
|
|
if call.positional.is_empty() {
|
|
return Err(ShellError::ExternalNotSupported(call.head));
|
|
}
|
|
|
|
Ok(Self {
|
|
name: &call.positional[0],
|
|
args: &call.positional[1..],
|
|
context,
|
|
})
|
|
}
|
|
|
|
pub fn get_name(&self) -> Result<String, ShellError> {
|
|
let value = eval_expression(self.context, self.name)?;
|
|
value.as_string()
|
|
}
|
|
|
|
pub fn get_args(&self) -> Vec<String> {
|
|
self.args
|
|
.iter()
|
|
.filter_map(|expr| eval_expression(self.context, expr).ok())
|
|
.filter_map(|value| value.as_string().ok())
|
|
.collect()
|
|
}
|
|
|
|
pub fn run_with_input(&self, _input: Value) -> Result<Value, ShellError> {
|
|
let mut process = self.create_command();
|
|
|
|
// TODO. We don't have a way to know the current directory
|
|
// This should be information from the EvaluationContex or EngineState
|
|
let path = env::current_dir().unwrap();
|
|
process.current_dir(path);
|
|
|
|
let envs = self.context.stack.get_env_vars();
|
|
process.envs(envs);
|
|
|
|
match process.spawn() {
|
|
Err(err) => Err(ShellError::ExternalCommand(
|
|
format!("{}", err),
|
|
self.name.span,
|
|
)),
|
|
Ok(mut child) => match child.wait() {
|
|
Err(err) => Err(ShellError::ExternalCommand(
|
|
format!("{}", err),
|
|
self.name.span,
|
|
)),
|
|
Ok(_) => Ok(Value::nothing()),
|
|
},
|
|
}
|
|
}
|
|
|
|
fn create_command(&self) -> CommandSys {
|
|
// in all the other cases shell out
|
|
if cfg!(windows) {
|
|
//TODO. This should be modifiable from the config file.
|
|
// We could give the option to call from powershell
|
|
// for minimal builds cwd is unused
|
|
let mut process = CommandSys::new("cmd");
|
|
process.arg("/c");
|
|
process.arg(&self.get_name().unwrap());
|
|
for arg in self.get_args() {
|
|
// Clean the args before we use them:
|
|
// https://stackoverflow.com/questions/1200235/how-to-pass-a-quoted-pipe-character-to-cmd-exe
|
|
// cmd.exe needs to have a caret to escape a pipe
|
|
let arg = arg.replace("|", "^|");
|
|
process.arg(&arg);
|
|
}
|
|
process
|
|
} else {
|
|
let cmd_with_args = vec![self.get_name().unwrap(), self.get_args().join(" ")].join(" ");
|
|
let mut process = CommandSys::new("sh");
|
|
process.arg("-c").arg(cmd_with_args);
|
|
process
|
|
}
|
|
}
|
|
}
|