2021-10-10 04:13:15 +00:00
|
|
|
use std::env::current_dir;
|
2021-10-11 18:58:10 +00:00
|
|
|
#[cfg(unix)]
|
2021-10-10 04:13:15 +00:00
|
|
|
use std::os::unix::prelude::FileTypeExt;
|
2021-10-10 04:17:08 +00:00
|
|
|
use std::path::PathBuf;
|
2021-10-10 04:13:15 +00:00
|
|
|
|
2021-10-14 17:54:51 +00:00
|
|
|
use super::interactive_helper::get_confirmation;
|
|
|
|
|
2021-10-10 04:13:15 +00:00
|
|
|
use nu_engine::CallExt;
|
2021-10-10 04:17:08 +00:00
|
|
|
use nu_protocol::ast::Call;
|
2021-10-10 04:13:15 +00:00
|
|
|
use nu_protocol::engine::{Command, EvaluationContext};
|
|
|
|
use nu_protocol::{ShellError, Signature, SyntaxShape, Value, ValueStream};
|
|
|
|
|
|
|
|
pub struct Rm;
|
|
|
|
|
|
|
|
// Where self.0 is the unexpanded target's positional index (i.e. call.positional[self.0].span)
|
|
|
|
struct Target(usize, PathBuf);
|
|
|
|
|
|
|
|
struct RmArgs {
|
|
|
|
targets: Vec<Target>,
|
|
|
|
recursive: bool,
|
|
|
|
trash: bool,
|
|
|
|
permanent: bool,
|
|
|
|
force: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Command for Rm {
|
|
|
|
fn name(&self) -> &str {
|
|
|
|
"rm"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn usage(&self) -> &str {
|
|
|
|
"Remove file(s)."
|
|
|
|
}
|
|
|
|
|
|
|
|
fn signature(&self) -> Signature {
|
|
|
|
Signature::build("rm")
|
|
|
|
.switch(
|
|
|
|
"trash",
|
|
|
|
"use the platform's recycle bin instead of permanently deleting",
|
|
|
|
Some('t'),
|
|
|
|
)
|
|
|
|
.switch(
|
|
|
|
"permanent",
|
|
|
|
"don't use recycle bin, delete permanently",
|
|
|
|
Some('p'),
|
|
|
|
)
|
|
|
|
.switch("recursive", "delete subdirectories recursively", Some('r'))
|
|
|
|
.switch("force", "suppress error when no file", Some('f'))
|
2021-10-13 22:29:08 +00:00
|
|
|
.switch("interactive", "ask user to confirm action", Some('i'))
|
2021-10-10 04:13:15 +00:00
|
|
|
.rest(
|
|
|
|
"rest",
|
|
|
|
SyntaxShape::GlobPattern,
|
|
|
|
"the file path(s) to remove",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run(
|
|
|
|
&self,
|
|
|
|
context: &EvaluationContext,
|
|
|
|
call: &Call,
|
|
|
|
_input: Value,
|
|
|
|
) -> Result<Value, ShellError> {
|
|
|
|
rm(context, call)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn rm(context: &EvaluationContext, call: &Call) -> Result<Value, ShellError> {
|
|
|
|
let trash = call.has_flag("trash");
|
|
|
|
let permanent = call.has_flag("permanent");
|
2021-10-13 22:29:08 +00:00
|
|
|
let interactive = call.has_flag("interactive");
|
2021-10-10 04:13:15 +00:00
|
|
|
|
|
|
|
if trash && permanent {
|
|
|
|
return Err(ShellError::IncompatibleParametersSingle(
|
|
|
|
"Can't use \"--trash\" with \"--permanent\"".to_string(),
|
|
|
|
call.head,
|
|
|
|
));
|
|
|
|
|
|
|
|
// let trash_span = call.get_flag_expr("trash").unwrap().span;
|
|
|
|
// let perm_span = call.get_flag_expr("permanent").unwrap().span;
|
|
|
|
|
|
|
|
// let left_message = "cannot use".to_string();
|
|
|
|
// let right_message = "with".to_string();
|
|
|
|
// let (left_span, right_span) = match trash_span.start < perm_span.start {
|
|
|
|
// true => (trash_span, perm_span),
|
|
|
|
// false => (perm_span, trash_span),
|
|
|
|
// };
|
|
|
|
|
|
|
|
// return Err(ShellError::IncompatibleParameters {
|
|
|
|
// left_message,
|
|
|
|
// left_span,
|
|
|
|
// right_message,
|
|
|
|
// right_span,
|
|
|
|
// });
|
|
|
|
}
|
|
|
|
|
|
|
|
let current_path = current_dir()?;
|
|
|
|
let mut paths = call
|
|
|
|
.rest::<String>(context, 0)?
|
|
|
|
.into_iter()
|
|
|
|
.map(|path| current_path.join(path))
|
|
|
|
.peekable();
|
|
|
|
|
|
|
|
if paths.peek().is_none() {
|
|
|
|
return Err(ShellError::FileNotFound(call.positional[0].span));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expand and flatten files
|
|
|
|
let resolve_path = |i: usize, path: PathBuf| {
|
|
|
|
glob::glob(&path.to_string_lossy()).map_or_else(
|
|
|
|
|_| Vec::new(),
|
|
|
|
|path_iter| path_iter.flatten().map(|f| Target(i, f)).collect(),
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut targets: Vec<Target> = vec![];
|
|
|
|
for (i, path) in paths.enumerate() {
|
|
|
|
let mut paths: Vec<Target> = resolve_path(i, path);
|
|
|
|
|
|
|
|
if paths.is_empty() {
|
|
|
|
return Err(ShellError::FileNotFound(call.positional[i].span));
|
|
|
|
}
|
|
|
|
|
|
|
|
targets.append(paths.as_mut());
|
|
|
|
}
|
|
|
|
|
|
|
|
let recursive = call.has_flag("recursive");
|
|
|
|
let force = call.has_flag("force");
|
|
|
|
|
2021-10-13 22:29:08 +00:00
|
|
|
if interactive && !force {
|
2021-10-14 17:54:51 +00:00
|
|
|
let mut remove_index: Vec<usize> = vec![];
|
|
|
|
for (index, file) in targets.iter().enumerate() {
|
|
|
|
let prompt: String = format!(
|
|
|
|
"Are you sure that you what to delete {}?",
|
2021-10-13 22:29:08 +00:00
|
|
|
file.1.file_name().unwrap().to_str().unwrap()
|
|
|
|
);
|
2021-10-14 17:54:51 +00:00
|
|
|
|
|
|
|
let input = get_confirmation(prompt)?;
|
|
|
|
|
|
|
|
if !input {
|
|
|
|
remove_index.push(index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for index in remove_index {
|
|
|
|
targets.remove(index);
|
|
|
|
}
|
|
|
|
|
|
|
|
if targets.len() == 0 {
|
|
|
|
return Err(ShellError::NoFileToBeRemoved());
|
2021-10-13 22:29:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-10 04:13:15 +00:00
|
|
|
let args = RmArgs {
|
|
|
|
targets,
|
|
|
|
recursive,
|
|
|
|
trash,
|
|
|
|
permanent,
|
|
|
|
force,
|
|
|
|
};
|
|
|
|
let response = rm_helper(call, args);
|
|
|
|
|
|
|
|
// let temp = rm_helper(call, args).flatten();
|
|
|
|
// let temp = input.flatten(call.head, move |_| rm_helper(call, args));
|
|
|
|
|
|
|
|
Ok(Value::Stream {
|
|
|
|
stream: ValueStream::from_stream(response.into_iter()),
|
|
|
|
span: call.head,
|
|
|
|
})
|
|
|
|
|
|
|
|
// Ok(Value::Nothing { span })
|
|
|
|
}
|
|
|
|
|
|
|
|
fn rm_helper(call: &Call, args: RmArgs) -> Vec<Value> {
|
2021-10-10 04:17:08 +00:00
|
|
|
let (targets, recursive, trash, _permanent, force) = (
|
2021-10-10 04:13:15 +00:00
|
|
|
args.targets,
|
|
|
|
args.recursive,
|
|
|
|
args.trash,
|
|
|
|
args.permanent,
|
|
|
|
args.force,
|
|
|
|
);
|
|
|
|
|
|
|
|
#[cfg(not(feature = "trash-support"))]
|
|
|
|
{
|
|
|
|
if trash {
|
|
|
|
return vec![Value::Error {
|
|
|
|
error: ShellError::FeatureNotEnabled(call.get_flag_expr("trash").unwrap().span),
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if targets.is_empty() && !force {
|
|
|
|
return vec![Value::Error {
|
|
|
|
error: ShellError::FileNotFound(call.head),
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
targets
|
|
|
|
.into_iter()
|
|
|
|
.map(move |target| {
|
|
|
|
let (i, f) = (target.0, target.1);
|
|
|
|
|
|
|
|
let is_empty = || match f.read_dir() {
|
|
|
|
Ok(mut p) => p.next().is_none(),
|
|
|
|
Err(_) => false,
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Ok(metadata) = f.symlink_metadata() {
|
|
|
|
#[cfg(unix)]
|
|
|
|
let is_socket = metadata.file_type().is_socket();
|
|
|
|
#[cfg(unix)]
|
|
|
|
let is_fifo = metadata.file_type().is_fifo();
|
|
|
|
|
|
|
|
#[cfg(not(unix))]
|
|
|
|
let is_socket = false;
|
|
|
|
#[cfg(not(unix))]
|
|
|
|
let is_fifo = false;
|
|
|
|
|
|
|
|
if metadata.is_file()
|
|
|
|
|| metadata.file_type().is_symlink()
|
|
|
|
|| recursive
|
|
|
|
|| is_socket
|
|
|
|
|| is_fifo
|
|
|
|
|| is_empty()
|
|
|
|
{
|
|
|
|
let result;
|
|
|
|
#[cfg(feature = "trash-support")]
|
|
|
|
{
|
|
|
|
use std::io::Error;
|
|
|
|
result = if trash {
|
|
|
|
trash::delete(&f).map_err(|e: trash::Error| {
|
|
|
|
Error::new(ErrorKind::Other, format!("{:?}", e))
|
|
|
|
})
|
|
|
|
} else if metadata.is_file() {
|
|
|
|
std::fs::remove_file(&f)
|
|
|
|
} else {
|
|
|
|
std::fs::remove_dir_all(&f)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
#[cfg(not(feature = "trash-support"))]
|
|
|
|
{
|
|
|
|
result = 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 {
|
2021-10-10 04:17:08 +00:00
|
|
|
Value::Error {
|
2021-10-10 04:13:15 +00:00
|
|
|
error: ShellError::RemoveNotPossible(
|
|
|
|
format!("Could not delete because: {:}\nTry '--trash' flag", e),
|
|
|
|
call.head,
|
|
|
|
),
|
2021-10-10 04:17:08 +00:00
|
|
|
}
|
2021-10-10 04:13:15 +00:00
|
|
|
} else {
|
2021-10-10 04:17:08 +00:00
|
|
|
Value::String {
|
|
|
|
val: format!("deleted {:}", f.to_string_lossy()),
|
2021-10-10 04:13:15 +00:00
|
|
|
span: call.positional[i].span,
|
2021-10-10 04:17:08 +00:00
|
|
|
}
|
2021-10-10 04:13:15 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-10-10 04:17:08 +00:00
|
|
|
Value::Error {
|
2021-10-10 04:13:15 +00:00
|
|
|
error: ShellError::RemoveNotPossible(
|
|
|
|
"Cannot remove. try --recursive".to_string(),
|
|
|
|
call.positional[i].span,
|
|
|
|
),
|
2021-10-10 04:17:08 +00:00
|
|
|
}
|
2021-10-10 04:13:15 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-10-10 04:17:08 +00:00
|
|
|
Value::Error {
|
2021-10-10 04:13:15 +00:00
|
|
|
error: ShellError::RemoveNotPossible(
|
|
|
|
"no such file or directory".to_string(),
|
|
|
|
call.positional[i].span,
|
|
|
|
),
|
2021-10-10 04:17:08 +00:00
|
|
|
}
|
2021-10-10 04:13:15 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
}
|