use std::process::Command;
use std::time::Instant;
pub struct Run {
pub code: Option<i32>,
pub seconds: u64,
}
impl Run {
pub fn passed(&self) -> bool {
self.code == Some(0)
}
pub fn event_body(&self, command: &str) -> String {
let verdict = match self.code {
Some(0) => "green".to_string(),
Some(code) => format!("red (exit {code})"),
None => "red (stopped before it finished)".to_string(),
};
format!("{verdict} in {}: {command}", duration(self.seconds))
}
}
pub fn body_is_green(body: &str) -> bool {
body.starts_with("green")
}
pub fn duration(seconds: u64) -> String {
match seconds {
0 => "under a second".to_string(),
1 => "1 second".to_string(),
s if s < 60 => format!("{s} seconds"),
s => {
let (m, rest) = (s / 60, s % 60);
match (m, rest) {
(1, 0) => "1 minute".to_string(),
(m, 0) => format!("{m} minutes"),
(1, r) => format!("1 minute {r}s"),
(m, r) => format!("{m} minutes {r}s"),
}
}
}
}
#[cfg(windows)]
fn shell() -> (std::ffi::OsString, &'static str) {
let shell = std::env::var_os("COMSPEC").unwrap_or_else(|| std::ffi::OsString::from("cmd.exe"));
(shell, "/C")
}
#[cfg(not(windows))]
fn shell() -> (std::ffi::OsString, &'static str) {
(std::ffi::OsString::from("sh"), "-c")
}
pub fn run(command: &str, dir: &std::path::Path) -> std::io::Result<Run> {
let (program, flag) = shell();
let started = Instant::now();
let status = Command::new(program).arg(flag).arg(command).current_dir(dir).status()?;
Ok(Run {
code: status.code(),
seconds: started.elapsed().as_secs(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_green_run_reads_back_as_green_and_a_red_one_does_not() {
let green = Run { code: Some(0), seconds: 12 }.event_body("cargo test");
assert!(body_is_green(&green), "{green}");
assert!(green.contains("cargo test"), "the command is in the line: {green}");
let red = Run { code: Some(101), seconds: 3 }.event_body("cargo test");
assert!(!body_is_green(&red), "{red}");
assert!(red.contains("101"), "what it exited with is in the line: {red}");
}
#[test]
fn a_run_that_never_finished_is_red() {
let stopped = Run { code: None, seconds: 8 };
assert!(!stopped.passed());
assert!(!body_is_green(&stopped.event_body("cargo test")));
}
#[test]
fn a_duration_is_said_the_way_a_person_would() {
assert_eq!(duration(0), "under a second");
assert_eq!(duration(1), "1 second");
assert_eq!(duration(45), "45 seconds");
assert_eq!(duration(60), "1 minute");
assert_eq!(duration(90), "1 minute 30s");
assert_eq!(duration(125), "2 minutes 5s");
assert_eq!(duration(180), "3 minutes");
}
}