diff --git a/Cargo.lock b/Cargo.lock index 65709e251..4cee1d420 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -154,6 +154,21 @@ dependencies = [ "chrono", ] +[[package]] +name = "console" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "regex", + "terminal_size", + "unicode-width", + "winapi", +] + [[package]] name = "core-foundation-sys" version = "0.8.2" @@ -240,6 +255,18 @@ dependencies = [ "syn", ] +[[package]] +name = "dialoguer" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9dd058f8b65922819fabb4a41e7d1964e56344042c26efbccd465202c23fa0c" +dependencies = [ + "console", + "lazy_static", + "tempfile", + "zeroize", +] + [[package]] name = "diff" version = "0.1.12" @@ -291,6 +318,12 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "engine-q" version = "0.1.0" @@ -541,6 +574,7 @@ version = "0.1.0" dependencies = [ "bytesize", "chrono", + "dialoguer", "glob", "lscolors", "nu-engine", @@ -1258,3 +1292,9 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "zeroize" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 5b24859f3..8053070e9 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -25,6 +25,7 @@ chrono = { version = "0.4.19", features = ["serde"] } terminal_size = "0.1.17" lscolors = { version = "0.8.0", features = ["crossterm"] } bytesize = "1.1.0" +dialoguer = "0.8.0" [features] trash-support = ["trash"] diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index f8e2b2ba6..3abeee458 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -1,6 +1,7 @@ use std::env::current_dir; use std::path::PathBuf; +use super::interactive_helper::get_confirmation; use nu_engine::CallExt; use nu_path::canonicalize_with; use nu_protocol::ast::Call; @@ -29,6 +30,7 @@ impl Command for Cp { "copy recursively through subdirectories", Some('r'), ) + .switch("force", "suppress error when no file", Some('f')) .switch("interactive", "ask user to confirm action", Some('i')) } @@ -41,19 +43,13 @@ impl Command for Cp { let source: String = call.req(context, 0)?; let destination: String = call.req(context, 1)?; let interactive = call.has_flag("interactive"); - - if interactive { - println!( - "Are you shure that you want to move {} to {}?", - source, destination - ); - } + let force = call.has_flag("force"); let path: PathBuf = current_dir().unwrap(); let source = path.join(source.as_str()); let destination = path.join(destination.as_str()); - let sources = + let mut sources = glob::glob(&source.to_string_lossy()).map_or_else(|_| Vec::new(), Iterator::collect); if sources.is_empty() { return Err(ShellError::FileNotFound(call.positional[0].span)); @@ -77,6 +73,35 @@ impl Command for Cp { )); } + if interactive && !force { + let mut remove_index: Vec = vec![]; + for (index, file) in sources.iter().enumerate() { + let prompt = format!( + "Are you shure that you want to copy {} to {}?", + file.as_ref() + .unwrap() + .file_name() + .unwrap() + .to_str() + .unwrap(), + destination.file_name().unwrap().to_str().unwrap() + ); + + let input = get_confirmation(prompt)?; + + if !input { + remove_index.push(index); + } + } + for index in remove_index { + sources.remove(index); + } + + if sources.len() == 0 { + return Err(ShellError::NoFileToBeCopied()); + } + } + for entry in sources.into_iter().flatten() { let mut sources = FileStructure::new(); sources.walk_decorate(&entry)?; diff --git a/crates/nu-command/src/filesystem/interactive_helper.rs b/crates/nu-command/src/filesystem/interactive_helper.rs new file mode 100644 index 000000000..939caa565 --- /dev/null +++ b/crates/nu-command/src/filesystem/interactive_helper.rs @@ -0,0 +1,26 @@ +use dialoguer::Input; +use std::error::Error; + +pub fn get_confirmation(prompt: String) -> Result> { + let input = Input::new() + .with_prompt(prompt) + .validate_with(|c_input: &String| -> Result<(), String> { + if c_input.len() == 1 + && (c_input == "y" || c_input == "Y" || c_input == "n" || c_input == "N") + { + Ok(()) + } else if c_input.len() > 1 { + Err("Enter only one letter (Y/N)".to_string()) + } else { + Err("Input not valid".to_string()) + } + }) + .default("Y/N".into()) + .interact_text()?; + + if input == "y" || input == "Y" { + Ok(true) + } else { + Ok(false) + } +} diff --git a/crates/nu-command/src/filesystem/mod.rs b/crates/nu-command/src/filesystem/mod.rs index db5bc4bea..964985994 100644 --- a/crates/nu-command/src/filesystem/mod.rs +++ b/crates/nu-command/src/filesystem/mod.rs @@ -1,5 +1,6 @@ mod cd; mod cp; +mod interactive_helper; mod ls; mod mkdir; mod mv; diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index 15d92241f..30dfbb7bb 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -1,6 +1,7 @@ use std::env::current_dir; use std::path::{Path, PathBuf}; +use super::interactive_helper::get_confirmation; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EvaluationContext}; @@ -59,8 +60,9 @@ impl Command for Mv { } if interactive && !force { - for file in &sources { - println!( + let mut remove_index: Vec = vec![]; + for (index, file) in sources.iter().enumerate() { + let prompt = format!( "Are you shure that you want to move {} to {}?", file.as_ref() .unwrap() @@ -70,6 +72,19 @@ impl Command for Mv { .unwrap(), destination.file_name().unwrap().to_str().unwrap() ); + + let input = get_confirmation(prompt)?; + + if !input { + remove_index.push(index); + } + } + for index in remove_index { + sources.remove(index); + } + + if sources.len() == 0 { + return Err(ShellError::NoFileToBeMoved()); } } diff --git a/crates/nu-command/src/filesystem/rm.rs b/crates/nu-command/src/filesystem/rm.rs index 96fd7e228..4d81616b5 100644 --- a/crates/nu-command/src/filesystem/rm.rs +++ b/crates/nu-command/src/filesystem/rm.rs @@ -3,6 +3,8 @@ use std::env::current_dir; use std::os::unix::prelude::FileTypeExt; use std::path::PathBuf; +use super::interactive_helper::get_confirmation; + use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EvaluationContext}; @@ -19,7 +21,6 @@ struct RmArgs { trash: bool, permanent: bool, force: bool, - interactive: bool, } impl Command for Rm { @@ -126,11 +127,26 @@ fn rm(context: &EvaluationContext, call: &Call) -> Result { let force = call.has_flag("force"); if interactive && !force { - for file in &targets { - println!( - "Are you shure that you want to delete {}?", + let mut remove_index: Vec = vec![]; + for (index, file) in targets.iter().enumerate() { + let prompt: String = format!( + "Are you sure that you what to delete {}?", file.1.file_name().unwrap().to_str().unwrap() ); + + 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()); } } @@ -140,7 +156,6 @@ fn rm(context: &EvaluationContext, call: &Call) -> Result { trash, permanent, force, - interactive, }; let response = rm_helper(call, args); diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 1329205c4..88a1880dd 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -158,6 +158,13 @@ pub enum ShellError { #[error("Remove not possible")] #[diagnostic(code(nu::shell::remove_not_possible), url(docsrs))] RemoveNotPossible(String, #[label("{0}")] Span), + + #[error("No file to be removed")] + NoFileToBeRemoved(), + #[error("No file to be moved")] + NoFileToBeMoved(), + #[error("No file to be copied")] + NoFileToBeCopied(), } impl From for ShellError {