diff --git a/Cargo.lock b/Cargo.lock index 7d277b45f..09ecece47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,112 +1,3 @@ -[root] -name = "uutils" -version = "0.0.1" -dependencies = [ - "arch 0.0.1", - "base32 0.0.1", - "base64 0.0.1", - "basename 0.0.1", - "cat 0.0.1", - "chgrp 0.0.1", - "chmod 0.0.1", - "chown 0.0.1", - "chroot 0.0.1", - "cksum 0.0.1", - "comm 0.0.1", - "cp 0.0.1", - "cut 0.0.1", - "date 0.0.1", - "dircolors 0.0.1", - "dirname 0.0.1", - "du 0.0.1", - "echo 0.0.1", - "env 0.0.1", - "expand 0.0.1", - "expr 0.0.1", - "factor 0.0.1", - "false 0.0.1", - "filetime 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "fmt 0.0.1", - "fold 0.0.1", - "groups 0.0.1", - "hashsum 0.0.1", - "head 0.0.1", - "hostid 0.0.1", - "hostname 0.0.1", - "id 0.0.1", - "install 0.0.1", - "join 0.0.1", - "kill 0.0.1", - "lazy_static 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", - "link 0.0.1", - "ln 0.0.1", - "logname 0.0.1", - "ls 0.0.1", - "mkdir 0.0.1", - "mkfifo 0.0.1", - "mknod 0.0.1", - "mktemp 0.0.1", - "more 0.0.1", - "mv 0.0.1", - "nice 0.0.1", - "nl 0.0.1", - "nohup 0.0.1", - "nproc 0.0.1", - "numfmt 0.0.1", - "od 0.0.1", - "paste 0.0.1", - "pathchk 0.0.1", - "pinky 0.0.1", - "printenv 0.0.1", - "printf 0.0.1", - "ptx 0.0.1", - "pwd 0.0.1", - "rand 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", - "readlink 0.0.1", - "realpath 0.0.1", - "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "relpath 0.0.1", - "rm 0.0.1", - "rmdir 0.0.1", - "seq 0.0.1", - "shred 0.0.1", - "shuf 0.0.1", - "sleep 0.0.1", - "sort 0.0.1", - "split 0.0.1", - "stat 0.0.1", - "stdbuf 0.0.1", - "sum 0.0.1", - "sync 0.0.1", - "tac 0.0.1", - "tail 0.0.1", - "tee 0.0.1", - "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "test 0.0.1", - "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", - "timeout 0.0.1", - "touch 0.0.1", - "tr 0.0.1", - "true 0.0.1", - "truncate 0.0.1", - "tsort 0.0.1", - "tty 0.0.1", - "uname 0.0.1", - "unexpand 0.0.1", - "unindent 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "uniq 0.0.1", - "unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unlink 0.0.1", - "uptime 0.0.1", - "users 0.0.1", - "uucore 0.0.1", - "wc 0.0.1", - "who 0.0.1", - "whoami 0.0.1", - "yes 0.0.1", -] - [[package]] name = "advapi32-sys" version = "0.2.0" @@ -435,6 +326,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "false" version = "0.0.1" +dependencies = [ + "uucore 0.0.1", +] [[package]] name = "filetime" @@ -593,7 +487,7 @@ dependencies = [ name = "join" version = "0.0.1" dependencies = [ - "getopts 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.27.1 (registry+https://github.com/rust-lang/crates.io-index)", "uucore 0.0.1", ] @@ -1170,6 +1064,7 @@ version = "0.0.1" dependencies = [ "cc 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", + "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "libstdbuf 0.0.1", "uucore 0.0.1", ] @@ -1345,6 +1240,9 @@ dependencies = [ [[package]] name = "true" version = "0.0.1" +dependencies = [ + "uucore 0.0.1", +] [[package]] name = "truncate" @@ -1467,6 +1365,115 @@ dependencies = [ "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "uutils" +version = "0.0.1" +dependencies = [ + "arch 0.0.1", + "base32 0.0.1", + "base64 0.0.1", + "basename 0.0.1", + "cat 0.0.1", + "chgrp 0.0.1", + "chmod 0.0.1", + "chown 0.0.1", + "chroot 0.0.1", + "cksum 0.0.1", + "comm 0.0.1", + "cp 0.0.1", + "cut 0.0.1", + "date 0.0.1", + "dircolors 0.0.1", + "dirname 0.0.1", + "du 0.0.1", + "echo 0.0.1", + "env 0.0.1", + "expand 0.0.1", + "expr 0.0.1", + "factor 0.0.1", + "false 0.0.1", + "filetime 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "fmt 0.0.1", + "fold 0.0.1", + "groups 0.0.1", + "hashsum 0.0.1", + "head 0.0.1", + "hostid 0.0.1", + "hostname 0.0.1", + "id 0.0.1", + "install 0.0.1", + "join 0.0.1", + "kill 0.0.1", + "lazy_static 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "link 0.0.1", + "ln 0.0.1", + "logname 0.0.1", + "ls 0.0.1", + "mkdir 0.0.1", + "mkfifo 0.0.1", + "mknod 0.0.1", + "mktemp 0.0.1", + "more 0.0.1", + "mv 0.0.1", + "nice 0.0.1", + "nl 0.0.1", + "nohup 0.0.1", + "nproc 0.0.1", + "numfmt 0.0.1", + "od 0.0.1", + "paste 0.0.1", + "pathchk 0.0.1", + "pinky 0.0.1", + "printenv 0.0.1", + "printf 0.0.1", + "ptx 0.0.1", + "pwd 0.0.1", + "rand 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", + "readlink 0.0.1", + "realpath 0.0.1", + "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "relpath 0.0.1", + "rm 0.0.1", + "rmdir 0.0.1", + "seq 0.0.1", + "shred 0.0.1", + "shuf 0.0.1", + "sleep 0.0.1", + "sort 0.0.1", + "split 0.0.1", + "stat 0.0.1", + "stdbuf 0.0.1", + "sum 0.0.1", + "sync 0.0.1", + "tac 0.0.1", + "tail 0.0.1", + "tee 0.0.1", + "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "test 0.0.1", + "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", + "timeout 0.0.1", + "touch 0.0.1", + "tr 0.0.1", + "true 0.0.1", + "truncate 0.0.1", + "tsort 0.0.1", + "tty 0.0.1", + "uname 0.0.1", + "unexpand 0.0.1", + "unindent 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "uniq 0.0.1", + "unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unlink 0.0.1", + "uptime 0.0.1", + "users 0.0.1", + "uucore 0.0.1", + "wc 0.0.1", + "who 0.0.1", + "whoami 0.0.1", + "yes 0.0.1", +] + [[package]] name = "vec_map" version = "0.8.0" diff --git a/src/install/install.rs b/src/install/install.rs index 8ff82d80b..c090a7951 100644 --- a/src/install/install.rs +++ b/src/install/install.rs @@ -109,7 +109,7 @@ fn parse_opts(args: Vec) -> getopts::Matches { // TODO implement flag .optflag("C", "compare", "(unimplemented) compare each pair of source and destination\n \ files, and in some cases, do not modify the destination at all") - .optflag("d", "directory", "treat all arguments as directory names\n \ + .optflag("d", "directory", "treat all arguments as directory names.\n \ create all components of the specified directories") // TODO implement flag .optflag("D", "", "(unimplemented) create all leading components of DEST except the\n \ @@ -286,6 +286,13 @@ fn directory(paths: &[PathBuf], b: Behaviour) -> i32 { } } +/// Test if the path is a a new file path that can be +/// created immediately +fn is_new_file_path(path: &Path) -> bool { + path.is_file() || + ! path.exists() && path.parent().map(|p| p.is_dir()).unwrap_or(true) +} + /// Perform an install, given a list of paths and behaviour. /// /// Returns an integer intended as a program return code. @@ -296,9 +303,13 @@ fn standard(paths: &[PathBuf], b: Behaviour) -> i32 { 1 } else { let sources = &paths[0..paths.len() - 1]; - let target_directory = &paths[paths.len() - 1]; + let target = &paths[paths.len() - 1]; - copy_files_into_dir(sources, target_directory, &b) + if (target.is_file() || is_new_file_path(target)) && sources.len() == 1 { + copy_file_to_file(&sources[0], target, &b) + } else { + copy_files_into_dir(sources, target, &b) + } } } @@ -338,6 +349,20 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behaviour) - if all_successful { 0 } else { 1 } } +/// Copy a file to another file. +/// +/// Prints verbose information and error messages. +/// Returns an integer intended as a program return code. +/// +/// # Parameters +/// +/// _file_ must exist as a non-directory. +/// _target_ must be a non-directory +/// +fn copy_file_to_file(file: &PathBuf, target: &PathBuf, b: &Behaviour) -> i32 { + if copy(file, &target, b).is_err() { 1 } else { 0 } +} + /// Copy one file to a new location, changing metadata. /// /// # Parameters diff --git a/tests/test_install.rs b/tests/test_install.rs index 82f8d737d..2ce5cb407 100644 --- a/tests/test_install.rs +++ b/tests/test_install.rs @@ -28,6 +28,20 @@ fn test_install_basic() { assert!(at.file_exists(&format!("{}/{}", dir, file2))); } +#[test] +fn test_install_failing_not_dir() { + let (at, mut ucmd) = at_and_ucmd!(); + let file1 = "test_install_target_dir_file_a1"; + let file2 = "test_install_target_dir_file_a2"; + let file3 = "test_install_target_dir_file_a3"; + + at.touch(file1); + at.touch(file2); + at.touch(file3); + assert!(ucmd.arg(file1).arg(file2).arg(file3) + .fails().stderr.contains("not a directory")); +} + #[test] fn test_install_unimplemented_arg() { let (at, mut ucmd) = at_and_ucmd!(); @@ -136,3 +150,47 @@ fn test_install_mode_directories() { let permissions = at.metadata(component).permissions(); assert_eq!(0o040333 as u32, PermissionsExt::mode(&permissions)); } + +#[test] +fn test_install_target_file() { + let (at, mut ucmd) = at_and_ucmd!(); + let file1 = "test_install_target_file_file_i1"; + let file2 = "test_install_target_file_file_i2"; + + at.touch(file1); + at.touch(file2); + ucmd.arg(file1).arg(file2).succeeds().no_stderr(); + + assert!(at.file_exists(file1)); + assert!(at.file_exists(file2)); +} + +#[test] +fn test_install_target_new_file() { + let (at, mut ucmd) = at_and_ucmd!(); + let file = "test_install_target_new_filer_file_j"; + let dir = "test_install_target_new_file_dir_j"; + + at.touch(file); + at.mkdir(dir); + ucmd.arg(file).arg(format!("{}/{}", dir, file)).succeeds().no_stderr(); + + assert!(at.file_exists(file)); + assert!(at.file_exists(&format!("{}/{}", dir, file))); +} + +#[test] +fn test_install_target_new_file_failing_nonexistent_parent() { + let (at, mut ucmd) = at_and_ucmd!(); + let file1 = "test_install_target_new_file_failing_file_k1"; + let file2 = "test_install_target_new_file_failing_file_k2"; + let dir = "test_install_target_new_file_failing_dir_k"; + + at.touch(file1); + + let err = ucmd.arg(file1).arg(format!("{}/{}", dir, file2)) + .fails().stderr; + + assert!(err.contains("not a directory")) +} +