From 9d49618e87cb6c77f7fc672a123fb638a2938cce Mon Sep 17 00:00:00 2001 From: xiuxiu62 Date: Tue, 5 Oct 2021 12:54:30 -0700 Subject: [PATCH 1/6] add impl From io::Error and dyn Error for ShellError --- crates/nu-protocol/src/shell_error.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 0bb59291e..73f5147fa 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -97,4 +97,20 @@ pub enum ShellError { #[label("{destination_message}")] destination_span: Span, }, + + #[error("Move not possible")] + #[diagnostic(code(nu::shell::move_not_possible_single), url(docsrs))] + MoveNotPossibleSingle(String, #[label("{0}")] Span), +} + +impl From for ShellError { + fn from(input: std::io::Error) -> ShellError { + ShellError::InternalError(format!("{:?}", input)) + } +} + +impl From> for ShellError { + fn from(input: Box) -> ShellError { + ShellError::InternalError(format!("{:?}", input)) + } } From 5da1310696ef807a1d83c0a79c21ebe34184d106 Mon Sep 17 00:00:00 2001 From: xiuxiu62 Date: Tue, 5 Oct 2021 12:55:33 -0700 Subject: [PATCH 2/6] add fs utils --- crates/nu-command/src/filesystem/mod.rs | 3 + crates/nu-command/src/filesystem/util.rs | 82 ++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 crates/nu-command/src/filesystem/util.rs diff --git a/crates/nu-command/src/filesystem/mod.rs b/crates/nu-command/src/filesystem/mod.rs index 90b697fcd..2d2212766 100644 --- a/crates/nu-command/src/filesystem/mod.rs +++ b/crates/nu-command/src/filesystem/mod.rs @@ -1,7 +1,10 @@ mod cd; +mod cp; mod ls; mod mv; +mod util; pub use cd::Cd; +pub use cp::Cp; pub use ls::Ls; pub use mv::Mv; diff --git a/crates/nu-command/src/filesystem/util.rs b/crates/nu-command/src/filesystem/util.rs new file mode 100644 index 000000000..86a0bd4c9 --- /dev/null +++ b/crates/nu-command/src/filesystem/util.rs @@ -0,0 +1,82 @@ +use std::path::{Path, PathBuf}; + +use nu_path::canonicalize_with; +use nu_protocol::ShellError; + +#[derive(Default)] +pub struct FileStructure { + pub resources: Vec, +} + +impl FileStructure { + pub fn new() -> FileStructure { + FileStructure { resources: vec![] } + } + + #[allow(dead_code)] + pub fn contains_more_than_one_file(&self) -> bool { + self.resources.len() > 1 + } + + #[allow(dead_code)] + pub fn contains_files(&self) -> bool { + !self.resources.is_empty() + } + + pub fn paths_applying_with( + &mut self, + to: F, + ) -> Result, Box> + where + F: Fn((PathBuf, usize)) -> Result<(PathBuf, PathBuf), Box>, + { + self.resources + .iter() + .map(|f| (PathBuf::from(&f.location), f.at)) + .map(|f| to(f)) + .collect() + } + + pub fn walk_decorate(&mut self, start_path: &Path) -> Result<(), ShellError> { + self.resources = Vec::::new(); + self.build(start_path, 0)?; + self.resources.sort(); + + Ok(()) + } + + fn build(&mut self, src: &Path, lvl: usize) -> Result<(), ShellError> { + let source = canonicalize_with(src, std::env::current_dir()?)?; + + if source.is_dir() { + for entry in std::fs::read_dir(src)? { + let entry = entry?; + let path = entry.path(); + + if path.is_dir() { + self.build(&path, lvl + 1)?; + } + + self.resources.push(Resource { + location: path.to_path_buf(), + at: lvl, + }); + } + } else { + self.resources.push(Resource { + location: source, + at: lvl, + }); + } + + Ok(()) + } +} + +#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct Resource { + pub at: usize, + pub location: PathBuf, +} + +impl Resource {} From 8dc3ebd6e23123657c8664906b442d093df48aa5 Mon Sep 17 00:00:00 2001 From: xiuxiu62 Date: Tue, 5 Oct 2021 12:55:46 -0700 Subject: [PATCH 3/6] start cp command --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filesystem/cp.rs | 155 +++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 crates/nu-command/src/filesystem/cp.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 8418b2a38..c36e7ea90 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -17,6 +17,7 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Benchmark)); working_set.add_decl(Box::new(BuildString)); working_set.add_decl(Box::new(Cd)); + working_set.add_decl(Box::new(Cp)); working_set.add_decl(Box::new(Def)); working_set.add_decl(Box::new(Do)); working_set.add_decl(Box::new(Each)); diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs new file mode 100644 index 000000000..6c6850c9e --- /dev/null +++ b/crates/nu-command/src/filesystem/cp.rs @@ -0,0 +1,155 @@ +use std::env::current_dir; +use std::path::PathBuf; + +use nu_engine::{eval_expression, CallExt}; +use nu_path::canonicalize_with; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{IntoValueStream, ShellError, Signature, SyntaxShape, Value}; + +use crate::filesystem::util::FileStructure; + +pub struct Cp; + +impl Command for Cp { + fn name(&self) -> &str { + "cp" + } + + fn usage(&self) -> &str { + "Copy files." + } + + fn signature(&self) -> Signature { + Signature::build("cp") + .required("source", SyntaxShape::GlobPattern, "the place to copy from") + .required("destination", SyntaxShape::Filepath, "the place to copy to") + .switch( + "recursive", + "copy recursively through subdirectories", + Some('r'), + ) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + let source: String = call.req(context, 0)?; + let destination: String = call.req(context, 1)?; + + let path: PathBuf = current_dir().unwrap(); + let source = path.join(source.as_str()); + let destination = path.join(destination.as_str()); + + 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)); + } + + if sources.len() > 1 && !destination.is_dir() { + return Err(ShellError::MoveNotPossible { + source_message: "Can't move many files".to_string(), + source_span: call.positional[0].span, + destination_message: "into single file".to_string(), + destination_span: call.positional[1].span, + }); + } + + let any_source_is_dir = sources.iter().any(|f| matches!(f, Ok(f) if f.is_dir())); + let recursive = call + .named + .iter() + .fold(false, |acc, p| acc || { &p.0 == "recursive" }); + if any_source_is_dir && !recursive { + return Err(ShellError::MoveNotPossibleSingle( + "Directories must be copied using \"--recursive\"".to_string(), + call.positional[0].span, + )); + } + + // for entry in sources.into_iter().flatten() { + // let mut sources = FileStructure::new(); + // sources.walk_decorate(&entry)?; + + // if entry.is_file() { + // let sources = sources.paths_applying_with(|(source_file, _depth_level)| { + // if destination.is_dir() { + // let mut dest = canonicalize_with(&destination.item, &path)?; + // if let Some(name) = entry.file_name() { + // dest.push(name); + // } + // Ok((source_file, dest)) + // } else { + // Ok((source_file, destination.clone())) + // } + // })?; + + // for (src, dst) in sources { + // if src.is_file() { + // std::fs::copy(src, dst).map_err(|e| { + // ShellError::labeled_error(e.to_string(), e.to_string(), &name_tag) + // })?; + // } + // } + // } else if entry.is_dir() { + // let destination = if !destination.exists() { + // destination.clone() + // } else { + // match entry.file_name() { + // Some(name) => destination.join(name), + // None => { + // return Err(ShellError::labeled_error( + // "Copy aborted. Not a valid path", + // "not a valid path", + // dst.tag, + // )) + // } + // } + // }; + + // std::fs::create_dir_all(&destination).map_err(|e| { + // ShellError::labeled_error(e.to_string(), e.to_string(), &dst.tag) + // })?; + + // let sources = sources.paths_applying_with(|(source_file, depth_level)| { + // let mut dest = destination.clone(); + // let path = canonicalize_with(&source_file, &path)?; + + // let comps: Vec<_> = path + // .components() + // .map(|fragment| fragment.as_os_str()) + // .rev() + // .take(1 + depth_level) + // .collect(); + + // for fragment in comps.into_iter().rev() { + // dest.push(fragment); + // } + + // Ok((PathBuf::from(&source_file), dest)) + // })?; + + // let dst_tag = &dst.tag; + // for (src, dst) in sources { + // if src.is_dir() && !dst.exists() { + // std::fs::create_dir_all(&dst).map_err(|e| { + // ShellError::labeled_error(e.to_string(), e.to_string(), dst_tag) + // })?; + // } + + // if src.is_file() { + // std::fs::copy(&src, &dst).map_err(|e| { + // ShellError::labeled_error(e.to_string(), e.to_string(), &name_tag) + // })?; + // } + // } + // } + // } + + Ok(Value::Nothing { span: call.head }) + } +} From 74d4c501a8a79de85915d6add55dfd86f012e6e5 Mon Sep 17 00:00:00 2001 From: xiuxiu62 Date: Tue, 5 Oct 2021 14:08:39 -0700 Subject: [PATCH 4/6] add move, recursive fill, and recursive create procedures --- crates/nu-command/src/filesystem/cp.rs | 167 +++++++++++++---------- crates/nu-command/src/filesystem/util.rs | 5 +- crates/nu-protocol/src/shell_error.rs | 14 ++ 3 files changed, 109 insertions(+), 77 deletions(-) diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index 6c6850c9e..d2a4fbb81 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -44,7 +44,7 @@ impl Command for Cp { let source = path.join(source.as_str()); let destination = path.join(destination.as_str()); - let mut sources = + let 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)); @@ -60,10 +60,7 @@ impl Command for Cp { } let any_source_is_dir = sources.iter().any(|f| matches!(f, Ok(f) if f.is_dir())); - let recursive = call - .named - .iter() - .fold(false, |acc, p| acc || { &p.0 == "recursive" }); + let recursive = call.named.iter().any(|p| &p.0 == "recursive"); if any_source_is_dir && !recursive { return Err(ShellError::MoveNotPossibleSingle( "Directories must be copied using \"--recursive\"".to_string(), @@ -71,84 +68,106 @@ impl Command for Cp { )); } - // for entry in sources.into_iter().flatten() { - // let mut sources = FileStructure::new(); - // sources.walk_decorate(&entry)?; + for entry in sources.into_iter().flatten() { + let mut sources = FileStructure::new(); + sources.walk_decorate(&entry)?; - // if entry.is_file() { - // let sources = sources.paths_applying_with(|(source_file, _depth_level)| { - // if destination.is_dir() { - // let mut dest = canonicalize_with(&destination.item, &path)?; - // if let Some(name) = entry.file_name() { - // dest.push(name); - // } - // Ok((source_file, dest)) - // } else { - // Ok((source_file, destination.clone())) - // } - // })?; + if entry.is_file() { + let sources = sources.paths_applying_with(|(source_file, _depth_level)| { + if destination.is_dir() { + let mut dest = canonicalize_with(&destination, &path)?; + if let Some(name) = entry.file_name() { + dest.push(name); + } + Ok((source_file, dest)) + } else { + Ok((source_file, destination.clone())) + } + })?; - // for (src, dst) in sources { - // if src.is_file() { - // std::fs::copy(src, dst).map_err(|e| { - // ShellError::labeled_error(e.to_string(), e.to_string(), &name_tag) - // })?; - // } - // } - // } else if entry.is_dir() { - // let destination = if !destination.exists() { - // destination.clone() - // } else { - // match entry.file_name() { - // Some(name) => destination.join(name), - // None => { - // return Err(ShellError::labeled_error( - // "Copy aborted. Not a valid path", - // "not a valid path", - // dst.tag, - // )) - // } - // } - // }; + for (src, dst) in sources { + if src.is_file() { + std::fs::copy(&src, dst).map_err(|e| { + ShellError::MoveNotPossibleSingle( + format!( + "failed to move containing file \"{}\": {}", + src.to_string_lossy(), + e + ), + call.positional[0].span, + ) + })?; + } + } + } else if entry.is_dir() { + let destination = if !destination.exists() { + destination.clone() + } else { + match entry.file_name() { + Some(name) => destination.join(name), + None => { + return Err(ShellError::FileNotFoundCustom( + format!("containing \"{:?}\" is not a valid path", entry), + call.positional[0].span, + )) + } + } + }; - // std::fs::create_dir_all(&destination).map_err(|e| { - // ShellError::labeled_error(e.to_string(), e.to_string(), &dst.tag) - // })?; + std::fs::create_dir_all(&destination).map_err(|e| { + ShellError::MoveNotPossibleSingle( + format!("failed to recursively fill destination: {}", e), + call.positional[1].span, + ) + })?; - // let sources = sources.paths_applying_with(|(source_file, depth_level)| { - // let mut dest = destination.clone(); - // let path = canonicalize_with(&source_file, &path)?; + let sources = sources.paths_applying_with(|(source_file, depth_level)| { + let mut dest = destination.clone(); + let path = canonicalize_with(&source_file, &path)?; - // let comps: Vec<_> = path - // .components() - // .map(|fragment| fragment.as_os_str()) - // .rev() - // .take(1 + depth_level) - // .collect(); + let comps: Vec<_> = path + .components() + .map(|fragment| fragment.as_os_str()) + .rev() + .take(1 + depth_level) + .collect(); - // for fragment in comps.into_iter().rev() { - // dest.push(fragment); - // } + for fragment in comps.into_iter().rev() { + dest.push(fragment); + } - // Ok((PathBuf::from(&source_file), dest)) - // })?; + Ok((PathBuf::from(&source_file), dest)) + })?; - // let dst_tag = &dst.tag; - // for (src, dst) in sources { - // if src.is_dir() && !dst.exists() { - // std::fs::create_dir_all(&dst).map_err(|e| { - // ShellError::labeled_error(e.to_string(), e.to_string(), dst_tag) - // })?; - // } + for (src, dst) in sources { + if src.is_dir() && !dst.exists() { + std::fs::create_dir_all(&dst).map_err(|e| { + ShellError::MoveNotPossibleSingle( + format!( + "failed to create containing directory \"{}\": {}", + dst.to_string_lossy(), + e + ), + call.positional[1].span, + ) + })?; + } - // if src.is_file() { - // std::fs::copy(&src, &dst).map_err(|e| { - // ShellError::labeled_error(e.to_string(), e.to_string(), &name_tag) - // })?; - // } - // } - // } - // } + if src.is_file() { + std::fs::copy(&src, &dst).map_err(|e| { + ShellError::MoveNotPossibleSingle( + format!( + "failed to move containing file \"{}\": {}", + src.to_string_lossy(), + e + ), + call.positional[0].span, + ) + })?; + } + } + } + } Ok(Value::Nothing { span: call.head }) } diff --git a/crates/nu-command/src/filesystem/util.rs b/crates/nu-command/src/filesystem/util.rs index 86a0bd4c9..a2d217cf0 100644 --- a/crates/nu-command/src/filesystem/util.rs +++ b/crates/nu-command/src/filesystem/util.rs @@ -8,17 +8,16 @@ pub struct FileStructure { pub resources: Vec, } +#[allow(dead_code)] impl FileStructure { pub fn new() -> FileStructure { FileStructure { resources: vec![] } } - #[allow(dead_code)] pub fn contains_more_than_one_file(&self) -> bool { self.resources.len() > 1 } - #[allow(dead_code)] pub fn contains_files(&self) -> bool { !self.resources.is_empty() } @@ -33,7 +32,7 @@ impl FileStructure { self.resources .iter() .map(|f| (PathBuf::from(&f.location), f.at)) - .map(|f| to(f)) + .map(to) .collect() } diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 73f5147fa..6a7464508 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -83,10 +83,18 @@ pub enum ShellError { #[diagnostic(code(nu::shell::file_not_found), url(docsrs))] FileNotFound(#[label("file not found")] Span), + #[error("File not found")] + #[diagnostic(code(nu::shell::file_not_found), url(docsrs))] + FileNotFoundCustom(String, #[label("{0}")] Span), + #[error("Directory not found")] #[diagnostic(code(nu::shell::directory_not_found), url(docsrs))] DirectoryNotFound(#[label("directory not found")] Span), + #[error("File not found")] + #[diagnostic(code(nu::shell::file_not_found), url(docsrs))] + DirectoryNotFoundCustom(String, #[label("{0}")] Span), + #[error("Move not possible")] #[diagnostic(code(nu::shell::move_not_possible), url(docsrs))] MoveNotPossible { @@ -109,6 +117,12 @@ impl From for ShellError { } } +impl std::convert::From> for ShellError { + fn from(input: Box) -> ShellError { + ShellError::InternalError(input.to_string()) + } +} + impl From> for ShellError { fn from(input: Box) -> ShellError { ShellError::InternalError(format!("{:?}", input)) From cc8a470668367a337167618e8e023ddec912570c Mon Sep 17 00:00:00 2001 From: xiuxiu62 Date: Tue, 5 Oct 2021 14:13:23 -0700 Subject: [PATCH 5/6] clean up unused imports --- crates/nu-command/src/filesystem/cp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index d2a4fbb81..6ebc30592 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -1,11 +1,11 @@ use std::env::current_dir; use std::path::PathBuf; -use nu_engine::{eval_expression, CallExt}; +use nu_engine::CallExt; use nu_path::canonicalize_with; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{IntoValueStream, ShellError, Signature, SyntaxShape, Value}; +use nu_protocol::{ShellError, Signature, SyntaxShape, Value}; use crate::filesystem::util::FileStructure; From b3b51a2ed65e02d9e8eee44189a0b3d2dacd4462 Mon Sep 17 00:00:00 2001 From: jacremer Date: Tue, 5 Oct 2021 15:09:51 -0700 Subject: [PATCH 6/6] drop redundant iter -> vec -> iter --- crates/nu-command/src/filesystem/cp.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/crates/nu-command/src/filesystem/cp.rs b/crates/nu-command/src/filesystem/cp.rs index 6ebc30592..79751f2c9 100644 --- a/crates/nu-command/src/filesystem/cp.rs +++ b/crates/nu-command/src/filesystem/cp.rs @@ -124,18 +124,13 @@ impl Command for Cp { let sources = sources.paths_applying_with(|(source_file, depth_level)| { let mut dest = destination.clone(); let path = canonicalize_with(&source_file, &path)?; - - let comps: Vec<_> = path + let components = path .components() .map(|fragment| fragment.as_os_str()) .rev() - .take(1 + depth_level) - .collect(); - - for fragment in comps.into_iter().rev() { - dest.push(fragment); - } + .take(1 + depth_level); + components.for_each(|fragment| dest.push(fragment)); Ok((PathBuf::from(&source_file), dest)) })?;