fix redirection, all tests passing
This commit is contained in:
parent
7958cda54e
commit
0a399011e3
|
@ -1,5 +1,5 @@
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{Block, Pipeline, PipelineRedirection, RedirectionSource},
|
ast::{Block, Pipeline, PipelineRedirection, RedirectionSource, RedirectionTarget},
|
||||||
engine::StateWorkingSet,
|
engine::StateWorkingSet,
|
||||||
ir::{Instruction, IrBlock, RedirectMode},
|
ir::{Instruction, IrBlock, RedirectMode},
|
||||||
CompileError, IntoSpanned, RegId, Span,
|
CompileError, IntoSpanned, RegId, Span,
|
||||||
|
@ -116,13 +116,13 @@ fn compile_pipeline(
|
||||||
let mut modes = redirect_modes_of_expression(working_set, &next_element.expr, span)?;
|
let mut modes = redirect_modes_of_expression(working_set, &next_element.expr, span)?;
|
||||||
|
|
||||||
// If there's a next element with no inherent redirection we always pipe out *unless*
|
// If there's a next element with no inherent redirection we always pipe out *unless*
|
||||||
// this is a single redirection to stderr (e>|)
|
// this is a single redirection of stderr to pipe (e>|)
|
||||||
if modes.out.is_none()
|
if modes.out.is_none()
|
||||||
&& !matches!(
|
&& !matches!(
|
||||||
element.redirection,
|
element.redirection,
|
||||||
Some(PipelineRedirection::Single {
|
Some(PipelineRedirection::Single {
|
||||||
source: RedirectionSource::Stderr,
|
source: RedirectionSource::Stderr,
|
||||||
..
|
target: RedirectionTarget::Pipe { .. }
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
@ -139,7 +139,7 @@ fn compile_pipeline(
|
||||||
|
|
||||||
let spec_redirect_modes = match &element.redirection {
|
let spec_redirect_modes = match &element.redirection {
|
||||||
Some(PipelineRedirection::Single { source, target }) => {
|
Some(PipelineRedirection::Single { source, target }) => {
|
||||||
let mode = redirection_target_to_mode(working_set, builder, target, false)?;
|
let mode = redirection_target_to_mode(working_set, builder, target)?;
|
||||||
match source {
|
match source {
|
||||||
RedirectionSource::Stdout => RedirectModes {
|
RedirectionSource::Stdout => RedirectModes {
|
||||||
out: Some(mode),
|
out: Some(mode),
|
||||||
|
@ -156,8 +156,19 @@ fn compile_pipeline(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(PipelineRedirection::Separate { out, err }) => {
|
Some(PipelineRedirection::Separate { out, err }) => {
|
||||||
let out = redirection_target_to_mode(working_set, builder, out, true)?;
|
// In this case, out and err must not both be Pipe
|
||||||
let err = redirection_target_to_mode(working_set, builder, err, true)?;
|
assert!(
|
||||||
|
!matches!(
|
||||||
|
(out, err),
|
||||||
|
(
|
||||||
|
RedirectionTarget::Pipe { .. },
|
||||||
|
RedirectionTarget::Pipe { .. }
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"for Separate redirection, out and err targets must not both be Pipe"
|
||||||
|
);
|
||||||
|
let out = redirection_target_to_mode(working_set, builder, out)?;
|
||||||
|
let err = redirection_target_to_mode(working_set, builder, err)?;
|
||||||
RedirectModes {
|
RedirectModes {
|
||||||
out: Some(out),
|
out: Some(out),
|
||||||
err: Some(err),
|
err: Some(err),
|
||||||
|
|
|
@ -40,7 +40,6 @@ pub(crate) fn redirection_target_to_mode(
|
||||||
working_set: &StateWorkingSet,
|
working_set: &StateWorkingSet,
|
||||||
builder: &mut BlockBuilder,
|
builder: &mut BlockBuilder,
|
||||||
target: &RedirectionTarget,
|
target: &RedirectionTarget,
|
||||||
separate: bool,
|
|
||||||
) -> Result<Spanned<RedirectMode>, CompileError> {
|
) -> Result<Spanned<RedirectMode>, CompileError> {
|
||||||
Ok(match target {
|
Ok(match target {
|
||||||
RedirectionTarget::File {
|
RedirectionTarget::File {
|
||||||
|
@ -68,12 +67,7 @@ pub(crate) fn redirection_target_to_mode(
|
||||||
)?;
|
)?;
|
||||||
RedirectMode::File { file_num }.into_spanned(*redir_span)
|
RedirectMode::File { file_num }.into_spanned(*redir_span)
|
||||||
}
|
}
|
||||||
RedirectionTarget::Pipe { span } => (if separate {
|
RedirectionTarget::Pipe { span } => RedirectMode::Pipe.into_spanned(*span),
|
||||||
RedirectMode::Capture
|
|
||||||
} else {
|
|
||||||
RedirectMode::Pipe
|
|
||||||
})
|
|
||||||
.into_spanned(*span),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,14 +100,24 @@ pub(crate) fn finish_redirection(
|
||||||
item: RedirectMode::File { file_num },
|
item: RedirectMode::File { file_num },
|
||||||
span,
|
span,
|
||||||
}) => {
|
}) => {
|
||||||
builder.push(
|
// If out is a file and err is a pipe, we must not consume the expression result -
|
||||||
Instruction::WriteFile {
|
// that is actually the err, in that case.
|
||||||
file_num,
|
if !matches!(
|
||||||
src: out_reg,
|
modes.err,
|
||||||
}
|
Some(Spanned {
|
||||||
.into_spanned(span),
|
item: RedirectMode::Pipe { .. },
|
||||||
)?;
|
..
|
||||||
builder.load_empty(out_reg)?;
|
})
|
||||||
|
) {
|
||||||
|
builder.push(
|
||||||
|
Instruction::WriteFile {
|
||||||
|
file_num,
|
||||||
|
src: out_reg,
|
||||||
|
}
|
||||||
|
.into_spanned(span),
|
||||||
|
)?;
|
||||||
|
builder.load_empty(out_reg)?;
|
||||||
|
}
|
||||||
builder.push(Instruction::CloseFile { file_num }.into_spanned(span))?;
|
builder.push(Instruction::CloseFile { file_num }.into_spanned(span))?;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
|
|
|
@ -308,19 +308,7 @@ fn eval_instruction<D: DebugContext>(
|
||||||
}
|
}
|
||||||
Instruction::Drain { src } => {
|
Instruction::Drain { src } => {
|
||||||
let data = ctx.take_reg(*src);
|
let data = ctx.take_reg(*src);
|
||||||
if let Some(exit_status) = data.drain()? {
|
drain(ctx, data)
|
||||||
ctx.stack.add_env_var(
|
|
||||||
"LAST_EXIT_CODE".into(),
|
|
||||||
Value::int(exit_status.code() as i64, *span),
|
|
||||||
);
|
|
||||||
if exit_status.code() == 0 {
|
|
||||||
Ok(Continue)
|
|
||||||
} else {
|
|
||||||
Ok(ExitCode(exit_status.code()))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Ok(Continue)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Instruction::LoadVariable { dst, var_id } => {
|
Instruction::LoadVariable { dst, var_id } => {
|
||||||
let value = get_var(ctx, *var_id, *span)?;
|
let value = get_var(ctx, *var_id, *span)?;
|
||||||
|
@ -463,11 +451,14 @@ 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 mut stack = ctx
|
let result = {
|
||||||
.stack
|
let mut stack = ctx
|
||||||
.push_redirection(Some(Redirection::File(file)), None);
|
.stack
|
||||||
src.write_to_out_dests(ctx.engine_state, &mut stack)?;
|
.push_redirection(Some(Redirection::File(file)), None);
|
||||||
Ok(Continue)
|
src.write_to_out_dests(ctx.engine_state, &mut stack)?
|
||||||
|
};
|
||||||
|
// Abort execution if there's an exit code from a failed external
|
||||||
|
drain(ctx, result)
|
||||||
}
|
}
|
||||||
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() {
|
||||||
|
@ -1198,6 +1189,25 @@ fn collect(data: PipelineData, fallback_span: Span) -> Result<PipelineData, Shel
|
||||||
Ok(PipelineData::Value(value, metadata))
|
Ok(PipelineData::Value(value, metadata))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper for drain behavior. Returns `Ok(ExitCode)` on failed external.
|
||||||
|
fn drain(ctx: &mut EvalContext<'_>, data: PipelineData) -> Result<InstructionResult, ShellError> {
|
||||||
|
use self::InstructionResult::*;
|
||||||
|
let span = data.span().unwrap_or(Span::unknown());
|
||||||
|
if let Some(exit_status) = data.drain()? {
|
||||||
|
ctx.stack.add_env_var(
|
||||||
|
"LAST_EXIT_CODE".into(),
|
||||||
|
Value::int(exit_status.code() as i64, span),
|
||||||
|
);
|
||||||
|
if exit_status.code() == 0 {
|
||||||
|
Ok(Continue)
|
||||||
|
} else {
|
||||||
|
Ok(ExitCode(exit_status.code()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(Continue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum RedirectionStream {
|
enum RedirectionStream {
|
||||||
Out,
|
Out,
|
||||||
Err,
|
Err,
|
||||||
|
|
|
@ -132,7 +132,8 @@ impl PipelineData {
|
||||||
/// without consuming input and without writing anything.
|
/// without consuming input and without writing anything.
|
||||||
///
|
///
|
||||||
/// For the other [`OutDest`]s, the given `PipelineData` will be completely consumed
|
/// For the other [`OutDest`]s, the given `PipelineData` will be completely consumed
|
||||||
/// and `PipelineData::Empty` will be returned.
|
/// and `PipelineData::Empty` will be returned, unless the data is from an external stream,
|
||||||
|
/// in which case an external stream containing only that exit code will be returned.
|
||||||
pub fn write_to_out_dests(
|
pub fn write_to_out_dests(
|
||||||
self,
|
self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
|
@ -140,7 +141,11 @@ impl PipelineData {
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
match (self, stack.stdout()) {
|
match (self, stack.stdout()) {
|
||||||
(PipelineData::ByteStream(stream, ..), stdout) => {
|
(PipelineData::ByteStream(stream, ..), stdout) => {
|
||||||
stream.write_to_out_dests(stdout, stack.stderr())?;
|
if let Some(exit_status) = stream.write_to_out_dests(stdout, stack.stderr())? {
|
||||||
|
return Ok(PipelineData::new_external_stream_with_only_exit_code(
|
||||||
|
exit_status.code(),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(data, OutDest::Pipe | OutDest::Capture) => return Ok(data),
|
(data, OutDest::Pipe | OutDest::Capture) => return Ok(data),
|
||||||
(PipelineData::Empty, ..) => {}
|
(PipelineData::Empty, ..) => {}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user