Skip to main content

forge_core/util/
mod.rs

1//! Shared utility functions for the Forge framework.
2
3use std::time::Duration;
4
5/// Parse a duration string into `Duration`.
6///
7/// Supports the following suffixes:
8/// - `ms` - milliseconds
9/// - `s` - seconds
10/// - `m` - minutes
11/// - `h` - hours
12/// - `d` - days
13///
14/// If no suffix is provided, the value is interpreted as seconds.
15pub fn parse_duration(s: &str) -> Option<Duration> {
16    let s = s.trim();
17    if let Some(num) = s.strip_suffix("ms") {
18        num.parse::<u64>().ok().map(Duration::from_millis)
19    } else if let Some(num) = s.strip_suffix('s') {
20        num.parse::<u64>().ok().map(Duration::from_secs)
21    } else if let Some(num) = s.strip_suffix('m') {
22        num.parse::<u64>().ok().map(|m| Duration::from_secs(m * 60))
23    } else if let Some(num) = s.strip_suffix('h') {
24        num.parse::<u64>()
25            .ok()
26            .map(|h| Duration::from_secs(h * 3600))
27    } else if let Some(num) = s.strip_suffix('d') {
28        num.parse::<u64>()
29            .ok()
30            .map(|d| Duration::from_secs(d * 86400))
31    } else {
32        s.parse::<u64>().ok().map(Duration::from_secs)
33    }
34}
35
36/// Convert a snake_case string to PascalCase.
37pub fn to_pascal_case(s: &str) -> String {
38    s.split('_')
39        .map(|part| {
40            let mut chars = part.chars();
41            match chars.next() {
42                None => String::new(),
43                Some(first) => first.to_uppercase().chain(chars).collect(),
44            }
45        })
46        .collect()
47}
48
49/// Convert a PascalCase or camelCase string to snake_case.
50pub fn to_snake_case(s: &str) -> String {
51    let mut result = String::new();
52    for (i, c) in s.chars().enumerate() {
53        if c.is_uppercase() {
54            if i > 0 {
55                result.push('_');
56            }
57            result.extend(c.to_lowercase());
58        } else {
59            result.push(c);
60        }
61    }
62    result
63}
64
65/// Convert a snake_case string to camelCase.
66pub fn to_camel_case(s: &str) -> String {
67    let mut result = String::new();
68    let mut capitalize_next = false;
69    for c in s.chars() {
70        if c == '_' {
71            capitalize_next = true;
72        } else if capitalize_next {
73            result.extend(c.to_uppercase());
74            capitalize_next = false;
75        } else {
76            result.push(c);
77        }
78    }
79    result
80}
81
82#[cfg(test)]
83#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn test_parse_duration_milliseconds() {
89        assert_eq!(parse_duration("100ms"), Some(Duration::from_millis(100)));
90        assert_eq!(parse_duration("1000ms"), Some(Duration::from_millis(1000)));
91    }
92
93    #[test]
94    fn test_parse_duration_seconds() {
95        assert_eq!(parse_duration("30s"), Some(Duration::from_secs(30)));
96        assert_eq!(parse_duration("60s"), Some(Duration::from_secs(60)));
97    }
98
99    #[test]
100    fn test_parse_duration_minutes() {
101        assert_eq!(parse_duration("5m"), Some(Duration::from_secs(300)));
102        assert_eq!(parse_duration("10m"), Some(Duration::from_secs(600)));
103    }
104
105    #[test]
106    fn test_parse_duration_hours() {
107        assert_eq!(parse_duration("1h"), Some(Duration::from_secs(3600)));
108        assert_eq!(parse_duration("24h"), Some(Duration::from_secs(86400)));
109    }
110
111    #[test]
112    fn test_parse_duration_days() {
113        assert_eq!(parse_duration("1d"), Some(Duration::from_secs(86400)));
114        assert_eq!(parse_duration("7d"), Some(Duration::from_secs(604800)));
115    }
116
117    #[test]
118    fn test_parse_duration_bare_number() {
119        assert_eq!(parse_duration("60"), Some(Duration::from_secs(60)));
120        assert_eq!(parse_duration("3600"), Some(Duration::from_secs(3600)));
121    }
122
123    #[test]
124    fn test_parse_duration_whitespace() {
125        assert_eq!(parse_duration("  30s  "), Some(Duration::from_secs(30)));
126    }
127
128    #[test]
129    fn test_parse_duration_invalid() {
130        assert_eq!(parse_duration("invalid"), None);
131        assert_eq!(parse_duration("abc123"), None);
132        assert_eq!(parse_duration(""), None);
133    }
134
135    #[test]
136    fn test_to_snake_case() {
137        assert_eq!(to_snake_case("GetUser"), "get_user");
138        assert_eq!(to_snake_case("ListAllProjects"), "list_all_projects");
139        assert_eq!(to_snake_case("Simple"), "simple");
140        assert_eq!(to_snake_case("ProjectStatus"), "project_status");
141    }
142
143    #[test]
144    fn test_to_pascal_case() {
145        assert_eq!(to_pascal_case("get_user"), "GetUser");
146        assert_eq!(to_pascal_case("list_all_projects"), "ListAllProjects");
147        assert_eq!(to_pascal_case("simple"), "Simple");
148    }
149
150    #[test]
151    fn test_to_camel_case() {
152        assert_eq!(to_camel_case("get_user"), "getUser");
153        assert_eq!(to_camel_case("list_all_projects"), "listAllProjects");
154        assert_eq!(to_camel_case("simple"), "simple");
155    }
156}