use clap::{App, AppSettings};
use rand::distributions::{Distribution, Uniform};
use std::process::Command;
use std::process::ExitStatus;
use std::thread::sleep;
use std::time::Duration;
use std::time::Instant;
pub enum SlotTime {
UserSpecified(Duration),
AutoGenerated(Duration),
}
pub fn clamp<T>(value: T, min: T, max: T) -> T
where
T: core::cmp::PartialOrd,
{
assert!(min <= max);
if value < min {
min
} else if value > max {
max
} else {
value
}
}
fn main() {
let max_n: u32 = 10;
let matches = App::new(env!("CARGO_PKG_NAME"))
.about(env!("CARGO_PKG_DESCRIPTION"))
.version(env!("CARGO_PKG_VERSION"))
.author(env!("CARGO_PKG_AUTHORS"))
.setting(AppSettings::AllowExternalSubcommands)
.get_matches();
let mut command: Command = match matches.subcommand() {
(name, Some(matches)) => {
let mut command = Command::new(name);
let args: Vec<&str> = matches.values_of("").map_or(Vec::new(), Iterator::collect);
for arg in args {
command.arg(arg);
}
command
}
_ => panic!("no command was given"),
};
let mut iterations: u32 = 0;
let mut slot_time: Option<SlotTime> = None;
let mut rng = rand::thread_rng();
loop {
let start: Instant = Instant::now();
let status: ExitStatus = command.status().expect("failed to execute process");
let elapsed: Duration = start.elapsed();
iterations += 1;
match status.code() {
Some(0) => break,
Some(_) => {}
None => {
eprintln!("Child terminated by signal; breaking");
break;
}
}
if let Some(SlotTime::AutoGenerated(dur)) = &slot_time {
let dur: Duration = (*dur * (iterations - 1) + elapsed) / iterations;
slot_time = Some(SlotTime::AutoGenerated(dur));
} else if slot_time.is_none() {
slot_time = Some(SlotTime::AutoGenerated(elapsed));
}
let delay: Duration = match &slot_time {
Some(SlotTime::UserSpecified(dur)) | Some(SlotTime::AutoGenerated(dur)) => {
let distribution = Uniform::new(0, 2_u32.pow(clamp(iterations + 1, 0, max_n)) - 1);
let multiplicand: u32 = distribution.sample(&mut rng);
*dur * multiplicand
}
None => Duration::new(0, 0),
};
sleep(delay);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clamp_i32_inside_range() {
assert_eq!(clamp(1_i32, 0_i32, 2_i32), 1_i32);
}
#[test]
fn clamp_i32_below_range() {
assert_eq!(clamp(-1_i32, 0_i32, 2_i32), 0_i32);
}
#[test]
fn clamp_i32_above_range() {
assert_eq!(clamp(3_i32, 0_i32, 2_i32), 2_i32);
}
#[test]
#[should_panic]
fn clamp_panics_if_not_ordered_properly() {
clamp(1_i32, 2_i32, 0_i32);
}
}