use std::ffi::OsString;
use std::time::Duration;
use anyhow::Result;
use anyhow::bail;
use futures::FutureExt;
use futures::future::LocalBoxFuture;
use crate::shell::types::ExecuteResult;
use crate::shell::types::ShellPipeWriter;
use super::ShellCommand;
use super::ShellCommandContext;
use super::args::ArgKind;
use super::args::parse_arg_kinds;
use super::execute_with_cancellation;
pub struct SleepCommand;
impl ShellCommand for SleepCommand {
fn execute(
&self,
context: ShellCommandContext,
) -> LocalBoxFuture<'static, ExecuteResult> {
async move {
execute_with_cancellation!(
sleep_command(&context.args, context.stderr),
context.state.kill_signal()
)
}
.boxed_local()
}
}
async fn sleep_command(
args: &[OsString],
mut stderr: ShellPipeWriter,
) -> ExecuteResult {
match execute_sleep(args).await {
Ok(()) => ExecuteResult::from_exit_code(0),
Err(err) => {
let _ = stderr.write_line(&format!("sleep: {err}"));
ExecuteResult::from_exit_code(1)
}
}
}
async fn execute_sleep(args: &[OsString]) -> Result<()> {
let ms = parse_args(args)?;
tokio::time::sleep(Duration::from_millis(ms)).await;
Ok(())
}
fn parse_arg(arg: &str) -> Result<f64> {
if let Some(t) = arg.strip_suffix('s') {
return Ok(t.parse()?);
}
if let Some(t) = arg.strip_suffix('m') {
return Ok(t.parse::<f64>()? * 60.);
}
if let Some(t) = arg.strip_suffix('h') {
return Ok(t.parse::<f64>()? * 60. * 60.);
}
if let Some(t) = arg.strip_suffix('d') {
return Ok(t.parse::<f64>()? * 60. * 60. * 24.);
}
Ok(arg.parse()?)
}
fn parse_args(args: &[OsString]) -> Result<u64> {
let mut total_time_ms = 0;
let mut had_value = false;
for arg in parse_arg_kinds(args) {
match arg {
ArgKind::Arg(arg) => {
match arg
.to_str()
.ok_or_else(|| anyhow::anyhow!("invalid utf-8"))
.and_then(parse_arg)
{
Ok(value_s) => {
let ms = (value_s * 1000f64) as u64;
total_time_ms += ms;
had_value = true;
}
Err(err) => {
bail!(
"error parsing argument '{}' to number: {}",
arg.to_string_lossy(),
err
);
}
}
}
ArgKind::LongFlag(_) | ArgKind::ShortFlag(_) => arg.bail_unsupported()?,
}
}
if !had_value {
bail!("missing operand");
}
Ok(total_time_ms)
}
#[cfg(test)]
mod test {
use std::time::Instant;
use super::*;
#[test]
fn should_parse_arg() {
assert_eq!(parse_arg("1").unwrap(), 1.);
assert_eq!(parse_arg("1s").unwrap(), 1.);
assert_eq!(parse_arg("1m").unwrap(), 1. * 60.);
assert_eq!(parse_arg("1h").unwrap(), 1. * 60. * 60.);
assert_eq!(parse_arg("1d").unwrap(), 1. * 60. * 60. * 24.);
assert!(parse_arg("d").err().is_some());
}
#[test]
fn should_parse_args() {
let value =
parse_args(&["0.5".into(), "1m".into(), "1.25".into()]).unwrap();
assert_eq!(value, 500 + 1000 * 60 + 1250);
let result = parse_args(&[]).err().unwrap();
assert_eq!(result.to_string(), "missing operand");
let result = parse_args(&["test".into()]).err().unwrap();
assert_eq!(
result.to_string(),
"error parsing argument 'test' to number: invalid float literal"
);
}
#[tokio::test]
async fn should_execute() {
let time = Instant::now();
execute_sleep(&["0.1".into()]).await.unwrap();
assert!(time.elapsed().as_millis() >= 100);
}
}