diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 091024ec416..b2a949a3178 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -12,7 +12,7 @@ use rand::RngCore; use std::fs::File; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use uucore::display::Quotable; -use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::{format_usage, help_about, help_usage}; mod rand_read_adapter; @@ -42,15 +42,21 @@ mod options { pub static RANDOM_SOURCE: &str = "random-source"; pub static REPEAT: &str = "repeat"; pub static ZERO_TERMINATED: &str = "zero-terminated"; - pub static FILE: &str = "file"; + pub static FILE_OR_ARGS: &str = "file-or-args"; } #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; - let mode = if let Some(args) = matches.get_many::(options::ECHO) { - Mode::Echo(args.map(String::from).collect()) + let mode = if matches.get_flag(options::ECHO) { + Mode::Echo( + matches + .get_many::(options::FILE_OR_ARGS) + .unwrap_or_default() + .map(String::from) + .collect(), + ) } else if let Some(range) = matches.get_one::(options::INPUT_RANGE) { match parse_range(range) { Ok(m) => Mode::InputRange(m), @@ -59,13 +65,17 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } } else { - Mode::Default( - matches - .get_one::(options::FILE) - .map(|s| s.as_str()) - .unwrap_or("-") - .to_string(), - ) + let mut operands = matches + .get_many::(options::FILE_OR_ARGS) + .unwrap_or_default(); + let file = operands.next().cloned().unwrap_or("-".into()); + if let Some(second_file) = operands.next() { + return Err(UUsageError::new( + 1, + format!("unexpected argument '{second_file}' found"), + )); + }; + Mode::Default(file) }; let options = Options { @@ -124,11 +134,9 @@ pub fn uu_app() -> Command { Arg::new(options::ECHO) .short('e') .long(options::ECHO) - .value_name("ARG") .help("treat each ARG as an input line") - .use_value_delimiter(false) - .num_args(0..) - .action(clap::ArgAction::Append) + .action(clap::ArgAction::SetTrue) + .overrides_with(options::ECHO) .conflicts_with(options::INPUT_RANGE), ) .arg( @@ -137,7 +145,7 @@ pub fn uu_app() -> Command { .long(options::INPUT_RANGE) .value_name("LO-HI") .help("treat each number LO through HI as an input line") - .conflicts_with(options::FILE), + .conflicts_with(options::FILE_OR_ARGS), ) .arg( Arg::new(options::HEAD_COUNT) @@ -178,7 +186,11 @@ pub fn uu_app() -> Command { .action(ArgAction::SetTrue) .overrides_with(options::ZERO_TERMINATED), ) - .arg(Arg::new(options::FILE).value_hint(clap::ValueHint::FilePath)) + .arg( + Arg::new(options::FILE_OR_ARGS) + .action(clap::ArgAction::Append) + .value_hint(clap::ValueHint::FilePath), + ) } fn read_input_file(filename: &str) -> UResult> { diff --git a/tests/by-util/test_shuf.rs b/tests/by-util/test_shuf.rs index 335af7909c5..2d90c95f4f0 100644 --- a/tests/by-util/test_shuf.rs +++ b/tests/by-util/test_shuf.rs @@ -32,6 +32,28 @@ fn test_output_is_random_permutation() { assert_eq!(result_seq, input_seq, "Output is not a permutation"); } +#[test] +fn test_explicit_stdin_file() { + let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let input = input_seq + .iter() + .map(ToString::to_string) + .collect::>() + .join("\n"); + + let result = new_ucmd!().arg("-").pipe_in(input.as_bytes()).succeeds(); + result.no_stderr(); + + let mut result_seq: Vec = result + .stdout_str() + .split('\n') + .filter(|x| !x.is_empty()) + .map(|x| x.parse().unwrap()) + .collect(); + result_seq.sort_unstable(); + assert_eq!(result_seq, input_seq, "Output is not a permutation"); +} + #[test] fn test_zero_termination() { let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; @@ -116,6 +138,36 @@ fn test_echo_multi() { assert_eq!(result_seq, ["a", "b", "c"], "Output is not a permutation"); } +#[test] +fn test_echo_postfix() { + let result = new_ucmd!().arg("a").arg("b").arg("c").arg("-e").succeeds(); + result.no_stderr(); + + let mut result_seq: Vec = result + .stdout_str() + .split('\n') + .filter(|x| !x.is_empty()) + .map(|x| x.into()) + .collect(); + result_seq.sort_unstable(); + assert_eq!(result_seq, ["a", "b", "c"], "Output is not a permutation"); +} + +#[test] +fn test_echo_short_collapsed_zero() { + let result = new_ucmd!().arg("-ez").arg("a").arg("b").arg("c").succeeds(); + result.no_stderr(); + + let mut result_seq: Vec = result + .stdout_str() + .split('\0') + .filter(|x| !x.is_empty()) + .map(|x| x.parse().unwrap()) + .collect(); + result_seq.sort_unstable(); + assert_eq!(result_seq, ["a", "b", "c"], "Output is not a permutation"); +} + #[test] fn test_head_count() { let repeat_limit = 5; @@ -365,6 +417,22 @@ fn test_shuf_multiple_outputs() { .stderr_contains("cannot be used multiple times"); } +#[test] +fn test_shuf_two_input_files() { + new_ucmd!() + .args(&["file_a", "file_b"]) + .fails() + .stderr_contains("unexpected argument 'file_b' found"); +} + +#[test] +fn test_shuf_three_input_files() { + new_ucmd!() + .args(&["file_a", "file_b", "file_c"]) + .fails() + .stderr_contains("unexpected argument 'file_b' found"); +} + #[test] fn test_shuf_invalid_input_line_count() { new_ucmd!()