diff --git a/src/hyperfine/benchmark.rs b/src/hyperfine/benchmark.rs index bde077b..76dac51 100644 --- a/src/hyperfine/benchmark.rs +++ b/src/hyperfine/benchmark.rs @@ -5,6 +5,7 @@ use std::process::{ExitStatus, Stdio}; use colored::*; use statistical::{mean, median, standard_deviation}; +use crate::hyperfine::command::Command; use crate::hyperfine::format::{format_duration, format_duration_unit}; use crate::hyperfine::internal::{get_progress_bar, max, min, MIN_EXECUTION_TIME}; use crate::hyperfine::outlier_detection::{modified_zscores, OUTLIER_THRESHOLD}; @@ -12,7 +13,7 @@ use crate::hyperfine::shell::execute_and_time; use crate::hyperfine::timer::wallclocktimer::WallClockTimer; use crate::hyperfine::timer::{TimerStart, TimerStop}; use crate::hyperfine::types::{ - BenchmarkResult, CmdFailureAction, Command, HyperfineOptions, OutputStyleOption, + BenchmarkResult, CmdFailureAction, HyperfineOptions, OutputStyleOption, }; use crate::hyperfine::units::Second; use crate::hyperfine::warnings::Warnings; diff --git a/src/hyperfine/command.rs b/src/hyperfine/command.rs new file mode 100644 index 0000000..6a14337 --- /dev/null +++ b/src/hyperfine/command.rs @@ -0,0 +1,115 @@ +use std::collections::BTreeMap; +use std::fmt; + +use super::types::ParameterValue; + +/// A command that should be benchmarked. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Command<'a> { + /// The command name (without parameter substitution) + name: Option<&'a str>, + + /// The command that should be executed (without parameter substitution) + expression: &'a str, + + /// Zero or more parameter values. + parameters: Vec<(&'a str, ParameterValue)>, +} + +impl<'a> Command<'a> { + pub fn new(name: Option<&'a str>, expression: &'a str) -> Command<'a> { + Command { + name, + expression, + parameters: Vec::new(), + } + } + + pub fn new_parametrized( + name: Option<&'a str>, + expression: &'a str, + parameters: Vec<(&'a str, ParameterValue)>, + ) -> Command<'a> { + Command { + name, + expression, + parameters, + } + } + + pub fn get_name(&self) -> String { + self.name.map_or_else( + || self.get_shell_command(), + |name| self.replace_parameters_in(name), + ) + } + + pub fn get_shell_command(&self) -> String { + self.replace_parameters_in(self.expression) + } + + pub fn get_parameters(&self) -> &Vec<(&'a str, ParameterValue)> { + &self.parameters + } + + fn replace_parameters_in(&self, original: &str) -> String { + let mut result = String::new(); + let mut replacements = BTreeMap::::new(); + for (param_name, param_value) in &self.parameters { + replacements.insert( + format!("{{{param_name}}}", param_name = param_name), + param_value.to_string(), + ); + } + let mut remaining = original; + // Manually replace consecutive occurrences to avoid double-replacing: e.g., + // + // hyperfine -L foo 'a,{bar}' -L bar 'baz,quux' 'echo {foo} {bar}' + // + // should not ever run 'echo baz baz'. See `test_get_shell_command_nonoverlapping`. + 'outer: while let Some(head) = remaining.chars().next() { + for (k, v) in &replacements { + if remaining.starts_with(k.as_str()) { + result.push_str(v); + remaining = &remaining[k.len()..]; + continue 'outer; + } + } + result.push(head); + remaining = &remaining[head.len_utf8()..]; + } + result + } +} + +#[test] +fn test_get_shell_command_nonoverlapping() { + let cmd = Command::new_parametrized( + None, + "echo {foo} {bar}", + vec![ + ("foo", ParameterValue::Text("{bar} baz".into())), + ("bar", ParameterValue::Text("quux".into())), + ], + ); + assert_eq!(cmd.get_shell_command(), "echo {bar} baz quux"); +} + +#[test] +fn test_get_parameterized_command_name() { + let cmd = Command::new_parametrized( + Some("name-{bar}-{foo}"), + "echo {foo} {bar}", + vec![ + ("foo", ParameterValue::Text("baz".into())), + ("bar", ParameterValue::Text("quux".into())), + ], + ); + assert_eq!(cmd.get_name(), "name-quux-baz"); +} + +impl<'a> fmt::Display for Command<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.get_shell_command()) + } +} diff --git a/src/hyperfine/mod.rs b/src/hyperfine/mod.rs index ceafc92..5a9f5b0 100644 --- a/src/hyperfine/mod.rs +++ b/src/hyperfine/mod.rs @@ -1,5 +1,6 @@ pub mod app; pub mod benchmark; +pub mod command; pub mod error; pub mod export; pub mod format; diff --git a/src/hyperfine/parameter_range.rs b/src/hyperfine/parameter_range.rs index 7115e3c..2096651 100644 --- a/src/hyperfine/parameter_range.rs +++ b/src/hyperfine/parameter_range.rs @@ -1,11 +1,14 @@ -use crate::hyperfine::error::ParameterScanError; -use crate::hyperfine::types::{Command, NumericType, ParameterValue}; -use clap::Values; -use rust_decimal::Decimal; use std::convert::TryInto; use std::ops::{Add, AddAssign, Div, Sub}; use std::str::FromStr; +use clap::Values; +use rust_decimal::Decimal; + +use crate::hyperfine::command::Command; +use crate::hyperfine::error::ParameterScanError; +use crate::hyperfine::types::{NumericType, ParameterValue}; + trait Numeric: Add + Sub diff --git a/src/hyperfine/types.rs b/src/hyperfine/types.rs index 93624a0..1259b88 100644 --- a/src/hyperfine/types.rs +++ b/src/hyperfine/types.rs @@ -1,11 +1,11 @@ -use rust_decimal::prelude::ToPrimitive; -use rust_decimal::Decimal; -/// This module contains common internal types. use serde::*; use std::collections::BTreeMap; use std::convert::TryFrom; use std::fmt; +use rust_decimal::prelude::ToPrimitive; +use rust_decimal::Decimal; + use crate::hyperfine::units::{Second, Unit}; #[cfg(not(windows))] @@ -71,117 +71,6 @@ impl<'a> ToString for ParameterValue { } } -/// A command that should be benchmarked. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Command<'a> { - /// The command name (without parameter substitution) - name: Option<&'a str>, - - /// The command that should be executed (without parameter substitution) - expression: &'a str, - - /// Zero or more parameter values. - parameters: Vec<(&'a str, ParameterValue)>, -} - -impl<'a> Command<'a> { - pub fn new(name: Option<&'a str>, expression: &'a str) -> Command<'a> { - Command { - name, - expression, - parameters: Vec::new(), - } - } - - pub fn new_parametrized( - name: Option<&'a str>, - expression: &'a str, - parameters: Vec<(&'a str, ParameterValue)>, - ) -> Command<'a> { - Command { - name, - expression, - parameters, - } - } - - pub fn get_name(&self) -> String { - self.name.map_or_else( - || self.get_shell_command(), - |name| self.replace_parameters_in(name), - ) - } - - pub fn get_shell_command(&self) -> String { - self.replace_parameters_in(self.expression) - } - - pub fn get_parameters(&self) -> &Vec<(&'a str, ParameterValue)> { - &self.parameters - } - - fn replace_parameters_in(&self, original: &str) -> String { - let mut result = String::new(); - let mut replacements = BTreeMap::::new(); - for (param_name, param_value) in &self.parameters { - replacements.insert( - format!("{{{param_name}}}", param_name = param_name), - param_value.to_string(), - ); - } - let mut remaining = original; - // Manually replace consecutive occurrences to avoid double-replacing: e.g., - // - // hyperfine -L foo 'a,{bar}' -L bar 'baz,quux' 'echo {foo} {bar}' - // - // should not ever run 'echo baz baz'. See `test_get_shell_command_nonoverlapping`. - 'outer: while let Some(head) = remaining.chars().next() { - for (k, v) in &replacements { - if remaining.starts_with(k.as_str()) { - result.push_str(v); - remaining = &remaining[k.len()..]; - continue 'outer; - } - } - result.push(head); - remaining = &remaining[head.len_utf8()..]; - } - result - } -} - -#[test] -fn test_get_shell_command_nonoverlapping() { - let cmd = Command::new_parametrized( - None, - "echo {foo} {bar}", - vec![ - ("foo", ParameterValue::Text("{bar} baz".into())), - ("bar", ParameterValue::Text("quux".into())), - ], - ); - assert_eq!(cmd.get_shell_command(), "echo {bar} baz quux"); -} - -#[test] -fn test_get_parameterized_command_name() { - let cmd = Command::new_parametrized( - Some("name-{bar}-{foo}"), - "echo {foo} {bar}", - vec![ - ("foo", ParameterValue::Text("baz".into())), - ("bar", ParameterValue::Text("quux".into())), - ], - ); - assert_eq!(cmd.get_name(), "name-quux-baz"); -} - -impl<'a> fmt::Display for Command<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.get_shell_command()) - } -} - /// Action to take when an executed command fails. #[derive(Debug, Clone, Copy, PartialEq)] pub enum CmdFailureAction { diff --git a/src/main.rs b/src/main.rs index aeb4a04..0f14941 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,12 +11,13 @@ mod hyperfine; use crate::hyperfine::app::get_arg_matches; use crate::hyperfine::benchmark::{mean_shell_spawning_time, run_benchmark}; +use crate::hyperfine::command::Command; use crate::hyperfine::error::OptionsError; use crate::hyperfine::export::{ExportManager, ExportType}; use crate::hyperfine::internal::{tokenize, write_benchmark_comparison}; use crate::hyperfine::parameter_range::get_parameterized_commands; use crate::hyperfine::types::{ - CmdFailureAction, Command, HyperfineOptions, OutputStyleOption, ParameterValue, + CmdFailureAction, HyperfineOptions, OutputStyleOption, ParameterValue, }; use crate::hyperfine::units::Unit;