mirror of
https://github.com/uutils/findutils.git
synced 2026-06-10 15:48:30 -07:00
Merge pull request #323 from sylvestre/replace
xargs: Implement replace / -I (Closes: #310)
This commit is contained in:
Generated
+23
@@ -215,6 +215,12 @@ dependencies = [
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "difflib"
|
||||
version = "0.4.0"
|
||||
@@ -285,6 +291,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"onig",
|
||||
"predicates",
|
||||
"pretty_assertions",
|
||||
"regex",
|
||||
"serial_test",
|
||||
"tempfile",
|
||||
@@ -591,6 +598,16 @@ dependencies = [
|
||||
"treeline",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
|
||||
dependencies = [
|
||||
"diff",
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.60"
|
||||
@@ -1058,3 +1075,9 @@ name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
||||
|
||||
@@ -26,6 +26,7 @@ nix = { version = "0.28", features = ["fs"] }
|
||||
predicates = "3"
|
||||
serial_test = "3.0"
|
||||
tempfile = "3"
|
||||
pretty_assertions = "1.4.0"
|
||||
|
||||
[[bin]]
|
||||
name = "find"
|
||||
|
||||
+87
-26
@@ -28,6 +28,8 @@ mod options {
|
||||
pub const MAX_PROCS: &str = "max-procs";
|
||||
pub const NO_RUN_IF_EMPTY: &str = "no-run-if-empty";
|
||||
pub const NULL: &str = "null";
|
||||
pub const REPLACE: &str = "replace";
|
||||
pub const REPLACE_I: &str = "replace-I";
|
||||
pub const VERBOSE: &str = "verbose";
|
||||
}
|
||||
|
||||
@@ -40,6 +42,7 @@ struct Options {
|
||||
max_lines: Option<usize>,
|
||||
no_run_if_empty: bool,
|
||||
null: bool,
|
||||
replace: Option<String>,
|
||||
verbose: bool,
|
||||
}
|
||||
|
||||
@@ -340,13 +343,14 @@ struct CommandBuilderOptions {
|
||||
limiters: LimiterCollection,
|
||||
verbose: bool,
|
||||
close_stdin: bool,
|
||||
replace: Option<String>,
|
||||
}
|
||||
|
||||
impl CommandBuilderOptions {
|
||||
fn new(
|
||||
action: ExecAction,
|
||||
env: HashMap<OsString, OsString>,
|
||||
mut limiters: LimiterCollection,
|
||||
replace: Option<String>,
|
||||
) -> Result<Self, ExhaustedCommandSpace> {
|
||||
let initial_args = match &action {
|
||||
ExecAction::Command(args) => args.iter().map(|arg| arg.as_ref()).collect(),
|
||||
@@ -366,6 +370,7 @@ impl CommandBuilderOptions {
|
||||
limiters,
|
||||
verbose: false,
|
||||
close_stdin: false,
|
||||
replace,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -398,14 +403,41 @@ impl CommandBuilder<'_> {
|
||||
};
|
||||
|
||||
let mut command = Command::new(entry_point);
|
||||
command
|
||||
.args(initial_args)
|
||||
.args(&self.extra_args)
|
||||
.env_clear()
|
||||
.envs(&self.options.env);
|
||||
|
||||
if let Some(replace_str) = &self.options.replace {
|
||||
// we replace the first instance of the replacement string with
|
||||
// the extra args, and then replace all instances of the replacement
|
||||
let replacement = self
|
||||
.extra_args
|
||||
.iter()
|
||||
.map(|s| s.to_string_lossy())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
let initial_args: Vec<OsString> = initial_args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
let arg_str = arg.to_string_lossy();
|
||||
OsString::from(arg_str.replace(replace_str, &replacement))
|
||||
})
|
||||
.collect();
|
||||
|
||||
command
|
||||
.args(&initial_args)
|
||||
.env_clear()
|
||||
.envs(&self.options.env);
|
||||
} else {
|
||||
// don't do any replacement
|
||||
command
|
||||
.args(initial_args)
|
||||
.args(&self.extra_args)
|
||||
.env_clear()
|
||||
.envs(&self.options.env);
|
||||
};
|
||||
|
||||
if self.options.close_stdin {
|
||||
command.stdin(Stdio::null());
|
||||
}
|
||||
|
||||
if self.options.verbose {
|
||||
eprintln!("{command:?}");
|
||||
}
|
||||
@@ -692,12 +724,14 @@ fn process_input(
|
||||
}
|
||||
|
||||
fn parse_delimiter(s: &str) -> Result<u8, String> {
|
||||
if let Some(hex) = s.strip_prefix("\\x") {
|
||||
u8::from_str_radix(hex, 16).map_err(|e| format!("Invalid hex sequence: {}", e))
|
||||
} else if let Some(oct) = s.strip_prefix("\\0") {
|
||||
u8::from_str_radix(oct, 8).map_err(|e| format!("Invalid octal sequence: {}", e))
|
||||
} else if let Some(special) = s.strip_prefix('\\') {
|
||||
match special {
|
||||
match s.strip_prefix('\\') {
|
||||
Some(hex) if hex.starts_with('x') => {
|
||||
u8::from_str_radix(&hex[1..], 16).map_err(|e| format!("Invalid hex sequence: {}", e))
|
||||
}
|
||||
Some(oct) if oct.starts_with('0') => {
|
||||
u8::from_str_radix(&oct[1..], 8).map_err(|e| format!("Invalid octal sequence: {}", e))
|
||||
}
|
||||
Some(special) => match special {
|
||||
"a" => Ok(b'\x07'),
|
||||
"b" => Ok(b'\x08'),
|
||||
"f" => Ok(b'\x0C'),
|
||||
@@ -705,17 +739,12 @@ fn parse_delimiter(s: &str) -> Result<u8, String> {
|
||||
"r" => Ok(b'\r'),
|
||||
"t" => Ok(b'\t'),
|
||||
"v" => Ok(b'\x0B'),
|
||||
"0" => Ok(b'\0'),
|
||||
"\\" => Ok(b'\\'),
|
||||
_ => Err(format!("Invalid escape sequence: {s}")),
|
||||
}
|
||||
} else {
|
||||
let bytes = s.as_bytes();
|
||||
if bytes.len() == 1 {
|
||||
Ok(bytes[0])
|
||||
} else {
|
||||
Err("Delimiter must be one byte".to_owned())
|
||||
}
|
||||
"0" => Ok(b'\0'),
|
||||
_ => Err(format!("Invalid escape sequence: \\{}", special)),
|
||||
},
|
||||
None if s.len() == 1 => Ok(s.as_bytes()[0]),
|
||||
None => Err("Delimiter must be one byte".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -819,6 +848,28 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
|
||||
.help("Be verbose")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::REPLACE)
|
||||
.long(options::REPLACE)
|
||||
.short('i')
|
||||
.num_args(0..=1)
|
||||
.require_equals(true)
|
||||
.value_parser(clap::value_parser!(String))
|
||||
.value_name("R")
|
||||
.help(
|
||||
"Replace R in INITIAL-ARGS with names read from standard input; \
|
||||
if R is unspecified, assume {}",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::REPLACE_I)
|
||||
.short('I')
|
||||
.num_args(1)
|
||||
.help("same as --replace=R")
|
||||
.value_name("R")
|
||||
.overrides_with(options::REPLACE)
|
||||
.value_parser(clap::value_parser!(String)),
|
||||
)
|
||||
.try_get_matches_from(args);
|
||||
|
||||
let matches = match matches {
|
||||
@@ -837,6 +888,16 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
|
||||
max_lines: matches.get_one::<usize>(options::MAX_LINES).copied(),
|
||||
no_run_if_empty: matches.get_flag(options::NO_RUN_IF_EMPTY),
|
||||
null: matches.get_flag(options::NULL),
|
||||
replace: [options::REPLACE_I, options::REPLACE]
|
||||
.iter()
|
||||
.find_map(|&option| {
|
||||
matches.contains_id(option).then(|| {
|
||||
matches
|
||||
.get_one::<String>(option)
|
||||
.map(|value| value.to_owned())
|
||||
.unwrap_or_else(|| "{}".to_string())
|
||||
})
|
||||
}),
|
||||
verbose: matches.get_flag(options::VERBOSE),
|
||||
};
|
||||
|
||||
@@ -861,7 +922,6 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
|
||||
}
|
||||
_ => ExecAction::Echo,
|
||||
};
|
||||
|
||||
let env = std::env::vars_os().collect();
|
||||
|
||||
let mut limiters = LimiterCollection::new();
|
||||
@@ -891,9 +951,10 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
|
||||
|
||||
limiters.add(MaxCharsCommandSizeLimiter::new_system(&env));
|
||||
|
||||
let mut builder_options = CommandBuilderOptions::new(action, env, limiters).map_err(|_| {
|
||||
"Base command and environment are too large to fit into one command execution"
|
||||
})?;
|
||||
let mut builder_options =
|
||||
CommandBuilderOptions::new(action, env, limiters, options.replace.clone()).map_err(
|
||||
|_| "Base command and environment are too large to fit into one command execution",
|
||||
)?;
|
||||
|
||||
builder_options.verbose = options.verbose;
|
||||
builder_options.close_stdin = options.arg_file.is_none();
|
||||
|
||||
+151
-34
@@ -12,6 +12,7 @@ use assert_cmd::Command;
|
||||
use predicates::prelude::*;
|
||||
|
||||
use common::test_helpers::path_to_testing_commandline;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
mod common;
|
||||
|
||||
@@ -208,7 +209,7 @@ fn xargs_exit_on_large() {
|
||||
|
||||
#[test]
|
||||
fn xargs_exec() {
|
||||
Command::cargo_bin("xargs")
|
||||
let result = Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args([
|
||||
"-n2",
|
||||
@@ -218,13 +219,20 @@ fn xargs_exec() {
|
||||
"--no_print_cwd",
|
||||
])
|
||||
.write_stdin("a b c\nd")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::diff(
|
||||
"stdin=\nargs=\n--print_stdin\n--no_print_cwd\na\nb\n\
|
||||
.output();
|
||||
assert!(result.is_ok(), "xargs failed: {:?}", result);
|
||||
let result = result.unwrap();
|
||||
assert_eq!(result.status.code(), Some(0));
|
||||
|
||||
assert!(result.stderr.is_empty(), "stderr: {:?}", result);
|
||||
|
||||
let stdout_string = String::from_utf8(result.stdout).expect("Found invalid UTF-8");
|
||||
|
||||
assert_eq!(
|
||||
stdout_string,
|
||||
"stdin=\nargs=\n--print_stdin\n--no_print_cwd\na\nb\n\
|
||||
stdin=\nargs=\n--print_stdin\n--no_print_cwd\nc\nd\n",
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -234,7 +242,7 @@ fn xargs_exec_stdin_open() {
|
||||
write!(temp_file, "a b c").unwrap();
|
||||
temp_file.seek(SeekFrom::Start(0)).unwrap();
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
let result = Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args([
|
||||
"-a",
|
||||
@@ -245,17 +253,25 @@ fn xargs_exec_stdin_open() {
|
||||
"--no_print_cwd",
|
||||
])
|
||||
.write_stdin("test")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::diff(
|
||||
"stdin=test\nargs=\n--print_stdin\n--no_print_cwd\na\nb\nc\n",
|
||||
));
|
||||
.output();
|
||||
|
||||
assert!(result.is_ok(), "xargs failed: {:?}", result);
|
||||
let result = result.unwrap();
|
||||
assert_eq!(result.status.code(), Some(0));
|
||||
|
||||
assert!(result.stderr.is_empty(), "stderr: {:?}", result);
|
||||
|
||||
let stdout_string = String::from_utf8(result.stdout).expect("Found invalid UTF-8");
|
||||
|
||||
assert_eq!(
|
||||
stdout_string,
|
||||
"stdin=test\nargs=\n--print_stdin\n--no_print_cwd\na\nb\nc\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_exec_failure() {
|
||||
Command::cargo_bin("xargs")
|
||||
let result = Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args([
|
||||
"-n1",
|
||||
@@ -265,19 +281,26 @@ fn xargs_exec_failure() {
|
||||
"--exit_with_failure",
|
||||
])
|
||||
.write_stdin("a b")
|
||||
.assert()
|
||||
.failure()
|
||||
.code(123)
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(
|
||||
"args=\n--no_print_cwd\n--exit_with_failure\na\n\
|
||||
.output();
|
||||
|
||||
assert!(result.is_ok(), "xargs failed: {:?}", result);
|
||||
let result = result.unwrap();
|
||||
assert_eq!(result.status.code(), Some(123));
|
||||
|
||||
assert!(result.stderr.is_empty(), "stderr: {:?}", result);
|
||||
|
||||
let stdout_string = String::from_utf8(result.stdout).expect("Found invalid UTF-8");
|
||||
|
||||
assert_eq!(
|
||||
stdout_string,
|
||||
"args=\n--no_print_cwd\n--exit_with_failure\na\n\
|
||||
args=\n--no_print_cwd\n--exit_with_failure\nb\n",
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_exec_urgent_failure() {
|
||||
Command::cargo_bin("xargs")
|
||||
let result = Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args([
|
||||
"-n1",
|
||||
@@ -287,17 +310,26 @@ fn xargs_exec_urgent_failure() {
|
||||
"--exit_with_urgent_failure",
|
||||
])
|
||||
.write_stdin("a b")
|
||||
.assert()
|
||||
.failure()
|
||||
.code(124)
|
||||
.stderr(predicate::str::contains("Error:"))
|
||||
.stdout("args=\n--no_print_cwd\n--exit_with_urgent_failure\na\n");
|
||||
.output();
|
||||
|
||||
assert!(result.is_ok(), "xargs failed: {:?}", result);
|
||||
let result = result.unwrap();
|
||||
assert_eq!(result.status.code(), Some(124));
|
||||
|
||||
assert!(!result.stderr.is_empty(), "stderr: {:?}", result);
|
||||
|
||||
let stdout_string = String::from_utf8(result.stdout).expect("Found invalid UTF-8");
|
||||
|
||||
assert_eq!(
|
||||
stdout_string,
|
||||
"args=\n--no_print_cwd\n--exit_with_urgent_failure\na\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn xargs_exec_with_signal() {
|
||||
Command::cargo_bin("xargs")
|
||||
let result = Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args([
|
||||
"-n1",
|
||||
@@ -307,11 +339,19 @@ fn xargs_exec_with_signal() {
|
||||
"--exit_with_signal",
|
||||
])
|
||||
.write_stdin("a b")
|
||||
.assert()
|
||||
.failure()
|
||||
.code(125)
|
||||
.stderr(predicate::str::contains("Error:"))
|
||||
.stdout("args=\n--no_print_cwd\n--exit_with_signal\na\n");
|
||||
.output();
|
||||
|
||||
assert!(result.is_ok(), "xargs failed: {:?}", result);
|
||||
let result = result.unwrap();
|
||||
assert_eq!(result.status.code(), Some(125));
|
||||
assert!(!result.stderr.is_empty(), "stderr: {:?}", result);
|
||||
|
||||
let stdout_string = String::from_utf8(result.stdout).expect("Found invalid UTF-8");
|
||||
|
||||
assert_eq!(
|
||||
stdout_string,
|
||||
"args=\n--no_print_cwd\n--exit_with_signal\na\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -385,3 +425,80 @@ fn xargs_zero_lines() {
|
||||
.stderr(predicate::str::contains("Value must be > 0, not: 0"))
|
||||
.stdout(predicate::str::is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_replace() {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-i={}", "echo", "{} bar"])
|
||||
.write_stdin("foo")
|
||||
.assert()
|
||||
.stdout(predicate::str::contains("foo bar"));
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-i=_", "echo", "_ bar"])
|
||||
.write_stdin("foo")
|
||||
.assert()
|
||||
.stdout(predicate::str::contains("foo bar"));
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["--replace=_", "echo", "_ _ bar"])
|
||||
.write_stdin("foo")
|
||||
.assert()
|
||||
.stdout(predicate::str::contains("foo foo bar"));
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-i=_", "echo", "_ _ bar"])
|
||||
.write_stdin("foo")
|
||||
.assert()
|
||||
.stdout(predicate::str::contains("foo foo bar"));
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-i", "echo", "{} {} bar"])
|
||||
.write_stdin("foo")
|
||||
.assert()
|
||||
.stdout(predicate::str::contains("foo foo bar"));
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-I={}", "echo", "{} bar {}"])
|
||||
.write_stdin("foo")
|
||||
.assert()
|
||||
.stdout(predicate::str::contains("foo bar foo"));
|
||||
|
||||
// Combine the two options to see which one wins
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-I=_", "-i", "echo", "{} bar {}"])
|
||||
.write_stdin("foo")
|
||||
.assert()
|
||||
.stdout(predicate::str::contains("foo bar foo"));
|
||||
|
||||
// other order
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-i", "-I=_", "echo", "{} bar {}"])
|
||||
.write_stdin("foo")
|
||||
.assert()
|
||||
.stdout(predicate::str::contains("{} bar {}"));
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-i", "-I", "_", "echo", "{} bar _"])
|
||||
.write_stdin("foo")
|
||||
.assert()
|
||||
.stdout(predicate::str::contains("{} bar foo"));
|
||||
|
||||
// Expected to fail
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-I", "echo", "_ _ bar"])
|
||||
.write_stdin("foo")
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("Error: Command not found"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user