From 9ea7cdfc3315f434144cc6353dd22413998c1927 Mon Sep 17 00:00:00 2001 From: Gabriel B Gutierrez Date: Wed, 13 Oct 2021 19:29:08 -0300 Subject: [PATCH 1/5] -i flag on signaure --- crates/nu-command/src/filesystem/cp.rs | 9 +++++++++ crates/nu-command/src/filesystem/mv.rs | 19 +++++++++++++++++++ crates/nu-command/src/filesystem/rm.rs | 13 +++++++++++++ 3 files changed, 41 insertions(+) diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index b042d50f1..f8e2b2ba6 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -29,6 +29,7 @@ impl Command for Cp { "copy recursively through subdirectories", Some('r'), ) + .switch("interactive", "ask user to confirm action", Some('i')) } fn run( @@ -39,6 +40,14 @@ impl Command for Cp { ) -> Result { 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 path: PathBuf = current_dir().unwrap(); let source = path.join(source.as_str()); diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index 98dd8a321..15d92241f 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -29,6 +29,8 @@ impl Command for Mv { SyntaxShape::Filepath, "the location to move files/directories to", ) + .switch("interactive", "ask user to confirm action", Some('i')) + .switch("force", "suppress error when no file", Some('f')) } fn run( @@ -40,6 +42,8 @@ impl Command for Mv { // TODO: handle invalid directory or insufficient permissions when moving let source: String = call.req(context, 0)?; let destination: String = call.req(context, 1)?; + let interactive = call.has_flag("interactive"); + let force = call.has_flag("force"); let path: PathBuf = current_dir().unwrap(); let source = path.join(source.as_str()); @@ -54,6 +58,21 @@ impl Command for Mv { )); } + if interactive && !force { + for file in &sources { + println!( + "Are you shure that you want to move {} to {}?", + file.as_ref() + .unwrap() + .file_name() + .unwrap() + .to_str() + .unwrap(), + destination.file_name().unwrap().to_str().unwrap() + ); + } + } + if (destination.exists() && !destination.is_dir() && sources.len() > 1) || (!destination.exists() && sources.len() > 1) { diff --git a/crates/nu-command/src/filesystem/rm.rs b/crates/nu-command/src/filesystem/rm.rs index e10206a1c..96fd7e228 100644 --- a/crates/nu-command/src/filesystem/rm.rs +++ b/crates/nu-command/src/filesystem/rm.rs @@ -19,6 +19,7 @@ struct RmArgs { trash: bool, permanent: bool, force: bool, + interactive: bool, } impl Command for Rm { @@ -44,6 +45,7 @@ impl Command for Rm { ) .switch("recursive", "delete subdirectories recursively", Some('r')) .switch("force", "suppress error when no file", Some('f')) + .switch("interactive", "ask user to confirm action", Some('i')) .rest( "rest", SyntaxShape::GlobPattern, @@ -64,6 +66,7 @@ impl Command for Rm { fn rm(context: &EvaluationContext, call: &Call) -> Result { let trash = call.has_flag("trash"); let permanent = call.has_flag("permanent"); + let interactive = call.has_flag("interactive"); if trash && permanent { return Err(ShellError::IncompatibleParametersSingle( @@ -122,12 +125,22 @@ fn rm(context: &EvaluationContext, call: &Call) -> Result { let recursive = call.has_flag("recursive"); let force = call.has_flag("force"); + if interactive && !force { + for file in &targets { + println!( + "Are you shure that you want to delete {}?", + file.1.file_name().unwrap().to_str().unwrap() + ); + } + } + let args = RmArgs { targets, recursive, trash, permanent, force, + interactive, }; let response = rm_helper(call, args); From 8c2ae1eed195c79e72fde3aeade864cdf35d3228 Mon Sep 17 00:00:00 2001 From: Gabriel B Gutierrez Date: Thu, 14 Oct 2021 14:54:51 -0300 Subject: [PATCH 2/5] -i flag finished, lacking tests --- Cargo.lock | 40 ++++++++++++++++++ crates/nu-command/Cargo.toml | 1 + crates/nu-command/src/filesystem/cp.rs | 41 +++++++++++++++---- .../src/filesystem/interactive_helper.rs | 26 ++++++++++++ crates/nu-command/src/filesystem/mod.rs | 1 + crates/nu-command/src/filesystem/mv.rs | 19 ++++++++- crates/nu-command/src/filesystem/rm.rs | 25 ++++++++--- crates/nu-protocol/src/shell_error.rs | 7 ++++ 8 files changed, 145 insertions(+), 15 deletions(-) create mode 100644 crates/nu-command/src/filesystem/interactive_helper.rs 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 { From b3192ddc977e5424c244ba11df8bce85a417ea64 Mon Sep 17 00:00:00 2001 From: Gabriel B Gutierrez Date: Thu, 14 Oct 2021 17:03:39 -0300 Subject: [PATCH 3/5] fix operating more than 2 file at the same time --- crates/nu-command/src/filesystem/cp.rs | 9 ++++++--- crates/nu-command/src/filesystem/mv.rs | 9 ++++++--- crates/nu-command/src/filesystem/rm.rs | 8 +++++--- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index 3abeee458..730725b70 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -74,7 +74,7 @@ impl Command for Cp { } if interactive && !force { - let mut remove_index: Vec = vec![]; + let mut remove: Vec = vec![]; for (index, file) in sources.iter().enumerate() { let prompt = format!( "Are you shure that you want to copy {} to {}?", @@ -90,10 +90,13 @@ impl Command for Cp { let input = get_confirmation(prompt)?; if !input { - remove_index.push(index); + remove.push(index); } } - for index in remove_index { + + remove.reverse(); + + for index in remove { sources.remove(index); } diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index 30dfbb7bb..70869d1d9 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -60,7 +60,7 @@ impl Command for Mv { } if interactive && !force { - let mut remove_index: Vec = vec![]; + let mut remove: Vec = vec![]; for (index, file) in sources.iter().enumerate() { let prompt = format!( "Are you shure that you want to move {} to {}?", @@ -76,10 +76,13 @@ impl Command for Mv { let input = get_confirmation(prompt)?; if !input { - remove_index.push(index); + remove.push(index); } } - for index in remove_index { + + remove.reverse(); + + for index in remove { sources.remove(index); } diff --git a/crates/nu-command/src/filesystem/rm.rs b/crates/nu-command/src/filesystem/rm.rs index 4d81616b5..0aca77147 100644 --- a/crates/nu-command/src/filesystem/rm.rs +++ b/crates/nu-command/src/filesystem/rm.rs @@ -127,7 +127,7 @@ fn rm(context: &EvaluationContext, call: &Call) -> Result { let force = call.has_flag("force"); if interactive && !force { - let mut remove_index: Vec = vec![]; + let mut remove: Vec = vec![]; for (index, file) in targets.iter().enumerate() { let prompt: String = format!( "Are you sure that you what to delete {}?", @@ -137,11 +137,13 @@ fn rm(context: &EvaluationContext, call: &Call) -> Result { let input = get_confirmation(prompt)?; if !input { - remove_index.push(index); + remove.push(index); } } - for index in remove_index { + remove.reverse(); + + for index in remove { targets.remove(index); } From 28b26ca44d4f7d5e5d3375f65b3e457438472158 Mon Sep 17 00:00:00 2001 From: Gabriel B Gutierrez Date: Thu, 14 Oct 2021 18:14:59 -0300 Subject: [PATCH 4/5] supress warnings --- crates/nu-command/src/filesystem/cp.rs | 1 + crates/nu-command/src/filesystem/mv.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index 730725b70..4c44b26a6 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -12,6 +12,7 @@ use crate::filesystem::util::FileStructure; pub struct Cp; +#[allow(unused_must_use)] impl Command for Cp { fn name(&self) -> &str { "cp" diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index 70869d1d9..37aa625b8 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -9,6 +9,7 @@ use nu_protocol::{ShellError, Signature, SyntaxShape, Value}; pub struct Mv; +#[allow(unused_must_use)] impl Command for Mv { fn name(&self) -> &str { "mv" From 5bd20e4d36a7a7e81301ab0bd8042c466a0d54fc Mon Sep 17 00:00:00 2001 From: Gabriel B Gutierrez Date: Fri, 15 Oct 2021 12:12:17 -0300 Subject: [PATCH 5/5] fix clippy warnings --- crates/nu-command/src/filesystem/cp.rs | 2 +- crates/nu-command/src/filesystem/mv.rs | 2 +- crates/nu-command/src/filesystem/rm.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index 4c44b26a6..8c6ea0655 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -101,7 +101,7 @@ impl Command for Cp { sources.remove(index); } - if sources.len() == 0 { + if sources.is_empty() { return Err(ShellError::NoFileToBeCopied()); } } diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index 37aa625b8..fe9b91943 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -87,7 +87,7 @@ impl Command for Mv { sources.remove(index); } - if sources.len() == 0 { + if sources.is_empty() { return Err(ShellError::NoFileToBeMoved()); } } diff --git a/crates/nu-command/src/filesystem/rm.rs b/crates/nu-command/src/filesystem/rm.rs index 0aca77147..48d912670 100644 --- a/crates/nu-command/src/filesystem/rm.rs +++ b/crates/nu-command/src/filesystem/rm.rs @@ -147,7 +147,7 @@ fn rm(context: &EvaluationContext, call: &Call) -> Result { targets.remove(index); } - if targets.len() == 0 { + if targets.is_empty() { return Err(ShellError::NoFileToBeRemoved()); } }