diff --git a/src/xargs/mod.rs b/src/xargs/mod.rs index 02a4a585..a59c5f5c 100644 --- a/src/xargs/mod.rs +++ b/src/xargs/mod.rs @@ -682,10 +682,33 @@ impl From for XargsError { } } +struct InputProcessOptions { + exit_if_pass_char_limit: bool, + max_args: Option, + max_lines: Option, + no_run_if_empty: bool, +} + +impl InputProcessOptions { + fn new( + exit_if_pass_char_limit: bool, + max_args: Option, + max_lines: Option, + no_run_if_empty: bool, + ) -> Self { + InputProcessOptions { + exit_if_pass_char_limit, + max_args, + max_lines, + no_run_if_empty, + } + } +} + fn process_input( builder_options: CommandBuilderOptions, mut args: Box, - options: &Options, + options: &InputProcessOptions, ) -> Result { let mut current_builder = CommandBuilder::new(&builder_options); let mut have_pending_command = false; @@ -752,6 +775,66 @@ fn validate_positive_usize(s: &str) -> Result { } } +fn normalize_options<'a>( + options: &'a Options, + matches: &'a clap::ArgMatches, +) -> (Option, Option, &'a Option, Option) { + let (max_args, max_lines, replace) = + match (options.max_args, options.max_lines, &options.replace) { + // These 3 options are mutually exclusive. + // But `max_args=1` and `replace` do not actually conflict, so no warning. + (None | Some(1), None, Some(_)) => { + // If `replace`, all matches in initial args should be replaced with extra args read from stdin. + // It is possible to have multiple matches and multiple extra args, and the Cartesian product is desired. + // To be specific, we process extra args one by one, and replace all matches with the same extra arg in each time. + (Some(1), None, &options.replace) + } + (Some(_), None, None) | (None, Some(_), None) | (None, None, None) => { + (options.max_args, options.max_lines, &None) + } + _ => { + eprintln!( + "WARNING: -L, -n and -I/-i are mutually exclusive, but more than one were given; \ + only the last option will be used" + ); + let lines_index = matches + .indices_of(options::MAX_LINES) + .and_then(|v| v.last()); + let args_index = matches.indices_of(options::MAX_ARGS).and_then(|v| v.last()); + let replace_index = [options::REPLACE, options::REPLACE_I] + .iter() + .flat_map(|o| matches.indices_of(o).and_then(|v| v.last())) + .max(); + if lines_index > args_index && lines_index > replace_index { + (None, options.max_lines, &None) + } else if args_index > lines_index && args_index > replace_index { + (options.max_args, None, &None) + } else { + (Some(1), None, &options.replace) + } + } + }; + + let delimiter = match (options.delimiter, options.null) { + (Some(delimiter), true) => { + if matches.indices_of(options::NULL).unwrap().last() + > matches.indices_of(options::DELIMITER).unwrap().last() + { + Some(b'\0') + } else { + Some(delimiter) + } + } + (Some(delimiter), false) => Some(delimiter), + (None, true) => Some(b'\0'), + // If `replace` and no delimiter specified, each line of stdin turns into a line of stdout, + // so the input should be split at newlines only. + (None, false) => replace.as_ref().map(|_| b'\n'), + }; + + (max_args, max_lines, replace, delimiter) +} + fn do_xargs(args: &[&str]) -> Result { let matches = clap::Command::new("xargs") .version(crate_version!()) @@ -897,22 +980,7 @@ fn do_xargs(args: &[&str]) -> Result { verbose: matches.get_flag(options::VERBOSE), }; - let delimiter = match (options.delimiter, options.null) { - (Some(delimiter), true) => { - if matches.indices_of(options::NULL).unwrap().last() - > matches.indices_of(options::DELIMITER).unwrap().last() - { - Some(b'\0') - } else { - Some(delimiter) - } - } - (Some(delimiter), false) => Some(delimiter), - (None, true) => Some(b'\0'), - // If `replace` and no delimiter specified, each line of stdin turns into a line of stdout, - // so the input should be split at newlines only. - (None, false) => options.replace.as_ref().map(|_| b'\n'), - }; + let (max_args, max_lines, replace, delimiter) = normalize_options(&options, &matches); let action = match matches.get_many::(options::COMMAND) { Some(args) if args.len() > 0 => { @@ -923,50 +991,21 @@ fn do_xargs(args: &[&str]) -> Result { let env = std::env::vars_os().collect(); let mut limiters = LimiterCollection::new(); - - match (options.max_args, options.max_lines, &options.replace) { - // These 3 options are mutually exclusive. - // But `max_args=1` and `replace` do not actually conflict, so no warning. - (None | Some(1), None, Some(_)) => { - // If `replace`, all matches in initial args should be replaced with extra args read from stdin. - // It is possible to have multiple matches and multiple extra args, and the Cartesian product is desired. - // To be specific, we process extra args one by one, and replace all matches with the same extra arg in each time. - limiters.add(MaxArgsCommandSizeLimiter::new(1)) - } - (Some(max_args), None, None) => limiters.add(MaxArgsCommandSizeLimiter::new(max_args)), - (None, Some(max_lines), None) => limiters.add(MaxLinesCommandSizeLimiter::new(max_lines)), - (None, None, None) => (), - _ => { - eprintln!( - "WARNING: -L, -n and -I/-i are mutually exclusive, but more than one were given; \ - only the last option will be used" - ); - let lines_index = matches - .indices_of(options::MAX_LINES) - .and_then(|v| v.last()); - let args_index = matches.indices_of(options::MAX_ARGS).and_then(|v| v.last()); - let replace_index = [options::REPLACE, options::REPLACE_I] - .iter() - .flat_map(|o| matches.indices_of(o).and_then(|v| v.last())) - .max(); - if lines_index > args_index && lines_index > replace_index { - limiters.add(MaxLinesCommandSizeLimiter::new(options.max_lines.unwrap())); - } else if args_index > lines_index && args_index > replace_index { - limiters.add(MaxArgsCommandSizeLimiter::new(options.max_args.unwrap())); - } - } + if let Some(max_args) = max_args { + limiters.add(MaxArgsCommandSizeLimiter::new(max_args)); + } + if let Some(max_lines) = max_lines { + limiters.add(MaxLinesCommandSizeLimiter::new(max_lines)); } - if let Some(max_chars) = options.max_chars { limiters.add(MaxCharsCommandSizeLimiter::new(max_chars)); } - limiters.add(MaxCharsCommandSizeLimiter::new_system(&env)); - 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", - )?; + let mut builder_options = CommandBuilderOptions::new(action, env, limiters, 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(); @@ -983,7 +1022,16 @@ fn do_xargs(args: &[&str]) -> Result { Box::new(WhitespaceDelimitedArgumentReader::new(args_file)) }; - let result = process_input(builder_options, args, &options)?; + let result = process_input( + builder_options, + args, + &InputProcessOptions::new( + options.exit_if_pass_char_limit, + max_args, + max_lines, + options.no_run_if_empty, + ), + )?; Ok(result) }