forked from extern/nushell
mv introduced. \¡Viva\!
This commit is contained in:
parent
dfcbaed1c6
commit
154063013f
@ -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 |
|
||||
|
@ -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),
|
||||
|
@ -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
279
src/commands/mv.rs
Normal 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
222
tests/command_mv_tests.rs
Normal 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)
|
||||
));
|
||||
}
|
Loading…
Reference in New Issue
Block a user