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, &mut stack,
&block, &block,
PipelineData::new(new_span), PipelineData::new(new_span),
true,
true,
); );
let v: Vec<_> = match result { 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 { if ignore_errors {
match result { 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::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
@ -71,6 +71,8 @@ impl Command for For {
let mut stack = stack.captures_to_stack(&capture_block.captures); let mut stack = stack.captures_to_stack(&capture_block.captures);
let orig_env_vars = stack.env_vars.clone(); let orig_env_vars = stack.env_vars.clone();
let orig_env_hidden = stack.env_hidden.clone(); let orig_env_hidden = stack.env_hidden.clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
match values { match values {
Value::List { vals, .. } => Ok(vals Value::List { vals, .. } => Ok(vals
@ -99,11 +101,13 @@ impl Command for For {
); );
//let block = engine_state.get_block(block_id); //let block = engine_state.get_block(block_id);
match eval_block_with_redirect( match eval_block(
&engine_state, &engine_state,
&mut stack, &mut stack,
&block, &block,
PipelineData::new(head), PipelineData::new(head),
redirect_stdout,
redirect_stderr,
) { ) {
Ok(pipeline_data) => pipeline_data.into_value(head), Ok(pipeline_data) => pipeline_data.into_value(head),
Err(error) => Value::Error { error }, Err(error) => Value::Error { error },
@ -137,11 +141,13 @@ impl Command for For {
); );
//let block = engine_state.get_block(block_id); //let block = engine_state.get_block(block_id);
match eval_block_with_redirect( match eval_block(
&engine_state, &engine_state,
&mut stack, &mut stack,
&block, &block,
PipelineData::new(head), PipelineData::new(head),
redirect_stdout,
redirect_stderr,
) { ) {
Ok(pipeline_data) => pipeline_data.into_value(head), Ok(pipeline_data) => pipeline_data.into_value(head),
Err(error) => Value::Error { error }, Err(error) => Value::Error { error },
@ -152,7 +158,14 @@ impl Command for For {
x => { x => {
stack.add_var(var_id, 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 { if *val {
let block = engine_state.get_block(then_block.block_id); let block = engine_state.get_block(then_block.block_id);
let mut stack = stack.captures_to_stack(&then_block.captures); 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 { } else if let Some(else_case) = else_case {
if let Some(else_expr) = else_case.as_keyword() { if let Some(else_expr) = else_case.as_keyword() {
if let Some(block_id) = else_expr.as_block() { 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 mut stack = stack.captures_to_stack(&else_block.captures);
let block = engine_state.get_block(block_id); 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 { } else {
eval_expression(engine_state, stack, else_expr) eval_expression(engine_state, stack, else_expr)
.map(|x| x.into_pipeline_data()) .map(|x| x.into_pipeline_data())

View File

@ -41,7 +41,14 @@ impl Command for Let {
.as_keyword() .as_keyword()
.expect("internal error: missing 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); //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_id: i64 = call.req(engine_state, stack, 1)?;
let block = engine_state.get_block(block_id as usize).clone(); 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> { 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: Add string conversions (e.g. int to string)
// TODO: Later expand env to take all Values // 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); .into_value(call.head);
stack.add_env_var(name, val); stack.add_env_var(name, val);

View File

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

View File

@ -39,7 +39,8 @@ impl Command for LetEnv {
.as_keyword() .as_keyword()
.expect("internal error: missing 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); .into_value(call.head);
if env_var == "PWD" { if env_var == "PWD" {

View File

@ -132,7 +132,14 @@ fn with_env(
stack.add_env_var(k, v); 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)] #[cfg(test)]

View File

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

View File

@ -68,7 +68,14 @@ impl Command for All {
stack.add_var(var_id, value); 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| { .map_or(false, |pipeline_data| {
pipeline_data.into_value(span).is_true() pipeline_data.into_value(span).is_true()
}) })

View File

@ -67,7 +67,14 @@ impl Command for Any {
stack.add_var(var_id, value); 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| { .map_or(false, |pipeline_data| {
pipeline_data.into_value(span).is_true() pipeline_data.into_value(span).is_true()
}) })

View File

@ -50,6 +50,8 @@ impl Command for Collect {
&mut stack, &mut stack,
&block, &block,
PipelineData::new(call.head), 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::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
@ -110,6 +110,8 @@ impl Command for Each {
let orig_env_vars = stack.env_vars.clone(); let orig_env_vars = stack.env_vars.clone();
let orig_env_hidden = stack.env_hidden.clone(); let orig_env_hidden = stack.env_hidden.clone();
let span = call.head; let span = call.head;
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
match input { match input {
PipelineData::Value(Value::Range { .. }, ..) PipelineData::Value(Value::Range { .. }, ..)
@ -143,11 +145,13 @@ impl Command for Each {
} }
} }
match eval_block_with_redirect( match eval_block(
&engine_state, &engine_state,
&mut stack, &mut stack,
&block, &block,
PipelineData::new(span), PipelineData::new(span),
redirect_stdout,
redirect_stderr,
) { ) {
Ok(v) => v.into_value(span), Ok(v) => v.into_value(span),
Err(error) => Value::Error { error }, Err(error) => Value::Error { error },
@ -188,87 +192,34 @@ impl Command for Each {
} }
} }
match eval_block_with_redirect( match eval_block(
&engine_state, &engine_state,
&mut stack, &mut stack,
&block, &block,
PipelineData::new(span), PipelineData::new(span),
redirect_stdout,
redirect_stderr,
) { ) {
Ok(v) => v.into_value(span), Ok(v) => v.into_value(span),
Err(error) => Value::Error { error }, Err(error) => Value::Error { error },
} }
}) })
.into_pipeline_data(ctrlc)), .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, ..) => { PipelineData::Value(x, ..) => {
//let block = engine_state.get_block(block_id);
if let Some(var) = block.signature.get_positional(0) { if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id { if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x); 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| { .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::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
@ -69,6 +69,8 @@ impl Command for EachGroup {
engine_state: engine_state.clone(), engine_state: engine_state.clone(),
stack: stack.clone(), stack: stack.clone(),
group_size: group_size.item, group_size: group_size.item,
redirect_stdout: call.redirect_stdout,
redirect_stderr: call.redirect_stderr,
input: Box::new(input.into_iter()), input: Box::new(input.into_iter()),
span: call.head, span: call.head,
}; };
@ -82,6 +84,8 @@ struct EachGroupIterator {
engine_state: EngineState, engine_state: EngineState,
stack: Stack, stack: Stack,
group_size: usize, group_size: usize,
redirect_stdout: bool,
redirect_stderr: bool,
input: Box<dyn Iterator<Item = Value> + Send>, input: Box<dyn Iterator<Item = Value> + Send>,
span: Span, span: Span,
} }
@ -118,6 +122,8 @@ impl Iterator for EachGroupIterator {
self.block.clone(), self.block.clone(),
self.engine_state.clone(), self.engine_state.clone(),
self.stack.clone(), self.stack.clone(),
self.redirect_stdout,
self.redirect_stderr,
self.span, self.span,
)) ))
} }
@ -128,6 +134,8 @@ pub(crate) fn run_block_on_vec(
capture_block: CaptureBlock, capture_block: CaptureBlock,
engine_state: EngineState, engine_state: EngineState,
stack: Stack, stack: Stack,
redirect_stdout: bool,
redirect_stderr: bool,
span: Span, span: Span,
) -> Value { ) -> Value {
let value = Value::List { vals: input, span }; 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), Ok(pipeline) => pipeline.into_value(span),
Err(error) => Value::Error { error }, 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::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
@ -108,6 +108,8 @@ impl Command for EachWindow {
stack: stack.clone(), stack: stack.clone(),
group_size: group_size.item, group_size: group_size.item,
input: Box::new(input.into_iter()), input: Box::new(input.into_iter()),
redirect_stdout: call.redirect_stdout,
redirect_stderr: call.redirect_stderr,
span: call.head, span: call.head,
previous: vec![], previous: vec![],
stride, stride,
@ -123,6 +125,8 @@ struct EachWindowIterator {
stack: Stack, stack: Stack,
group_size: usize, group_size: usize,
input: Box<dyn Iterator<Item = Value> + Send>, input: Box<dyn Iterator<Item = Value> + Send>,
redirect_stdout: bool,
redirect_stderr: bool,
span: Span, span: Span,
previous: Vec<Value>, previous: Vec<Value>,
stride: usize, stride: usize,
@ -186,6 +190,8 @@ impl Iterator for EachWindowIterator {
self.block.clone(), self.block.clone(),
self.engine_state.clone(), self.engine_state.clone(),
self.stack.clone(), self.stack.clone(),
self.redirect_stdout,
self.redirect_stderr,
self.span, self.span,
)) ))
} }
@ -196,6 +202,8 @@ pub(crate) fn run_block_on_vec(
capture_block: CaptureBlock, capture_block: CaptureBlock,
engine_state: EngineState, engine_state: EngineState,
stack: Stack, stack: Stack,
redirect_stdout: bool,
redirect_stderr: bool,
span: Span, span: Span,
) -> Value { ) -> Value {
let value = Value::List { vals: input, span }; 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), Ok(pipeline) => pipeline.into_value(span),
Err(error) => Value::Error { error }, Err(error) => Value::Error { error },
} }

View File

@ -118,7 +118,14 @@ fn empty(
.ok_or_else(|| ShellError::TypeMismatch("expected row condition".to_owned(), head))?; .ok_or_else(|| ShellError::TypeMismatch("expected row condition".to_owned(), head))?;
let b = engine_state.get_block(block_id); 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)) Some(evaluated_block.into_value(head))
} else { } else {
None None

View File

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

View File

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

View File

@ -58,6 +58,9 @@ impl Command for KeepUntil {
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone(); let engine_state = engine_state.clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
Ok(input Ok(input
.into_iter() .into_iter()
.take_while(move |value| { .take_while(move |value| {
@ -65,7 +68,14 @@ impl Command for KeepUntil {
stack.add_var(var_id, value.clone()); 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| { .map_or(false, |pipeline_data| {
pipeline_data.into_value(span).is_true() pipeline_data.into_value(span).is_true()
}) })

View File

@ -58,6 +58,9 @@ impl Command for KeepWhile {
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone(); let engine_state = engine_state.clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
Ok(input Ok(input
.into_iter() .into_iter()
.take_while(move |value| { .take_while(move |value| {
@ -65,7 +68,14 @@ impl Command for KeepWhile {
stack.add_var(var_id, value.clone()); 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| { .map_or(false, |pipeline_data| {
pipeline_data.into_value(span).is_true() pipeline_data.into_value(span).is_true()
}) })

View File

@ -82,6 +82,8 @@ impl Command for Merge {
&mut stack, &mut stack,
block, block,
PipelineData::new(call.head), PipelineData::new(call.head),
call.redirect_stdout,
call.redirect_stderr,
); );
let table = match result { 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::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
@ -53,6 +53,8 @@ impl Command for ParEach {
let block_id = capture_block.block_id; let block_id = capture_block.block_id;
let mut stack = stack.captures_to_stack(&capture_block.captures); let mut stack = stack.captures_to_stack(&capture_block.captures);
let span = call.head; let span = call.head;
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
match input { match input {
PipelineData::Value(Value::Range { val, .. }, ..) => Ok(val 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, &engine_state,
&mut stack, &mut stack,
block, block,
PipelineData::new(span), PipelineData::new(span),
redirect_stdout,
redirect_stderr,
) { ) {
Ok(v) => v, Ok(v) => v,
Err(error) => Value::Error { error }.into_pipeline_data(), 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, &engine_state,
&mut stack, &mut stack,
block, block,
PipelineData::new(span), PipelineData::new(span),
redirect_stdout,
redirect_stderr,
) { ) {
Ok(v) => v, Ok(v) => v,
Err(error) => Value::Error { error }.into_pipeline_data(), 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, &engine_state,
&mut stack, &mut stack,
block, block,
PipelineData::new(span), PipelineData::new(span),
redirect_stdout,
redirect_stderr,
) { ) {
Ok(v) => v, Ok(v) => v,
Err(error) => Value::Error { error }.into_pipeline_data(), 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, &engine_state,
&mut stack, &mut stack,
block, block,
PipelineData::new(span), PipelineData::new(span),
redirect_stdout,
redirect_stderr,
) { ) {
Ok(v) => v, Ok(v) => v,
Err(error) => Value::Error { error }.into_pipeline_data(), Err(error) => Value::Error { error }.into_pipeline_data(),
@ -242,64 +252,6 @@ impl Command for ParEach {
.into_iter() .into_iter()
.flatten() .flatten()
.into_pipeline_data(ctrlc)), .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, ..) => { PipelineData::Value(x, ..) => {
let block = engine_state.get_block(block_id); 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::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
@ -49,6 +49,8 @@ impl Command for ParEachGroup {
let capture_block: CaptureBlock = call.req(engine_state, stack, 1)?; let capture_block: CaptureBlock = call.req(engine_state, stack, 1)?;
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
let span = call.head; 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); 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, engine_state,
&mut stack, &mut stack,
block, block,
PipelineData::new(span), PipelineData::new(span),
redirect_stdout,
redirect_stderr,
) { ) {
Ok(v) => v.into_value(span), Ok(v) => v.into_value(span),
Err(error) => Value::Error { error }, 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_vars = stack.env_vars.clone();
let orig_env_hidden = stack.env_hidden.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 mut input_iter = input.into_iter();
let (off, start_val) = if let Some(val) = fold { 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), Ok(v) => v.into_value(span),
Err(error) => Value::Error { error }, Err(error) => Value::Error { error },
}; };

View File

@ -58,6 +58,9 @@ impl Command for SkipUntil {
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone(); let engine_state = engine_state.clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
Ok(input Ok(input
.into_iter() .into_iter()
.skip_while(move |value| { .skip_while(move |value| {
@ -65,7 +68,14 @@ impl Command for SkipUntil {
stack.add_var(var_id, value.clone()); 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| { .map_or(false, |pipeline_data| {
pipeline_data.into_value(span).is_true() pipeline_data.into_value(span).is_true()
}) })

View File

@ -58,6 +58,9 @@ impl Command for SkipWhile {
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone(); let engine_state = engine_state.clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
Ok(input Ok(input
.into_iter() .into_iter()
.skip_while(move |value| { .skip_while(move |value| {
@ -65,7 +68,14 @@ impl Command for SkipWhile {
stack.add_var(var_id, value.clone()); 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| { .map_or(false, |pipeline_data| {
pipeline_data.into_value(span).is_true() 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 cell_path: CellPath = call.req(engine_state, stack, 0)?;
let replacement: Value = call.req(engine_state, stack, 1)?; 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 engine_state = engine_state.clone();
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
@ -97,6 +101,8 @@ fn update(
&mut stack, &mut stack,
&block, &block,
input.clone().into_pipeline_data(), input.clone().into_pipeline_data(),
redirect_stdout,
redirect_stderr,
); );
match output { match output {

View File

@ -132,6 +132,9 @@ impl Command for UpdateCells {
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
let block: Block = engine_state.get_block(block.block_id).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; let span = call.head;
stack.with_env(&orig_env_vars, &orig_env_hidden); stack.with_env(&orig_env_vars, &orig_env_hidden);
@ -156,6 +159,8 @@ impl Command for UpdateCells {
stack, stack,
block, block,
columns, columns,
redirect_stdout,
redirect_stderr,
span, span,
} }
.into_pipeline_data(ctrlc)) .into_pipeline_data(ctrlc))
@ -168,6 +173,8 @@ struct UpdateCellIterator {
engine_state: EngineState, engine_state: EngineState,
stack: Stack, stack: Stack,
block: Block, block: Block,
redirect_stdout: bool,
redirect_stderr: bool,
span: Span, span: Span,
} }
@ -195,6 +202,8 @@ impl Iterator for UpdateCellIterator {
&self.engine_state, &self.engine_state,
&mut self.stack, &mut self.stack,
&self.block, &self.block,
self.redirect_stdout,
self.redirect_stderr,
span, span,
), ),
}) })
@ -207,6 +216,8 @@ impl Iterator for UpdateCellIterator {
&self.engine_state, &self.engine_state,
&mut self.stack, &mut self.stack,
&self.block, &self.block,
self.redirect_stdout,
self.redirect_stderr,
self.span, self.span,
)), )),
} }
@ -221,6 +232,8 @@ fn process_cell(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
block: &Block, block: &Block,
redirect_stdout: bool,
redirect_stderr: bool,
span: Span, span: Span,
) -> Value { ) -> Value {
if let Some(var) = block.signature.get_positional(0) { if let Some(var) = block.signature.get_positional(0) {
@ -228,7 +241,14 @@ fn process_cell(
stack.add_var(*var_id, val.clone()); 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), Ok(pd) => pd.into_value(span),
Err(e) => Value::Error { error: e }, 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::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack}; use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
@ -41,6 +41,9 @@ impl Command for Where {
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone(); let engine_state = engine_state.clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
Ok(input Ok(input
.into_iter() .into_iter()
.filter_map(move |value| { .filter_map(move |value| {
@ -49,11 +52,13 @@ impl Command for Where {
stack.add_var(*var_id, value.clone()); stack.add_var(*var_id, value.clone());
} }
} }
let result = eval_block_with_redirect( let result = eval_block(
&engine_state, &engine_state,
&mut stack, &mut stack,
&block, &block,
PipelineData::new(span), PipelineData::new(span),
redirect_stdout,
redirect_stderr,
); );
match result { match result {

View File

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

View File

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

View File

@ -354,3 +354,15 @@ fn str_reverse() {
assert!(actual.out.contains("llehsun")); 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); 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 // FIXME: jt: needs more work
#[ignore] #[ignore]
#[test] #[test]

View File

@ -161,8 +161,14 @@ fn get_converted_value(
stack.add_var(*var_id, orig_val.clone()); stack.add_var(*var_id, orig_val.clone());
} }
let result = let result = eval_block(
eval_block(engine_state, &mut stack, block, PipelineData::new(val_span)); engine_state,
&mut stack,
block,
PipelineData::new(val_span),
true,
true,
);
match result { match result {
Ok(data) => ConversionResult::Ok(data.into_value(val_span)), 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 { if block.redirect_env {
let caller_env_vars = caller_stack.get_env_var_names(engine_state); let caller_env_vars = caller_stack.get_env_var_names(engine_state);
@ -169,7 +169,8 @@ fn eval_external(
head: &Expression, head: &Expression,
args: &[Expression], args: &[Expression],
input: PipelineData, input: PipelineData,
last_expression: bool, redirect_stdout: bool,
redirect_stderr: bool,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let decl_id = engine_state let decl_id = engine_state
.find_decl("run-external".as_bytes()) .find_decl("run-external".as_bytes())
@ -185,10 +186,20 @@ fn eval_external(
call.positional.push(arg.clone()) call.positional.push(arg.clone())
} }
if last_expression { if redirect_stdout {
call.named.push(( call.named.push((
Spanned { 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, span: head.span,
}, },
None, None,
@ -277,6 +288,7 @@ pub fn eval_expression(
args, args,
PipelineData::new(span), PipelineData::new(span),
false, false,
false,
)? )?
.into_value(span)) .into_value(span))
} }
@ -431,20 +443,37 @@ pub fn eval_expression_with_input(
stack: &mut Stack, stack: &mut Stack,
expr: &Expression, expr: &Expression,
mut input: PipelineData, mut input: PipelineData,
last_expression: bool, redirect_stdout: bool,
redirect_stderr: bool,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
match expr { match expr {
Expression { Expression {
expr: Expr::Call(call), 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)?; input = eval_call(engine_state, stack, call, input)?;
} }
}
Expression { Expression {
expr: Expr::ExternalCall(head, args), 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 { Expression {
@ -470,6 +499,8 @@ pub fn eval_block(
stack: &mut Stack, stack: &mut Stack,
block: &Block, block: &Block,
mut input: PipelineData, mut input: PipelineData,
redirect_stdout: bool,
redirect_stderr: bool,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let num_pipelines = block.len(); let num_pipelines = block.len();
for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() { for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() {
@ -479,7 +510,8 @@ pub fn eval_block(
stack, stack,
elem, elem,
input, 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) 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( pub fn eval_subexpression(
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
@ -623,7 +583,7 @@ pub fn eval_subexpression(
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
for pipeline in block.pipelines.iter() { for pipeline in block.pipelines.iter() {
for expr in pipeline.expressions.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 documentation::{generate_docs, get_brief_help, get_documentation, get_full_help};
pub use env::*; pub use env::*;
pub use eval::{ pub use eval::{
eval_block, eval_block_with_redirect, eval_expression, eval_expression_with_input, eval_block, eval_expression, eval_expression_with_input, eval_operator, eval_subexpression,
eval_operator, eval_subexpression,
}; };
pub use glob_from::glob_from; pub use glob_from::glob_from;

View File

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

View File

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

View File

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

View File

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

View File

@ -95,7 +95,7 @@ pub(crate) fn evaluate(
std::process::exit(1); 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) => { Ok(pipeline_data) => {
crate::eval_file::print_table_or_error(engine_state, &mut stack, pipeline_data, &config) 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\"", "-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); 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) => { Ok(pipeline_data) => {
if let Err(err) = print_pipeline_data(pipeline_data, engine_state, stack) { if let Err(err) = print_pipeline_data(pipeline_data, engine_state, stack) {
let working_set = StateWorkingSet::new(engine_state); let working_set = StateWorkingSet::new(engine_state);