mirror of
https://github.com/nushell/nushell
synced 2024-12-26 04:53:09 +00:00
Fix try
printing when it is not the last pipeline element (#13992)
# Description Fixes #13991. This was done by more clearly separating the case when a pipeline is drained vs when it is being written (to a file). I also added an `OutDest::Print` case which might not be strictly necessary, but is a helpful addition. # User-Facing Changes Bug fix. # Tests + Formatting Added a test. # After Submitting There are still a few redirection bugs that I found, but they require larger code changes, so I'll leave them until after the release.
This commit is contained in:
parent
0e3a8c552c
commit
de08b68ba8
14 changed files with 127 additions and 194 deletions
|
@ -63,7 +63,7 @@ impl Command for Try {
|
||||||
let eval_block = get_eval_block(engine_state);
|
let eval_block = get_eval_block(engine_state);
|
||||||
|
|
||||||
let result = eval_block(engine_state, stack, try_block, input)
|
let result = eval_block(engine_state, stack, try_block, input)
|
||||||
.and_then(|pipeline| pipeline.write_to_out_dests(engine_state, stack));
|
.and_then(|pipeline| pipeline.drain_to_out_dests(engine_state, stack));
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Err(err) => run_catch(err, head, catch_block, engine_state, stack, eval_block),
|
Err(err) => run_catch(err, head, catch_block, engine_state, stack, eval_block),
|
||||||
|
|
|
@ -163,7 +163,7 @@ use it in your pipeline."#
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
OutDest::Null => copy_on_thread(tee, io::sink(), &info).map(Some),
|
OutDest::Null => copy_on_thread(tee, io::sink(), &info).map(Some),
|
||||||
OutDest::Inherit => {
|
OutDest::Print | OutDest::Inherit => {
|
||||||
copy_on_thread(tee, io::stderr(), &info).map(Some)
|
copy_on_thread(tee, io::stderr(), &info).map(Some)
|
||||||
}
|
}
|
||||||
OutDest::File(file) => {
|
OutDest::File(file) => {
|
||||||
|
@ -181,7 +181,9 @@ use it in your pipeline."#
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
OutDest::Null => copy_pipe(stdout, io::sink(), &info),
|
OutDest::Null => copy_pipe(stdout, io::sink(), &info),
|
||||||
OutDest::Inherit => copy_pipe(stdout, io::stdout(), &info),
|
OutDest::Print | OutDest::Inherit => {
|
||||||
|
copy_pipe(stdout, io::stdout(), &info)
|
||||||
|
}
|
||||||
OutDest::File(file) => copy_pipe(stdout, file.as_ref(), &info),
|
OutDest::File(file) => copy_pipe(stdout, file.as_ref(), &info),
|
||||||
}?;
|
}?;
|
||||||
}
|
}
|
||||||
|
@ -198,7 +200,7 @@ use it in your pipeline."#
|
||||||
OutDest::Null => {
|
OutDest::Null => {
|
||||||
copy_pipe_on_thread(stderr, io::sink(), &info).map(Some)
|
copy_pipe_on_thread(stderr, io::sink(), &info).map(Some)
|
||||||
}
|
}
|
||||||
OutDest::Inherit => {
|
OutDest::Print | OutDest::Inherit => {
|
||||||
copy_pipe_on_thread(stderr, io::stderr(), &info).map(Some)
|
copy_pipe_on_thread(stderr, io::stderr(), &info).map(Some)
|
||||||
}
|
}
|
||||||
OutDest::File(file) => {
|
OutDest::File(file) => {
|
||||||
|
@ -218,7 +220,7 @@ use it in your pipeline."#
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
OutDest::Null => copy(tee, io::sink(), &info),
|
OutDest::Null => copy(tee, io::sink(), &info),
|
||||||
OutDest::Inherit => copy(tee, io::stdout(), &info),
|
OutDest::Print | OutDest::Inherit => copy(tee, io::stdout(), &info),
|
||||||
OutDest::File(file) => copy(tee, file.as_ref(), &info),
|
OutDest::File(file) => copy(tee, file.as_ref(), &info),
|
||||||
}?;
|
}?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,13 +107,22 @@ fn exit_code_available_in_catch() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn try_catches_exit_code_in_assignment() {
|
fn catches_exit_code_in_assignment() {
|
||||||
let actual = nu!("let x = try { nu -c 'exit 42' } catch { |e| $e.exit_code }; $x");
|
let actual = nu!("let x = try { nu -c 'exit 42' } catch { |e| $e.exit_code }; $x");
|
||||||
assert_eq!(actual.out, "42");
|
assert_eq!(actual.out, "42");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn try_catches_exit_code_in_expr() {
|
fn catches_exit_code_in_expr() {
|
||||||
let actual = nu!("print (try { nu -c 'exit 42' } catch { |e| $e.exit_code })");
|
let actual = nu!("print (try { nu -c 'exit 42' } catch { |e| $e.exit_code })");
|
||||||
assert_eq!(actual.out, "42");
|
assert_eq!(actual.out, "42");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prints_only_if_last_pipeline() {
|
||||||
|
let actual = nu!("try { 'should not print' }; 'last value'");
|
||||||
|
assert_eq!(actual.out, "last value");
|
||||||
|
|
||||||
|
let actual = nu!("try { ['should not print'] | every 1 }; 'last value'");
|
||||||
|
assert_eq!(actual.out, "last value");
|
||||||
|
}
|
||||||
|
|
|
@ -205,7 +205,7 @@ impl BlockBuilder {
|
||||||
Instruction::Span { src_dst } => allocate(&[*src_dst], &[*src_dst]),
|
Instruction::Span { src_dst } => allocate(&[*src_dst], &[*src_dst]),
|
||||||
Instruction::Drop { src } => allocate(&[*src], &[]),
|
Instruction::Drop { src } => allocate(&[*src], &[]),
|
||||||
Instruction::Drain { src } => allocate(&[*src], &[]),
|
Instruction::Drain { src } => allocate(&[*src], &[]),
|
||||||
Instruction::WriteToOutDests { src } => allocate(&[*src], &[]),
|
Instruction::DrainIfEnd { src } => allocate(&[*src], &[]),
|
||||||
Instruction::LoadVariable { dst, var_id: _ } => allocate(&[], &[*dst]),
|
Instruction::LoadVariable { dst, var_id: _ } => allocate(&[], &[*dst]),
|
||||||
Instruction::StoreVariable { var_id: _, src } => allocate(&[*src], &[]),
|
Instruction::StoreVariable { var_id: _, src } => allocate(&[*src], &[]),
|
||||||
Instruction::DropVariable { var_id: _ } => Ok(()),
|
Instruction::DropVariable { var_id: _ } => Ok(()),
|
||||||
|
|
|
@ -473,7 +473,7 @@ pub(crate) fn compile_try(
|
||||||
if let Some(mode) = redirect_modes.err {
|
if let Some(mode) = redirect_modes.err {
|
||||||
builder.push(mode.map(|mode| Instruction::RedirectErr { mode }))?;
|
builder.push(mode.map(|mode| Instruction::RedirectErr { mode }))?;
|
||||||
}
|
}
|
||||||
builder.push(Instruction::WriteToOutDests { src: io_reg }.into_spanned(call.head))?;
|
builder.push(Instruction::DrainIfEnd { src: io_reg }.into_spanned(call.head))?;
|
||||||
builder.push(Instruction::PopErrorHandler.into_spanned(call.head))?;
|
builder.push(Instruction::PopErrorHandler.into_spanned(call.head))?;
|
||||||
|
|
||||||
// Jump over the failure case
|
// Jump over the failure case
|
||||||
|
|
|
@ -151,7 +151,8 @@ pub(crate) fn out_dest_to_redirect_mode(
|
||||||
OutDest::PipeSeparate => Ok(RedirectMode::PipeSeparate),
|
OutDest::PipeSeparate => Ok(RedirectMode::PipeSeparate),
|
||||||
OutDest::Value => Ok(RedirectMode::Value),
|
OutDest::Value => Ok(RedirectMode::Value),
|
||||||
OutDest::Null => Ok(RedirectMode::Null),
|
OutDest::Null => Ok(RedirectMode::Null),
|
||||||
OutDest::Inherit => Ok(RedirectMode::Inherit),
|
OutDest::Print => Ok(RedirectMode::Print),
|
||||||
|
OutDest::Inherit => Err(CompileError::InvalidRedirectMode { span }),
|
||||||
OutDest::File(_) => Err(CompileError::InvalidRedirectMode { span }),
|
OutDest::File(_) => Err(CompileError::InvalidRedirectMode { span }),
|
||||||
})
|
})
|
||||||
.transpose()
|
.transpose()
|
||||||
|
|
|
@ -395,13 +395,13 @@ fn eval_element_with_input_inner<D: DebugContext>(
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
let data = eval_expression_with_input::<D>(engine_state, stack, &element.expr, input)?;
|
let data = eval_expression_with_input::<D>(engine_state, stack, &element.expr, input)?;
|
||||||
|
|
||||||
if let Some(redirection) = element.redirection.as_ref() {
|
let is_external = if let PipelineData::ByteStream(stream, ..) = &data {
|
||||||
let is_external = if let PipelineData::ByteStream(stream, ..) = &data {
|
matches!(stream.source(), ByteStreamSource::Child(..))
|
||||||
matches!(stream.source(), ByteStreamSource::Child(..))
|
} else {
|
||||||
} else {
|
false
|
||||||
false
|
};
|
||||||
};
|
|
||||||
|
|
||||||
|
if let Some(redirection) = element.redirection.as_ref() {
|
||||||
if !is_external {
|
if !is_external {
|
||||||
match redirection {
|
match redirection {
|
||||||
&PipelineRedirection::Single {
|
&PipelineRedirection::Single {
|
||||||
|
@ -437,30 +437,24 @@ fn eval_element_with_input_inner<D: DebugContext>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let has_stdout_file = matches!(stack.pipe_stdout(), Some(OutDest::File(_)));
|
let data = if let Some(OutDest::File(file)) = stack.pipe_stdout() {
|
||||||
|
match &data {
|
||||||
let data = match &data {
|
PipelineData::Value(..) | PipelineData::ListStream(..) => {
|
||||||
PipelineData::Value(..) | PipelineData::ListStream(..) => {
|
data.write_to(file.as_ref())?;
|
||||||
if has_stdout_file {
|
|
||||||
data.write_to_out_dests(engine_state, stack)?;
|
|
||||||
PipelineData::Empty
|
PipelineData::Empty
|
||||||
} else {
|
|
||||||
data
|
|
||||||
}
|
}
|
||||||
}
|
PipelineData::ByteStream(..) => {
|
||||||
PipelineData::ByteStream(stream, ..) => {
|
if !is_external {
|
||||||
let write = match stream.source() {
|
data.write_to(file.as_ref())?;
|
||||||
ByteStreamSource::Read(_) | ByteStreamSource::File(_) => has_stdout_file,
|
PipelineData::Empty
|
||||||
ByteStreamSource::Child(_) => false,
|
} else {
|
||||||
};
|
data
|
||||||
if write {
|
}
|
||||||
data.write_to_out_dests(engine_state, stack)?;
|
|
||||||
PipelineData::Empty
|
|
||||||
} else {
|
|
||||||
data
|
|
||||||
}
|
}
|
||||||
|
PipelineData::Empty => PipelineData::Empty,
|
||||||
}
|
}
|
||||||
PipelineData::Empty => PipelineData::Empty,
|
} else {
|
||||||
|
data
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(data)
|
Ok(data)
|
||||||
|
|
|
@ -322,13 +322,13 @@ fn eval_instruction<D: DebugContext>(
|
||||||
let data = ctx.take_reg(*src);
|
let data = ctx.take_reg(*src);
|
||||||
drain(ctx, data)
|
drain(ctx, data)
|
||||||
}
|
}
|
||||||
Instruction::WriteToOutDests { src } => {
|
Instruction::DrainIfEnd { src } => {
|
||||||
let data = ctx.take_reg(*src);
|
let data = ctx.take_reg(*src);
|
||||||
let res = {
|
let res = {
|
||||||
let stack = &mut ctx
|
let stack = &mut ctx
|
||||||
.stack
|
.stack
|
||||||
.push_redirection(ctx.redirect_out.clone(), ctx.redirect_err.clone());
|
.push_redirection(ctx.redirect_out.clone(), ctx.redirect_err.clone());
|
||||||
data.write_to_out_dests(ctx.engine_state, stack)?
|
data.drain_to_out_dests(ctx.engine_state, stack)?
|
||||||
};
|
};
|
||||||
ctx.put_reg(*src, res);
|
ctx.put_reg(*src, res);
|
||||||
Ok(Continue)
|
Ok(Continue)
|
||||||
|
@ -507,14 +507,19 @@ fn eval_instruction<D: DebugContext>(
|
||||||
msg: format!("Tried to write to file #{file_num}, but it is not open"),
|
msg: format!("Tried to write to file #{file_num}, but it is not open"),
|
||||||
span: Some(*span),
|
span: Some(*span),
|
||||||
})?;
|
})?;
|
||||||
let result = {
|
let is_external = if let PipelineData::ByteStream(stream, ..) = &src {
|
||||||
let mut stack = ctx
|
matches!(stream.source(), ByteStreamSource::Child(..))
|
||||||
.stack
|
} else {
|
||||||
.push_redirection(Some(Redirection::File(file)), None);
|
false
|
||||||
src.write_to_out_dests(ctx.engine_state, &mut stack)?
|
|
||||||
};
|
};
|
||||||
// Abort execution if there's an exit code from a failed external
|
if let Err(err) = src.write_to(file.as_ref()) {
|
||||||
drain(ctx, result)
|
if is_external {
|
||||||
|
ctx.stack.set_last_error(&err);
|
||||||
|
}
|
||||||
|
Err(err)?
|
||||||
|
} else {
|
||||||
|
Ok(Continue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Instruction::CloseFile { file_num } => {
|
Instruction::CloseFile { file_num } => {
|
||||||
if ctx.files[*file_num as usize].take().is_some() {
|
if ctx.files[*file_num as usize].take().is_some() {
|
||||||
|
@ -1421,6 +1426,7 @@ fn eval_redirection(
|
||||||
RedirectMode::Value => Ok(Some(Redirection::Pipe(OutDest::Value))),
|
RedirectMode::Value => Ok(Some(Redirection::Pipe(OutDest::Value))),
|
||||||
RedirectMode::Null => Ok(Some(Redirection::Pipe(OutDest::Null))),
|
RedirectMode::Null => Ok(Some(Redirection::Pipe(OutDest::Null))),
|
||||||
RedirectMode::Inherit => Ok(Some(Redirection::Pipe(OutDest::Inherit))),
|
RedirectMode::Inherit => Ok(Some(Redirection::Pipe(OutDest::Inherit))),
|
||||||
|
RedirectMode::Print => Ok(Some(Redirection::Pipe(OutDest::Print))),
|
||||||
RedirectMode::File { file_num } => {
|
RedirectMode::File { file_num } => {
|
||||||
let file = ctx
|
let file = ctx
|
||||||
.files
|
.files
|
||||||
|
|
|
@ -62,8 +62,8 @@ pub(crate) struct StackOutDest {
|
||||||
impl StackOutDest {
|
impl StackOutDest {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
pipe_stdout: None,
|
pipe_stdout: Some(OutDest::Print),
|
||||||
pipe_stderr: None,
|
pipe_stderr: Some(OutDest::Print),
|
||||||
stdout: OutDest::Inherit,
|
stdout: OutDest::Inherit,
|
||||||
stderr: OutDest::Inherit,
|
stderr: OutDest::Inherit,
|
||||||
parent_stdout: None,
|
parent_stdout: None,
|
||||||
|
|
|
@ -92,8 +92,8 @@ impl<'a> fmt::Display for FmtInstruction<'a> {
|
||||||
Instruction::Drain { src } => {
|
Instruction::Drain { src } => {
|
||||||
write!(f, "{:WIDTH$} {src}", "drain")
|
write!(f, "{:WIDTH$} {src}", "drain")
|
||||||
}
|
}
|
||||||
Instruction::WriteToOutDests { src } => {
|
Instruction::DrainIfEnd { src } => {
|
||||||
write!(f, "{:WIDTH$} {src}", "write-to-out-dests")
|
write!(f, "{:WIDTH$} {src}", "drain-if-end")
|
||||||
}
|
}
|
||||||
Instruction::LoadVariable { dst, var_id } => {
|
Instruction::LoadVariable { dst, var_id } => {
|
||||||
let var = FmtVar::new(self.engine_state, *var_id);
|
let var = FmtVar::new(self.engine_state, *var_id);
|
||||||
|
@ -312,6 +312,7 @@ impl fmt::Display for RedirectMode {
|
||||||
RedirectMode::Value => write!(f, "value"),
|
RedirectMode::Value => write!(f, "value"),
|
||||||
RedirectMode::Null => write!(f, "null"),
|
RedirectMode::Null => write!(f, "null"),
|
||||||
RedirectMode::Inherit => write!(f, "inherit"),
|
RedirectMode::Inherit => write!(f, "inherit"),
|
||||||
|
RedirectMode::Print => write!(f, "print"),
|
||||||
RedirectMode::File { file_num } => write!(f, "file({file_num})"),
|
RedirectMode::File { file_num } => write!(f, "file({file_num})"),
|
||||||
RedirectMode::Caller => write!(f, "caller"),
|
RedirectMode::Caller => write!(f, "caller"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,9 +123,9 @@ pub enum Instruction {
|
||||||
/// code, and invokes any available error handler with Empty, or if not available, returns an
|
/// code, and invokes any available error handler with Empty, or if not available, returns an
|
||||||
/// exit-code-only stream, leaving the block.
|
/// exit-code-only stream, leaving the block.
|
||||||
Drain { src: RegId },
|
Drain { src: RegId },
|
||||||
/// Write to the output destinations in the stack
|
/// Drain the value/stream in a register and discard only if this is the last pipeline element.
|
||||||
// TODO: see if it's possible to remove this
|
// TODO: see if it's possible to remove this
|
||||||
WriteToOutDests { src: RegId },
|
DrainIfEnd { src: RegId },
|
||||||
/// Load the value of a variable into the `dst` register
|
/// Load the value of a variable into the `dst` register
|
||||||
LoadVariable { dst: RegId, var_id: VarId },
|
LoadVariable { dst: RegId, var_id: VarId },
|
||||||
/// Store the value of a variable from the `src` register
|
/// Store the value of a variable from the `src` register
|
||||||
|
@ -290,7 +290,7 @@ impl Instruction {
|
||||||
Instruction::Span { src_dst } => Some(src_dst),
|
Instruction::Span { src_dst } => Some(src_dst),
|
||||||
Instruction::Drop { .. } => None,
|
Instruction::Drop { .. } => None,
|
||||||
Instruction::Drain { .. } => None,
|
Instruction::Drain { .. } => None,
|
||||||
Instruction::WriteToOutDests { .. } => None,
|
Instruction::DrainIfEnd { .. } => None,
|
||||||
Instruction::LoadVariable { dst, .. } => Some(dst),
|
Instruction::LoadVariable { dst, .. } => Some(dst),
|
||||||
Instruction::StoreVariable { .. } => None,
|
Instruction::StoreVariable { .. } => None,
|
||||||
Instruction::DropVariable { .. } => None,
|
Instruction::DropVariable { .. } => None,
|
||||||
|
@ -450,6 +450,7 @@ pub enum RedirectMode {
|
||||||
Value,
|
Value,
|
||||||
Null,
|
Null,
|
||||||
Inherit,
|
Inherit,
|
||||||
|
Print,
|
||||||
/// Use the given numbered file.
|
/// Use the given numbered file.
|
||||||
File {
|
File {
|
||||||
file_num: u32,
|
file_num: u32,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Module managing the streaming of raw bytes between pipeline elements
|
//! Module managing the streaming of raw bytes between pipeline elements
|
||||||
use crate::{
|
use crate::{
|
||||||
process::{ChildPipe, ChildProcess},
|
process::{ChildPipe, ChildProcess},
|
||||||
ErrSpan, IntoSpanned, OutDest, PipelineData, ShellError, Signals, Span, Type, Value,
|
ErrSpan, IntoSpanned, PipelineData, ShellError, Signals, Span, Type, Value,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -13,7 +13,6 @@ use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{self, BufRead, BufReader, Cursor, ErrorKind, Read, Write},
|
io::{self, BufRead, BufReader, Cursor, ErrorKind, Read, Write},
|
||||||
process::Stdio,
|
process::Stdio,
|
||||||
thread,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The source of bytes for a [`ByteStream`].
|
/// The source of bytes for a [`ByteStream`].
|
||||||
|
@ -600,80 +599,6 @@ impl ByteStream {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn write_to_out_dests(
|
|
||||||
self,
|
|
||||||
stdout: &OutDest,
|
|
||||||
stderr: &OutDest,
|
|
||||||
) -> Result<(), ShellError> {
|
|
||||||
let span = self.span;
|
|
||||||
let signals = &self.signals;
|
|
||||||
|
|
||||||
match self.stream {
|
|
||||||
ByteStreamSource::Read(read) => {
|
|
||||||
write_to_out_dest(read, stdout, true, span, signals)?;
|
|
||||||
}
|
|
||||||
ByteStreamSource::File(file) => match stdout {
|
|
||||||
OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value | OutDest::Null => {}
|
|
||||||
OutDest::Inherit => {
|
|
||||||
copy_with_signals(file, io::stdout(), span, signals)?;
|
|
||||||
}
|
|
||||||
OutDest::File(f) => {
|
|
||||||
copy_with_signals(file, f.as_ref(), span, signals)?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ByteStreamSource::Child(mut child) => {
|
|
||||||
match (child.stdout.take(), child.stderr.take()) {
|
|
||||||
(Some(out), Some(err)) => {
|
|
||||||
// To avoid deadlocks, we must spawn a separate thread to wait on stderr.
|
|
||||||
thread::scope(|s| {
|
|
||||||
let err_thread = thread::Builder::new()
|
|
||||||
.name("stderr writer".into())
|
|
||||||
.spawn_scoped(s, || match err {
|
|
||||||
ChildPipe::Pipe(pipe) => {
|
|
||||||
write_to_out_dest(pipe, stderr, false, span, signals)
|
|
||||||
}
|
|
||||||
ChildPipe::Tee(tee) => {
|
|
||||||
write_to_out_dest(tee, stderr, false, span, signals)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.err_span(span);
|
|
||||||
|
|
||||||
match out {
|
|
||||||
ChildPipe::Pipe(pipe) => {
|
|
||||||
write_to_out_dest(pipe, stdout, true, span, signals)
|
|
||||||
}
|
|
||||||
ChildPipe::Tee(tee) => {
|
|
||||||
write_to_out_dest(tee, stdout, true, span, signals)
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
|
|
||||||
if let Ok(result) = err_thread?.join() {
|
|
||||||
result?;
|
|
||||||
} else {
|
|
||||||
// thread panicked, which should not happen
|
|
||||||
debug_assert!(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok::<_, ShellError>(())
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
(Some(out), None) => {
|
|
||||||
// single output stream, we can consume directly
|
|
||||||
write_to_out_dest(out, stdout, true, span, signals)?;
|
|
||||||
}
|
|
||||||
(None, Some(err)) => {
|
|
||||||
// single output stream, we can consume directly
|
|
||||||
write_to_out_dest(err, stderr, false, span, signals)?;
|
|
||||||
}
|
|
||||||
(None, None) => {}
|
|
||||||
}
|
|
||||||
child.wait()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ByteStream> for PipelineData {
|
impl From<ByteStream> for PipelineData {
|
||||||
|
@ -962,23 +887,6 @@ fn trim_end_newline(string: &mut String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_to_out_dest(
|
|
||||||
read: impl Read,
|
|
||||||
stream: &OutDest,
|
|
||||||
stdout: bool,
|
|
||||||
span: Span,
|
|
||||||
signals: &Signals,
|
|
||||||
) -> Result<(), ShellError> {
|
|
||||||
match stream {
|
|
||||||
OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value => return Ok(()),
|
|
||||||
OutDest::Null => copy_with_signals(read, io::sink(), span, signals),
|
|
||||||
OutDest::Inherit if stdout => copy_with_signals(read, io::stdout(), span, signals),
|
|
||||||
OutDest::Inherit => copy_with_signals(read, io::stderr(), span, signals),
|
|
||||||
OutDest::File(file) => copy_with_signals(read, file.as_ref(), span, signals),
|
|
||||||
}?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub(crate) fn convert_file<T: From<OwnedFd>>(file: impl Into<OwnedFd>) -> T {
|
pub(crate) fn convert_file<T: From<OwnedFd>>(file: impl Into<OwnedFd>) -> T {
|
||||||
file.into().into()
|
file.into().into()
|
||||||
|
|
|
@ -25,10 +25,16 @@ pub enum OutDest {
|
||||||
///
|
///
|
||||||
/// This will forward output to the null device for the platform.
|
/// This will forward output to the null device for the platform.
|
||||||
Null,
|
Null,
|
||||||
/// Output to nushell's stdout or stderr.
|
/// Output to nushell's stdout or stderr (only for external commands).
|
||||||
///
|
///
|
||||||
/// This causes external commands to inherit nushell's stdout or stderr.
|
/// This causes external commands to inherit nushell's stdout or stderr. This also causes
|
||||||
|
/// [`ListStream`](crate::ListStream)s to be drained, but not to be printed.
|
||||||
Inherit,
|
Inherit,
|
||||||
|
/// Print to nushell's stdout or stderr.
|
||||||
|
///
|
||||||
|
/// This is just like `Inherit`, except that [`ListStream`](crate::ListStream)s and
|
||||||
|
/// [`Value`](crate::Value)s are also printed.
|
||||||
|
Print,
|
||||||
/// Redirect output to a file.
|
/// Redirect output to a file.
|
||||||
File(Arc<File>), // Arc<File>, since we sometimes need to clone `OutDest` into iterators, etc.
|
File(Arc<File>), // Arc<File>, since we sometimes need to clone `OutDest` into iterators, etc.
|
||||||
}
|
}
|
||||||
|
@ -52,7 +58,7 @@ impl TryFrom<&OutDest> for Stdio {
|
||||||
match out_dest {
|
match out_dest {
|
||||||
OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value => Ok(Self::piped()),
|
OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value => Ok(Self::piped()),
|
||||||
OutDest::Null => Ok(Self::null()),
|
OutDest::Null => Ok(Self::null()),
|
||||||
OutDest::Inherit => Ok(Self::inherit()),
|
OutDest::Print | OutDest::Inherit => Ok(Self::inherit()),
|
||||||
OutDest::File(file) => Ok(file.try_clone()?.into()),
|
OutDest::File(file) => Ok(file.try_clone()?.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,66 +165,71 @@ impl PipelineData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes all values or redirects all output to the current [`OutDest`]s in `stack`.
|
/// Drain and write this [`PipelineData`] to `dest`.
|
||||||
///
|
///
|
||||||
/// For [`OutDest::Pipe`] and [`OutDest::PipeSeparate`], this will return the `PipelineData` as
|
/// Values are converted to bytes and separated by newlines if this is a `ListStream`.
|
||||||
/// is without consuming input and without writing anything.
|
pub fn write_to(self, mut dest: impl Write) -> Result<(), ShellError> {
|
||||||
|
match self {
|
||||||
|
PipelineData::Empty => Ok(()),
|
||||||
|
PipelineData::Value(value, ..) => {
|
||||||
|
let bytes = value_to_bytes(value)?;
|
||||||
|
dest.write_all(&bytes)?;
|
||||||
|
dest.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
PipelineData::ListStream(stream, ..) => {
|
||||||
|
for value in stream {
|
||||||
|
let bytes = value_to_bytes(value)?;
|
||||||
|
dest.write_all(&bytes)?;
|
||||||
|
dest.write_all(b"\n")?;
|
||||||
|
}
|
||||||
|
dest.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
PipelineData::ByteStream(stream, ..) => stream.write_to(dest),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drain this [`PipelineData`] according to the current stdout [`OutDest`]s in `stack`.
|
||||||
///
|
///
|
||||||
/// For the other [`OutDest`]s, the given `PipelineData` will be completely consumed
|
/// For [`OutDest::Pipe`] and [`OutDest::PipeSeparate`], this will return the [`PipelineData`]
|
||||||
/// and `PipelineData::Empty` will be returned (assuming no errors).
|
/// as is. For [`OutDest::Value`], this will collect into a value and return it. For
|
||||||
pub fn write_to_out_dests(
|
/// [`OutDest::Print`], the [`PipelineData`] is drained and printed. Otherwise, the
|
||||||
|
/// [`PipelineData`] is drained, but only printed if it is the output of an external command.
|
||||||
|
pub fn drain_to_out_dests(
|
||||||
self,
|
self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
stack: &mut Stack,
|
stack: &mut Stack,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<Self, ShellError> {
|
||||||
match (self, stack.stdout()) {
|
match stack.pipe_stdout().unwrap_or(&OutDest::Inherit) {
|
||||||
(PipelineData::Empty, ..) => {}
|
OutDest::Print => {
|
||||||
(data, OutDest::Pipe | OutDest::PipeSeparate) => return Ok(data),
|
self.print(engine_state, stack, false, false)?;
|
||||||
(data, OutDest::Value) => {
|
Ok(Self::Empty)
|
||||||
let metadata = data.metadata();
|
|
||||||
let span = data.span().unwrap_or(Span::unknown());
|
|
||||||
return data
|
|
||||||
.into_value(span)
|
|
||||||
.map(|val| PipelineData::Value(val, metadata));
|
|
||||||
}
|
}
|
||||||
(PipelineData::ByteStream(stream, ..), stdout) => {
|
OutDest::Pipe | OutDest::PipeSeparate => Ok(self),
|
||||||
stream.write_to_out_dests(stdout, stack.stderr())?;
|
OutDest::Value => {
|
||||||
|
let metadata = self.metadata();
|
||||||
|
let span = self.span().unwrap_or(Span::unknown());
|
||||||
|
self.into_value(span).map(|val| Self::Value(val, metadata))
|
||||||
}
|
}
|
||||||
(PipelineData::Value(..), OutDest::Null) => {}
|
OutDest::File(file) => {
|
||||||
(PipelineData::ListStream(stream, ..), OutDest::Null) => {
|
self.write_to(file.as_ref())?;
|
||||||
// we need to drain the stream in case there are external commands in the pipeline
|
Ok(Self::Empty)
|
||||||
stream.drain()?;
|
|
||||||
}
|
}
|
||||||
(PipelineData::Value(value, ..), OutDest::File(file)) => {
|
OutDest::Null | OutDest::Inherit => {
|
||||||
let bytes = value_to_bytes(value)?;
|
self.drain()?;
|
||||||
let mut file = file.as_ref();
|
Ok(Self::Empty)
|
||||||
file.write_all(&bytes)?;
|
|
||||||
file.flush()?;
|
|
||||||
}
|
|
||||||
(PipelineData::ListStream(stream, ..), OutDest::File(file)) => {
|
|
||||||
let mut file = file.as_ref();
|
|
||||||
// use BufWriter here?
|
|
||||||
for value in stream {
|
|
||||||
let bytes = value_to_bytes(value)?;
|
|
||||||
file.write_all(&bytes)?;
|
|
||||||
file.write_all(b"\n")?;
|
|
||||||
}
|
|
||||||
file.flush()?;
|
|
||||||
}
|
|
||||||
(data @ (PipelineData::Value(..) | PipelineData::ListStream(..)), OutDest::Inherit) => {
|
|
||||||
data.print(engine_state, stack, false, false)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(PipelineData::Empty)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drain(self) -> Result<(), ShellError> {
|
pub fn drain(self) -> Result<(), ShellError> {
|
||||||
match self {
|
match self {
|
||||||
PipelineData::Empty => Ok(()),
|
Self::Empty => Ok(()),
|
||||||
PipelineData::Value(Value::Error { error, .. }, ..) => Err(*error),
|
Self::Value(Value::Error { error, .. }, ..) => Err(*error),
|
||||||
PipelineData::Value(..) => Ok(()),
|
Self::Value(..) => Ok(()),
|
||||||
PipelineData::ListStream(stream, ..) => stream.drain(),
|
Self::ListStream(stream, ..) => stream.drain(),
|
||||||
PipelineData::ByteStream(stream, ..) => stream.drain(),
|
Self::ByteStream(stream, ..) => stream.drain(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue