Jakub Žádník
2022-12-22 00:33:26 +02:00
committed by GitHub
parent 440feaf74a
commit 757d7479af
13 changed files with 379 additions and 151 deletions

View File

@ -1,7 +1,7 @@
use super::run_external::ExternalCommand;
use nu_engine::{current_dir, env_to_strings, CallExt};
use super::run_external::create_external_command;
use nu_engine::{current_dir, CallExt};
use nu_protocol::{
ast::{Call, Expr},
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type,
};
@ -19,11 +19,7 @@ impl Command for Exec {
Signature::build("exec")
.input_output_types(vec![(Type::Nothing, Type::Any)])
.required("command", SyntaxShape::String, "the command to execute")
.rest(
"rest",
SyntaxShape::String,
"any additional arguments for the command",
)
.allows_unknown_args()
.category(Category::System)
}
@ -69,36 +65,22 @@ fn exec(
let name: Spanned<String> = call.req(engine_state, stack, 0)?;
let name_span = name.span;
let args: Vec<Spanned<String>> = call.rest(engine_state, stack, 1)?;
let args_expr: Vec<nu_protocol::ast::Expression> =
call.positional_iter().skip(1).cloned().collect();
let mut arg_keep_raw = vec![];
for one_arg_expr in args_expr {
match one_arg_expr.expr {
// refer to `parse_dollar_expr` function
// the expression type of $variable_name, $"($variable_name)"
// will be Expr::StringInterpolation, Expr::FullCellPath
Expr::StringInterpolation(_) | Expr::FullCellPath(_) => arg_keep_raw.push(true),
_ => arg_keep_raw.push(false),
}
}
let redirect_stdout = call.has_flag("redirect-stdout");
let redirect_stderr = call.has_flag("redirect-stderr");
let trim_end_newline = call.has_flag("trim-end-newline");
let external_command = create_external_command(
engine_state,
stack,
call,
redirect_stdout,
redirect_stderr,
trim_end_newline,
)?;
let cwd = current_dir(engine_state, stack)?;
let env_vars = env_to_strings(engine_state, stack)?;
let current_dir = current_dir(engine_state, stack)?;
let external_command = ExternalCommand {
name,
args,
arg_keep_raw,
env_vars,
redirect_stdout: true,
redirect_stderr: false,
trim_end_newline: false,
};
let mut command = external_command.spawn_simple_command(&cwd.to_string_lossy())?;
command.current_dir(current_dir);
command.current_dir(cwd);
let err = command.exec(); // this replaces our process, should not return

View File

@ -52,70 +52,19 @@ impl Command for External {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let name: Spanned<String> = call.req(engine_state, stack, 0)?;
let args: Vec<Value> = call.rest(engine_state, stack, 1)?;
let redirect_stdout = call.has_flag("redirect-stdout");
let redirect_stderr = call.has_flag("redirect-stderr");
let trim_end_newline = call.has_flag("trim-end-newline");
// Translate environment variables from Values to Strings
let env_vars_str = env_to_strings(engine_state, stack)?;
fn value_as_spanned(value: Value) -> Result<Spanned<String>, ShellError> {
let span = value.span()?;
value
.as_string()
.map(|item| Spanned { item, span })
.map_err(|_| {
ShellError::ExternalCommand(
format!("Cannot convert {} to a string", value.get_type()),
"All arguments to an external command need to be string-compatible".into(),
span,
)
})
}
let mut spanned_args = vec![];
let args_expr: Vec<Expression> = call.positional_iter().skip(1).cloned().collect();
let mut arg_keep_raw = vec![];
for (one_arg, one_arg_expr) in args.into_iter().zip(args_expr) {
match one_arg {
Value::List { vals, .. } => {
// turn all the strings in the array into params.
// Example: one_arg may be something like ["ls" "-a"]
// convert it to "ls" "-a"
for v in vals {
spanned_args.push(value_as_spanned(v)?);
// for arguments in list, it's always treated as a whole arguments
arg_keep_raw.push(true);
}
}
val => {
spanned_args.push(value_as_spanned(val)?);
match one_arg_expr.expr {
// refer to `parse_dollar_expr` function
// the expression type of $variable_name, $"($variable_name)"
// will be Expr::StringInterpolation, Expr::FullCellPath
Expr::StringInterpolation(_) | Expr::FullCellPath(_) => {
arg_keep_raw.push(true)
}
_ => arg_keep_raw.push(false),
}
{}
}
}
}
let command = ExternalCommand {
name,
args: spanned_args,
arg_keep_raw,
let command = create_external_command(
engine_state,
stack,
call,
redirect_stdout,
redirect_stderr,
env_vars: env_vars_str,
trim_end_newline,
};
)?;
command.run_with_input(engine_state, stack, input, false)
}
@ -135,6 +84,76 @@ impl Command for External {
}
}
/// Creates ExternalCommand from a call
pub fn create_external_command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
redirect_stdout: bool,
redirect_stderr: bool,
trim_end_newline: bool,
) -> Result<ExternalCommand, ShellError> {
let name: Spanned<String> = call.req(engine_state, stack, 0)?;
let args: Vec<Value> = call.rest(engine_state, stack, 1)?;
// Translate environment variables from Values to Strings
let env_vars_str = env_to_strings(engine_state, stack)?;
fn value_as_spanned(value: Value) -> Result<Spanned<String>, ShellError> {
let span = value.span()?;
value
.as_string()
.map(|item| Spanned { item, span })
.map_err(|_| {
ShellError::ExternalCommand(
format!("Cannot convert {} to a string", value.get_type()),
"All arguments to an external command need to be string-compatible".into(),
span,
)
})
}
let mut spanned_args = vec![];
let args_expr: Vec<Expression> = call.positional_iter().skip(1).cloned().collect();
let mut arg_keep_raw = vec![];
for (one_arg, one_arg_expr) in args.into_iter().zip(args_expr) {
match one_arg {
Value::List { vals, .. } => {
// turn all the strings in the array into params.
// Example: one_arg may be something like ["ls" "-a"]
// convert it to "ls" "-a"
for v in vals {
spanned_args.push(value_as_spanned(v)?);
// for arguments in list, it's always treated as a whole arguments
arg_keep_raw.push(true);
}
}
val => {
spanned_args.push(value_as_spanned(val)?);
match one_arg_expr.expr {
// refer to `parse_dollar_expr` function
// the expression type of $variable_name, $"($variable_name)"
// will be Expr::StringInterpolation, Expr::FullCellPath
Expr::StringInterpolation(_) | Expr::FullCellPath(_) => arg_keep_raw.push(true),
_ => arg_keep_raw.push(false),
}
{}
}
}
}
Ok(ExternalCommand {
name,
args: spanned_args,
arg_keep_raw,
redirect_stdout,
redirect_stderr,
env_vars: env_vars_str,
trim_end_newline,
})
}
#[derive(Clone)]
pub struct ExternalCommand {
pub name: Spanned<String>,

View File

@ -0,0 +1,58 @@
use nu_test_support::playground::Playground;
use nu_test_support::{nu, pipeline};
#[test]
fn basic_exec() {
Playground::setup("test_exec_1", |dirs, _| {
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
nu -c 'exec nu --testbin cococo a b c'
"#
));
assert_eq!(actual.out, "a b c");
})
}
#[test]
fn exec_complex_args() {
Playground::setup("test_exec_2", |dirs, _| {
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
nu -c 'exec nu --testbin cococo b --bar=2 -sab --arwr - -DTEEE=aasd-290 -90 --'
"#
));
assert_eq!(actual.out, "b --bar=2 -sab --arwr - -DTEEE=aasd-290 -90 --");
})
}
#[test]
fn exec_fail_batched_short_args() {
Playground::setup("test_exec_3", |dirs, _| {
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
nu -c 'exec nu --testbin cococo -ab 10'
"#
));
assert_eq!(actual.out, "");
})
}
#[test]
fn exec_misc_values() {
Playground::setup("test_exec_4", |dirs, _| {
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
nu -c 'let x = "abc"; exec nu --testbin cococo $x [ a b c ]'
"#
));
assert_eq!(actual.out, "abc a b c");
})
}

View File

@ -20,6 +20,8 @@ mod empty;
mod enter;
mod error_make;
mod every;
#[cfg(not(windows))]
mod exec;
mod export_def;
mod find;
mod first;