sleep_utils/
duration_parser.rs

1use crate::{Result, SleepError};
2use std::time::Duration;
3
4/// Parse sleep duration with support for multiple formats
5pub fn parse_sleep_duration(input: &str) -> Result<Duration> {
6    let input = input.trim().to_lowercase();
7
8    if input.is_empty() {
9        return Ok(Duration::ZERO);
10    }
11
12    // Try to parse as plain number (default to milliseconds)
13    if let Ok(millis) = input.parse::<isize>() {
14        if millis <= 0 {
15            return Ok(Duration::ZERO);
16        }
17        return Ok(Duration::from_millis(millis as u64));
18    }
19
20    // Parse time with units
21    if let Some(duration) = parse_duration_with_unit(&input)? {
22        Ok(duration)
23    } else {
24        Err(SleepError::InvalidDuration(format!(
25            "Invalid sleep duration format: '{}'",
26            input
27        )))
28    }
29}
30
31/// Parse duration with time units
32fn parse_duration_with_unit(input: &str) -> Result<Option<Duration>> {
33    use lazy_static::lazy_static;
34    use regex::Regex;
35
36    lazy_static! {
37        static ref PATTERNS: Vec<(&'static str, f64)> = vec![
38            // Milliseconds
39            (r"^(\d+)\s*(ms|millis?|milliseconds?)$", 1.0),
40            // Seconds
41            (r"^(\d+)\s*(s|sec|seconds?)$", 1000.0),
42            // Minutes
43            (r"^(\d+)\s*(m|min|minutes?)$", 60_000.0),
44            // Short format (no spaces)
45            (r"^(\d+)(ms)$", 1.0),
46            (r"^(\d+)(s)$", 1000.0),
47            (r"^(\d+)(m)$", 60_000.0),
48        ];
49
50        static ref FLOAT_PATTERNS: Vec<(&'static str, f64)> = vec![
51            (r"^(\d*\.?\d+)\s*(s|sec|seconds?)$", 1000.0),
52            (r"^(\d*\.?\d+)\s*(m|min|minutes?)$", 60_000.0),
53            (r"^(\d*\.?\d+)(s)$", 1000.0),
54            (r"^(\d*\.?\d+)(m)$", 60_000.0),
55        ];
56    }
57
58    for (pattern, multiplier) in PATTERNS.iter() {
59        let re = Regex::new(pattern).unwrap();
60        if let Some(caps) = re.captures(input) {
61            if let Ok(value) = caps[1].parse::<isize>() {
62                if value <= 0 {
63                    return Ok(Some(Duration::ZERO));
64                }
65                let millis = (value as f64 * multiplier) as u64;
66                return Ok(Some(Duration::from_millis(millis)));
67            }
68        }
69    }
70
71    for (pattern, multiplier) in FLOAT_PATTERNS.iter() {
72        let re = Regex::new(pattern).unwrap();
73        if let Some(caps) = re.captures(input) {
74            if let Ok(value) = caps[1].parse::<f64>() {
75                if value <= 0.0 {
76                    return Ok(Some(Duration::ZERO));
77                }
78                let millis = (value * multiplier) as u64;
79                return Ok(Some(Duration::from_millis(millis)));
80            }
81        }
82    }
83
84    Ok(None)
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn test_parse_sleep_duration() -> Result<()> {
93        assert_eq!(parse_sleep_duration("100")?, Duration::from_millis(100));
94        assert_eq!(parse_sleep_duration("100ms")?, Duration::from_millis(100));
95        assert_eq!(parse_sleep_duration("1s")?, Duration::from_secs(1));
96        assert_eq!(parse_sleep_duration("1.5s")?, Duration::from_millis(1500));
97        assert_eq!(parse_sleep_duration("0")?, Duration::ZERO);
98        Ok(())
99    }
100}