1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

cp: accept shortcuts for stringly-enum arguments

This commit is contained in:
Ben Wiederhake 2024-04-01 06:00:15 +02:00
parent bfc7411dec
commit d4546ced26
3 changed files with 88 additions and 63 deletions

View file

@ -37,8 +37,8 @@ use uucore::{backup_control, update_control};
// requires these enum. // requires these enum.
pub use uucore::{backup_control::BackupMode, update_control::UpdateMode}; pub use uucore::{backup_control::BackupMode, update_control::UpdateMode};
use uucore::{ use uucore::{
format_usage, help_about, help_section, help_usage, prompt_yes, show_error, show_warning, format_usage, help_about, help_section, help_usage, prompt_yes,
util_name, shortcut_value_parser::ShortcutValueParser, show_error, show_warning, util_name,
}; };
use crate::copydir::copy_directory; use crate::copydir::copy_directory;
@ -396,22 +396,14 @@ static PRESERVABLE_ATTRIBUTES: &[&str] = &[
"ownership", "ownership",
"timestamps", "timestamps",
"context", "context",
"link",
"links", "links",
"xattr", "xattr",
"all", "all",
]; ];
#[cfg(not(unix))] #[cfg(not(unix))]
static PRESERVABLE_ATTRIBUTES: &[&str] = &[ static PRESERVABLE_ATTRIBUTES: &[&str] =
"mode", &["mode", "timestamps", "context", "links", "xattr", "all"];
"timestamps",
"context",
"link",
"links",
"xattr",
"all",
];
pub fn uu_app() -> Command { pub fn uu_app() -> Command {
const MODE_ARGS: &[&str] = &[ const MODE_ARGS: &[&str] = &[
@ -543,7 +535,7 @@ pub fn uu_app() -> Command {
.overrides_with_all(MODE_ARGS) .overrides_with_all(MODE_ARGS)
.require_equals(true) .require_equals(true)
.default_missing_value("always") .default_missing_value("always")
.value_parser(["auto", "always", "never"]) .value_parser(ShortcutValueParser::new(["auto", "always", "never"]))
.num_args(0..=1) .num_args(0..=1)
.help("control clone/CoW copies. See below"), .help("control clone/CoW copies. See below"),
) )
@ -559,9 +551,7 @@ pub fn uu_app() -> Command {
.long(options::PRESERVE) .long(options::PRESERVE)
.action(ArgAction::Append) .action(ArgAction::Append)
.use_value_delimiter(true) .use_value_delimiter(true)
.value_parser(clap::builder::PossibleValuesParser::new( .value_parser(ShortcutValueParser::new(PRESERVABLE_ATTRIBUTES))
PRESERVABLE_ATTRIBUTES,
))
.num_args(0..) .num_args(0..)
.require_equals(true) .require_equals(true)
.value_name("ATTR_LIST") .value_name("ATTR_LIST")
@ -655,7 +645,7 @@ pub fn uu_app() -> Command {
Arg::new(options::SPARSE) Arg::new(options::SPARSE)
.long(options::SPARSE) .long(options::SPARSE)
.value_name("WHEN") .value_name("WHEN")
.value_parser(["never", "auto", "always"]) .value_parser(ShortcutValueParser::new(["never", "auto", "always"]))
.help("control creation of sparse files. See below"), .help("control creation of sparse files. See below"),
) )
// TODO: implement the following args // TODO: implement the following args

View file

@ -61,6 +61,7 @@ pub enum UpdateMode {
} }
pub mod arguments { pub mod arguments {
use crate::shortcut_value_parser::ShortcutValueParser;
use clap::ArgAction; use clap::ArgAction;
pub static OPT_UPDATE: &str = "update"; pub static OPT_UPDATE: &str = "update";
@ -71,7 +72,7 @@ pub mod arguments {
clap::Arg::new(OPT_UPDATE) clap::Arg::new(OPT_UPDATE)
.long("update") .long("update")
.help("move only when the SOURCE file is newer than the destination file or when the destination file is missing") .help("move only when the SOURCE file is newer than the destination file or when the destination file is missing")
.value_parser(["none", "all", "older"]) .value_parser(ShortcutValueParser::new(["none", "all", "older"]))
.num_args(0..=1) .num_args(0..=1)
.default_missing_value("older") .default_missing_value("older")
.require_equals(true) .require_equals(true)

View file

@ -2,7 +2,7 @@
// //
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs ROOTDIR USERDIR procfs outfile uufs xattrs // spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs neve ROOTDIR USERDIR procfs outfile uufs xattrs
use crate::common::util::TestScenario; use crate::common::util::TestScenario;
#[cfg(not(windows))] #[cfg(not(windows))]
@ -286,16 +286,18 @@ fn test_cp_arg_update_interactive_error() {
#[test] #[test]
fn test_cp_arg_update_none() { fn test_cp_arg_update_none() {
let (at, mut ucmd) = at_and_ucmd!(); for argument in ["--update=none", "--update=non", "--update=n"] {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg(TEST_HELLO_WORLD_SOURCE) ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE) .arg(TEST_HOW_ARE_YOU_SOURCE)
.arg("--update=none") .arg(argument)
.succeeds() .succeeds()
.no_stderr() .no_stderr()
.no_stdout(); .no_stdout();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n"); assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n");
}
} }
#[test] #[test]
@ -1402,29 +1404,28 @@ fn test_cp_preserve_no_args_before_opts() {
#[test] #[test]
fn test_cp_preserve_all() { fn test_cp_preserve_all() {
let (at, mut ucmd) = at_and_ucmd!(); for argument in ["--preserve=all", "--preserve=al"] {
let src_file = "a"; let (at, mut ucmd) = at_and_ucmd!();
let dst_file = "b"; let src_file = "a";
let dst_file = "b";
// Prepare the source file // Prepare the source file
at.touch(src_file); at.touch(src_file);
#[cfg(unix)] #[cfg(unix)]
at.set_mode(src_file, 0o0500); at.set_mode(src_file, 0o0500);
// TODO: create a destination that does not allow copying of xattr and context // TODO: create a destination that does not allow copying of xattr and context
// Copy // Copy
ucmd.arg(src_file) ucmd.arg(src_file).arg(dst_file).arg(argument).succeeds();
.arg(dst_file)
.arg("--preserve=all")
.succeeds();
#[cfg(all(unix, not(target_os = "freebsd")))] #[cfg(all(unix, not(target_os = "freebsd")))]
{ {
// Assert that the mode, ownership, and timestamps are preserved // Assert that the mode, ownership, and timestamps are preserved
// NOTICE: the ownership is not modified on the src file, because that requires root permissions // NOTICE: the ownership is not modified on the src file, because that requires root permissions
let metadata_src = at.metadata(src_file); let metadata_src = at.metadata(src_file);
let metadata_dst = at.metadata(dst_file); let metadata_dst = at.metadata(dst_file);
assert_metadata_eq!(metadata_src, metadata_dst); assert_metadata_eq!(metadata_src, metadata_dst);
}
} }
} }
@ -1472,6 +1473,35 @@ fn test_cp_preserve_all_context_fails_on_non_selinux() {
.fails(); .fails();
} }
#[test]
fn test_cp_preserve_link_parses() {
// TODO: Also check whether --preserve=link did the right thing!
for argument in [
"--preserve=links",
"--preserve=link",
"--preserve=li",
"--preserve=l",
] {
new_ucmd!()
.arg(argument)
.arg(TEST_COPY_FROM_FOLDER_FILE)
.arg(TEST_HELLO_WORLD_DEST)
.succeeds()
.no_output();
}
}
#[test]
fn test_cp_preserve_invalid_rejected() {
new_ucmd!()
.arg("--preserve=invalid-value")
.arg(TEST_COPY_FROM_FOLDER_FILE)
.arg(TEST_HELLO_WORLD_DEST)
.fails()
.code_is(1)
.no_stdout();
}
#[test] #[test]
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
#[cfg(disabled_until_fixed)] // FIXME: the test looks to .succeed on android #[cfg(disabled_until_fixed)] // FIXME: the test looks to .succeed on android
@ -2196,14 +2226,16 @@ fn test_cp_reflink_none() {
#[test] #[test]
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))] #[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
fn test_cp_reflink_never() { fn test_cp_reflink_never() {
let (at, mut ucmd) = at_and_ucmd!(); for argument in ["--reflink=never", "--reflink=neve", "--reflink=n"] {
ucmd.arg("--reflink=never") let (at, mut ucmd) = at_and_ucmd!();
.arg(TEST_HELLO_WORLD_SOURCE) ucmd.arg(argument)
.arg(TEST_EXISTING_FILE) .arg(TEST_HELLO_WORLD_SOURCE)
.succeeds(); .arg(TEST_EXISTING_FILE)
.succeeds();
// Check the content of the destination file // Check the content of the destination file
assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n"); assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n");
}
} }
#[test] #[test]
@ -2286,19 +2318,21 @@ fn test_cp_sparse_never_empty() {
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
#[test] #[test]
fn test_cp_sparse_always_empty() { fn test_cp_sparse_always_empty() {
let (at, mut ucmd) = at_and_ucmd!(); for argument in ["--sparse=always", "--sparse=alway", "--sparse=al"] {
let (at, mut ucmd) = at_and_ucmd!();
const BUFFER_SIZE: usize = 4096 * 4; const BUFFER_SIZE: usize = 4096 * 4;
let buf: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; let buf: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
at.make_file("src_file1"); at.make_file("src_file1");
at.write_bytes("src_file1", &buf); at.write_bytes("src_file1", &buf);
ucmd.args(&["--sparse=always", "src_file1", "dst_file_sparse"]) ucmd.args(&[argument, "src_file1", "dst_file_sparse"])
.succeeds(); .succeeds();
assert_eq!(at.read_bytes("dst_file_sparse"), buf); assert_eq!(at.read_bytes("dst_file_sparse"), buf);
assert_eq!(at.metadata("dst_file_sparse").blocks(), 0); assert_eq!(at.metadata("dst_file_sparse").blocks(), 0);
}
} }
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]