Add pipeline redirection support (#4594)

* redirection

* Remove commented-out

* fix tests

* more fixes
This commit is contained in:
JT 2022-02-21 17:22:21 -05:00 committed by GitHub
parent 739e403cd5
commit 9888f8f298
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 463 additions and 288 deletions

View file

@ -278,6 +278,8 @@ impl NuCompleter {
&mut stack,
&block,
PipelineData::new(new_span),
true,
true,
);
let v: Vec<_> = match result {

View file

@ -84,7 +84,14 @@ impl Command for Do {
)
}
}
let result = eval_block(engine_state, &mut stack, block, input);
let result = eval_block(
engine_state,
&mut stack,
block,
input,
call.redirect_stdout,
ignore_errors,
);
if ignore_errors {
match result {

View file

@ -1,4 +1,4 @@
use nu_engine::{eval_block_with_redirect, eval_expression, CallExt};
use nu_engine::{eval_block, eval_expression, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
@ -71,6 +71,8 @@ impl Command for For {
let mut stack = stack.captures_to_stack(&capture_block.captures);
let orig_env_vars = stack.env_vars.clone();
let orig_env_hidden = stack.env_hidden.clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
match values {
Value::List { vals, .. } => Ok(vals
@ -99,11 +101,13 @@ impl Command for For {
);
//let block = engine_state.get_block(block_id);
match eval_block_with_redirect(
match eval_block(
&engine_state,
&mut stack,
&block,
PipelineData::new(head),
redirect_stdout,
redirect_stderr,
) {
Ok(pipeline_data) => pipeline_data.into_value(head),
Err(error) => Value::Error { error },
@ -137,11 +141,13 @@ impl Command for For {
);
//let block = engine_state.get_block(block_id);
match eval_block_with_redirect(
match eval_block(
&engine_state,
&mut stack,
&block,
PipelineData::new(head),
redirect_stdout,
redirect_stderr,
) {
Ok(pipeline_data) => pipeline_data.into_value(head),
Err(error) => Value::Error { error },
@ -152,7 +158,14 @@ impl Command for For {
x => {
stack.add_var(var_id, x);
eval_block_with_redirect(&engine_state, &mut stack, &block, PipelineData::new(head))
eval_block(
&engine_state,
&mut stack,
&block,
PipelineData::new(head),
redirect_stdout,
redirect_stderr,
)
}
}
}

View file

@ -51,7 +51,14 @@ impl Command for If {
if *val {
let block = engine_state.get_block(then_block.block_id);
let mut stack = stack.captures_to_stack(&then_block.captures);
eval_block(engine_state, &mut stack, block, input)
eval_block(
engine_state,
&mut stack,
block,
input,
call.redirect_stdout,
call.redirect_stderr,
)
} else if let Some(else_case) = else_case {
if let Some(else_expr) = else_case.as_keyword() {
if let Some(block_id) = else_expr.as_block() {
@ -60,7 +67,14 @@ impl Command for If {
let mut stack = stack.captures_to_stack(&else_block.captures);
let block = engine_state.get_block(block_id);
eval_block(engine_state, &mut stack, block, input)
eval_block(
engine_state,
&mut stack,
block,
input,
call.redirect_stdout,
call.redirect_stderr,
)
} else {
eval_expression(engine_state, stack, else_expr)
.map(|x| x.into_pipeline_data())

View file

@ -41,7 +41,14 @@ impl Command for Let {
.as_keyword()
.expect("internal error: missing keyword");
let rhs = eval_expression_with_input(engine_state, stack, keyword_expr, input, false)?;
let rhs = eval_expression_with_input(
engine_state,
stack,
keyword_expr,
input,
call.redirect_stdout,
call.redirect_stderr,
)?;
//println!("Adding: {:?} to {}", rhs, var_id);

View file

@ -38,7 +38,14 @@ impl Command for Source {
let block_id: i64 = call.req(engine_state, stack, 1)?;
let block = engine_state.get_block(block_id as usize).clone();
eval_block(engine_state, stack, &block, input)
eval_block(
engine_state,
stack,
&block,
input,
call.redirect_stdout,
call.redirect_stderr,
)
}
fn examples(&self) -> Vec<Example> {

View file

@ -96,7 +96,14 @@ impl Command for Use {
// TODO: Add string conversions (e.g. int to string)
// TODO: Later expand env to take all Values
let val = eval_block(engine_state, stack, block, PipelineData::new(call.head))?
let val = eval_block(
engine_state,
stack,
block,
PipelineData::new(call.head),
false,
true,
)?
.into_value(call.head);
stack.add_env_var(name, val);

View file

@ -72,6 +72,8 @@ pub fn test_dataframe(cmds: Vec<Box<dyn Command + 'static>>) {
&mut stack,
&block,
PipelineData::new(Span::test_data()),
true,
true,
) {
Err(err) => panic!("test eval error in `{}`: {:?}", example.example, err),
Ok(result) => {

View file

@ -39,7 +39,8 @@ impl Command for LetEnv {
.as_keyword()
.expect("internal error: missing keyword");
let rhs = eval_expression_with_input(engine_state, stack, keyword_expr, input, false)?
let rhs =
eval_expression_with_input(engine_state, stack, keyword_expr, input, false, true)?
.into_value(call.head);
if env_var == "PWD" {

View file

@ -132,7 +132,14 @@ fn with_env(
stack.add_env_var(k, v);
}
eval_block(engine_state, &mut stack, block, input)
eval_block(
engine_state,
&mut stack,
block,
input,
call.redirect_stdout,
call.redirect_stderr,
)
}
#[cfg(test)]

View file

@ -121,6 +121,8 @@ pub fn test_examples(cmd: impl Command + 'static) {
&mut stack,
&block,
PipelineData::new(Span::test_data()),
true,
true,
) {
Err(err) => panic!("test eval error in `{}`: {:?}", example.example, err),
Ok(result) => {

View file

@ -68,7 +68,14 @@ impl Command for All {
stack.add_var(var_id, value);
}
eval_block(&engine_state, &mut stack, block, PipelineData::new(span))
eval_block(
&engine_state,
&mut stack,
block,
PipelineData::new(span),
call.redirect_stdout,
call.redirect_stderr,
)
.map_or(false, |pipeline_data| {
pipeline_data.into_value(span).is_true()
})

View file

@ -67,7 +67,14 @@ impl Command for Any {
stack.add_var(var_id, value);
}
eval_block(&engine_state, &mut stack, block, PipelineData::new(span))
eval_block(
&engine_state,
&mut stack,
block,
PipelineData::new(span),
call.redirect_stdout,
call.redirect_stderr,
)
.map_or(false, |pipeline_data| {
pipeline_data.into_value(span).is_true()
})

View file

@ -50,6 +50,8 @@ impl Command for Collect {
&mut stack,
&block,
PipelineData::new(call.head),
call.redirect_stdout,
call.redirect_stderr,
)
}

View file

@ -1,4 +1,4 @@
use nu_engine::{eval_block_with_redirect, CallExt};
use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
@ -110,6 +110,8 @@ impl Command for Each {
let orig_env_vars = stack.env_vars.clone();
let orig_env_hidden = stack.env_hidden.clone();
let span = call.head;
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
match input {
PipelineData::Value(Value::Range { .. }, ..)
@ -143,11 +145,13 @@ impl Command for Each {
}
}
match eval_block_with_redirect(
match eval_block(
&engine_state,
&mut stack,
&block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => v.into_value(span),
Err(error) => Value::Error { error },
@ -188,87 +192,34 @@ impl Command for Each {
}
}
match eval_block_with_redirect(
match eval_block(
&engine_state,
&mut stack,
&block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => v.into_value(span),
Err(error) => Value::Error { error },
}
})
.into_pipeline_data(ctrlc)),
// JT: we'll turn this off for now until we get a better design
// leaving it here, but commented-out, for the time being
// PipelineData::Value(Value::Record { cols, vals, .. }, ..) => {
// let mut output_cols = vec![];
// let mut output_vals = vec![];
// for (col, val) in cols.into_iter().zip(vals.into_iter()) {
// //let block = engine_state.get_block(block_id);
// stack.with_env(&orig_env_vars, &orig_env_hidden);
// if let Some(var) = block.signature.get_positional(0) {
// if let Some(var_id) = &var.var_id {
// stack.add_var(
// *var_id,
// Value::Record {
// cols: vec!["column".into(), "value".into()],
// vals: vec![
// Value::String {
// val: col.clone(),
// span: call.head,
// },
// val,
// ],
// span: call.head,
// },
// );
// }
// }
// match eval_block_with_redirect(
// &engine_state,
// &mut stack,
// &block,
// PipelineData::new(span),
// )? {
// PipelineData::Value(
// Value::Record {
// mut cols, mut vals, ..
// },
// ..,
// ) => {
// // TODO check that the lengths match when traversing record
// output_cols.append(&mut cols);
// output_vals.append(&mut vals);
// }
// x => {
// output_cols.push(col);
// output_vals.push(x.into_value(span));
// }
// }
// }
// Ok(Value::Record {
// cols: output_cols,
// vals: output_vals,
// span: call.head,
// }
// .into_pipeline_data())
// }
PipelineData::Value(x, ..) => {
//let block = engine_state.get_block(block_id);
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x);
}
}
eval_block_with_redirect(&engine_state, &mut stack, &block, PipelineData::new(span))
eval_block(
&engine_state,
&mut stack,
&block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
)
}
}
.and_then(|x| {

View file

@ -1,4 +1,4 @@
use nu_engine::{eval_block_with_redirect, CallExt};
use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
@ -69,6 +69,8 @@ impl Command for EachGroup {
engine_state: engine_state.clone(),
stack: stack.clone(),
group_size: group_size.item,
redirect_stdout: call.redirect_stdout,
redirect_stderr: call.redirect_stderr,
input: Box::new(input.into_iter()),
span: call.head,
};
@ -82,6 +84,8 @@ struct EachGroupIterator {
engine_state: EngineState,
stack: Stack,
group_size: usize,
redirect_stdout: bool,
redirect_stderr: bool,
input: Box<dyn Iterator<Item = Value> + Send>,
span: Span,
}
@ -118,6 +122,8 @@ impl Iterator for EachGroupIterator {
self.block.clone(),
self.engine_state.clone(),
self.stack.clone(),
self.redirect_stdout,
self.redirect_stderr,
self.span,
))
}
@ -128,6 +134,8 @@ pub(crate) fn run_block_on_vec(
capture_block: CaptureBlock,
engine_state: EngineState,
stack: Stack,
redirect_stdout: bool,
redirect_stderr: bool,
span: Span,
) -> Value {
let value = Value::List { vals: input, span };
@ -142,7 +150,14 @@ pub(crate) fn run_block_on_vec(
}
}
match eval_block_with_redirect(&engine_state, &mut stack, block, PipelineData::new(span)) {
match eval_block(
&engine_state,
&mut stack,
block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
) {
Ok(pipeline) => pipeline.into_value(span),
Err(error) => Value::Error { error },
}

View file

@ -1,4 +1,4 @@
use nu_engine::{eval_block_with_redirect, CallExt};
use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
@ -108,6 +108,8 @@ impl Command for EachWindow {
stack: stack.clone(),
group_size: group_size.item,
input: Box::new(input.into_iter()),
redirect_stdout: call.redirect_stdout,
redirect_stderr: call.redirect_stderr,
span: call.head,
previous: vec![],
stride,
@ -123,6 +125,8 @@ struct EachWindowIterator {
stack: Stack,
group_size: usize,
input: Box<dyn Iterator<Item = Value> + Send>,
redirect_stdout: bool,
redirect_stderr: bool,
span: Span,
previous: Vec<Value>,
stride: usize,
@ -186,6 +190,8 @@ impl Iterator for EachWindowIterator {
self.block.clone(),
self.engine_state.clone(),
self.stack.clone(),
self.redirect_stdout,
self.redirect_stderr,
self.span,
))
}
@ -196,6 +202,8 @@ pub(crate) fn run_block_on_vec(
capture_block: CaptureBlock,
engine_state: EngineState,
stack: Stack,
redirect_stdout: bool,
redirect_stderr: bool,
span: Span,
) -> Value {
let value = Value::List { vals: input, span };
@ -210,7 +218,14 @@ pub(crate) fn run_block_on_vec(
}
}
match eval_block_with_redirect(&engine_state, &mut stack, block, PipelineData::new(span)) {
match eval_block(
&engine_state,
&mut stack,
block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
) {
Ok(pipeline) => pipeline.into_value(span),
Err(error) => Value::Error { error },
}

View file

@ -118,7 +118,14 @@ fn empty(
.ok_or_else(|| ShellError::TypeMismatch("expected row condition".to_owned(), head))?;
let b = engine_state.get_block(block_id);
let evaluated_block = eval_block(engine_state, stack, b, PipelineData::new(head))?;
let evaluated_block = eval_block(
engine_state,
stack,
b,
PipelineData::new(head),
call.redirect_stdout,
call.redirect_stderr,
)?;
Some(evaluated_block.into_value(head))
} else {
None

View file

@ -93,6 +93,9 @@ impl Command for Find {
let metadata = input.metadata();
let config = stack.get_config()?;
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
match call.get_flag::<CaptureBlock>(&engine_state, stack, "predicate")? {
Some(predicate) => {
let capture_block = predicate;
@ -121,6 +124,8 @@ impl Command for Find {
&mut stack,
&block,
PipelineData::new_with_metadata(metadata.clone(), span),
redirect_stdout,
redirect_stderr,
)
.map_or(false, |pipeline_data| {
pipeline_data.into_value(span).is_true()

View file

@ -119,8 +119,14 @@ pub fn group_by(
if let Some(capture_block) = &block {
let mut stack = stack.captures_to_stack(&capture_block.captures);
let block = engine_state.get_block(capture_block.block_id);
let pipeline =
eval_block(engine_state, &mut stack, block, value.into_pipeline_data());
let pipeline = eval_block(
engine_state,
&mut stack,
block,
value.into_pipeline_data(),
call.redirect_stdout,
call.redirect_stderr,
);
match pipeline {
Ok(s) => {

View file

@ -58,6 +58,9 @@ impl Command for KeepUntil {
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
Ok(input
.into_iter()
.take_while(move |value| {
@ -65,7 +68,14 @@ impl Command for KeepUntil {
stack.add_var(var_id, value.clone());
}
!eval_block(&engine_state, &mut stack, &block, PipelineData::new(span))
!eval_block(
&engine_state,
&mut stack,
&block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
)
.map_or(false, |pipeline_data| {
pipeline_data.into_value(span).is_true()
})

View file

@ -58,6 +58,9 @@ impl Command for KeepWhile {
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
Ok(input
.into_iter()
.take_while(move |value| {
@ -65,7 +68,14 @@ impl Command for KeepWhile {
stack.add_var(var_id, value.clone());
}
eval_block(&engine_state, &mut stack, &block, PipelineData::new(span))
eval_block(
&engine_state,
&mut stack,
&block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
)
.map_or(false, |pipeline_data| {
pipeline_data.into_value(span).is_true()
})

View file

@ -82,6 +82,8 @@ impl Command for Merge {
&mut stack,
block,
PipelineData::new(call.head),
call.redirect_stdout,
call.redirect_stderr,
);
let table = match result {

View file

@ -1,4 +1,4 @@
use nu_engine::{eval_block_with_redirect, CallExt};
use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
@ -53,6 +53,8 @@ impl Command for ParEach {
let block_id = capture_block.block_id;
let mut stack = stack.captures_to_stack(&capture_block.captures);
let span = call.head;
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
match input {
PipelineData::Value(Value::Range { val, .. }, ..) => Ok(val
@ -87,11 +89,13 @@ impl Command for ParEach {
}
}
match eval_block_with_redirect(
match eval_block(
&engine_state,
&mut stack,
block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => v,
Err(error) => Value::Error { error }.into_pipeline_data(),
@ -133,11 +137,13 @@ impl Command for ParEach {
}
}
match eval_block_with_redirect(
match eval_block(
&engine_state,
&mut stack,
block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => v,
Err(error) => Value::Error { error }.into_pipeline_data(),
@ -178,11 +184,13 @@ impl Command for ParEach {
}
}
match eval_block_with_redirect(
match eval_block(
&engine_state,
&mut stack,
block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => v,
Err(error) => Value::Error { error }.into_pipeline_data(),
@ -228,11 +236,13 @@ impl Command for ParEach {
}
}
match eval_block_with_redirect(
match eval_block(
&engine_state,
&mut stack,
block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => v,
Err(error) => Value::Error { error }.into_pipeline_data(),
@ -242,64 +252,6 @@ impl Command for ParEach {
.into_iter()
.flatten()
.into_pipeline_data(ctrlc)),
PipelineData::Value(Value::Record { cols, vals, .. }, ..) => {
let mut output_cols = vec![];
let mut output_vals = vec![];
for (col, val) in cols.into_iter().zip(vals.into_iter()) {
let block = engine_state.get_block(block_id);
let mut stack = stack.clone();
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(
*var_id,
Value::Record {
cols: vec!["column".into(), "value".into()],
vals: vec![
Value::String {
val: col.clone(),
span: call.head,
},
val,
],
span: call.head,
},
);
}
}
match eval_block_with_redirect(
&engine_state,
&mut stack,
block,
PipelineData::new(span),
)? {
PipelineData::Value(
Value::Record {
mut cols, mut vals, ..
},
..,
) => {
// TODO check that the lengths match when traversing record
output_cols.append(&mut cols);
output_vals.append(&mut vals);
}
x => {
output_cols.push(col);
output_vals.push(x.into_value(span));
}
}
}
Ok(Value::Record {
cols: output_cols,
vals: output_vals,
span: call.head,
}
.into_pipeline_data())
}
PipelineData::Value(x, ..) => {
let block = engine_state.get_block(block_id);
@ -309,7 +261,14 @@ impl Command for ParEach {
}
}
eval_block_with_redirect(&engine_state, &mut stack, block, PipelineData::new(span))
eval_block(
&engine_state,
&mut stack,
block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
)
}
}
}

View file

@ -1,4 +1,4 @@
use nu_engine::{eval_block_with_redirect, CallExt};
use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
@ -49,6 +49,8 @@ impl Command for ParEachGroup {
let capture_block: CaptureBlock = call.req(engine_state, stack, 1)?;
let ctrlc = engine_state.ctrlc.clone();
let span = call.head;
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
let stack = stack.captures_to_stack(&capture_block.captures);
@ -72,11 +74,13 @@ impl Command for ParEachGroup {
}
}
match eval_block_with_redirect(
match eval_block(
engine_state,
&mut stack,
block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => v.into_value(span),
Err(error) => Value::Error { error },

View file

@ -109,6 +109,9 @@ impl Command for Reduce {
let orig_env_vars = stack.env_vars.clone();
let orig_env_hidden = stack.env_hidden.clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
let mut input_iter = input.into_iter();
let (off, start_val) = if let Some(val) = fold {
@ -170,7 +173,14 @@ impl Command for Reduce {
}
}
let v = match eval_block(engine_state, &mut stack, block, PipelineData::new(span)) {
let v = match eval_block(
engine_state,
&mut stack,
block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => v.into_value(span),
Err(error) => Value::Error { error },
};

View file

@ -58,6 +58,9 @@ impl Command for SkipUntil {
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
Ok(input
.into_iter()
.skip_while(move |value| {
@ -65,7 +68,14 @@ impl Command for SkipUntil {
stack.add_var(var_id, value.clone());
}
!eval_block(&engine_state, &mut stack, &block, PipelineData::new(span))
!eval_block(
&engine_state,
&mut stack,
&block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
)
.map_or(false, |pipeline_data| {
pipeline_data.into_value(span).is_true()
})

View file

@ -58,6 +58,9 @@ impl Command for SkipWhile {
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
Ok(input
.into_iter()
.skip_while(move |value| {
@ -65,7 +68,14 @@ impl Command for SkipWhile {
stack.add_var(var_id, value.clone());
}
eval_block(&engine_state, &mut stack, &block, PipelineData::new(span))
eval_block(
&engine_state,
&mut stack,
&block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
)
.map_or(false, |pipeline_data| {
pipeline_data.into_value(span).is_true()
})

View file

@ -70,6 +70,10 @@ fn update(
let cell_path: CellPath = call.req(engine_state, stack, 0)?;
let replacement: Value = call.req(engine_state, stack, 1)?;
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
let engine_state = engine_state.clone();
let ctrlc = engine_state.ctrlc.clone();
@ -97,6 +101,8 @@ fn update(
&mut stack,
&block,
input.clone().into_pipeline_data(),
redirect_stdout,
redirect_stderr,
);
match output {

View file

@ -132,6 +132,9 @@ impl Command for UpdateCells {
let ctrlc = engine_state.ctrlc.clone();
let block: Block = engine_state.get_block(block.block_id).clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
let span = call.head;
stack.with_env(&orig_env_vars, &orig_env_hidden);
@ -156,6 +159,8 @@ impl Command for UpdateCells {
stack,
block,
columns,
redirect_stdout,
redirect_stderr,
span,
}
.into_pipeline_data(ctrlc))
@ -168,6 +173,8 @@ struct UpdateCellIterator {
engine_state: EngineState,
stack: Stack,
block: Block,
redirect_stdout: bool,
redirect_stderr: bool,
span: Span,
}
@ -195,6 +202,8 @@ impl Iterator for UpdateCellIterator {
&self.engine_state,
&mut self.stack,
&self.block,
self.redirect_stdout,
self.redirect_stderr,
span,
),
})
@ -207,6 +216,8 @@ impl Iterator for UpdateCellIterator {
&self.engine_state,
&mut self.stack,
&self.block,
self.redirect_stdout,
self.redirect_stderr,
self.span,
)),
}
@ -221,6 +232,8 @@ fn process_cell(
engine_state: &EngineState,
stack: &mut Stack,
block: &Block,
redirect_stdout: bool,
redirect_stderr: bool,
span: Span,
) -> Value {
if let Some(var) = block.signature.get_positional(0) {
@ -228,7 +241,14 @@ fn process_cell(
stack.add_var(*var_id, val.clone());
}
}
match eval_block(engine_state, stack, block, val.into_pipeline_data()) {
match eval_block(
engine_state,
stack,
block,
val.into_pipeline_data(),
redirect_stdout,
redirect_stderr,
) {
Ok(pd) => pd.into_value(span),
Err(e) => Value::Error { error: e },
}

View file

@ -1,4 +1,4 @@
use nu_engine::{eval_block_with_redirect, CallExt};
use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
@ -41,6 +41,9 @@ impl Command for Where {
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
Ok(input
.into_iter()
.filter_map(move |value| {
@ -49,11 +52,13 @@ impl Command for Where {
stack.add_var(*var_id, value.clone());
}
}
let result = eval_block_with_redirect(
let result = eval_block(
&engine_state,
&mut stack,
&block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
);
match result {

View file

@ -39,6 +39,9 @@ impl Command for Benchmark {
let capture_block: CaptureBlock = call.req(engine_state, stack, 0)?;
let block = engine_state.get_block(capture_block.block_id);
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
let mut stack = stack.captures_to_stack(&capture_block.captures);
let start_time = Instant::now();
eval_block(
@ -46,6 +49,8 @@ impl Command for Benchmark {
&mut stack,
block,
PipelineData::new(call.head),
redirect_stdout,
redirect_stderr,
)?
.into_value(call.head);

View file

@ -84,7 +84,8 @@ fn exec(
name,
args,
env_vars,
last_expression: true,
redirect_stdout: true,
redirect_stderr: false,
};
let mut command = external_command.spawn_simple_command(&cwd.to_string_lossy().to_string())?;

View file

@ -36,7 +36,8 @@ impl Command for External {
fn signature(&self) -> nu_protocol::Signature {
Signature::build("run-external")
.switch("last-expression", "last-expression", None)
.switch("redirect-stdout", "redirect-stdout", None)
.switch("redirect-stderr", "redirect-stderr", None)
.rest("rest", SyntaxShape::Any, "external command to run")
.category(Category::System)
}
@ -50,7 +51,8 @@ impl Command for External {
) -> Result<PipelineData, ShellError> {
let name: Spanned<String> = call.req(engine_state, stack, 0)?;
let args: Vec<Value> = call.rest(engine_state, stack, 1)?;
let last_expression = call.has_flag("last-expression");
let redirect_stdout = call.has_flag("redirect-stdout");
let redirect_stderr = call.has_flag("redirect-stderr");
// Translate environment variables from Values to Strings
let config = stack.get_config().unwrap_or_default();
@ -93,7 +95,8 @@ impl Command for External {
let command = ExternalCommand {
name,
args: args_strs,
last_expression,
redirect_stdout,
redirect_stderr,
env_vars: env_vars_str,
};
command.run_with_input(engine_state, stack, input)
@ -103,7 +106,8 @@ impl Command for External {
pub struct ExternalCommand {
pub name: Spanned<String>,
pub args: Vec<Spanned<String>>,
pub last_expression: bool,
pub redirect_stdout: bool,
pub redirect_stderr: bool,
pub env_vars: HashMap<String, String>,
}
@ -138,10 +142,14 @@ impl ExternalCommand {
// If the external is not the last command, its output will get piped
// either as a string or binary
if !self.last_expression {
if self.redirect_stdout {
process.stdout(Stdio::piped());
}
if self.redirect_stderr {
process.stderr(Stdio::piped());
}
// If there is an input from the pipeline. The stdin from the process
// is piped so it can be used to send the input information
if !matches!(input, PipelineData::Value(Value::Nothing { .. }, ..)) {
@ -173,10 +181,14 @@ impl ExternalCommand {
// If the external is not the last command, its output will get piped
// either as a string or binary
if !self.last_expression {
if self.redirect_stdout {
process.stdout(Stdio::piped());
}
if self.redirect_stderr {
process.stderr(Stdio::piped());
}
// If there is an input from the pipeline. The stdin from the process
// is piped so it can be used to send the input information
if !matches!(input, PipelineData::Value(Value::Nothing { .. }, ..)) {
@ -241,7 +253,8 @@ impl ExternalCommand {
}
}
let last_expression = self.last_expression;
let redirect_stdout = self.redirect_stdout;
let redirect_stderr = self.redirect_stderr;
let span = self.name.span;
let output_ctrlc = ctrlc.clone();
let (tx, rx) = mpsc::channel();
@ -249,7 +262,12 @@ impl ExternalCommand {
std::thread::spawn(move || {
// If this external is not the last expression, then its output is piped to a channel
// and we create a ValueStream that can be consumed
if !last_expression {
if redirect_stderr {
let _ = child.stderr.take();
}
if redirect_stdout {
let stdout = child.stdout.take().ok_or_else(|| {
ShellError::ExternalCommand(
"Error taking stdout from external".to_string(),

View file

@ -354,3 +354,15 @@ fn str_reverse() {
assert!(actual.out.contains("llehsun"));
}
#[test]
fn test_redirection_trim() {
let actual = nu!(
cwd: ".", pipeline(
r#"
let x = (nu --testbin cococo niceone); $x | str trim | str length
"#
));
assert_eq!(actual.out, "7");
}

View file

@ -55,6 +55,16 @@ fn with_env_and_shorthand_same_result() {
assert_eq!(actual_shorthand.out, actual_normal.out);
}
#[test]
fn test_redirection2() {
let actual = nu!(
cwd: "tests/fixtures/formats",
"let x = (FOO=BAR nu --testbin cococo niceenvvar); $x | str trim | str length"
);
assert_eq!(actual.out, "10");
}
// FIXME: jt: needs more work
#[ignore]
#[test]

View file

@ -161,8 +161,14 @@ fn get_converted_value(
stack.add_var(*var_id, orig_val.clone());
}
let result =
eval_block(engine_state, &mut stack, block, PipelineData::new(val_span));
let result = eval_block(
engine_state,
&mut stack,
block,
PipelineData::new(val_span),
true,
true,
);
match result {
Ok(data) => ConversionResult::Ok(data.into_value(val_span)),

View file

@ -133,7 +133,7 @@ fn eval_call(
}
}
let result = eval_block(engine_state, &mut callee_stack, block, input);
let result = eval_block(engine_state, &mut callee_stack, block, input, false, true);
if block.redirect_env {
let caller_env_vars = caller_stack.get_env_var_names(engine_state);
@ -169,7 +169,8 @@ fn eval_external(
head: &Expression,
args: &[Expression],
input: PipelineData,
last_expression: bool,
redirect_stdout: bool,
redirect_stderr: bool,
) -> Result<PipelineData, ShellError> {
let decl_id = engine_state
.find_decl("run-external".as_bytes())
@ -185,10 +186,20 @@ fn eval_external(
call.positional.push(arg.clone())
}
if last_expression {
if redirect_stdout {
call.named.push((
Spanned {
item: "last-expression".into(),
item: "redirect-stdout".into(),
span: head.span,
},
None,
))
}
if redirect_stderr {
call.named.push((
Spanned {
item: "redirect-stderr".into(),
span: head.span,
},
None,
@ -277,6 +288,7 @@ pub fn eval_expression(
args,
PipelineData::new(span),
false,
false,
)?
.into_value(span))
}
@ -431,20 +443,37 @@ pub fn eval_expression_with_input(
stack: &mut Stack,
expr: &Expression,
mut input: PipelineData,
last_expression: bool,
redirect_stdout: bool,
redirect_stderr: bool,
) -> Result<PipelineData, ShellError> {
match expr {
Expression {
expr: Expr::Call(call),
..
} => {
if !redirect_stdout || redirect_stderr {
// we're doing something different than the defaults
let mut call = call.clone();
call.redirect_stdout = redirect_stdout;
call.redirect_stderr = redirect_stderr;
input = eval_call(engine_state, stack, &call, input)?;
} else {
input = eval_call(engine_state, stack, call, input)?;
}
}
Expression {
expr: Expr::ExternalCall(head, args),
..
} => {
input = eval_external(engine_state, stack, head, args, input, last_expression)?;
input = eval_external(
engine_state,
stack,
head,
args,
input,
redirect_stdout,
redirect_stderr,
)?;
}
Expression {
@ -470,6 +499,8 @@ pub fn eval_block(
stack: &mut Stack,
block: &Block,
mut input: PipelineData,
redirect_stdout: bool,
redirect_stderr: bool,
) -> Result<PipelineData, ShellError> {
let num_pipelines = block.len();
for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() {
@ -479,7 +510,8 @@ pub fn eval_block(
stack,
elem,
input,
i == pipeline.expressions.len() - 1,
redirect_stdout || (i != pipeline.expressions.len() - 1),
redirect_stderr,
)?
}
@ -543,78 +575,6 @@ pub fn eval_block(
Ok(input)
}
pub fn eval_block_with_redirect(
engine_state: &EngineState,
stack: &mut Stack,
block: &Block,
mut input: PipelineData,
) -> Result<PipelineData, ShellError> {
let num_pipelines = block.len();
for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() {
for elem in pipeline.expressions.iter() {
input = eval_expression_with_input(engine_state, stack, elem, input, false)?
}
if pipeline_idx < (num_pipelines) - 1 {
match input {
PipelineData::Value(Value::Nothing { .. }, ..) => {}
_ => {
// Drain the input to the screen via tabular output
let config = stack.get_config().unwrap_or_default();
match engine_state.find_decl("table".as_bytes()) {
Some(decl_id) => {
let table = engine_state.get_decl(decl_id).run(
engine_state,
stack,
&Call::new(Span::new(0, 0)),
input,
)?;
for item in table {
let stdout = std::io::stdout();
if let Value::Error { error } = item {
return Err(error);
}
let mut out = item.into_string("\n", &config);
out.push('\n');
match stdout.lock().write_all(out.as_bytes()) {
Ok(_) => (),
Err(err) => eprintln!("{}", err),
};
}
}
None => {
for item in input {
let stdout = std::io::stdout();
if let Value::Error { error } = item {
return Err(error);
}
let mut out = item.into_string("\n", &config);
out.push('\n');
match stdout.lock().write_all(out.as_bytes()) {
Ok(_) => (),
Err(err) => eprintln!("{}", err),
};
}
}
};
}
}
input = PipelineData::new(Span { start: 0, end: 0 })
}
}
Ok(input)
}
pub fn eval_subexpression(
engine_state: &EngineState,
stack: &mut Stack,
@ -623,7 +583,7 @@ pub fn eval_subexpression(
) -> Result<PipelineData, ShellError> {
for pipeline in block.pipelines.iter() {
for expr in pipeline.expressions.iter() {
input = eval_expression_with_input(engine_state, stack, expr, input, false)?
input = eval_expression_with_input(engine_state, stack, expr, input, true, false)?
}
}

View file

@ -10,7 +10,6 @@ pub use column::get_columns;
pub use documentation::{generate_docs, get_brief_help, get_documentation, get_full_help};
pub use env::*;
pub use eval::{
eval_block, eval_block_with_redirect, eval_expression, eval_expression_with_input,
eval_operator, eval_subexpression,
eval_block, eval_expression, eval_expression_with_input, eval_operator, eval_subexpression,
};
pub use glob_from::glob_from;

View file

@ -1,7 +1,7 @@
use nu_protocol::ast::Expr;
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
use nu_protocol::PipelineData;
use nu_protocol::{ast::Call, engine::Command, ShellError, Signature};
use nu_protocol::{PipelineData, Spanned};
#[derive(Clone)]
pub struct KnownExternal {
@ -61,15 +61,25 @@ impl Command for KnownExternal {
call.positional.push(arg.clone())
}
// if last_expression {
// call.named.push((
// Spanned {
// item: "last-expression".into(),
// span: head.span,
// },
// None,
// ))
// }
if call.redirect_stdout {
call.named.push((
Spanned {
item: "redirect-stdout".into(),
span: call_span,
},
None,
))
}
if call.redirect_stderr {
call.named.push((
Spanned {
item: "redirect-stderr".into(),
span: call_span,
},
None,
))
}
command.run(engine_state, stack, &call, input)
}

View file

@ -579,6 +579,8 @@ pub fn parse_export(
decl_id: export_decl_id,
positional: vec![],
named: vec![],
redirect_stdout: true,
redirect_stderr: false,
});
let exportable = if let Some(kw_span) = spans.get(1) {
@ -986,6 +988,8 @@ pub fn parse_module(
decl_id: module_decl_id,
positional: vec![module_name_expr, block_expr],
named: vec![],
redirect_stdout: true,
redirect_stderr: false,
});
(
@ -1200,6 +1204,8 @@ pub fn parse_use(
decl_id: use_decl_id,
positional: vec![import_pattern_expr],
named: vec![],
redirect_stdout: true,
redirect_stderr: false,
});
(
@ -1399,6 +1405,8 @@ pub fn parse_hide(
decl_id: hide_decl_id,
positional: vec![import_pattern_expr],
named: vec![],
redirect_stdout: true,
redirect_stderr: false,
});
(
@ -1480,6 +1488,8 @@ pub fn parse_let(
head: spans[0],
positional: vec![lvalue, rvalue],
named: vec![],
redirect_stdout: true,
redirect_stderr: false,
});
return (

View file

@ -3420,7 +3420,7 @@ pub fn parse_expression(
(
Expression {
expr: Expr::String(String::new()),
span: spans[pos],
span: Span { start: 0, end: 0 },
ty: Type::Nothing,
custom_completion: None,
},
@ -3556,6 +3556,8 @@ pub fn parse_expression(
decl_id,
named: vec![],
positional,
redirect_stdout: true,
redirect_stderr: false,
}));
(
@ -4108,6 +4110,8 @@ fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression)
named: vec![],
positional: output,
decl_id,
redirect_stdout: true,
redirect_stderr: false,
})),
span,
ty: Type::String,

View file

@ -8,6 +8,8 @@ pub struct Call {
pub head: Span,
pub positional: Vec<Expression>,
pub named: Vec<(Spanned<String>, Option<Expression>)>,
pub redirect_stdout: bool,
pub redirect_stderr: bool,
}
impl Call {
@ -17,6 +19,8 @@ impl Call {
head,
positional: vec![],
named: vec![],
redirect_stdout: true,
redirect_stderr: false,
}
}

View file

@ -95,7 +95,7 @@ pub(crate) fn evaluate(
std::process::exit(1);
}
match eval_block(engine_state, &mut stack, &block, input) {
match eval_block(engine_state, &mut stack, &block, input, false, false) {
Ok(pipeline_data) => {
crate::eval_file::print_table_or_error(engine_state, &mut stack, pipeline_data, &config)
}

View file

@ -251,3 +251,9 @@ fn with_env_shorthand_nested_quotes() -> TestResult {
"-arg \"hello world\"",
)
}
#[test]
fn test_redirection_stderr() -> TestResult {
// try a nonsense binary
run_test(r#"do -i { asdjw4j5cnaabw44rd }; echo done"#, "done")
}

View file

@ -226,7 +226,7 @@ pub(crate) fn eval_source(
report_error(&working_set, &err);
}
match eval_block(engine_state, stack, &block, input) {
match eval_block(engine_state, stack, &block, input, false, false) {
Ok(pipeline_data) => {
if let Err(err) = print_pipeline_data(pipeline_data, engine_state, stack) {
let working_set = StateWorkingSet::new(engine_state);