From e325fd114d3c16d382d47dd4da799e626d730a64 Mon Sep 17 00:00:00 2001 From: xiuxiu62 Date: Mon, 4 Oct 2021 04:32:08 -0700 Subject: [PATCH 1/6] port the mv command --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filesystem/mod.rs | 2 + crates/nu-command/src/filesystem/mv.rs | 151 +++++++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 crates/nu-command/src/filesystem/mv.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 46639151de..8418b2a384 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -35,6 +35,7 @@ pub fn create_default_context() -> Rc> { working_set.add_decl(Box::new(Lines)); working_set.add_decl(Box::new(Ls)); working_set.add_decl(Box::new(Module)); + working_set.add_decl(Box::new(Mv)); working_set.add_decl(Box::new(Ps)); working_set.add_decl(Box::new(Select)); working_set.add_decl(Box::new(Sys)); diff --git a/crates/nu-command/src/filesystem/mod.rs b/crates/nu-command/src/filesystem/mod.rs index 13148b5358..90b697fcd8 100644 --- a/crates/nu-command/src/filesystem/mod.rs +++ b/crates/nu-command/src/filesystem/mod.rs @@ -1,5 +1,7 @@ mod cd; mod ls; +mod mv; pub use cd::Cd; pub use ls::Ls; +pub use mv::Mv; diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs new file mode 100644 index 0000000000..3b909cb0fe --- /dev/null +++ b/crates/nu-command/src/filesystem/mv.rs @@ -0,0 +1,151 @@ +use std::env::current_dir; +use std::path::{Path, PathBuf}; + +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EvaluationContext}; +use nu_protocol::{ShellError, Signature, SyntaxShape, Value}; + +pub struct Mv; + +impl Command for Mv { + fn name(&self) -> &str { + "mv" + } + + fn usage(&self) -> &str { + "Move files or directories." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("mv") + .optional( + "source", + SyntaxShape::GlobPattern, + "the location to move files/directories from", + ) + .optional( + "destination", + SyntaxShape::FilePath, + "the location to move files/directories to", + ) + // .required( + // "source", + // SyntaxShape::GlobPattern, + // "the location to move files/directories from", + // ) + // .required( + // "destination", + // SyntaxShape::FilePath, + // "the location to move files/directories to", + // ) + } + + fn run( + &self, + context: &EvaluationContext, + call: &Call, + _input: Value, + ) -> Result { + // TODO: handle invalid directory or insufficient permissions + 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::InternalError(format!( + "source \"{:?}\" does not exist", + source + ))); + } + + if (destination.exists() && !destination.is_dir() && sources.len() > 1) + || (!destination.exists() && sources.len() > 1) + { + return Err(ShellError::InternalError( + "can only move multiple sources if destination is a directory".to_string(), + )); + } + + let some_if_source_is_destination = sources + .iter() + .find(|f| matches!(f, Ok(f) if destination.starts_with(f))); + if destination.exists() && destination.is_dir() && sources.len() == 1 { + if let Some(Ok(filename)) = some_if_source_is_destination { + return Err(ShellError::InternalError(format!( + "Not possible to move {:?} to itself", + filename.file_name().expect("Invalid file name") + ))); + } + } + + if let Some(Ok(_filename)) = some_if_source_is_destination { + sources = sources + .into_iter() + .filter(|f| matches!(f, Ok(f) if !destination.starts_with(f))) + .collect(); + } + + for entry in sources.into_iter().flatten() { + move_file(&entry, &destination)? + } + + Ok(Value::Nothing { span: call.head }) + } +} + +fn move_file(from: &PathBuf, to: &PathBuf) -> Result<(), ShellError> { + if to.exists() && from.is_dir() && to.is_file() { + return Err(ShellError::InternalError(format!( + "Cannot rename {:?} to a file", + from.file_name().expect("Invalid directory name") + ))); + } + + let destination_dir_exists = if to.is_dir() { + true + } else { + to.parent().map(Path::exists).unwrap_or(true) + }; + + if !destination_dir_exists { + return Err(ShellError::InternalError(format!( + "{:?} does not exist", + to.file_name().expect("Invalid directory name") + ))); + } + + let mut to = to.clone(); + if to.is_dir() { + let from_file_name = match from.file_name() { + Some(name) => name, + None => { + return Err(ShellError::InternalError(format!( + "{:?} is not a valid entry", + from.file_name().expect("Invalid directory name") + ))) + } + }; + + to.push(from_file_name); + } + + move_item(&from, &to) +} + +fn move_item(from: &Path, to: &Path) -> Result<(), ShellError> { + // We first try a rename, which is a quick operation. If that doesn't work, we'll try a copy + // and remove the old file/folder. This is necessary if we're moving across filesystems or devices. + std::fs::rename(&from, &to).or_else(|_| { + Err(ShellError::InternalError(format!( + "Could not move {:?} to {:?}", + from, to, + ))) + }) +} From b2148e32b85ad5afb13a36613b4a0e554c46fe7b Mon Sep 17 00:00:00 2001 From: xiuxiu62 Date: Mon, 4 Oct 2021 05:13:47 -0700 Subject: [PATCH 2/6] make mv parameters required --- crates/nu-command/src/filesystem/mv.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index 3b909cb0fe..b3d9ec2f07 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -19,26 +19,16 @@ impl Command for Mv { fn signature(&self) -> nu_protocol::Signature { Signature::build("mv") - .optional( + .required( "source", SyntaxShape::GlobPattern, "the location to move files/directories from", ) - .optional( + .required( "destination", SyntaxShape::FilePath, "the location to move files/directories to", ) - // .required( - // "source", - // SyntaxShape::GlobPattern, - // "the location to move files/directories from", - // ) - // .required( - // "destination", - // SyntaxShape::FilePath, - // "the location to move files/directories to", - // ) } fn run( From 1b96da5e5b9783c8c7fbc2cded7871453bac60a4 Mon Sep 17 00:00:00 2001 From: xiuxiu62 Date: Mon, 4 Oct 2021 20:43:07 -0700 Subject: [PATCH 3/6] add custom filesystem shell errors --- crates/nu-command/src/filesystem/mv.rs | 68 +++++++++++++------------- crates/nu-protocol/src/shell_error.rs | 19 +++++++ 2 files changed, 53 insertions(+), 34 deletions(-) diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index b3d9ec2f07..58c9c6dafc 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -49,29 +49,33 @@ impl Command for Mv { glob::glob(&source.to_string_lossy()).map_or_else(|_| Vec::new(), Iterator::collect); if sources.is_empty() { - return Err(ShellError::InternalError(format!( - "source \"{:?}\" does not exist", - source - ))); + return Err(ShellError::FileNotFound( + call.positional.first().unwrap().span, + )); } if (destination.exists() && !destination.is_dir() && sources.len() > 1) || (!destination.exists() && sources.len() > 1) { - return Err(ShellError::InternalError( - "can only move multiple sources if destination is a directory".to_string(), - )); + 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 some_if_source_is_destination = sources .iter() .find(|f| matches!(f, Ok(f) if destination.starts_with(f))); if destination.exists() && destination.is_dir() && sources.len() == 1 { - if let Some(Ok(filename)) = some_if_source_is_destination { - return Err(ShellError::InternalError(format!( - "Not possible to move {:?} to itself", - filename.file_name().expect("Invalid file name") - ))); + if let Some(Ok(_filename)) = some_if_source_is_destination { + return Err(ShellError::MoveNotPossible { + source_message: "Can't move directory".to_string(), + source_span: call.positional[0].span, + destination_message: "into itself".to_string(), + destination_span: call.positional[1].span, + }); } } @@ -83,19 +87,21 @@ impl Command for Mv { } for entry in sources.into_iter().flatten() { - move_file(&entry, &destination)? + move_file(call, &entry, &destination)? } Ok(Value::Nothing { span: call.head }) } } -fn move_file(from: &PathBuf, to: &PathBuf) -> Result<(), ShellError> { +fn move_file(call: &Call, from: &PathBuf, to: &PathBuf) -> Result<(), ShellError> { if to.exists() && from.is_dir() && to.is_file() { - return Err(ShellError::InternalError(format!( - "Cannot rename {:?} to a file", - from.file_name().expect("Invalid directory name") - ))); + return Err(ShellError::MoveNotPossible { + source_message: "Can't move a directory".to_string(), + source_span: call.positional[0].span, + destination_message: "to a file".to_string(), + destination_span: call.positional[1].span, + }); } let destination_dir_exists = if to.is_dir() { @@ -105,37 +111,31 @@ fn move_file(from: &PathBuf, to: &PathBuf) -> Result<(), ShellError> { }; if !destination_dir_exists { - return Err(ShellError::InternalError(format!( - "{:?} does not exist", - to.file_name().expect("Invalid directory name") - ))); + return Err(ShellError::DirectoryNotFound(call.positional[1].span)); } let mut to = to.clone(); if to.is_dir() { let from_file_name = match from.file_name() { Some(name) => name, - None => { - return Err(ShellError::InternalError(format!( - "{:?} is not a valid entry", - from.file_name().expect("Invalid directory name") - ))) - } + None => return Err(ShellError::DirectoryNotFound(call.positional[1].span)), }; to.push(from_file_name); } - move_item(&from, &to) + move_item(call, &from, &to) } -fn move_item(from: &Path, to: &Path) -> Result<(), ShellError> { +fn move_item(call: &Call, from: &Path, to: &Path) -> Result<(), ShellError> { // We first try a rename, which is a quick operation. If that doesn't work, we'll try a copy // and remove the old file/folder. This is necessary if we're moving across filesystems or devices. std::fs::rename(&from, &to).or_else(|_| { - Err(ShellError::InternalError(format!( - "Could not move {:?} to {:?}", - from, to, - ))) + Err(ShellError::MoveNotPossible { + source_message: "failed to move".to_string(), + source_span: call.positional[0].span, + destination_message: "into".to_string(), + destination_span: call.positional[1].span, + }) }) } diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 8307e99617..0bb59291e7 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -78,4 +78,23 @@ pub enum ShellError { #[error("Flag not found")] #[diagnostic(code(nu::shell::flag_not_found), url(docsrs))] FlagNotFound(String, #[label("{0} not found")] Span), + + #[error("File not found")] + #[diagnostic(code(nu::shell::file_not_found), url(docsrs))] + FileNotFound(#[label("file not found")] Span), + + #[error("Directory not found")] + #[diagnostic(code(nu::shell::directory_not_found), url(docsrs))] + DirectoryNotFound(#[label("directory not found")] Span), + + #[error("Move not possible")] + #[diagnostic(code(nu::shell::move_not_possible), url(docsrs))] + MoveNotPossible { + source_message: String, + #[label("{source_message}")] + source_span: Span, + destination_message: String, + #[label("{destination_message}")] + destination_span: Span, + }, } From 80e7a8d5949b1aefe2c001a02b2dda599a3813cf Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Tue, 5 Oct 2021 16:58:49 +1300 Subject: [PATCH 4/6] Update mv.rs --- crates/nu-command/src/filesystem/mv.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index 58c9c6dafc..ebedb42446 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -26,7 +26,7 @@ impl Command for Mv { ) .required( "destination", - SyntaxShape::FilePath, + SyntaxShape::Filepath, "the location to move files/directories to", ) } From 0ef0588e295e085198c2f2a9227d5bc4908cbd39 Mon Sep 17 00:00:00 2001 From: jacremer Date: Mon, 4 Oct 2021 21:40:26 -0700 Subject: [PATCH 5/6] mv clippy suggestions --- crates/nu-command/src/filesystem/mv.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index ebedb42446..4955cf0031 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -26,7 +26,7 @@ impl Command for Mv { ) .required( "destination", - SyntaxShape::Filepath, + SyntaxShape::FilePath, "the location to move files/directories to", ) } @@ -94,7 +94,7 @@ impl Command for Mv { } } -fn move_file(call: &Call, from: &PathBuf, to: &PathBuf) -> Result<(), ShellError> { +fn move_file(call: &Call, from: &Path, to: &Path) -> Result<(), ShellError> { if to.exists() && from.is_dir() && to.is_file() { return Err(ShellError::MoveNotPossible { source_message: "Can't move a directory".to_string(), @@ -114,7 +114,7 @@ fn move_file(call: &Call, from: &PathBuf, to: &PathBuf) -> Result<(), ShellError return Err(ShellError::DirectoryNotFound(call.positional[1].span)); } - let mut to = to.clone(); + let mut to = to.to_path_buf(); if to.is_dir() { let from_file_name = match from.file_name() { Some(name) => name, @@ -124,18 +124,16 @@ fn move_file(call: &Call, from: &PathBuf, to: &PathBuf) -> Result<(), ShellError to.push(from_file_name); } - move_item(call, &from, &to) + move_item(call, from, &to) } fn move_item(call: &Call, from: &Path, to: &Path) -> Result<(), ShellError> { // We first try a rename, which is a quick operation. If that doesn't work, we'll try a copy // and remove the old file/folder. This is necessary if we're moving across filesystems or devices. - std::fs::rename(&from, &to).or_else(|_| { - Err(ShellError::MoveNotPossible { - source_message: "failed to move".to_string(), - source_span: call.positional[0].span, - destination_message: "into".to_string(), - destination_span: call.positional[1].span, - }) + std::fs::rename(&from, &to).map_err(|_| ShellError::MoveNotPossible { + source_message: "failed to move".to_string(), + source_span: call.positional[0].span, + destination_message: "into".to_string(), + destination_span: call.positional[1].span, }) } From 27dcbe5c8aab18ae865ec440100dc66dd77fecb9 Mon Sep 17 00:00:00 2001 From: jacremer Date: Mon, 4 Oct 2021 22:08:15 -0700 Subject: [PATCH 6/6] fix SyntaxShape::Filepath build error --- crates/nu-command/src/filesystem/mv.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-command/src/filesystem/mv.rs b/crates/nu-command/src/filesystem/mv.rs index 4955cf0031..19cdf0d896 100644 --- a/crates/nu-command/src/filesystem/mv.rs +++ b/crates/nu-command/src/filesystem/mv.rs @@ -26,7 +26,7 @@ impl Command for Mv { ) .required( "destination", - SyntaxShape::FilePath, + SyntaxShape::Filepath, "the location to move files/directories to", ) }