mv introduced. \¡Viva\!

This commit is contained in:
Andrés N. Robalino 2019-08-14 15:08:10 -05:00
parent dfcbaed1c6
commit 154063013f
5 changed files with 507 additions and 2 deletions

View file

@ -130,6 +130,7 @@ Nu adheres closely to a set of goals that make up its design philosophy. As feat
| cp source path | Copy files |
| ls (path) | View the contents of the current or given path |
| mkdir path | Make directories, creates intermediary directories as required. |
| mv source target | Move files or directories. |
| date (--utc) | Get the current datetime |
| ps | View current processes |
| sys | View information about the current system |

View file

@ -189,9 +189,10 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
static_command(Exit),
static_command(Clip),
static_command(Autoview),
static_command(Copycp),
static_command(Cpy),
static_command(Date),
static_command(Mkdir),
static_command(Move),
static_command(Save),
static_command(Table),
static_command(VTable),

View file

@ -25,6 +25,7 @@ crate mod get;
crate mod lines;
crate mod ls;
crate mod mkdir;
crate mod mv;
crate mod next;
crate mod nth;
crate mod open;
@ -61,12 +62,13 @@ crate use command::{
RawCommandArgs, StaticCommand, UnevaluatedCallInfo,
};
crate use config::Config;
crate use cp::Copycp;
crate use cp::Cpy;
crate use date::Date;
crate use enter::Enter;
crate use exit::Exit;
crate use get::Get;
crate use mkdir::Mkdir;
crate use mv::Move;
crate use open::Open;
crate use rm::Remove;
crate use save::Save;

279
src/commands/mv.rs Normal file
View file

@ -0,0 +1,279 @@
use crate::errors::ShellError;
use crate::parser::hir::SyntaxType;
use crate::parser::registry::{CommandRegistry, Signature};
use crate::prelude::*;
use std::path::PathBuf;
#[cfg(windows)]
use crate::utils::FileStructure;
pub struct Move;
impl StaticCommand for Move {
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
mv(args, registry)
}
fn name(&self) -> &str {
"mv"
}
fn signature(&self) -> Signature {
Signature::build("mv").named("file", SyntaxType::Any)
}
}
pub fn mv(args: CommandArgs, registry: &CommandRegistry) -> Result<OutputStream, ShellError> {
let mut source = PathBuf::from(args.shell_manager.path());
let mut destination = PathBuf::from(args.shell_manager.path());
let span = args.name_span();
let args = args.evaluate_once(registry)?;
match args
.nth(0)
.ok_or_else(|| ShellError::string(&format!("No file or directory specified")))?
.as_string()?
.as_str()
{
file => {
source.push(file);
}
}
match args
.nth(1)
.ok_or_else(|| ShellError::string(&format!("No file or directory specified")))?
.as_string()?
.as_str()
{
file => {
destination.push(file);
}
}
let sources = glob::glob(&source.to_string_lossy());
if sources.is_err() {
return Err(ShellError::labeled_error(
"Invalid pattern.",
"Invalid pattern.",
args.nth(0).unwrap().span(),
));
}
let sources: Vec<_> = sources.unwrap().collect();
if sources.len() == 1 {
if let Ok(entry) = &sources[0] {
if destination.exists() && destination.is_dir() {
destination = dunce::canonicalize(&destination).unwrap();
destination.push(source.file_name().unwrap());
}
if entry.is_file() {
match std::fs::rename(&entry, &destination) {
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Rename {:?} to {:?} aborted. {:}",
entry.file_name().unwrap(),
destination.file_name().unwrap(),
e.to_string(),
),
format!(
"Rename {:?} to {:?} aborted. {:}",
entry.file_name().unwrap(),
destination.file_name().unwrap(),
e.to_string(),
),
span,
));
}
Ok(o) => o,
};
}
if entry.is_dir() {
match std::fs::create_dir_all(&destination) {
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Rename {:?} to {:?} aborted. {:}",
entry.file_name().unwrap(),
destination.file_name().unwrap(),
e.to_string(),
),
format!(
"Rename {:?} to {:?} aborted. {:}",
entry.file_name().unwrap(),
destination.file_name().unwrap(),
e.to_string(),
),
span,
));
}
Ok(o) => o,
};
#[cfg(not(windows))]
{
match std::fs::rename(&entry, &destination) {
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Rename {:?} to {:?} aborted. {:}",
entry.file_name().unwrap(),
destination.file_name().unwrap(),
e.to_string(),
),
format!(
"Rename {:?} to {:?} aborted. {:}",
entry.file_name().unwrap(),
destination.file_name().unwrap(),
e.to_string(),
),
span,
));
}
Ok(o) => o,
};
}
#[cfg(windows)]
{
let mut sources: FileStructure = FileStructure::new();
sources.walk_decorate(&entry);
let strategy = |(source_file, depth_level)| {
let mut new_dst = destination.clone();
let path = dunce::canonicalize(&source_file).unwrap();
let mut comps: Vec<_> = path
.components()
.map(|fragment| fragment.as_os_str())
.rev()
.take(1 + depth_level)
.collect();
comps.reverse();
for fragment in comps.iter() {
new_dst.push(fragment);
}
(PathBuf::from(&source_file), PathBuf::from(new_dst))
};
for (ref src, ref dst) in sources.paths_applying_with(strategy) {
if src.is_dir() {
if !dst.exists() {
match std::fs::create_dir_all(dst) {
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Rename {:?} to {:?} aborted. {:}",
entry.file_name().unwrap(),
destination.file_name().unwrap(),
e.to_string(),
),
format!(
"Rename {:?} to {:?} aborted. {:}",
entry.file_name().unwrap(),
destination.file_name().unwrap(),
e.to_string(),
),
span,
));
}
Ok(o) => o,
};
}
}
if src.is_file() {
match std::fs::rename(src, dst) {
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Rename {:?} to {:?} aborted. {:}",
entry.file_name().unwrap(),
destination.file_name().unwrap(),
e.to_string(),
),
format!(
"Rename {:?} to {:?} aborted. {:}",
entry.file_name().unwrap(),
destination.file_name().unwrap(),
e.to_string(),
),
span,
));
}
Ok(o) => o,
};
}
}
std::fs::remove_dir_all(entry).expect("can not remove directory");
}
}
}
} else {
if destination.exists() {
if !sources.iter().all(|x| (x.as_ref().unwrap()).is_file()) {
return Err(ShellError::labeled_error(
"Rename aborted (directories found).",
"Rename aborted (directories found).",
args.nth(0).unwrap().span(),
));
}
for entry in sources {
if let Ok(entry) = entry {
let mut to = PathBuf::from(&destination);
to.push(&entry.file_name().unwrap());
if entry.is_file() {
match std::fs::rename(&entry, &to) {
Err(e) => {
return Err(ShellError::labeled_error(
format!(
"Rename {:?} to {:?} aborted. {:}",
entry.file_name().unwrap(),
destination.file_name().unwrap(),
e.to_string(),
),
format!(
"Rename {:?} to {:?} aborted. {:}",
entry.file_name().unwrap(),
destination.file_name().unwrap(),
e.to_string(),
),
span,
));
}
Ok(o) => o,
};
}
}
}
} else {
return Err(ShellError::labeled_error(
format!(
"Rename aborted. (Does {:?} exist?)",
&destination.file_name().unwrap()
),
format!(
"Rename aborted. (Does {:?} exist?)",
&destination.file_name().unwrap()
),
args.nth(1).unwrap().span(),
));
}
}
Ok(OutputStream::empty())
}

222
tests/command_mv_tests.rs Normal file
View file

@ -0,0 +1,222 @@
mod helpers;
use h::{in_directory as cwd, Playground, Stub::*};
use helpers as h;
use std::path::{Path, PathBuf};
#[test]
fn moves_a_file() {
let sandbox = Playground::setup_for("mv_test_1")
.with_files(vec![
EmptyFile("andres.txt"),
])
.mkdir("expected")
.test_dir_name();
let full_path = format!("{}/{}", Playground::root(), sandbox);
let original = format!("{}/{}", full_path, "andres.txt");
let expected = format!("{}/{}", full_path, "expected/yehuda.txt");
nu!(
_output,
cwd(&full_path),
"mv andres.txt expected/yehuda.txt"
);
assert!(!h::file_exists_at(PathBuf::from(original)));
assert!(h::file_exists_at(PathBuf::from(expected)));
}
#[test]
fn overwrites_if_moving_to_existing_file() {
let sandbox = Playground::setup_for("mv_test_2")
.with_files(vec![
EmptyFile("andres.txt"),
EmptyFile("jonathan.txt"),
])
.test_dir_name();
let full_path = format!("{}/{}", Playground::root(), sandbox);
let original = format!("{}/{}", full_path, "andres.txt");
let expected = format!("{}/{}", full_path, "jonathan.txt");
nu!(
_output,
cwd(&full_path),
"mv andres.txt jonathan.txt"
);
assert!(!h::file_exists_at(PathBuf::from(original)));
assert!(h::file_exists_at(PathBuf::from(expected)));
}
#[test]
fn moves_a_directory() {
let sandbox = Playground::setup_for("mv_test_3")
.mkdir("empty_dir")
.test_dir_name();
let full_path = format!("{}/{}", Playground::root(), sandbox);
let original_dir = format!("{}/{}", full_path, "empty_dir");
let expected = format!("{}/{}", full_path, "renamed_dir");
nu!(
_output,
cwd(&full_path),
"mv empty_dir renamed_dir"
);
assert!(!h::dir_exists_at(PathBuf::from(original_dir)));
assert!(h::dir_exists_at(PathBuf::from(expected)));
}
#[test]
fn moves_the_file_inside_directory_if_path_to_move_is_existing_directory() {
let sandbox = Playground::setup_for("mv_test_4")
.with_files(vec![
EmptyFile("jonathan.txt"),
])
.mkdir("expected")
.test_dir_name();
let full_path = format!("{}/{}", Playground::root(), sandbox);
let original_dir = format!("{}/{}", full_path, "jonathan.txt");
let expected = format!("{}/{}", full_path, "expected/jonathan.txt");
nu!(
_output,
cwd(&full_path),
"mv jonathan.txt expected"
);
assert!(!h::file_exists_at(PathBuf::from(original_dir)));
assert!(h::file_exists_at(PathBuf::from(expected)));
}
#[test]
fn moves_the_directory_inside_directory_if_path_to_move_is_existing_directory() {
let sandbox = Playground::setup_for("mv_test_5")
.within("contributors")
.with_files(vec![
EmptyFile("jonathan.txt"),
])
.mkdir("expected")
.test_dir_name();
let full_path = format!("{}/{}", Playground::root(), sandbox);
let original_dir = format!("{}/{}", full_path, "contributors");
let expected = format!("{}/{}", full_path, "expected/contributors");
nu!(
_output,
cwd(&full_path),
"mv contributors expected"
);
assert!(!h::dir_exists_at(PathBuf::from(original_dir)));
assert!(h::file_exists_at(PathBuf::from(expected)));
}
#[test]
fn moves_the_directory_inside_directory_if_path_to_move_is_nonexistent_directory() {
let sandbox = Playground::setup_for("mv_test_6")
.within("contributors")
.with_files(vec![
EmptyFile("jonathan.txt"),
])
.mkdir("expected")
.test_dir_name();
let full_path = format!("{}/{}", Playground::root(), sandbox);
let original_dir = format!("{}/{}", full_path, "contributors");
nu!(
_output,
cwd(&full_path),
"mv contributors expected/this_dir_exists_now/los_tres_amigos"
);
let expected = format!("{}/{}", full_path, "expected/this_dir_exists_now/los_tres_amigos");
assert!(!h::dir_exists_at(PathBuf::from(original_dir)));
assert!(h::file_exists_at(PathBuf::from(expected)));
}
#[test]
fn moves_using_path_with_wildcard() {
let sandbox = Playground::setup_for("mv_test_7")
.within("originals")
.with_files(vec![
EmptyFile("andres.ini"),
EmptyFile("caco3_plastics.csv"),
EmptyFile("cargo_sample.toml"),
EmptyFile("jonathan.ini"),
EmptyFile("jonathan.xml"),
EmptyFile("sgml_description.json"),
EmptyFile("sample.ini"),
EmptyFile("utf16.ini"),
EmptyFile("yehuda.ini"),
])
.mkdir("work_dir")
.mkdir("expected")
.test_dir_name();
let full_path = format!("{}/{}", Playground::root(), sandbox);
let work_dir = format!("{}/{}", full_path, "work_dir");
let expected_copies_path = format!("{}/{}", full_path, "expected");
nu!(
_output,
cwd(&work_dir),
"mv ../originals/*.ini ../expected"
);
assert!(h::files_exist_at(
vec![
Path::new("yehuda.ini"),
Path::new("jonathan.ini"),
Path::new("sample.ini"),
Path::new("andres.ini"),
],
PathBuf::from(&expected_copies_path)
));
}
#[test]
fn moves_using_a_glob() {
let sandbox = Playground::setup_for("mv_test_8")
.within("meals")
.with_files(vec![
EmptyFile("arepa.txt"),
EmptyFile("empanada.txt"),
EmptyFile("taquiza.txt"),
])
.mkdir("work_dir")
.mkdir("expected")
.test_dir_name();
let full_path = format!("{}/{}", Playground::root(), sandbox);
let meal_dir = format!("{}/{}", full_path, "meals");
let work_dir = format!("{}/{}", full_path, "work_dir");
let expected_copies_path = format!("{}/{}", full_path, "expected");
nu!(
_output,
cwd(&work_dir),
"mv ../meals/* ../expected"
);
assert!(h::dir_exists_at(PathBuf::from(meal_dir)));
assert!(h::files_exist_at(
vec![
Path::new("arepa.txt"),
Path::new("empanada.txt"),
Path::new("taquiza.txt"),
],
PathBuf::from(&expected_copies_path)
));
}