From 82d90f4930d7fc2d1b06f0c4866042f133c008a8 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Thu, 13 Jan 2022 19:17:45 +1100 Subject: [PATCH] Add support for var/string interp for external names (#729) --- crates/nu-command/src/system/run_external.rs | 13 +++++- crates/nu-engine/src/eval.rs | 43 +++++++------------- crates/nu-parser/src/flatten.rs | 17 +++++++- crates/nu-parser/src/parser.rs | 39 +++++++++++++----- crates/nu-protocol/src/ast/expr.rs | 2 +- crates/nu-protocol/src/ast/expression.rs | 8 +++- 6 files changed, 77 insertions(+), 45 deletions(-) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 8825d3700d..257a40afc4 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -260,7 +260,18 @@ impl<'call> ExternalCommand<'call> { /// Spawn a command without shelling out to an external shell fn spawn_simple_command(&self, cwd: &str) -> std::process::Command { - let mut process = std::process::Command::new(&self.name.item); + let head = trim_enclosing_quotes(&self.name.item); + let head = if head.starts_with('~') || head.starts_with("..") { + nu_path::expand_path_with(head, cwd) + .to_string_lossy() + .to_string() + } else { + head + }; + + let head = head.replace("\\", "\\\\"); + + let mut process = std::process::Command::new(&head); for arg in &self.args { let arg = trim_enclosing_quotes(arg); diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index c57d45eb1b..0a7692ca0a 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -6,7 +6,7 @@ use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement}; use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{ IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Range, ShellError, Span, - Spanned, Type, Unit, Value, VarId, ENV_VARIABLE_ID, + Spanned, Unit, Value, VarId, ENV_VARIABLE_ID, }; use crate::{current_dir_str, get_full_help}; @@ -139,26 +139,20 @@ fn eval_call( fn eval_external( engine_state: &EngineState, stack: &mut Stack, - name: &str, - name_span: &Span, + head: &Expression, args: &[Expression], input: PipelineData, last_expression: bool, ) -> Result { let decl_id = engine_state .find_decl("run_external".as_bytes()) - .ok_or_else(|| ShellError::ExternalNotSupported(*name_span))?; + .ok_or(ShellError::ExternalNotSupported(head.span))?; let command = engine_state.get_decl(decl_id); let mut call = Call::new(); - call.positional.push(Expression { - expr: Expr::String(name.trim_start_matches('^').to_string()), - span: *name_span, - ty: Type::String, - custom_completion: None, - }); + call.positional.push(head.clone()); for arg in args { call.positional.push(arg.clone()) @@ -168,7 +162,7 @@ fn eval_external( call.named.push(( Spanned { item: "last_expression".into(), - span: *name_span, + span: head.span, }, None, )) @@ -246,18 +240,18 @@ pub fn eval_expression( .into_value(call.head), ) } - Expr::ExternalCall(name, span, args) => { + Expr::ExternalCall(head, args) => { + let span = head.span; // FIXME: protect this collect with ctrl-c Ok(eval_external( engine_state, stack, - name, - span, + head, args, - PipelineData::new(*span), + PipelineData::new(span), false, )? - .into_value(*span)) + .into_value(span)) } Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }), Expr::BinaryOp(lhs, op, rhs) => { @@ -413,14 +407,13 @@ pub fn eval_block( input = eval_call(engine_state, stack, call, input)?; } Expression { - expr: Expr::ExternalCall(name, name_span, args), + expr: Expr::ExternalCall(head, args), .. } => { input = eval_external( engine_state, stack, - name, - name_span, + head, args, input, i == pipeline.expressions.len() - 1, @@ -511,18 +504,10 @@ pub fn eval_subexpression( input = eval_call(engine_state, stack, call, input)?; } Expression { - expr: Expr::ExternalCall(name, name_span, args), + expr: Expr::ExternalCall(head, args), .. } => { - input = eval_external( - engine_state, - stack, - name, - name_span, - args, - input, - false, - )?; + input = eval_external(engine_state, stack, head, args, input, false)?; } elem => { diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index 1f10f4d1d9..7971bf3a77 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -156,8 +156,21 @@ pub fn flatten_expression( output.extend(args); output } - Expr::ExternalCall(_, name_span, args) => { - let mut output = vec![(*name_span, FlatShape::External)]; + Expr::ExternalCall(head, args) => { + let mut output = vec![]; + + match **head { + Expression { + expr: Expr::String(..), + span, + .. + } => { + output.push((span, FlatShape::External)); + } + _ => { + output.extend(flatten_expression(working_set, head)); + } + } for arg in args { //output.push((*arg, FlatShape::ExternalArg)); diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 9080cbda2a..2541deb506 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -191,19 +191,35 @@ pub fn parse_external_call( spans: &[Span], ) -> (Expression, Option) { let mut args = vec![]; - let name_span = spans[0]; - let name = String::from_utf8_lossy(working_set.get_span_contents(name_span)).to_string(); - let cwd = working_set.get_cwd(); - let name = if name.starts_with('.') || name.starts_with('~') { - nu_path::expand_path_with(name, cwd) - .to_string_lossy() - .to_string() + + let head_contents = working_set.get_span_contents(spans[0]); + + let head_span = if head_contents.starts_with(b"^") { + Span { + start: spans[0].start + 1, + end: spans[0].end, + } } else { - name + spans[0] }; + let head_contents = working_set.get_span_contents(head_span); + let mut error = None; + let head = if head_contents.starts_with(b"$") || head_contents.starts_with(b"(") { + let (arg, err) = parse_expression(working_set, &[head_span], true); + error = error.or(err); + Box::new(arg) + } else { + Box::new(Expression { + expr: Expr::String(String::from_utf8_lossy(head_contents).to_string()), + span: head_span, + ty: Type::String, + custom_completion: None, + }) + }; + for span in &spans[1..] { let contents = working_set.get_span_contents(*span); @@ -222,7 +238,7 @@ pub fn parse_external_call( } ( Expression { - expr: Expr::ExternalCall(name, name_span, args), + expr: Expr::ExternalCall(head, args), span: span(spans), ty: Type::Unknown, custom_completion: None, @@ -3684,7 +3700,10 @@ pub fn find_captures_in_expr( } } Expr::CellPath(_) => {} - Expr::ExternalCall(_, _, exprs) => { + Expr::ExternalCall(head, exprs) => { + let result = find_captures_in_expr(working_set, head, seen); + output.extend(&result); + for expr in exprs { let result = find_captures_in_expr(working_set, expr, seen); output.extend(&result); diff --git a/crates/nu-protocol/src/ast/expr.rs b/crates/nu-protocol/src/ast/expr.rs index c728a9b6af..cc6d532251 100644 --- a/crates/nu-protocol/src/ast/expr.rs +++ b/crates/nu-protocol/src/ast/expr.rs @@ -15,7 +15,7 @@ pub enum Expr { Var(VarId), VarDecl(VarId), Call(Box), - ExternalCall(String, Span, Vec), + ExternalCall(Box, Vec), Operator(Operator), RowCondition(BlockId), BinaryOp(Box, Box, Box), //lhs, op, rhs diff --git a/crates/nu-protocol/src/ast/expression.rs b/crates/nu-protocol/src/ast/expression.rs index c4a0e520a0..c05e12d5e7 100644 --- a/crates/nu-protocol/src/ast/expression.rs +++ b/crates/nu-protocol/src/ast/expression.rs @@ -142,7 +142,10 @@ impl Expression { false } Expr::CellPath(_) => false, - Expr::ExternalCall(_, _, args) => { + Expr::ExternalCall(head, args) => { + if head.has_in_variable(working_set) { + return true; + } for arg in args { if arg.has_in_variable(working_set) { return true; @@ -298,7 +301,8 @@ impl Expression { } } Expr::CellPath(_) => {} - Expr::ExternalCall(_, _, args) => { + Expr::ExternalCall(head, args) => { + head.replace_in_variable(working_set, new_var_id); for arg in args { arg.replace_in_variable(working_set, new_var_id) }