mv: cleanup the code

This commit is contained in:
Knight 2016-08-21 14:57:28 +08:00
parent 2981eb5112
commit a4bf852207
4 changed files with 123 additions and 77 deletions

View file

@ -194,7 +194,7 @@ To do
* [x] mkfifo * [x] mkfifo
* [x] mknod * [x] mknod
* [x] mktemp * [x] mktemp
* [ ] mv (almost done, one more option) * [x] mv
* [x] nice * [x] nice
* [x] nl * [x] nl
* [x] nohup * [x] nohup

View file

@ -9,11 +9,10 @@ path = "mv.rs"
[dependencies] [dependencies]
getopts = "*" getopts = "*"
libc = "*"
uucore = { path="../uucore" }
[dev-dependencies] [dependencies.uucore]
time = "*" path = "../uucore"
default-features = false
[[bin]] [[bin]]
name = "mv" name = "mv"

View file

@ -1,22 +1,21 @@
#![crate_name = "uu_mv"] #![crate_name = "uu_mv"]
/* // This file is part of the uutils coreutils package.
* This file is part of the uutils coreutils package. //
* // (c) Orvar Segerström <orvarsegerstrom@gmail.com>
* (c) Orvar Segerström <orvarsegerstrom@gmail.com> // (c) Sokovikov Evgeniy <skv-headless@yandex.ru>
* (c) Sokovikov Evgeniy <skv-headless@yandex.ru> //
* // For the full copyright and license information, please view the LICENSE file
* For the full copyright and license information, please view the LICENSE file // that was distributed with this source code.
* that was distributed with this source code. //
*/
extern crate getopts; extern crate getopts;
extern crate libc;
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use std::fs; use std::fs;
use std::env;
use std::io::{BufRead, BufReader, Result, stdin, Write}; use std::io::{BufRead, BufReader, Result, stdin, Write};
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -41,7 +40,7 @@ pub enum OverwriteMode {
Force, Force,
} }
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Copy, Eq, PartialEq)]
pub enum BackupMode { pub enum BackupMode {
NoBackup, NoBackup,
SimpleBackup, SimpleBackup,
@ -52,17 +51,27 @@ pub enum BackupMode {
pub fn uumain(args: Vec<String>) -> i32 { pub fn uumain(args: Vec<String>) -> i32 {
let mut opts = getopts::Options::new(); let mut opts = getopts::Options::new();
opts.optflagopt("", "backup", "make a backup of each existing destination file", "CONTROL"); opts.optflagopt("",
"backup",
"make a backup of each existing destination file",
"CONTROL");
opts.optflag("b", "", "like --backup but does not accept an argument"); opts.optflag("b", "", "like --backup but does not accept an argument");
opts.optflag("f", "force", "do not prompt before overwriting"); opts.optflag("f", "force", "do not prompt before overwriting");
opts.optflag("i", "interactive", "prompt before override"); opts.optflag("i", "interactive", "prompt before override");
opts.optflag("n", "no-clobber", "do not overwrite an existing file"); opts.optflag("n", "no-clobber", "do not overwrite an existing file");
opts.optflag("", "strip-trailing-slashes", "remove any trailing slashes from each SOURCE\n \ opts.optflag("",
"strip-trailing-slashes",
"remove any trailing slashes from each SOURCE\n \
argument"); argument");
opts.optopt("S", "suffix", "override the usual backup suffix", "SUFFIX"); opts.optopt("S", "suffix", "override the usual backup suffix", "SUFFIX");
opts.optopt("t", "target-directory", "move all SOURCE arguments into DIRECTORY", "DIRECTORY"); opts.optopt("t",
"target-directory",
"move all SOURCE arguments into DIRECTORY",
"DIRECTORY");
opts.optflag("T", "no-target-directory", "treat DEST as a normal file"); opts.optflag("T", "no-target-directory", "treat DEST as a normal file");
opts.optflag("u", "update", "move only when the SOURCE file is newer\n \ opts.optflag("u",
"update",
"move only when the SOURCE file is newer\n \
than the destination file or when the\n \ than the destination file or when the\n \
destination file is missing"); destination file is missing");
opts.optflag("v", "verbose", "explain what is being done"); opts.optflag("v", "verbose", "explain what is being done");
@ -78,11 +87,11 @@ pub fn uumain(args: Vec<String>) -> i32 {
}; };
let usage = opts.usage("Move SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY."); let usage = opts.usage("Move SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.");
/* This does not exactly match the GNU implementation: // This does not exactly match the GNU implementation:
* The GNU mv defaults to Force, but if more than one of the // The GNU mv defaults to Force, but if more than one of the
* overwrite options are supplied, only the last takes effect. // overwrite options are supplied, only the last takes effect.
* To default to no-clobber in that situation seems safer: // To default to no-clobber in that situation seems safer:
*/ //
let overwrite_mode = if matches.opt_present("no-clobber") { let overwrite_mode = if matches.opt_present("no-clobber") {
OverwriteMode::NoClobber OverwriteMode::NoClobber
} else if matches.opt_present("interactive") { } else if matches.opt_present("interactive") {
@ -96,15 +105,19 @@ pub fn uumain(args: Vec<String>) -> i32 {
} else if matches.opt_present("backup") { } else if matches.opt_present("backup") {
match matches.opt_str("backup") { match matches.opt_str("backup") {
None => BackupMode::SimpleBackup, None => BackupMode::SimpleBackup,
Some(mode) => match &mode[..] { Some(mode) => {
"simple" | "never" => BackupMode::SimpleBackup, match &mode[..] {
"numbered" | "t" => BackupMode::NumberedBackup, "simple" | "never" => BackupMode::SimpleBackup,
"existing" | "nil" => BackupMode::ExistingBackup, "numbered" | "t" => BackupMode::NumberedBackup,
"none" | "off" => BackupMode::NoBackup, "existing" | "nil" => BackupMode::ExistingBackup,
x => { "none" | "off" => BackupMode::NoBackup,
show_error!("invalid argument {} for backup type\n\ x => {
Try '{} --help' for more information.", x, NAME); show_error!("invalid argument {} for backup type\n\
return 1; Try '{} --help' for more information.",
x,
NAME);
return 1;
}
} }
} }
} }
@ -114,7 +127,8 @@ pub fn uumain(args: Vec<String>) -> i32 {
if overwrite_mode == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup { if overwrite_mode == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup {
show_error!("options --backup and --no-clobber are mutually exclusive\n\ show_error!("options --backup and --no-clobber are mutually exclusive\n\
Try '{} --help' for more information.", NAME); Try '{} --help' for more information.",
NAME);
return 1; return 1;
} }
@ -123,12 +137,17 @@ pub fn uumain(args: Vec<String>) -> i32 {
Some(x) => x, Some(x) => x,
None => { None => {
show_error!("option '--suffix' requires an argument\n\ show_error!("option '--suffix' requires an argument\n\
Try '{} --help' for more information.", NAME); Try '{} --help' for more information.",
NAME);
return 1; return 1;
} }
} }
} else { } else {
"~".to_owned() if let (Ok(s), BackupMode::SimpleBackup) = (env::var("SIMPLE_BACKUP_SUFFIX"), backup_mode) {
s
} else {
"~".to_owned()
}
}; };
if matches.opt_present("T") && matches.opt_present("t") { if matches.opt_present("T") && matches.opt_present("t") {
@ -147,14 +166,11 @@ pub fn uumain(args: Vec<String>) -> i32 {
}; };
let paths: Vec<PathBuf> = { let paths: Vec<PathBuf> = {
fn string_to_path<'a>(s: &'a String) -> &'a Path {
Path::new(s)
};
fn strip_slashes<'a>(p: &'a Path) -> &'a Path { fn strip_slashes<'a>(p: &'a Path) -> &'a Path {
p.components().as_path() p.components().as_path()
} }
let to_owned = |p: &Path| p.to_owned(); let to_owned = |p: &Path| p.to_owned();
let arguments = matches.free.iter().map(string_to_path); let arguments = matches.free.iter().map(Path::new);
if matches.opt_present("strip-trailing-slashes") { if matches.opt_present("strip-trailing-slashes") {
arguments.map(strip_slashes).map(to_owned).collect() arguments.map(strip_slashes).map(to_owned).collect()
} else { } else {
@ -177,25 +193,29 @@ fn help(usage: &str) {
println!("{0} {1}\n\n\ println!("{0} {1}\n\n\
Usage: {0} SOURCE DEST\n \ Usage: {0} SOURCE DEST\n \
or: {0} SOURCE... DIRECTORY\n\n\ or: {0} SOURCE... DIRECTORY\n\n\
{2}", NAME, VERSION, usage); {2}",
NAME,
VERSION,
usage);
} }
fn exec(files: &[PathBuf], b: Behaviour) -> i32 { fn exec(files: &[PathBuf], b: Behaviour) -> i32 {
match b.target_dir { if let Some(ref name) = b.target_dir {
Some(ref name) => return move_files_into_dir(files, &PathBuf::from(name), &b), return move_files_into_dir(files, &PathBuf::from(name), &b);
None => {}
} }
match files.len() { match files.len() {
0 | 1 => { 0 | 1 => {
show_error!("missing file operand\n\ show_error!("missing file operand\n\
Try '{} --help' for more information.", NAME); Try '{} --help' for more information.",
NAME);
return 1; return 1;
}, }
2 => { 2 => {
let source = &files[0]; let source = &files[0];
let target = &files[1]; let target = &files[1];
if !source.exists() { if !source.exists() {
show_error!("cannot stat {}: No such file or directory", source.display()); show_error!("cannot stat {}: No such file or directory",
source.display());
return 1; return 1;
} }
@ -203,7 +223,7 @@ fn exec(files: &[PathBuf], b: Behaviour) -> i32 {
if b.no_target_dir { if b.no_target_dir {
if !source.is_dir() { if !source.is_dir() {
show_error!("cannot overwrite directory {} with non-directory", show_error!("cannot overwrite directory {} with non-directory",
target.display()); target.display());
return 1; return 1;
} }
@ -211,9 +231,9 @@ fn exec(files: &[PathBuf], b: Behaviour) -> i32 {
Err(e) => { Err(e) => {
show_error!("{}", e); show_error!("{}", e);
1 1
}, }
_ => 0 _ => 0,
} };
} }
return move_files_into_dir(&[source.clone()], target, &b); return move_files_into_dir(&[source.clone()], target, &b);
@ -227,11 +247,13 @@ fn exec(files: &[PathBuf], b: Behaviour) -> i32 {
_ => { _ => {
if b.no_target_dir { if b.no_target_dir {
show_error!("mv: extra operand {}\n\ show_error!("mv: extra operand {}\n\
Try '{} --help' for more information.", files[2].display(), NAME); Try '{} --help' for more information.",
files[2].display(),
NAME);
return 1; return 1;
} }
let target_dir = files.last().unwrap(); let target_dir = files.last().unwrap();
move_files_into_dir(&files[0..files.len()-1], target_dir, &b); move_files_into_dir(&files[..files.len() - 1], target_dir, &b);
} }
} }
0 0
@ -258,11 +280,17 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behaviour) -
if let Err(e) = rename(sourcepath, &targetpath, b) { if let Err(e) = rename(sourcepath, &targetpath, b) {
show_error!("mv: cannot move {} to {}: {}", show_error!("mv: cannot move {} to {}: {}",
sourcepath.display(), targetpath.display(), e); sourcepath.display(),
targetpath.display(),
e);
all_successful = false; all_successful = false;
} }
}; }
if all_successful { 0 } else { 1 } if all_successful {
0
} else {
1
}
} }
fn rename(from: &PathBuf, to: &PathBuf, b: &Behaviour) -> Result<()> { fn rename(from: &PathBuf, to: &PathBuf, b: &Behaviour) -> Result<()> {
@ -276,7 +304,7 @@ fn rename(from: &PathBuf, to: &PathBuf, b: &Behaviour) -> Result<()> {
if !read_yes() { if !read_yes() {
return Ok(()); return Ok(());
} }
}, }
OverwriteMode::Force => {} OverwriteMode::Force => {}
}; };
@ -284,7 +312,7 @@ fn rename(from: &PathBuf, to: &PathBuf, b: &Behaviour) -> Result<()> {
BackupMode::NoBackup => None, BackupMode::NoBackup => None,
BackupMode::SimpleBackup => Some(simple_backup_path(to, &b.suffix)), BackupMode::SimpleBackup => Some(simple_backup_path(to, &b.suffix)),
BackupMode::NumberedBackup => Some(numbered_backup_path(to)), BackupMode::NumberedBackup => Some(numbered_backup_path(to)),
BackupMode::ExistingBackup => Some(existing_backup_path(to, &b.suffix)) BackupMode::ExistingBackup => Some(existing_backup_path(to, &b.suffix)),
}; };
if let Some(ref p) = backup_path { if let Some(ref p) = backup_path {
try!(fs::rename(to, p)); try!(fs::rename(to, p));
@ -303,7 +331,7 @@ fn rename(from: &PathBuf, to: &PathBuf, b: &Behaviour) -> Result<()> {
print!("{} -> {}", from.display(), to.display()); print!("{} -> {}", from.display(), to.display());
match backup_path { match backup_path {
Some(path) => println!(" (backup: {})", path.display()), Some(path) => println!(" (backup: {})", path.display()),
None => println!("") None => println!(""),
} }
} }
Ok(()) Ok(())
@ -312,35 +340,35 @@ fn rename(from: &PathBuf, to: &PathBuf, b: &Behaviour) -> Result<()> {
fn read_yes() -> bool { fn read_yes() -> bool {
let mut s = String::new(); let mut s = String::new();
match BufReader::new(stdin()).read_line(&mut s) { match BufReader::new(stdin()).read_line(&mut s) {
Ok(_) => match s.char_indices().nth(0) { Ok(_) => {
Some((_, x)) => x == 'y' || x == 'Y', match s.chars().nth(0) {
_ => false Some(x) => x == 'y' || x == 'Y',
}, _ => false,
_ => false }
}
_ => false,
} }
} }
fn simple_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { fn simple_backup_path(path: &PathBuf, suffix: &str) -> PathBuf {
let mut p = path.as_os_str().to_str().unwrap().to_owned(); let mut p = path.to_string_lossy().into_owned();
p.push_str(suffix); p.push_str(suffix);
PathBuf::from(p) PathBuf::from(p)
} }
fn numbered_backup_path(path: &PathBuf) -> PathBuf { fn numbered_backup_path(path: &PathBuf) -> PathBuf {
let mut i: u64 = 1; (1_u64..)
loop { .map(|i| path.with_extension(format!("~{}~", i)))
let new_path = simple_backup_path(path, &format!(".~{}~", i)); .skip_while(|p| p.exists())
if !new_path.exists() { .next()
return new_path; .expect("cannot create backup")
}
i = i + 1;
}
} }
fn existing_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { fn existing_backup_path(path: &PathBuf, suffix: &str) -> PathBuf {
let test_path = simple_backup_path(path, &".~1~".to_owned()); let test_path = path.with_extension("~1~");
if test_path.exists() { if test_path.exists() {
return numbered_backup_path(path); numbered_backup_path(path)
} else {
simple_backup_path(path, suffix)
} }
simple_backup_path(path, suffix)
} }

View file

@ -208,6 +208,25 @@ fn test_mv_custom_backup_suffix() {
assert!(at.file_exists(&format!("{}{}", file_b, suffix))); assert!(at.file_exists(&format!("{}{}", file_b, suffix)));
} }
#[test]
fn test_mv_custom_backup_suffix_via_env() {
let (at, mut ucmd) = at_and_ucmd();
let file_a = "test_mv_custom_backup_suffix_file_a";
let file_b = "test_mv_custom_backup_suffix_file_b";
let suffix = "super-suffix-of-the-century";
at.touch(file_a);
at.touch(file_b);
ucmd.arg("-b")
.env("SIMPLE_BACKUP_SUFFIX", suffix)
.arg(file_a)
.arg(file_b)
.succeeds().no_stderr();
assert!(!at.file_exists(file_a));
assert!(at.file_exists(file_b));
assert!(at.file_exists(&format!("{}{}", file_b, suffix)));
}
#[test] #[test]
fn test_mv_backup_numbering() { fn test_mv_backup_numbering() {
let (at, mut ucmd) = at_and_ucmd(); let (at, mut ucmd) = at_and_ucmd();