1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 03:57:44 +00:00

Merge pull request #1046 from Matt8898/cp

cp: Add -r flag
This commit is contained in:
Alex Lyon 2017-07-02 14:12:08 -07:00 committed by GitHub
commit 7ef27acb05
3 changed files with 134 additions and 42 deletions

View file

@ -11,6 +11,7 @@ path = "cp.rs"
getopts = "*" getopts = "*"
libc = "*" libc = "*"
uucore = { path="../uucore" } uucore = { path="../uucore" }
walkdir = "*"
[[bin]] [[bin]]
name = "cp" name = "cp"

View file

@ -14,6 +14,10 @@ extern crate getopts;
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
extern crate walkdir;
use walkdir::WalkDir;
use getopts::Options; use getopts::Options;
use std::fs; use std::fs;
use std::io::{ErrorKind, Result, Write}; use std::io::{ErrorKind, Result, Write};
@ -34,9 +38,15 @@ pub fn uumain(args: Vec<String>) -> i32 {
let mut opts = Options::new(); let mut opts = Options::new();
opts.optflag("h", "help", "display this help and exit"); opts.optflag("h", "help", "display this help and exit");
opts.optflag("r", "recursive", "copy directories recursively");
opts.optflag("", "version", "output version information and exit"); opts.optflag("", "version", "output version information and exit");
opts.optopt("t", "target-directory", "copy all SOURCE arguments into DIRECTORY", "DEST"); opts.optopt("t",
opts.optflag("T", "no-target-directory", "Treat DEST as a regular file and not a directory"); "target-directory",
"copy all SOURCE arguments into DIRECTORY",
"DEST");
opts.optflag("T",
"no-target-directory",
"Treat DEST as a regular file and not a directory");
opts.optflag("v", "verbose", "explicitly state what is being done"); opts.optflag("v", "verbose", "explicitly state what is being done");
let matches = match opts.parse(&args[1..]) { let matches = match opts.parse(&args[1..]) {
@ -44,7 +54,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
Err(e) => { Err(e) => {
show_error!("{}", e); show_error!("{}", e);
panic!() panic!()
}, }
}; };
let usage = opts.usage("Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY."); let usage = opts.usage("Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.");
let mode = if matches.opt_present("version") { let mode = if matches.opt_present("version") {
@ -56,8 +66,8 @@ pub fn uumain(args: Vec<String>) -> i32 {
}; };
match mode { match mode {
Mode::Copy => copy(matches), Mode::Copy => copy(matches),
Mode::Help => help(&usage), Mode::Help => help(&usage),
Mode::Version => version(), Mode::Version => version(),
} }
@ -74,7 +84,10 @@ fn help(usage: &str) {
or: {0} SOURCE... DIRECTORY\n \ or: {0} SOURCE... DIRECTORY\n \
or: {0} -t DIRECTORY SOURCE...\n\ or: {0} -t DIRECTORY SOURCE...\n\
\n\ \n\
{2}", NAME, VERSION, usage); {2}",
NAME,
VERSION,
usage);
println!("{}", msg); println!("{}", msg);
} }
@ -84,12 +97,19 @@ fn copy(matches: getopts::Matches) {
show_error!("Missing SOURCE or DEST argument. Try --help."); show_error!("Missing SOURCE or DEST argument. Try --help.");
panic!() panic!()
} else if !matches.opt_present("target-directory") { } else if !matches.opt_present("target-directory") {
matches.free[..matches.free.len() - 1].iter().cloned().collect() matches.free[..matches.free.len() - 1]
.iter()
.cloned()
.collect()
} else { } else {
matches.free.iter().cloned().collect() matches.free.iter().cloned().collect()
}; };
let recursive: bool = matches.opt_present("recursive");
let dest_str = if matches.opt_present("target-directory") { let dest_str = if matches.opt_present("target-directory") {
matches.opt_str("target-directory").expect("Option -t/--target-directory requires an argument") matches
.opt_str("target-directory")
.expect("Option -t/--target-directory requires an argument")
} else { } else {
matches.free[matches.free.len() - 1].clone() matches.free[matches.free.len() - 1].clone()
}; };
@ -100,7 +120,8 @@ fn copy(matches: getopts::Matches) {
//the argument to the -t/--target-directory= options //the argument to the -t/--target-directory= options
let path = Path::new(&dest_str); let path = Path::new(&dest_str);
if !path.is_dir() && matches.opt_present("target-directory") { if !path.is_dir() && matches.opt_present("target-directory") {
show_error!("Target {} is not a directory", matches.opt_str("target-directory").unwrap()); show_error!("Target {} is not a directory",
matches.opt_str("target-directory").unwrap());
panic!() panic!()
} else { } else {
path path
@ -110,38 +131,59 @@ fn copy(matches: getopts::Matches) {
assert!(sources.len() >= 1); assert!(sources.len() >= 1);
if matches.opt_present("no-target-directory") && dest.is_dir() { if matches.opt_present("no-target-directory") && dest.is_dir() {
show_error!("Can't overwrite directory {} with non-directory", dest.display()); show_error!("Can't overwrite directory {} with non-directory",
dest.display());
panic!() panic!()
} }
if sources.len() == 1 { if sources.len() == 1 {
let source = Path::new(&sources[0]); let source = Path::new(&sources[0]);
let same_file = paths_refer_to_same_file(source, dest).unwrap_or_else(|err| { let same_file =
match err.kind() { paths_refer_to_same_file(source, dest).unwrap_or_else(|err| match err.kind() {
ErrorKind::NotFound => false, ErrorKind::NotFound => false,
_ => { _ => {
show_error!("{}", err); show_error!("{}", err);
panic!() panic!()
}
} }
}); });
if same_file { if same_file {
show_error!("\"{}\" and \"{}\" are the same file", show_error!("\"{}\" and \"{}\" are the same file",
source.display(), source.display(),
dest.display()); dest.display());
panic!(); panic!();
} }
let mut full_dest = dest.to_path_buf(); let mut full_dest = dest.to_path_buf();
if dest.is_dir() { if recursive {
full_dest.push(source.file_name().unwrap()); //the destination path is the destination for entry in WalkDir::new(source) {
} // directory + the file name we're copying let entry = entry.unwrap();
if verbose { if entry.path().is_dir() {
println!("{} -> {}", source.display(), full_dest.display()); let mut dst_path = full_dest.clone();
} dst_path.push(entry.path());
if let Err(err) = fs::copy(source, full_dest) { if let Err(err) = fs::create_dir(dst_path) {
show_error!("{}", err); show_error!("{}", err);
panic!(); panic!();
}
} else {
let mut dst_path = full_dest.clone();
dst_path.push(entry.path());
if let Err(err) = fs::copy(entry.path(), dst_path) {
show_error!("{}", err);
panic!();
}
}
}
} else {
if dest.is_dir() {
full_dest.push(source.file_name().unwrap()); //the destination path is the destination
} // directory + the file name we're copying
if verbose {
println!("{} -> {}", source.display(), full_dest.display());
}
if let Err(err) = fs::copy(source, full_dest) {
show_error!("{}", err);
panic!();
}
} }
} else { } else {
if !dest.is_dir() { if !dest.is_dir() {
@ -151,24 +193,47 @@ fn copy(matches: getopts::Matches) {
for src in &sources { for src in &sources {
let source = Path::new(&src); let source = Path::new(&src);
if !source.is_file() { if !recursive {
show_error!("\"{}\" is not a file", source.display()); if !source.is_file() {
continue; show_error!("\"{}\" is not a file", source.display());
} continue;
}
let mut full_dest = dest.to_path_buf(); let mut full_dest = dest.to_path_buf();
full_dest.push(source.file_name().unwrap()); full_dest.push(source.file_name().unwrap());
if verbose { if verbose {
println!("{} -> {}", source.display(), full_dest.display()); println!("{} -> {}", source.display(), full_dest.display());
} }
let io_result = fs::copy(source, full_dest); let io_result = fs::copy(source, full_dest);
if let Err(err) = io_result { if let Err(err) = io_result {
show_error!("{}", err); show_error!("{}", err);
panic!() panic!()
}
} else {
for entry in WalkDir::new(source) {
let entry = entry.unwrap();
let full_dest = dest.to_path_buf();
if entry.path().is_dir() {
let mut dst_path = full_dest.clone();
dst_path.push(entry.path());
if let Err(err) = fs::create_dir(dst_path) {
show_error!("{}", err);
panic!();
}
} else {
let mut dst_path = full_dest.clone();
dst_path.push(entry.path());
if let Err(err) = fs::copy(entry.path(), dst_path) {
show_error!("{}", err);
panic!();
}
}
}
} }
} }
} }

View file

@ -4,6 +4,7 @@ static TEST_HELLO_WORLD_SOURCE: &'static str = "hello_world.txt";
static TEST_HELLO_WORLD_DEST: &'static str = "copy_of_hello_world.txt"; static TEST_HELLO_WORLD_DEST: &'static str = "copy_of_hello_world.txt";
static TEST_COPY_TO_FOLDER: &'static str = "hello_dir/"; static TEST_COPY_TO_FOLDER: &'static str = "hello_dir/";
static TEST_COPY_TO_FOLDER_FILE: &'static str = "hello_dir/hello_world.txt"; static TEST_COPY_TO_FOLDER_FILE: &'static str = "hello_dir/hello_world.txt";
static TEST_COPY_FROM_FOLDER: &'static str = "hello_dir_with_file/";
static TEST_COPY_FROM_FOLDER_FILE: &'static str = "hello_dir_with_file/hello_world.txt"; static TEST_COPY_FROM_FOLDER_FILE: &'static str = "hello_dir_with_file/hello_world.txt";
#[test] #[test]
@ -22,6 +23,31 @@ fn test_cp_cp() {
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
} }
#[test]
fn test_cp_recurse() {
//let (at, mut ucmd) = at_and_ucmd!();
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
// Invoke our binary to make the copy.
let result_to_dir = scene.ucmd()
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_COPY_TO_FOLDER)
.run();
assert!(result_to_dir.success);
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
let result = scene.ucmd()
.arg("-r")
.arg(TEST_COPY_FROM_FOLDER)
.arg(TEST_COPY_TO_FOLDER)
.run();
assert!(result.success);
// Check the content of the destination file that was copied.
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
}
#[test] #[test]
fn test_cp_with_dirs_t() { fn test_cp_with_dirs_t() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();