From 8f07f40f224e2e98f693c7f779a3732d42713a35 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sun, 12 Sep 2021 16:34:43 +0100 Subject: [PATCH 1/5] external call --- crates/nu-engine/src/eval.rs | 33 ++++++++++++++++++- crates/nu-protocol/src/engine/engine_state.rs | 2 +- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index b3da15c87a..e7d40894fc 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,3 +1,5 @@ +use std::process::Command; + use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::engine::EvaluationContext; use nu_protocol::{Range, ShellError, Span, Value}; @@ -68,6 +70,33 @@ fn eval_call(context: &EvaluationContext, call: &Call, input: Value) -> Result, + expr: &Expression, +) -> Result { + let state = context.engine_state.borrow(); + let name = state.get_span_contents(command_span); + let cmd = std::str::from_utf8(name).unwrap(); + + let args = spans + .iter() + .map(|span| { + let val = state.get_span_contents(span); + std::str::from_utf8(val).unwrap() + }) + .collect::>(); + + let output = Command::new(cmd).args(args).output().unwrap(); + + println!("{:?}", output); + let test = output.stdout; + println!("{:?}", test); + + Err(ShellError::ExternalNotSupported(expr.span)) +} + pub fn eval_expression( context: &EvaluationContext, expr: &Expression, @@ -137,7 +166,9 @@ pub fn eval_expression( } Expr::RowCondition(_, expr) => eval_expression(context, expr), Expr::Call(call) => eval_call(context, call, Value::nothing()), - Expr::ExternalCall(_, _) => Err(ShellError::ExternalNotSupported(expr.span)), + Expr::ExternalCall(command_span, spans) => { + eval_external(context, command_span, spans, expr) + } Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }), Expr::BinaryOp(lhs, op, rhs) => { let op_span = op.span; diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index a9c307fc49..771bac24bb 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -137,7 +137,7 @@ impl EngineState { output } - pub fn get_span_contents(&self, span: Span) -> &[u8] { + pub fn get_span_contents(&self, span: &Span) -> &[u8] { &self.file_contents[span.start..span.end] } From bafc50fd5cd25c9e6e1244798fca2a2d219127ac Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sun, 19 Sep 2021 20:29:58 +0100 Subject: [PATCH 2/5] external command --- crates/nu-cli/src/errors.rs | 9 ++ crates/nu-command/src/default_context.rs | 6 +- crates/nu-command/src/lib.rs | 2 + crates/nu-command/src/run_external.rs | 123 ++++++++++++++++++ crates/nu-engine/src/eval.rs | 64 ++++----- .../src/engine/evaluation_context.rs | 4 + crates/nu-protocol/src/shell_error.rs | 1 + 7 files changed, 175 insertions(+), 34 deletions(-) create mode 100644 crates/nu-command/src/run_external.rs diff --git a/crates/nu-cli/src/errors.rs b/crates/nu-cli/src/errors.rs index a7ee992168..4c04481fe0 100644 --- a/crates/nu-cli/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -395,6 +395,15 @@ pub fn report_shell_error( Label::primary(diag_file_id, diag_range).with_message("cannot find column") ]) } + ShellError::ExternalCommand(error, span) => { + let (diag_file_id, diag_range) = convert_span_to_diag(working_set, span)?; + + Diagnostic::error() + .with_message("External command") + .with_labels(vec![ + Label::primary(diag_file_id, diag_range).with_message(format!("{}", error)) + ]) + } }; // println!("DIAG"); diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 9291a9742b..91c13590f6 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -6,8 +6,8 @@ use nu_protocol::{ }; use crate::{ - where_::Where, Alias, Benchmark, BuildString, Def, Do, Each, For, Git, GitCheckout, If, Length, - Let, LetEnv, ListGitBranches, Ls, Table, + where_::Where, Alias, Benchmark, BuildString, Def, Do, Each, External, For, Git, GitCheckout, + If, Length, Let, LetEnv, ListGitBranches, Ls, Table, }; pub fn create_default_context() -> Rc> { @@ -48,6 +48,8 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Table)); + working_set.add_decl(Box::new(External)); + // This is a WIP proof of concept working_set.add_decl(Box::new(ListGitBranches)); working_set.add_decl(Box::new(Git)); diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index a508e0cb47..96f0839fb3 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -14,6 +14,7 @@ mod let_; mod let_env; mod list_git_branches; mod ls; +mod run_external; mod table; mod where_; @@ -33,4 +34,5 @@ pub use let_::Let; pub use let_env::LetEnv; pub use list_git_branches::ListGitBranches; pub use ls::Ls; +pub use run_external::External; pub use table::Table; diff --git a/crates/nu-command/src/run_external.rs b/crates/nu-command/src/run_external.rs new file mode 100644 index 0000000000..78ac39286a --- /dev/null +++ b/crates/nu-command/src/run_external.rs @@ -0,0 +1,123 @@ +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 { + 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 { + if call.positional.len() == 0 { + return Err(ShellError::ExternalNotSupported(call.head)); + } + + Ok(Self { + name: &call.positional[0], + args: &call.positional[1..], + context: context, + }) + } + + pub fn get_name(&self) -> Result { + let value = eval_expression(self.context, self.name)?; + value.as_string() + } + + pub fn get_args(&self) -> Vec { + 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 { + 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 + } + } +} diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 143379a75c..8153081c14 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -1,8 +1,6 @@ -use std::process::Command; - use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::engine::EvaluationContext; -use nu_protocol::{Range, ShellError, Span, Value}; +use nu_protocol::{Range, ShellError, Span, Type, Value}; pub fn eval_operator(op: &Expression) -> Result { match op { @@ -70,33 +68,6 @@ fn eval_call(context: &EvaluationContext, call: &Call, input: Value) -> Result, - expr: &Expression, -) -> Result { - let state = context.engine_state.borrow(); - let name = state.get_span_contents(command_span); - let cmd = std::str::from_utf8(name).unwrap(); - - let args = spans - .iter() - .map(|span| { - let val = state.get_span_contents(span); - std::str::from_utf8(val).unwrap() - }) - .collect::>(); - - let output = Command::new(cmd).args(args).output().unwrap(); - - println!("{:?}", output); - let test = output.stdout; - println!("{:?}", test); - - Err(ShellError::ExternalNotSupported(expr.span)) -} - pub fn eval_expression( context: &EvaluationContext, expr: &Expression, @@ -154,8 +125,37 @@ pub fn eval_expression( } Expr::RowCondition(_, expr) => eval_expression(context, expr), Expr::Call(call) => eval_call(context, call, Value::nothing()), - Expr::ExternalCall(command_span, spans) => { - eval_external(context, command_span, spans, expr) + Expr::ExternalCall(name, args) => { + let engine_state = context.engine_state.borrow(); + + let decl_id = engine_state + .find_decl("run_external".as_bytes()) + .ok_or(ShellError::ExternalNotSupported(name.clone()))?; + + let command = engine_state.get_decl(decl_id); + let new_context = context.enter_scope(); + + let mut call = Call::new(); + call.positional = [name.clone()] + .iter() + .chain(args.iter()) + .map(|span| { + let contents = engine_state.get_span_contents(span); + let val = String::from_utf8_lossy(contents); + Expression { + expr: Expr::String(val.into()), + span: span.clone(), + ty: Type::String, + custom_completion: None, + } + }) + .collect(); + + let value = Value::Nothing { + span: Span::new(0, 1), + }; + + command.run(&new_context, &call, value) } Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }), Expr::BinaryOp(lhs, op, rhs) => { diff --git a/crates/nu-protocol/src/engine/evaluation_context.rs b/crates/nu-protocol/src/engine/evaluation_context.rs index 776a69fb12..3dd90d94dd 100644 --- a/crates/nu-protocol/src/engine/evaluation_context.rs +++ b/crates/nu-protocol/src/engine/evaluation_context.rs @@ -104,6 +104,10 @@ impl Stack { }))) } + pub fn get_env_vars(&self) -> HashMap { + self.0.borrow().env_vars.clone() + } + pub fn print_stack(&self) { println!("===frame==="); println!("vars:"); diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 678816b353..1f18799883 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -21,4 +21,5 @@ pub enum ShellError { AccessBeyondEndOfStream(Span), IncompatiblePathAccess(String, Span), CantFindColumn(Span), + ExternalCommand(String, Span), } From 96af23f3706307a86a5ad05868d474e6c21e6c63 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sun, 19 Sep 2021 20:41:35 +0100 Subject: [PATCH 3/5] clippy errors --- crates/nu-engine/src/eval.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index 8153081c14..705fa19213 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -130,13 +130,13 @@ pub fn eval_expression( let decl_id = engine_state .find_decl("run_external".as_bytes()) - .ok_or(ShellError::ExternalNotSupported(name.clone()))?; + .ok_or_else(|| ShellError::ExternalNotSupported(*name))?; let command = engine_state.get_decl(decl_id); let new_context = context.enter_scope(); let mut call = Call::new(); - call.positional = [name.clone()] + call.positional = [*name] .iter() .chain(args.iter()) .map(|span| { @@ -144,7 +144,7 @@ pub fn eval_expression( let val = String::from_utf8_lossy(contents); Expression { expr: Expr::String(val.into()), - span: span.clone(), + span: *span, ty: Type::String, custom_completion: None, } From 5a6aebfcb28963fd79559a6a52e2eb6d7a0b4356 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sun, 19 Sep 2021 21:09:11 +0100 Subject: [PATCH 4/5] clippy errors --- crates/nu-command/src/run_external.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-command/src/run_external.rs b/crates/nu-command/src/run_external.rs index 78ac39286a..15204ba377 100644 --- a/crates/nu-command/src/run_external.rs +++ b/crates/nu-command/src/run_external.rs @@ -53,7 +53,7 @@ impl<'call, 'contex> ExternalCommand<'call, 'contex> { Ok(Self { name: &call.positional[0], args: &call.positional[1..], - context: context, + context, }) } From 6731e3542d45e6098d3615fe6b7e9dde5bc10e92 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Sun, 19 Sep 2021 22:05:24 +0100 Subject: [PATCH 5/5] clippy errors --- crates/nu-cli/src/errors.rs | 2 +- crates/nu-command/src/run_external.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/nu-cli/src/errors.rs b/crates/nu-cli/src/errors.rs index 4c04481fe0..fc63b34714 100644 --- a/crates/nu-cli/src/errors.rs +++ b/crates/nu-cli/src/errors.rs @@ -401,7 +401,7 @@ pub fn report_shell_error( Diagnostic::error() .with_message("External command") .with_labels(vec![ - Label::primary(diag_file_id, diag_range).with_message(format!("{}", error)) + Label::primary(diag_file_id, diag_range).with_message(error.to_string()) ]) } }; diff --git a/crates/nu-command/src/run_external.rs b/crates/nu-command/src/run_external.rs index 15204ba377..e89e471eda 100644 --- a/crates/nu-command/src/run_external.rs +++ b/crates/nu-command/src/run_external.rs @@ -46,7 +46,7 @@ impl<'call, 'contex> ExternalCommand<'call, 'contex> { call: &'call Call, context: &'contex EvaluationContext, ) -> Result { - if call.positional.len() == 0 { + if call.positional.is_empty() { return Err(ShellError::ExternalNotSupported(call.head)); }