Skip to content

Commit 573b9b7

Browse files
authored
xargs: add support for -E option (#593)
* xargs: add support for -E option * Additional test coverage for xargs -E * Simplify EofArgumentReader and test its error pass-through
1 parent 783fc30 commit 573b9b7

File tree

2 files changed

+123
-1
lines changed

2 files changed

+123
-1
lines changed

src/xargs/mod.rs

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ mod options {
2121

2222
pub const ARG_FILE: &str = "arg-file";
2323
pub const DELIMITER: &str = "delimiter";
24+
pub const EOF: &str = "eof";
25+
pub const EOF_E: &str = "eof-E";
2426
pub const EXIT: &str = "exit";
2527
pub const MAX_ARGS: &str = "max-args";
2628
pub const MAX_CHARS: &str = "max-chars";
@@ -44,6 +46,7 @@ struct Options {
4446
null: bool,
4547
replace: Option<String>,
4648
verbose: bool,
49+
eof_delimiter: Option<String>,
4750
}
4851

4952
#[derive(Debug, PartialEq, Eq)]
@@ -637,6 +640,39 @@ where
637640
}
638641
}
639642

643+
struct EofArgumentReader {
644+
reader: Box<dyn ArgumentReader>,
645+
eof_delimiter: OsString,
646+
eof_found: bool,
647+
}
648+
649+
impl EofArgumentReader {
650+
fn new(reader: Box<dyn ArgumentReader>, eof_delimiter: &String) -> Self {
651+
Self {
652+
reader,
653+
eof_delimiter: eof_delimiter.into(),
654+
eof_found: false,
655+
}
656+
}
657+
}
658+
659+
impl ArgumentReader for EofArgumentReader {
660+
fn next(&mut self) -> io::Result<Option<Argument>> {
661+
Ok(if self.eof_found {
662+
None
663+
} else {
664+
self.reader.next()?.and_then(|arg| {
665+
if arg.arg == self.eof_delimiter {
666+
self.eof_found = true;
667+
None
668+
} else {
669+
Some(arg)
670+
}
671+
})
672+
})
673+
}
674+
}
675+
640676
#[derive(Debug)]
641677
enum XargsError {
642678
ArgumentTooLarge,
@@ -952,6 +988,27 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
952988
.overrides_with(options::REPLACE)
953989
.value_parser(clap::value_parser!(String)),
954990
)
991+
.arg(
992+
Arg::new(options::EOF)
993+
.short('E')
994+
.num_args(1)
995+
.value_name("eof-string")
996+
.help(
997+
"If specified, stop processing the input upon reaching an input \
998+
item that matches eof-string",
999+
)
1000+
.value_parser(clap::value_parser!(String)),
1001+
)
1002+
.arg(
1003+
Arg::new(options::EOF_E)
1004+
.short('e')
1005+
.long(options::EOF)
1006+
.num_args(0..=1)
1007+
.value_name("eof-string")
1008+
.help("Alias for -E")
1009+
.overrides_with(options::EOF)
1010+
.value_parser(clap::value_parser!(String)),
1011+
)
9551012
.try_get_matches_from(args);
9561013

9571014
let matches = match matches {
@@ -988,6 +1045,13 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
9881045
})
9891046
}),
9901047
verbose: matches.get_flag(options::VERBOSE),
1048+
eof_delimiter: [options::EOF_E, options::EOF].iter().find_map(|&option| {
1049+
matches.contains_id(option).then(|| {
1050+
matches
1051+
.get_one::<String>(option)
1052+
.map_or_else(|| "{}".to_string(), std::borrow::ToOwned::to_owned)
1053+
})
1054+
}),
9911055
};
9921056

9931057
let (max_args, max_lines, replace, delimiter) = normalize_options(&options, &matches);
@@ -1026,12 +1090,16 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
10261090
Box::new(io::stdin())
10271091
};
10281092

1029-
let args: Box<dyn ArgumentReader> = if let Some(delimiter) = delimiter {
1093+
let mut args: Box<dyn ArgumentReader> = if let Some(delimiter) = delimiter {
10301094
Box::new(ByteDelimitedArgumentReader::new(args_file, delimiter))
10311095
} else {
10321096
Box::new(WhitespaceDelimitedArgumentReader::new(args_file))
10331097
};
10341098

1099+
if let Some(eof_delimiter) = options.eof_delimiter {
1100+
args = Box::new(EofArgumentReader::new(args, &eof_delimiter));
1101+
}
1102+
10351103
let result = process_input(
10361104
&builder_options,
10371105
args,
@@ -1293,6 +1361,47 @@ mod tests {
12931361
assert_eq!(reader.next().unwrap(), None);
12941362
}
12951363

1364+
#[test]
1365+
fn test_eof_argument_reader() {
1366+
let filter = String::from("def");
1367+
1368+
let reader = WhitespaceDelimitedArgumentReader::new(ChunkReader::new(vec![Chunk::Data(
1369+
b"abc def ghi",
1370+
)]));
1371+
let mut wrapper = EofArgumentReader::new(Box::new(reader), &filter);
1372+
assert_eq!(wrapper.next().unwrap().unwrap(), make_arg_soft("abc"));
1373+
assert_eq!(wrapper.next().unwrap(), None);
1374+
assert_eq!(wrapper.next().unwrap(), None);
1375+
1376+
let reader = WhitespaceDelimitedArgumentReader::new(ChunkReader::new(vec![Chunk::Data(
1377+
b"abc define undef undefined ghi",
1378+
)]));
1379+
let mut wrapper = EofArgumentReader::new(Box::new(reader), &filter);
1380+
assert_eq!(wrapper.next().unwrap().unwrap(), make_arg_soft("abc"));
1381+
assert_eq!(wrapper.next().unwrap().unwrap(), make_arg_soft("define"));
1382+
assert_eq!(wrapper.next().unwrap().unwrap(), make_arg_soft("undef"));
1383+
assert_eq!(wrapper.next().unwrap().unwrap(), make_arg_soft("undefined"));
1384+
assert_eq!(wrapper.next().unwrap().unwrap(), make_arg_soft("ghi"));
1385+
assert_eq!(wrapper.next().unwrap(), None);
1386+
1387+
let reader = WhitespaceDelimitedArgumentReader::new(ChunkReader::new(vec![
1388+
Chunk::Data(b"abc "),
1389+
Chunk::Error(io::ErrorKind::Interrupted),
1390+
Chunk::Data(b"deF "),
1391+
Chunk::Error(io::ErrorKind::BrokenPipe),
1392+
Chunk::Data(b"ghi "),
1393+
Chunk::Data(b"def "),
1394+
Chunk::Error(io::ErrorKind::BrokenPipe),
1395+
]));
1396+
let mut wrapper = EofArgumentReader::new(Box::new(reader), &filter);
1397+
assert_eq!(wrapper.next().unwrap().unwrap(), make_arg_soft("abc"));
1398+
assert_eq!(wrapper.next().unwrap().unwrap(), make_arg_soft("deF"));
1399+
assert!(wrapper.next().err().unwrap().kind() == io::ErrorKind::BrokenPipe);
1400+
assert_eq!(wrapper.next().unwrap().unwrap(), make_arg_soft("ghi"));
1401+
assert_eq!(wrapper.next().unwrap(), None);
1402+
assert_eq!(wrapper.next().unwrap(), None);
1403+
}
1404+
12961405
#[test]
12971406
fn test_byte_delimited_reader() {
12981407
let mut reader = ByteDelimitedArgumentReader::new(

tests/xargs_tests.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,3 +552,16 @@ fn xargs_version() {
552552
.stdout(predicate::str::starts_with("xargs "));
553553
}
554554
}
555+
556+
#[test]
557+
fn xargs_eof() {
558+
for option_style in [vec!["-ecd"], vec!["-E", "cd"], vec!["--eof", "cd"]] {
559+
cargo_bin_cmd!("xargs")
560+
.args(option_style.as_slice())
561+
.write_stdin("ab cd ef")
562+
.assert()
563+
.success()
564+
.stderr(predicate::str::is_empty())
565+
.stdout(predicate::str::diff("ab\n"));
566+
}
567+
}

0 commit comments

Comments
 (0)