mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
install: implement the --no-target-directory option (#7867)
* implement --no-target-directory option * add test for --no-target-directory
This commit is contained in:
parent
8ec5fe189b
commit
68c91c17ba
2 changed files with 182 additions and 6 deletions
|
@ -50,6 +50,7 @@ pub struct Behavior {
|
|||
strip_program: String,
|
||||
create_leading: bool,
|
||||
target_dir: Option<String>,
|
||||
no_target_dir: bool,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -104,6 +105,9 @@ enum InstallError {
|
|||
|
||||
#[error("'{0}' and '{1}' are the same file")]
|
||||
SameFile(PathBuf, PathBuf),
|
||||
|
||||
#[error("extra operand {}\n{}", .0.quote(), .1.quote())]
|
||||
ExtraOperand(String, String),
|
||||
}
|
||||
|
||||
impl UError for InstallError {
|
||||
|
@ -279,11 +283,10 @@ pub fn uu_app() -> Command {
|
|||
.value_hint(clap::ValueHint::DirPath),
|
||||
)
|
||||
.arg(
|
||||
// TODO implement flag
|
||||
Arg::new(OPT_NO_TARGET_DIRECTORY)
|
||||
.short('T')
|
||||
.long(OPT_NO_TARGET_DIRECTORY)
|
||||
.help("(unimplemented) treat DEST as a normal file")
|
||||
.help("treat DEST as a normal file")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
|
@ -328,9 +331,7 @@ pub fn uu_app() -> Command {
|
|||
///
|
||||
///
|
||||
fn check_unimplemented(matches: &ArgMatches) -> UResult<()> {
|
||||
if matches.get_flag(OPT_NO_TARGET_DIRECTORY) {
|
||||
Err(InstallError::Unimplemented(String::from("--no-target-directory, -T")).into())
|
||||
} else if matches.get_flag(OPT_PRESERVE_CONTEXT) {
|
||||
if matches.get_flag(OPT_PRESERVE_CONTEXT) {
|
||||
Err(InstallError::Unimplemented(String::from("--preserve-context, -P")).into())
|
||||
} else if matches.get_flag(OPT_CONTEXT) {
|
||||
Err(InstallError::Unimplemented(String::from("--context, -Z")).into())
|
||||
|
@ -368,6 +369,11 @@ fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
|
|||
|
||||
let backup_mode = backup_control::determine_backup_mode(matches)?;
|
||||
let target_dir = matches.get_one::<String>(OPT_TARGET_DIRECTORY).cloned();
|
||||
let no_target_dir = matches.get_flag(OPT_NO_TARGET_DIRECTORY);
|
||||
if target_dir.is_some() && no_target_dir {
|
||||
show_error!("Options --target-directory and --no-target-directory are mutually exclusive");
|
||||
return Err(1.into());
|
||||
}
|
||||
|
||||
let preserve_timestamps = matches.get_flag(OPT_PRESERVE_TIMESTAMPS);
|
||||
let compare = matches.get_flag(OPT_COMPARE);
|
||||
|
@ -430,6 +436,7 @@ fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
|
|||
),
|
||||
create_leading: matches.get_flag(OPT_CREATE_LEADING),
|
||||
target_dir,
|
||||
no_target_dir,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -522,6 +529,9 @@ fn standard(mut paths: Vec<String>, b: &Behavior) -> UResult<()> {
|
|||
if paths.is_empty() {
|
||||
return Err(UUsageError::new(1, "missing file operand"));
|
||||
}
|
||||
if b.no_target_dir && paths.len() > 2 {
|
||||
return Err(InstallError::ExtraOperand(paths[2].clone(), format_usage(USAGE)).into());
|
||||
}
|
||||
|
||||
// get the target from either "-t foo" param or from the last given paths argument
|
||||
let target: PathBuf = if let Some(path) = &b.target_dir {
|
||||
|
@ -591,7 +601,7 @@ fn standard(mut paths: Vec<String>, b: &Behavior) -> UResult<()> {
|
|||
}
|
||||
}
|
||||
|
||||
if sources.len() > 1 || is_potential_directory_path(&target) {
|
||||
if sources.len() > 1 {
|
||||
copy_files_into_dir(sources, &target, b)
|
||||
} else {
|
||||
let source = sources.first().unwrap();
|
||||
|
@ -600,6 +610,16 @@ fn standard(mut paths: Vec<String>, b: &Behavior) -> UResult<()> {
|
|||
return Err(InstallError::OmittingDirectory(source.clone()).into());
|
||||
}
|
||||
|
||||
if b.no_target_dir && target.exists() {
|
||||
return Err(
|
||||
InstallError::OverrideDirectoryFailed(target.clone(), source.clone()).into(),
|
||||
);
|
||||
}
|
||||
|
||||
if is_potential_directory_path(&target) {
|
||||
return copy_files_into_dir(sources, &target, b);
|
||||
}
|
||||
|
||||
if target.is_file() || is_new_file_path(&target) {
|
||||
copy(source, &target, b)
|
||||
} else {
|
||||
|
|
|
@ -1808,3 +1808,159 @@ fn test_install_symlink_same_file() {
|
|||
"'{target_dir}/{file}' and '{target_link}/{file}' are the same file"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_no_target_directory_failing_cannot_overwrite() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
let file = "file";
|
||||
let dir = "dir";
|
||||
|
||||
at.touch(file);
|
||||
at.mkdir(dir);
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-T")
|
||||
.arg(file)
|
||||
.arg(dir)
|
||||
.fails()
|
||||
.stderr_contains("cannot overwrite directory 'dir' with non-directory");
|
||||
|
||||
assert!(!at.dir_exists("dir/file"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_no_target_directory_failing_omitting_directory() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
let dir1 = "dir1";
|
||||
let dir2 = "dir2";
|
||||
|
||||
at.mkdir(dir1);
|
||||
at.mkdir(dir2);
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-T")
|
||||
.arg(dir1)
|
||||
.arg(dir2)
|
||||
.fails()
|
||||
.stderr_contains("omitting directory 'dir1'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_no_target_directory_creating_leading_dirs_with_single_source_and_target_dir() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
let source1 = "file";
|
||||
let target_dir = "missing_target_dir/";
|
||||
|
||||
at.touch(source1);
|
||||
|
||||
// installing a single file into a missing directory will fail, when -D is used w/o -t parameter
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-TD")
|
||||
.arg(source1)
|
||||
.arg(at.plus(target_dir))
|
||||
.fails()
|
||||
.stderr_contains("missing_target_dir/' is not a directory");
|
||||
|
||||
assert!(!at.dir_exists(target_dir));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_no_target_directory_failing_combine_with_target_directory() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
let file = "file";
|
||||
let dir1 = "dir1";
|
||||
|
||||
at.touch(file);
|
||||
at.mkdir(dir1);
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-T")
|
||||
.arg(file)
|
||||
.arg("-t")
|
||||
.arg(dir1)
|
||||
.fails()
|
||||
.stderr_contains(
|
||||
"Options --target-directory and --no-target-directory are mutually exclusive",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_no_target_directory_failing_usage_with_target_directory() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
let file = "file";
|
||||
|
||||
at.touch(file);
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-T")
|
||||
.arg(file)
|
||||
.arg("-t")
|
||||
.fails()
|
||||
.stderr_contains(
|
||||
"a value is required for '--target-directory <DIRECTORY>' but none was supplied",
|
||||
)
|
||||
.stderr_contains("For more information, try '--help'");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_no_target_multiple_sources_and_target_dir() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
let file1 = "file1";
|
||||
let file2 = "file2";
|
||||
let dir1 = "dir1";
|
||||
let dir2 = "dir2";
|
||||
|
||||
at.touch(file1);
|
||||
at.touch(file2);
|
||||
at.mkdir(dir1);
|
||||
at.mkdir(dir2);
|
||||
|
||||
// installing multiple files into a missing directory will fail, when -D is used w/o -t parameter
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-T")
|
||||
.arg(file1)
|
||||
.arg(file2)
|
||||
.arg(dir1)
|
||||
.fails()
|
||||
.stderr_contains("extra operand 'dir1'")
|
||||
.stderr_contains("[OPTION]... [FILE]...");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-T")
|
||||
.arg(file1)
|
||||
.arg(file2)
|
||||
.arg(dir1)
|
||||
.arg(dir2)
|
||||
.fails()
|
||||
.stderr_contains("extra operand 'dir1'")
|
||||
.stderr_contains("[OPTION]... [FILE]...");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_install_no_target_basic() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file = "file";
|
||||
let dir = "dir";
|
||||
|
||||
at.touch(file);
|
||||
at.mkdir(dir);
|
||||
ucmd.arg("-T")
|
||||
.arg(file)
|
||||
.arg(format!("{dir}/{file}"))
|
||||
.succeeds()
|
||||
.no_stderr();
|
||||
|
||||
assert!(at.file_exists(file));
|
||||
assert!(at.file_exists(format!("{dir}/{file}")));
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue