Refactor message printing in rm (#12799)

# Description
Changes the iterator in `rm` to be an iterator over
`Result<Option<String>, ShellError>` (an optional message or error)
instead of an iterator over `Value`. Then, the iterator is consumed and
each message is printed. This allows the
`PipelineData::print_not_formatted` method to be removed.
This commit is contained in:
Ian Manske 2024-05-09 05:36:47 +00:00 committed by GitHub
parent 948b299e65
commit 3b3f48202c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 116 additions and 146 deletions

View file

@ -3,7 +3,7 @@ use super::util::{get_rest_for_glob_pattern, try_interaction};
use nu_engine::{command_prelude::*, env::current_dir}; use nu_engine::{command_prelude::*, env::current_dir};
use nu_glob::MatchOptions; use nu_glob::MatchOptions;
use nu_path::expand_path_with; use nu_path::expand_path_with;
use nu_protocol::NuGlob; use nu_protocol::{report_error_new, NuGlob};
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::prelude::FileTypeExt; use std::os::unix::prelude::FileTypeExt;
use std::{ use std::{
@ -118,8 +118,6 @@ fn rm(
let interactive = call.has_flag(engine_state, stack, "interactive")?; let interactive = call.has_flag(engine_state, stack, "interactive")?;
let interactive_once = call.has_flag(engine_state, stack, "interactive-once")? && !interactive; let interactive_once = call.has_flag(engine_state, stack, "interactive-once")? && !interactive;
let ctrlc = engine_state.ctrlc.clone();
let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?; let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
if paths.is_empty() { if paths.is_empty() {
@ -341,132 +339,130 @@ fn rm(
} }
} }
all_targets let iter = all_targets.into_iter().map(move |(f, span)| {
.into_iter() let is_empty = || match f.read_dir() {
.map(move |(f, span)| { Ok(mut p) => p.next().is_none(),
let is_empty = || match f.read_dir() { Err(_) => false,
Ok(mut p) => p.next().is_none(), };
Err(_) => false,
};
if let Ok(metadata) = f.symlink_metadata() { if let Ok(metadata) = f.symlink_metadata() {
#[cfg(unix)] #[cfg(unix)]
let is_socket = metadata.file_type().is_socket(); let is_socket = metadata.file_type().is_socket();
#[cfg(unix)] #[cfg(unix)]
let is_fifo = metadata.file_type().is_fifo(); let is_fifo = metadata.file_type().is_fifo();
#[cfg(not(unix))] #[cfg(not(unix))]
let is_socket = false; let is_socket = false;
#[cfg(not(unix))] #[cfg(not(unix))]
let is_fifo = false; let is_fifo = false;
if metadata.is_file() if metadata.is_file()
|| metadata.file_type().is_symlink() || metadata.file_type().is_symlink()
|| recursive || recursive
|| is_socket || is_socket
|| is_fifo || is_fifo
|| is_empty() || is_empty()
{ {
let (interaction, confirmed) = try_interaction( let (interaction, confirmed) = try_interaction(
interactive, interactive,
format!("rm: remove '{}'? ", f.to_string_lossy()), format!("rm: remove '{}'? ", f.to_string_lossy()),
); );
let result = if let Err(e) = interaction { let result = if let Err(e) = interaction {
let e = Error::new(ErrorKind::Other, &*e.to_string()); Err(Error::new(ErrorKind::Other, &*e.to_string()))
Err(e) } else if interactive && !confirmed {
} else if interactive && !confirmed { Ok(())
Ok(()) } else if TRASH_SUPPORTED && (trash || (rm_always_trash && !permanent)) {
} else if TRASH_SUPPORTED && (trash || (rm_always_trash && !permanent)) { #[cfg(all(
#[cfg(all( feature = "trash-support",
feature = "trash-support", not(any(target_os = "android", target_os = "ios"))
not(any(target_os = "android", target_os = "ios")) ))]
))] {
{ trash::delete(&f).map_err(|e: trash::Error| {
trash::delete(&f).map_err(|e: trash::Error| { Error::new(ErrorKind::Other, format!("{e:?}\nTry '--permanent' flag"))
Error::new( })
ErrorKind::Other,
format!("{e:?}\nTry '--permanent' flag"),
)
})
}
// Should not be reachable since we error earlier if
// these options are given on an unsupported platform
#[cfg(any(
not(feature = "trash-support"),
target_os = "android",
target_os = "ios"
))]
{
unreachable!()
}
} else if metadata.is_symlink() {
// In Windows, symlink pointing to a directory can be removed using
// std::fs::remove_dir instead of std::fs::remove_file.
#[cfg(windows)]
{
f.metadata().and_then(|metadata| {
if metadata.is_dir() {
std::fs::remove_dir(&f)
} else {
std::fs::remove_file(&f)
}
})
}
#[cfg(not(windows))]
std::fs::remove_file(&f)
} else if metadata.is_file() || is_socket || is_fifo {
std::fs::remove_file(&f)
} else {
std::fs::remove_dir_all(&f)
};
if let Err(e) = result {
let msg = format!("Could not delete {:}: {e:}", f.to_string_lossy());
Value::error(ShellError::RemoveNotPossible { msg, span }, span)
} else if verbose {
let msg = if interactive && !confirmed {
"not deleted"
} else {
"deleted"
};
let val = format!("{} {:}", msg, f.to_string_lossy());
Value::string(val, span)
} else {
Value::nothing(span)
} }
// Should not be reachable since we error earlier if
// these options are given on an unsupported platform
#[cfg(any(
not(feature = "trash-support"),
target_os = "android",
target_os = "ios"
))]
{
unreachable!()
}
} else if metadata.is_symlink() {
// In Windows, symlink pointing to a directory can be removed using
// std::fs::remove_dir instead of std::fs::remove_file.
#[cfg(windows)]
{
f.metadata().and_then(|metadata| {
if metadata.is_dir() {
std::fs::remove_dir(&f)
} else {
std::fs::remove_file(&f)
}
})
}
#[cfg(not(windows))]
std::fs::remove_file(&f)
} else if metadata.is_file() || is_socket || is_fifo {
std::fs::remove_file(&f)
} else { } else {
let error = format!("Cannot remove {:}. try --recursive", f.to_string_lossy()); std::fs::remove_dir_all(&f)
Value::error( };
ShellError::GenericError {
error, if let Err(e) = result {
msg: "cannot remove non-empty directory".into(), let msg = format!("Could not delete {:}: {e:}", f.to_string_lossy());
span: Some(span), Err(ShellError::RemoveNotPossible { msg, span })
help: None, } else if verbose {
inner: vec![], let msg = if interactive && !confirmed {
}, "not deleted"
span, } else {
) "deleted"
};
Ok(Some(format!("{} {:}", msg, f.to_string_lossy())))
} else {
Ok(None)
} }
} else { } else {
let error = format!("no such file or directory: {:}", f.to_string_lossy()); let error = format!("Cannot remove {:}. try --recursive", f.to_string_lossy());
Value::error( Err(ShellError::GenericError {
ShellError::GenericError { error,
error, msg: "cannot remove non-empty directory".into(),
msg: "no such file or directory".into(), span: Some(span),
span: Some(span), help: None,
help: None, inner: vec![],
inner: vec![], })
},
span,
)
} }
}) } else {
.filter(|x| !matches!(x.get_type(), Type::Nothing)) let error = format!("no such file or directory: {:}", f.to_string_lossy());
.into_pipeline_data(span, ctrlc) Err(ShellError::GenericError {
.print_not_formatted(engine_state, false, true)?; error,
msg: "no such file or directory".into(),
span: Some(span),
help: None,
inner: vec![],
})
}
});
for result in iter {
if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) {
return Err(ShellError::InterruptedByUser {
span: Some(call.head),
});
}
match result {
Ok(None) => {}
Ok(Some(msg)) => eprintln!("{msg}"),
Err(err) => report_error_new(engine_state, &err),
}
}
Ok(PipelineData::empty()) Ok(PipelineData::empty())
} }

View file

@ -877,32 +877,6 @@ impl PipelineData {
Ok(0) Ok(0)
} }
/// Consume and print self data immediately.
///
/// Unlike [`.print()`] does not call `table` to format data and just prints it
/// one element on a line
/// * `no_newline` controls if we need to attach newline character to output.
/// * `to_stderr` controls if data is output to stderr, when the value is false, the data is output to stdout.
pub fn print_not_formatted(
self,
engine_state: &EngineState,
no_newline: bool,
to_stderr: bool,
) -> Result<i64, ShellError> {
if let PipelineData::ExternalStream {
stdout: stream,
stderr: stderr_stream,
exit_code,
..
} = self
{
print_if_stream(stream, stderr_stream, to_stderr, exit_code)
} else {
let config = engine_state.get_config();
self.write_all_and_flush(engine_state, config, no_newline, to_stderr)
}
}
fn write_all_and_flush( fn write_all_and_flush(
self, self,
engine_state: &EngineState, engine_state: &EngineState,