1#![feature(exit_status_error)]
20
21use core::str::FromStr;
22use std::process::Command;
23use std::process::ExitStatusError;
24use std::time::Duration;
25use std::time::Instant;
26
27use color_eyre::eyre;
28use color_eyre::eyre::WrapErr;
29use color_eyre::eyre::bail;
30use itertools::Itertools;
31use ytil_sys::cli::Args;
32
33enum ExitCond {
35 Ok,
37 Ko,
39}
40
41impl ExitCond {
42 #[allow(clippy::suspicious_operation_groupings)]
44 pub const fn should_break(&self, cmd_res: Result<(), ExitStatusError>) -> bool {
45 self.is_ok() && cmd_res.is_ok() || !self.is_ok() && cmd_res.is_err()
46 }
47
48 const fn is_ok(&self) -> bool {
50 match self {
51 Self::Ok => true,
52 Self::Ko => false,
53 }
54 }
55}
56
57impl FromStr for ExitCond {
59 type Err = eyre::Error;
60
61 fn from_str(s: &str) -> Result<Self, Self::Err> {
62 Ok(match s {
63 "ok" => Self::Ok,
64 "ko" => Self::Ko,
65 unexpected => bail!("unexpected exit condition | value={unexpected}"),
66 })
67 }
68}
69
70fn main() -> color_eyre::Result<()> {
72 color_eyre::install()?;
73
74 let args = ytil_sys::cli::get();
75
76 if args.has_help() {
77 println!("{}", include_str!("../help.txt"));
78 return Ok(());
79 }
80
81 let Some((cooldown_secs, args)) = args.split_first() else {
82 bail!("missing cooldown arg | args={args:#?}");
83 };
84 let cooldown = Duration::from_secs(
85 cooldown_secs
86 .parse()
87 .with_context(|| format!("invalid cooldown secs | value={cooldown_secs}"))?,
88 );
89
90 let Some((exit_cond, args)) = args.split_first() else {
91 bail!("missing exit condition arg | args={args:#?}");
92 };
93 let exit_cond =
94 ExitCond::from_str(exit_cond).with_context(|| format!("invalid exit condition | args={args:#?}"))?;
95
96 let cmd = args.iter().join(" ");
97
98 let mut tries = vec![];
99 loop {
100 let now = Instant::now();
101 let output = Command::new("sh")
102 .arg("-c")
103 .arg(&cmd)
104 .output()
105 .with_context(|| format!("error running cmd | cmd={cmd:?}"))?;
106 tries.push(now.elapsed());
107
108 let terminal_output = if output.status.success() {
109 output.stdout
110 } else {
111 output.stderr
112 };
113 println!("{}", String::from_utf8_lossy(&terminal_output));
114
115 if exit_cond.should_break(output.status.exit_ok()) {
116 break;
117 }
118 std::thread::sleep(cooldown);
119 }
120
121 let tries_count =
122 u32::try_from(tries.len()).with_context(|| format!("cannot convert tries len to u32 | len={}", tries.len()))?;
123 let total_time = tries.iter().fold(Duration::ZERO, |acc, &d| acc.saturating_add(d));
124 let avg_runs_time = if tries_count > 0 {
125 total_time.checked_div(tries_count).unwrap_or(Duration::ZERO)
126 } else {
127 Duration::ZERO
128 };
129 println!("Summary:\n - tries {tries_count}\n - avg time {avg_runs_time:#?}");
130
131 Ok(())
132}