mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 06:55:36 +02:00
Fix try
not working with let
, etc. (#13885)
# Description Partialy addresses #13868. `try` does not catch non-zero exit code errors from the last command in a pipeline if the result is assigned to a variable using `let` (or `mut`). This was fixed by adding a new `OutDest::Value` case. This is used when the pipeline is in a "value" position. I.e., it will be collected into a value. This ended up replacing most of the usages of `OutDest::Capture`. So, this PR also renames `OutDest::Capture` to `OutDest::PipeSeparate` to better fit the few remaining use cases for it. # User-Facing Changes Bug fix. # Tests + Formatting Added two tests.
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
engine::{
|
||||
ArgumentStack, EngineState, ErrorHandlerStack, Redirection, StackCallArgGuard,
|
||||
StackCaptureGuard, StackIoGuard, StackOutDest, DEFAULT_OVERLAY_NAME,
|
||||
StackCollectValueGuard, StackIoGuard, StackOutDest, DEFAULT_OVERLAY_NAME,
|
||||
},
|
||||
Config, OutDest, ShellError, Span, Value, VarId, ENV_VARIABLE_ID, NU_VARIABLE_ID,
|
||||
};
|
||||
@ -68,7 +68,7 @@ impl Stack {
|
||||
/// stdout and stderr will be set to [`OutDest::Inherit`]. So, if the last command is an external command,
|
||||
/// then its output will be forwarded to the terminal/stdio streams.
|
||||
///
|
||||
/// Use [`Stack::capture`] afterwards if you need to evaluate an expression to a [`Value`]
|
||||
/// Use [`Stack::collect_value`] afterwards if you need to evaluate an expression to a [`Value`]
|
||||
/// (as opposed to a [`PipelineData`](crate::PipelineData)).
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@ -299,7 +299,8 @@ impl Stack {
|
||||
}
|
||||
|
||||
pub fn captures_to_stack(&self, captures: Vec<(VarId, Value)>) -> Stack {
|
||||
self.captures_to_stack_preserve_out_dest(captures).capture()
|
||||
self.captures_to_stack_preserve_out_dest(captures)
|
||||
.collect_value()
|
||||
}
|
||||
|
||||
pub fn captures_to_stack_preserve_out_dest(&self, captures: Vec<(VarId, Value)>) -> Stack {
|
||||
@ -589,11 +590,11 @@ impl Stack {
|
||||
self.out_dest.pipe_stderr.as_ref()
|
||||
}
|
||||
|
||||
/// Temporarily set the pipe stdout redirection to [`OutDest::Capture`].
|
||||
/// Temporarily set the pipe stdout redirection to [`OutDest::Value`].
|
||||
///
|
||||
/// This is used before evaluating an expression into a `Value`.
|
||||
pub fn start_capture(&mut self) -> StackCaptureGuard {
|
||||
StackCaptureGuard::new(self)
|
||||
pub fn start_collect_value(&mut self) -> StackCollectValueGuard {
|
||||
StackCollectValueGuard::new(self)
|
||||
}
|
||||
|
||||
/// Temporarily use the output redirections in the parent scope.
|
||||
@ -612,14 +613,14 @@ impl Stack {
|
||||
StackIoGuard::new(self, stdout, stderr)
|
||||
}
|
||||
|
||||
/// Mark stdout for the last command as [`OutDest::Capture`].
|
||||
/// Mark stdout for the last command as [`OutDest::Value`].
|
||||
///
|
||||
/// This will irreversibly alter the output redirections, and so it only makes sense to use this on an owned `Stack`
|
||||
/// (which is why this function does not take `&mut self`).
|
||||
///
|
||||
/// See [`Stack::start_capture`] which can temporarily set stdout as [`OutDest::Capture`] for a mutable `Stack` reference.
|
||||
pub fn capture(mut self) -> Self {
|
||||
self.out_dest.pipe_stdout = Some(OutDest::Capture);
|
||||
/// See [`Stack::start_collect_value`] which can temporarily set stdout as [`OutDest::Value`] for a mutable `Stack` reference.
|
||||
pub fn collect_value(mut self) -> Self {
|
||||
self.out_dest.pipe_stdout = Some(OutDest::Value);
|
||||
self.out_dest.pipe_stderr = None;
|
||||
self
|
||||
}
|
||||
|
@ -184,15 +184,15 @@ impl Drop for StackIoGuard<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StackCaptureGuard<'a> {
|
||||
pub struct StackCollectValueGuard<'a> {
|
||||
stack: &'a mut Stack,
|
||||
old_pipe_stdout: Option<OutDest>,
|
||||
old_pipe_stderr: Option<OutDest>,
|
||||
}
|
||||
|
||||
impl<'a> StackCaptureGuard<'a> {
|
||||
impl<'a> StackCollectValueGuard<'a> {
|
||||
pub(crate) fn new(stack: &'a mut Stack) -> Self {
|
||||
let old_pipe_stdout = mem::replace(&mut stack.out_dest.pipe_stdout, Some(OutDest::Capture));
|
||||
let old_pipe_stdout = mem::replace(&mut stack.out_dest.pipe_stdout, Some(OutDest::Value));
|
||||
let old_pipe_stderr = stack.out_dest.pipe_stderr.take();
|
||||
Self {
|
||||
stack,
|
||||
@ -202,7 +202,7 @@ impl<'a> StackCaptureGuard<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for StackCaptureGuard<'a> {
|
||||
impl<'a> Deref for StackCollectValueGuard<'a> {
|
||||
type Target = Stack;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@ -210,13 +210,13 @@ impl<'a> Deref for StackCaptureGuard<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DerefMut for StackCaptureGuard<'a> {
|
||||
impl<'a> DerefMut for StackCollectValueGuard<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.stack
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for StackCaptureGuard<'_> {
|
||||
impl Drop for StackCollectValueGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.out_dest.pipe_stdout = self.old_pipe_stdout.take();
|
||||
self.out_dest.pipe_stderr = self.old_pipe_stderr.take();
|
||||
@ -233,7 +233,7 @@ pub struct StackCallArgGuard<'a> {
|
||||
|
||||
impl<'a> StackCallArgGuard<'a> {
|
||||
pub(crate) fn new(stack: &'a mut Stack) -> Self {
|
||||
let old_pipe_stdout = mem::replace(&mut stack.out_dest.pipe_stdout, Some(OutDest::Capture));
|
||||
let old_pipe_stdout = mem::replace(&mut stack.out_dest.pipe_stdout, Some(OutDest::Value));
|
||||
let old_pipe_stderr = stack.out_dest.pipe_stderr.take();
|
||||
|
||||
let old_stdout = stack
|
||||
|
@ -1,8 +1,6 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::{ast::Pattern, engine::EngineState, DeclId, VarId};
|
||||
|
||||
use super::{DataSlice, Instruction, IrBlock, Literal, RedirectMode};
|
||||
use crate::{ast::Pattern, engine::EngineState, DeclId, VarId};
|
||||
use std::fmt::{self};
|
||||
|
||||
pub struct FmtIrBlock<'a> {
|
||||
pub(super) engine_state: &'a EngineState,
|
||||
@ -310,7 +308,8 @@ impl fmt::Display for RedirectMode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
RedirectMode::Pipe => write!(f, "pipe"),
|
||||
RedirectMode::Capture => write!(f, "capture"),
|
||||
RedirectMode::PipeSeparate => write!(f, "pipe separate"),
|
||||
RedirectMode::Value => write!(f, "value"),
|
||||
RedirectMode::Null => write!(f, "null"),
|
||||
RedirectMode::Inherit => write!(f, "inherit"),
|
||||
RedirectMode::File { file_num } => write!(f, "file({file_num})"),
|
||||
|
@ -446,7 +446,8 @@ pub enum Literal {
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub enum RedirectMode {
|
||||
Pipe,
|
||||
Capture,
|
||||
PipeSeparate,
|
||||
Value,
|
||||
Null,
|
||||
Inherit,
|
||||
/// Use the given numbered file.
|
||||
|
@ -580,8 +580,8 @@ impl ByteStream {
|
||||
copy_with_signals(file, dest, span, signals)?;
|
||||
}
|
||||
ByteStreamSource::Child(mut child) => {
|
||||
// All `OutDest`s except `OutDest::Capture` will cause `stderr` to be `None`.
|
||||
// Only `save`, `tee`, and `complete` set the stderr `OutDest` to `OutDest::Capture`,
|
||||
// All `OutDest`s except `OutDest::PipeSeparate` will cause `stderr` to be `None`.
|
||||
// Only `save`, `tee`, and `complete` set the stderr `OutDest` to `OutDest::PipeSeparate`,
|
||||
// and those commands have proper simultaneous handling of stdout and stderr.
|
||||
debug_assert!(child.stderr.is_none(), "stderr should not exist");
|
||||
|
||||
@ -614,7 +614,7 @@ impl ByteStream {
|
||||
write_to_out_dest(read, stdout, true, span, signals)?;
|
||||
}
|
||||
ByteStreamSource::File(file) => match stdout {
|
||||
OutDest::Pipe | OutDest::Capture | OutDest::Null => {}
|
||||
OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value | OutDest::Null => {}
|
||||
OutDest::Inherit => {
|
||||
copy_with_signals(file, io::stdout(), span, signals)?;
|
||||
}
|
||||
@ -970,7 +970,7 @@ fn write_to_out_dest(
|
||||
signals: &Signals,
|
||||
) -> Result<(), ShellError> {
|
||||
match stream {
|
||||
OutDest::Pipe | OutDest::Capture => return Ok(()),
|
||||
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),
|
||||
|
@ -10,13 +10,17 @@ pub enum OutDest {
|
||||
/// If stdout and stderr are both set to `Pipe`,
|
||||
/// then they will combined into the `stdout` of [`ChildProcess`](crate::process::ChildProcess).
|
||||
Pipe,
|
||||
/// Capture output to later be collected into a [`Value`](crate::Value), `Vec`, or used in some other way.
|
||||
/// Redirect the stdout and/or stderr of one command as the input for the next command in the pipeline.
|
||||
///
|
||||
/// The output stream(s) will be available in the `stdout` or `stderr` of [`ChildProcess`](crate::process::ChildProcess).
|
||||
///
|
||||
/// This is similar to `Pipe` but will never combine stdout and stderr
|
||||
/// or place an external command's stderr into `stdout` of [`ChildProcess`](crate::process::ChildProcess).
|
||||
Capture,
|
||||
PipeSeparate,
|
||||
/// Signifies the result of the pipeline will be immediately collected into a value after this command.
|
||||
///
|
||||
/// So, it is fine to collect the stream ahead of time in the current command.
|
||||
Value,
|
||||
/// Ignore output.
|
||||
///
|
||||
/// This will forward output to the null device for the platform.
|
||||
@ -46,7 +50,7 @@ impl TryFrom<&OutDest> for Stdio {
|
||||
|
||||
fn try_from(out_dest: &OutDest) -> Result<Self, Self::Error> {
|
||||
match out_dest {
|
||||
OutDest::Pipe | OutDest::Capture => Ok(Self::piped()),
|
||||
OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value => Ok(Self::piped()),
|
||||
OutDest::Null => Ok(Self::null()),
|
||||
OutDest::Inherit => Ok(Self::inherit()),
|
||||
OutDest::File(file) => Ok(file.try_clone()?.into()),
|
||||
|
@ -167,8 +167,8 @@ impl PipelineData {
|
||||
|
||||
/// Writes all values or redirects all output to the current [`OutDest`]s in `stack`.
|
||||
///
|
||||
/// For [`OutDest::Pipe`] and [`OutDest::Capture`], this will return the `PipelineData` as is
|
||||
/// without consuming input and without writing anything.
|
||||
/// For [`OutDest::Pipe`] and [`OutDest::PipeSeparate`], this will return the `PipelineData` as
|
||||
/// is without consuming input and without writing anything.
|
||||
///
|
||||
/// For the other [`OutDest`]s, the given `PipelineData` will be completely consumed
|
||||
/// and `PipelineData::Empty` will be returned (assuming no errors).
|
||||
@ -178,11 +178,18 @@ impl PipelineData {
|
||||
stack: &mut Stack,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
match (self, stack.stdout()) {
|
||||
(data, OutDest::Pipe | OutDest::Capture) => return Ok(data),
|
||||
(PipelineData::Empty, ..) => {}
|
||||
(data, OutDest::Pipe | OutDest::PipeSeparate) => return Ok(data),
|
||||
(data, OutDest::Value) => {
|
||||
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) => {
|
||||
stream.write_to_out_dests(stdout, stack.stderr())?;
|
||||
}
|
||||
(PipelineData::Empty, ..) => {}
|
||||
(PipelineData::Value(..), OutDest::Null) => {}
|
||||
(PipelineData::ListStream(stream, ..), OutDest::Null) => {
|
||||
// we need to drain the stream in case there are external commands in the pipeline
|
||||
|
Reference in New Issue
Block a user