From a6baf49b726fa0883048c79b3b2cecb7e1a87110 Mon Sep 17 00:00:00 2001 From: David Peter Date: Sun, 6 Feb 2022 21:02:15 +0100 Subject: [PATCH] Introduce Commands struct --- src/app.rs | 2 +- src/benchmark/schedule.rs | 5 +- src/command.rs | 245 ++++++++++++++++++++------------------ src/main.rs | 12 +- 4 files changed, 139 insertions(+), 125 deletions(-) diff --git a/src/app.rs b/src/app.rs index fbf11c4..8d8fe7c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,7 +2,7 @@ use std::ffi::OsString; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; -pub fn get_arg_matches<'a, I, T>(args: I) -> ArgMatches +pub fn get_cli_arguments<'a, I, T>(args: I) -> ArgMatches where I: IntoIterator, T: Into + Clone + 'a, diff --git a/src/benchmark/schedule.rs b/src/benchmark/schedule.rs index 5251bbd..2040dad 100644 --- a/src/benchmark/schedule.rs +++ b/src/benchmark/schedule.rs @@ -1,5 +1,6 @@ +use crate::command::Commands; +use crate::export::ExportManager; use crate::options::{Options, OutputStyleOption}; -use crate::{command::Command, export::ExportManager}; use super::{mean_shell_spawning_time, relative_speed, result::BenchmarkResult, run_benchmark}; @@ -45,7 +46,7 @@ pub fn write_benchmark_comparison(results: &[BenchmarkResult]) { } pub fn run_benchmarks_and_print_comparison( - commands: &[Command<'_>], + commands: &Commands, options: &Options, export_manager: &ExportManager, ) -> Result<()> { diff --git a/src/command.rs b/src/command.rs index dbfff4b..8b079b5 100644 --- a/src/command.rs +++ b/src/command.rs @@ -90,115 +90,9 @@ impl<'a> Command<'a> { } } -/// Build the commands to benchmark -pub fn build_commands(matches: &ArgMatches) -> Result> { - let command_names = matches.values_of("command-name"); - let command_strings = matches.values_of("command").unwrap(); - - if let Some(args) = matches.values_of("parameter-scan") { - let step_size = matches.value_of("parameter-step-size"); - Ok(get_parameterized_commands( - command_names, - command_strings, - args, - step_size, - )?) - } else if let Some(args) = matches.values_of("parameter-list") { - let command_names = command_names.map_or(vec![], |names| names.collect::>()); - - let args: Vec<_> = args.collect(); - let param_names_and_values: Vec<(&str, Vec)> = args - .chunks_exact(2) - .map(|pair| { - let name = pair[0]; - let list_str = pair[1]; - (name, tokenize(list_str)) - }) - .collect(); - { - let dupes = find_dupes(param_names_and_values.iter().map(|(name, _)| *name)); - if !dupes.is_empty() { - bail!("Duplicate parameter names: {}", &dupes.join(", ")); - } - } - let command_list = command_strings.collect::>(); - - let dimensions: Vec = std::iter::once(command_list.len()) - .chain( - param_names_and_values - .iter() - .map(|(_, values)| values.len()), - ) - .collect(); - let param_space_size = dimensions.iter().product(); - if param_space_size == 0 { - return Ok(Vec::new()); - } - - // `--command-name` should appear exactly once or exactly B times, - // where B is the total number of benchmarks. - let command_name_count = command_names.len(); - if command_name_count > 1 && command_name_count != param_space_size { - return Err(OptionsError::UnexpectedCommandNameCount( - command_name_count, - param_space_size, - ) - .into()); - } - - let mut i = 0; - let mut commands = Vec::with_capacity(param_space_size); - let mut index = vec![0usize; dimensions.len()]; - 'outer: loop { - let name = command_names - .get(i) - .or_else(|| command_names.get(0)) - .copied(); - i += 1; - - let (command_index, params_indices) = index.split_first().unwrap(); - let parameters = param_names_and_values - .iter() - .zip(params_indices) - .map(|((name, values), i)| (*name, ParameterValue::Text(values[*i].clone()))) - .collect(); - commands.push(Command::new_parametrized( - name, - command_list[*command_index], - parameters, - )); - - // Increment index, exiting loop on overflow. - for (i, n) in index.iter_mut().zip(dimensions.iter()) { - *i += 1; - if *i < *n { - continue 'outer; - } else { - *i = 0; - } - } - break 'outer; - } - - Ok(commands) - } else { - let command_names = command_names.map_or(vec![], |names| names.collect::>()); - if command_names.len() > command_strings.len() { - return Err(OptionsError::TooManyCommandNames(command_strings.len()).into()); - } - - let command_list = command_strings.collect::>(); - let mut commands = Vec::with_capacity(command_list.len()); - for (i, s) in command_list.iter().enumerate() { - commands.push(Command::new(command_names.get(i).copied(), s)); - } - Ok(commands) - } -} - /// Finds all the strings that appear multiple times in the input iterator, returning them in /// sorted order. If no string appears more than once, the result is an empty vector. -fn find_dupes<'a, I: IntoIterator>(i: I) -> Vec<&'a str> { +fn find_duplicates<'a, I: IntoIterator>(i: I) -> Vec<&'a str> { let mut counts = BTreeMap::<&'a str, usize>::new(); for s in i { *counts.entry(s).or_default() += 1; @@ -209,6 +103,125 @@ fn find_dupes<'a, I: IntoIterator>(i: I) -> Vec<&'a str> { .collect() } +pub struct Commands<'a>(Vec>); + +impl<'a> Commands<'a> { + /// Build the commands to benchmark + pub fn from_cli_arguments(matches: &'a ArgMatches) -> Result { + let command_names = matches.values_of("command-name"); + let command_strings = matches.values_of("command").unwrap(); + + if let Some(args) = matches.values_of("parameter-scan") { + let step_size = matches.value_of("parameter-step-size"); + Ok(Self(get_parameterized_commands( + command_names, + command_strings, + args, + step_size, + )?)) + } else if let Some(args) = matches.values_of("parameter-list") { + let command_names = command_names.map_or(vec![], |names| names.collect::>()); + + let args: Vec<_> = args.collect(); + let param_names_and_values: Vec<(&str, Vec)> = args + .chunks_exact(2) + .map(|pair| { + let name = pair[0]; + let list_str = pair[1]; + (name, tokenize(list_str)) + }) + .collect(); + { + let duplicates = + find_duplicates(param_names_and_values.iter().map(|(name, _)| *name)); + if !duplicates.is_empty() { + bail!("Duplicate parameter names: {}", &duplicates.join(", ")); + } + } + let command_list = command_strings.collect::>(); + + let dimensions: Vec = std::iter::once(command_list.len()) + .chain( + param_names_and_values + .iter() + .map(|(_, values)| values.len()), + ) + .collect(); + let param_space_size = dimensions.iter().product(); + if param_space_size == 0 { + return Ok(Self(Vec::new())); + } + + // `--command-name` should appear exactly once or exactly B times, + // where B is the total number of benchmarks. + let command_name_count = command_names.len(); + if command_name_count > 1 && command_name_count != param_space_size { + return Err(OptionsError::UnexpectedCommandNameCount( + command_name_count, + param_space_size, + ) + .into()); + } + + let mut i = 0; + let mut commands = Vec::with_capacity(param_space_size); + let mut index = vec![0usize; dimensions.len()]; + 'outer: loop { + let name = command_names + .get(i) + .or_else(|| command_names.get(0)) + .copied(); + i += 1; + + let (command_index, params_indices) = index.split_first().unwrap(); + let parameters = param_names_and_values + .iter() + .zip(params_indices) + .map(|((name, values), i)| (*name, ParameterValue::Text(values[*i].clone()))) + .collect(); + commands.push(Command::new_parametrized( + name, + command_list[*command_index], + parameters, + )); + + // Increment index, exiting loop on overflow. + for (i, n) in index.iter_mut().zip(dimensions.iter()) { + *i += 1; + if *i < *n { + continue 'outer; + } else { + *i = 0; + } + } + break 'outer; + } + + Ok(Self(commands)) + } else { + let command_names = command_names.map_or(vec![], |names| names.collect::>()); + if command_names.len() > command_strings.len() { + return Err(OptionsError::TooManyCommandNames(command_strings.len()).into()); + } + + let command_list = command_strings.collect::>(); + let mut commands = Vec::with_capacity(command_list.len()); + for (i, s) in command_list.iter().enumerate() { + commands.push(Command::new(command_names.get(i).copied(), s)); + } + Ok(Self(commands)) + } + } + + pub fn iter(&self) -> impl Iterator> { + self.0.iter() + } + + pub fn len(&self) -> usize { + self.0.len() + } +} + #[test] fn test_get_shell_command_nonoverlapping() { let cmd = Command::new_parametrized( @@ -243,9 +256,9 @@ impl<'a> fmt::Display for Command<'a> { #[test] fn test_build_commands_cross_product() { - use crate::app::get_arg_matches; + use crate::app::get_cli_arguments; - let matches = get_arg_matches(vec![ + let matches = get_cli_arguments(vec![ "hyperfine", "-L", "par1", @@ -256,7 +269,7 @@ fn test_build_commands_cross_product() { "echo {par1} {par2}", "printf '%s\n' {par1} {par2}", ]); - let result = build_commands(&matches).unwrap(); + let result = Commands::from_cli_arguments(&matches).unwrap().0; // Iteration order: command list first, then parameters in listed order (here, "par1" before // "par2", which is distinct from their sorted order), with parameter values in listed order. @@ -281,9 +294,9 @@ fn test_build_commands_cross_product() { #[test] fn test_build_parameter_list_commands() { - use crate::app::get_arg_matches; + use crate::app::get_cli_arguments; - let matches = get_arg_matches(vec![ + let matches = get_cli_arguments(vec![ "hyperfine", "echo {foo}", "--parameter-list", @@ -292,7 +305,7 @@ fn test_build_parameter_list_commands() { "--command-name", "name-{foo}", ]); - let commands = build_commands(&matches).unwrap(); + let commands = Commands::from_cli_arguments(&matches).unwrap().0; assert_eq!(commands.len(), 2); assert_eq!(commands[0].get_name(), "name-1"); assert_eq!(commands[1].get_name(), "name-2"); @@ -302,8 +315,8 @@ fn test_build_parameter_list_commands() { #[test] fn test_build_parameter_range_commands() { - use crate::app::get_arg_matches; - let matches = get_arg_matches(vec![ + use crate::app::get_cli_arguments; + let matches = get_cli_arguments(vec![ "hyperfine", "echo {val}", "--parameter-scan", @@ -315,7 +328,7 @@ fn test_build_parameter_range_commands() { "--command-name", "name-{val}", ]); - let commands = build_commands(&matches).unwrap(); + let commands = Commands::from_cli_arguments(&matches).unwrap().0; assert_eq!(commands.len(), 2); assert_eq!(commands[0].get_name(), "name-1"); assert_eq!(commands[1].get_name(), "name-2"); diff --git a/src/main.rs b/src/main.rs index 6826cff..9bbf636 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ use std::env; -use app::get_arg_matches; +use app::get_cli_arguments; use benchmark::schedule::run_benchmarks_and_print_comparison; -use command::build_commands; +use command::Commands; use export::ExportManager; use options::Options; @@ -27,10 +27,10 @@ fn run() -> Result<()> { #[cfg(windows)] colored::control::set_virtual_terminal(true).unwrap(); - let matches = get_arg_matches(env::args_os()); - let options = Options::from_cli_arguments(&matches)?; - let commands = build_commands(&matches)?; - let export_manager = ExportManager::from_cli_arguments(&matches)?; + let cli_arguments = get_cli_arguments(env::args_os()); + let options = Options::from_cli_arguments(&cli_arguments)?; + let commands = Commands::from_cli_arguments(&cli_arguments)?; + let export_manager = ExportManager::from_cli_arguments(&cli_arguments)?; run_benchmarks_and_print_comparison(&commands, &options, &export_manager) }