From 87e2fa137a9374c015e2a548f02b9717af0c1539 Mon Sep 17 00:00:00 2001 From: Matthew Ma Date: Mon, 25 Jul 2022 00:42:25 -0700 Subject: [PATCH] Allow cp multiple files at once (#6114) * Allow cp multiple files at once * Expand destination with expand_ndots --- crates/nu-command/src/filesystem/cp.rs | 102 ++++++++++++++++++------- crates/nu-command/tests/commands/cp.rs | 16 ++++ crates/nu-protocol/src/lib.rs | 2 +- 3 files changed, 91 insertions(+), 29 deletions(-) diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index 92b0b5c940..bdf5382bb4 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -1,11 +1,16 @@ +use std::collections::HashMap; use std::fs::read_link; use std::path::PathBuf; +use itertools::Itertools; use nu_engine::env::current_dir; use nu_engine::CallExt; +use nu_glob::GlobResult; +use nu_path::dots::expand_ndots; use nu_path::{canonicalize_with, expand_path_with}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::span::span as merge_spans; use nu_protocol::{ Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value, @@ -41,7 +46,12 @@ impl Command for Cp { fn signature(&self) -> Signature { Signature::build("cp") - .required("source", SyntaxShape::GlobPattern, "the place to copy from") + .rest( + "source(s)", + SyntaxShape::String, + "the place(s) to copy from", + ) + // .required("source", SyntaxShape::GlobPattern, "the place to copy from") .required("destination", SyntaxShape::Filepath, "the place to copy to") .switch( "recursive", @@ -71,15 +81,16 @@ impl Command for Cp { call: &Call, _input: PipelineData, ) -> Result { - let src: Spanned = call.req(engine_state, stack, 0)?; - let dst: Spanned = call.req(engine_state, stack, 1)?; + let mut src_vec: Vec> = call.rest(engine_state, stack, 0)?; + // read dst as final argument + let dst: Spanned = src_vec.pop().expect("Final argument is destination"); + let recursive = call.has_flag("recursive"); let verbose = call.has_flag("verbose"); let interactive = call.has_flag("interactive"); let current_dir_path = current_dir(engine_state, stack)?; - let source = current_dir_path.join(src.item.as_str()); - let destination = current_dir_path.join(dst.item.as_str()); + let destination = expand_ndots(current_dir_path.join(dst.item.as_str())); let path_last_char = destination.as_os_str().to_string_lossy().chars().last(); let is_directory = path_last_char == Some('/') || path_last_char == Some('\\'); @@ -92,24 +103,54 @@ impl Command for Cp { let ctrlc = engine_state.ctrlc.clone(); let span = call.head; - let sources: Vec<_> = match nu_glob::glob_with(&source.to_string_lossy(), GLOB_PARAMS) { - Ok(files) => files.collect(), - Err(e) => { - return Err(ShellError::GenericError( - e.to_string(), - "invalid pattern".to_string(), - Some(src.span), - None, - Vec::new(), - )) + let mut sources: Vec = vec![]; + let mut path_to_span: HashMap = HashMap::new(); + + for src in &src_vec { + let source = current_dir_path.join(src.item.as_str()); + let glob_results: Vec = + match nu_glob::glob_with(&source.to_string_lossy(), GLOB_PARAMS) { + Ok(files) => files.collect(), + Err(e) => { + return Err(ShellError::GenericError( + e.to_string(), + "invalid pattern".to_string(), + Some(src.span), + None, + Vec::new(), + )) + } + }; + + let mut new_sources: Vec = vec![]; + for glob_result in glob_results { + match glob_result { + Ok(path) => { + path_to_span.insert(path.clone(), src.span); + new_sources.push(path); + } + Err(e) => { + return Err(ShellError::GenericError( + e.to_string(), + "glob iteration error".to_string(), + Some(src.span), + None, + Vec::new(), + )) + } + } } - }; + + sources.append(&mut new_sources); + } if sources.is_empty() { return Err(ShellError::GenericError( "No matches found".into(), "no matches found".into(), - Some(src.span), + Some(merge_spans( + &src_vec.into_iter().map(|src| src.span).collect_vec(), + )), None, Vec::new(), )); @@ -125,21 +166,25 @@ impl Command for Cp { )); } - let any_source_is_dir = sources.iter().any(|f| matches!(f, Ok(f) if f.is_dir())); + let any_source_is_dir = sources.iter().find(|f| f.is_dir()); - if any_source_is_dir && !recursive { - return Err(ShellError::GenericError( - "Directories must be copied using \"--recursive\"".into(), - "resolves to a directory (not copied)".into(), - Some(src.span), - None, - Vec::new(), - )); + if let Some(dir_source) = any_source_is_dir { + if !recursive { + return Err(ShellError::GenericError( + "Directories must be copied using \"--recursive\"".into(), + "resolves to a directory (not copied)".into(), + Some(*path_to_span.get(dir_source).unwrap_or_else(|| { + panic!("Key {:?} should exist", dir_source.as_os_str()) + })), + None, + Vec::new(), + )); + } } let mut result = Vec::new(); - for entry in sources.into_iter().flatten() { + for entry in sources.into_iter() { let mut sources = FileStructure::new(); sources.walk_decorate(&entry, engine_state, stack)?; @@ -163,7 +208,8 @@ impl Command for Cp { let res = if src == dst { let message = format!( "src {:?} and dst {:?} are identical(not copied)", - source, destination + src.as_os_str(), + destination ); return Err(ShellError::GenericError( diff --git a/crates/nu-command/tests/commands/cp.rs b/crates/nu-command/tests/commands/cp.rs index b12ec132d5..aa7c9a9dc8 100644 --- a/crates/nu-command/tests/commands/cp.rs +++ b/crates/nu-command/tests/commands/cp.rs @@ -17,6 +17,22 @@ fn copies_a_file() { }); } +#[test] +fn copies_multiple_files() { + Playground::setup("cp_test_1_1", |dirs, sandbox| { + sandbox + .with_files(vec![EmptyFile("a.txt"), EmptyFile("b.txt")]) + .mkdir("dest"); + nu!( + cwd: dirs.test(), + "cp a.txt b.txt dest", + ); + + assert!(dirs.test().join("dest/a.txt").exists()); + assert!(dirs.test().join("dest/b.txt").exists()); + }); +} + #[test] fn copies_the_file_inside_directory_if_path_to_copy_is_directory() { Playground::setup("cp_test_2", |dirs, _| { diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index 25d3c63eac..5f34197b8b 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -9,7 +9,7 @@ mod module; mod pipeline_data; mod shell_error; mod signature; -mod span; +pub mod span; mod syntax_shape; mod ty; mod value;