From 5e03b7c75dc93445d21a36257d4991e4219a6ba3 Mon Sep 17 00:00:00 2001 From: Jeremy Neptune Date: Fri, 15 Jul 2016 11:34:49 -0400 Subject: [PATCH 1/6] cp: Added -t flag, and fixed path bug for source files. * Added flag -t/--target-directory * No longer assumes that the source arguments are files in the CWD (in other words, can copy files from directories other than CWD) --- src/cp/cp.rs | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/cp/cp.rs b/src/cp/cp.rs index 24068e3b2..7d48d42be 100644 --- a/src/cp/cp.rs +++ b/src/cp/cp.rs @@ -34,7 +34,8 @@ pub fn uumain(args: Vec) -> i32 { let mut opts = Options::new(); opts.optflag("h", "help", "display this help and exit"); - opts.optflag("", "version", "output version information and exit"); + opts.optflag("v", "version", "output version information and exit"); + opts.optopt("t", "target-directory", "copy all SOURCE arguments into DIRECTORY", "DEST"); let matches = match opts.parse(&args[1..]) { Ok(m) => m, @@ -70,26 +71,40 @@ fn help(usage: &str) { let msg = format!("{0} {1}\n\n\ Usage: {0} SOURCE DEST\n \ or: {0} SOURCE... DIRECTORY\n \ - or: {0} -t DIRECTORY SOURCE\n\ + or: {0} -t DIRECTORY SOURCE...\n\ \n\ {2}", NAME, VERSION, usage); println!("{}", msg); } fn copy(matches: getopts::Matches) { + let sources: Vec = if matches.free.is_empty() { - show_error!("Missing SOURCE argument. Try --help."); + show_error!("Missing SOURCE or DEST argument. Try --help."); panic!() - } else { - // All but the last argument: + } else if !matches.opt_present("target-directory") { matches.free[..matches.free.len() - 1].iter().cloned().collect() + } else { + matches.free.iter().cloned().collect() }; - let dest = if matches.free.len() < 2 { + let dest_str = if matches.opt_present("target-directory") { + matches.opt_str("target-directory").expect("Option -t/--target-directory requires an argument") + } else { + matches.free[matches.free.len() - 1].clone() + }; + let dest = if matches.free.len() < 2 && !matches.opt_present("target-directory") { show_error!("Missing DEST argument. Try --help."); panic!() } else { - // Only the last argument: - Path::new(&matches.free[matches.free.len() - 1]) + //the argument to the -t/--target-directory= options + let path = Path::new(&dest_str); + if !path.is_dir() && matches.opt_present("target-directory") { + show_error!("Argument to -t/--target-directory must be a directory"); + panic!() + } else { + path + } + }; assert!(sources.len() >= 1); @@ -112,8 +127,11 @@ fn copy(matches: getopts::Matches) { dest.display()); panic!(); } - - if let Err(err) = fs::copy(source, dest) { + let mut full_dest = dest.to_path_buf(); + 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 let Err(err) = fs::copy(source, full_dest) { show_error!("{}", err); panic!(); } @@ -122,7 +140,6 @@ fn copy(matches: getopts::Matches) { show_error!("TARGET must be a directory"); panic!(); } - for src in &sources { let source = Path::new(&src); @@ -133,7 +150,7 @@ fn copy(matches: getopts::Matches) { let mut full_dest = dest.to_path_buf(); - full_dest.push(source.to_str().unwrap()); + full_dest.push(source.file_name().unwrap()); println!("{}", full_dest.display()); From 567a63257ade5b0d0821438f3500251b2328f220 Mon Sep 17 00:00:00 2001 From: Jeremy Neptune Date: Fri, 15 Jul 2016 13:17:30 -0400 Subject: [PATCH 2/6] cp: wrote tests for the -t flag, and copying to and from directories --- tests/test_cp.rs | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/tests/test_cp.rs b/tests/test_cp.rs index d1952ae0f..3832e3f56 100644 --- a/tests/test_cp.rs +++ b/tests/test_cp.rs @@ -1,9 +1,11 @@ use common::util::*; - static UTIL_NAME: &'static str = "cp"; static TEST_HELLO_WORLD_SOURCE: &'static str = "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_FILE: &'static str = "hello_dir/hello_world.txt"; +static TEST_COPY_FROM_FOLDER_FILE: &'static str = "hello_dir_with_file/hello_world.txt"; #[test] fn test_cp_cp() { @@ -15,8 +17,44 @@ fn test_cp_cp() { // Check that the exit code represents a successful copy. let exit_success = result.success; - assert_eq!(exit_success, true); + assert!(exit_success); // Check the content of the destination file that was copied. assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); } + +#[test] +fn test_cp_with_dirs_t() { + let ts = TestSet::new(UTIL_NAME); + let at = &ts.fixtures; + + //using -t option + let result_to_dir_t = ts.util_cmd() + .arg("-t") + .arg(TEST_COPY_TO_FOLDER) + .arg(TEST_HELLO_WORLD_SOURCE) + .run(); + assert!(result_to_dir_t.success); + assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); +} + +#[test] +fn test_cp_with_dirs() { + let ts = TestSet::new(UTIL_NAME); + let at = &ts.fixtures; + + //using -t option + let result_to_dir_t = ts.util_cmd() + .arg(TEST_HELLO_WORLD_SOURCE) + .arg(TEST_COPY_TO_FOLDER) + .run(); + assert!(result_to_dir_t.success); + assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); + + let result_from_dir_t = ts.util_cmd() + .arg(TEST_COPY_FROM_FOLDER_FILE) + .arg(TEST_HELLO_WORLD_DEST) + .run(); + assert!(result_from_dir_t.success); + assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); +} \ No newline at end of file From 97bb134fc9f37c26fcef996428a4c26b583b6346 Mon Sep 17 00:00:00 2001 From: Jeremy Neptune Date: Fri, 15 Jul 2016 13:23:12 -0400 Subject: [PATCH 3/6] cp: added fixtures required for testing --- tests/fixtures/cp/hello_dir_with_file/hello_world.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/fixtures/cp/hello_dir_with_file/hello_world.txt diff --git a/tests/fixtures/cp/hello_dir_with_file/hello_world.txt b/tests/fixtures/cp/hello_dir_with_file/hello_world.txt new file mode 100644 index 000000000..8ab686eaf --- /dev/null +++ b/tests/fixtures/cp/hello_dir_with_file/hello_world.txt @@ -0,0 +1 @@ +Hello, World! From cc57ce7699a62d7896a41389885afd24c2e7ae52 Mon Sep 17 00:00:00 2001 From: Jeremy Neptune Date: Fri, 15 Jul 2016 14:08:04 -0400 Subject: [PATCH 4/6] cp: added -T/--no-target-directory flag --- src/cp/cp.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/cp/cp.rs b/src/cp/cp.rs index 7d48d42be..b3255dd9b 100644 --- a/src/cp/cp.rs +++ b/src/cp/cp.rs @@ -36,6 +36,7 @@ pub fn uumain(args: Vec) -> i32 { opts.optflag("h", "help", "display this help and exit"); opts.optflag("v", "version", "output version information and exit"); opts.optopt("t", "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"); let matches = match opts.parse(&args[1..]) { Ok(m) => m, @@ -99,7 +100,7 @@ fn copy(matches: getopts::Matches) { //the argument to the -t/--target-directory= options let path = Path::new(&dest_str); if !path.is_dir() && matches.opt_present("target-directory") { - show_error!("Argument to -t/--target-directory must be a directory"); + show_error!("Target {} is not a directory", matches.opt_str("target-directory").unwrap()); panic!() } else { path @@ -108,6 +109,10 @@ fn copy(matches: getopts::Matches) { }; assert!(sources.len() >= 1); + if matches.opt_present("no-target-directory") && dest.is_dir() { + show_error!("Can't overwrite directory {} with non-directory", dest.display()); + panic!() + } if sources.len() == 1 { let source = Path::new(&sources[0]); From a3004fbbff00703ac37938c42b4e07f227c0046b Mon Sep 17 00:00:00 2001 From: Jeremy Neptune Date: Fri, 15 Jul 2016 14:41:50 -0400 Subject: [PATCH 5/6] cp: added -v/--verbose flag I forgot that -v refers to "verbose" and not "version" when making earlier changes. So I fixed that and for good measure added the verbose flag anyway. --- src/cp/cp.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/cp/cp.rs b/src/cp/cp.rs index b3255dd9b..102f9eda3 100644 --- a/src/cp/cp.rs +++ b/src/cp/cp.rs @@ -34,9 +34,10 @@ pub fn uumain(args: Vec) -> i32 { let mut opts = Options::new(); opts.optflag("h", "help", "display this help and exit"); - opts.optflag("v", "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.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"); let matches = match opts.parse(&args[1..]) { Ok(m) => m, @@ -45,7 +46,6 @@ pub fn uumain(args: Vec) -> i32 { panic!() }, }; - let usage = opts.usage("Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY."); let mode = if matches.opt_present("version") { Mode::Version @@ -79,7 +79,7 @@ fn help(usage: &str) { } fn copy(matches: getopts::Matches) { - + let verbose = matches.opt_present("verbose"); let sources: Vec = if matches.free.is_empty() { show_error!("Missing SOURCE or DEST argument. Try --help."); panic!() @@ -136,6 +136,9 @@ fn copy(matches: getopts::Matches) { 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!(); @@ -157,7 +160,9 @@ fn copy(matches: getopts::Matches) { full_dest.push(source.file_name().unwrap()); - println!("{}", full_dest.display()); + if verbose { + println!("{} -> {}", source.display(), full_dest.display()); + } let io_result = fs::copy(source, full_dest); From b59016cbd7ec375028a7e940ca78e4a172876db7 Mon Sep 17 00:00:00 2001 From: Jeremy Neptune Date: Fri, 15 Jul 2016 15:29:47 -0400 Subject: [PATCH 6/6] cp: fixed failing tests due to git's weirdness with files Made variable names in test_cp.rs more descriptive --- tests/fixtures/cp/hello_dir/hello.txt | 0 tests/test_cp.rs | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/cp/hello_dir/hello.txt diff --git a/tests/fixtures/cp/hello_dir/hello.txt b/tests/fixtures/cp/hello_dir/hello.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_cp.rs b/tests/test_cp.rs index 3832e3f56..e5b86d8a7 100644 --- a/tests/test_cp.rs +++ b/tests/test_cp.rs @@ -44,17 +44,17 @@ fn test_cp_with_dirs() { let at = &ts.fixtures; //using -t option - let result_to_dir_t = ts.util_cmd() + let result_to_dir = ts.util_cmd() .arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_COPY_TO_FOLDER) .run(); - assert!(result_to_dir_t.success); + assert!(result_to_dir.success); assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n"); - let result_from_dir_t = ts.util_cmd() + let result_from_dir = ts.util_cmd() .arg(TEST_COPY_FROM_FOLDER_FILE) .arg(TEST_HELLO_WORLD_DEST) .run(); - assert!(result_from_dir_t.success); + assert!(result_from_dir.success); assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n"); } \ No newline at end of file