1use std::time::Duration;
4
5pub 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
36pub fn parse_size(s: &str) -> Option<usize> {
46 let s = s.trim().to_lowercase();
47 if let Some(num) = s.strip_suffix("gb") {
48 num.trim()
49 .parse::<usize>()
50 .ok()
51 .map(|n| n * 1024 * 1024 * 1024)
52 } else if let Some(num) = s.strip_suffix("mb") {
53 num.trim().parse::<usize>().ok().map(|n| n * 1024 * 1024)
54 } else if let Some(num) = s.strip_suffix("kb") {
55 num.trim().parse::<usize>().ok().map(|n| n * 1024)
56 } else if let Some(num) = s.strip_suffix('b') {
57 num.trim().parse::<usize>().ok()
58 } else {
59 s.parse::<usize>().ok()
60 }
61}
62
63pub fn to_pascal_case(s: &str) -> String {
65 s.split('_')
66 .map(|part| {
67 let mut chars = part.chars();
68 match chars.next() {
69 None => String::new(),
70 Some(first) => first.to_uppercase().chain(chars).collect(),
71 }
72 })
73 .collect()
74}
75
76pub fn to_snake_case(s: &str) -> String {
78 let chars: Vec<char> = s.chars().collect();
79 let mut result = String::new();
80 for (i, &c) in chars.iter().enumerate() {
81 if c.is_uppercase() {
82 let prev_lower = i > 0 && chars.get(i - 1).is_some_and(|p| p.is_lowercase());
83 let next_lower = chars.get(i + 1).is_some_and(|n| n.is_lowercase());
84 if i > 0 && (prev_lower || next_lower) {
85 result.push('_');
86 }
87 result.extend(c.to_lowercase());
88 } else {
89 result.push(c);
90 }
91 }
92 result
93}
94
95pub fn pluralize(s: &str) -> String {
97 if s.ends_with("ss")
98 || s.ends_with("sh")
99 || s.ends_with("ch")
100 || s.ends_with('x')
101 || s.ends_with("zz")
102 {
103 format!("{s}es")
104 } else if s.ends_with('z') {
105 format!("{s}zes")
106 } else if s.ends_with('s') {
107 format!("{s}es")
108 } else if let Some(stem) = s.strip_suffix('y') {
109 if !s.ends_with("ay") && !s.ends_with("ey") && !s.ends_with("oy") && !s.ends_with("uy") {
110 format!("{stem}ies")
111 } else {
112 format!("{s}s")
113 }
114 } else {
115 format!("{s}s")
116 }
117}
118
119pub fn to_camel_case(s: &str) -> String {
121 let mut result = String::new();
122 let mut capitalize_next = false;
123 for c in s.chars() {
124 if c == '_' {
125 capitalize_next = true;
126 } else if capitalize_next {
127 result.extend(c.to_uppercase());
128 capitalize_next = false;
129 } else {
130 result.push(c);
131 }
132 }
133 result
134}
135
136#[cfg(test)]
137#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_parse_duration_milliseconds() {
143 assert_eq!(parse_duration("100ms"), Some(Duration::from_millis(100)));
144 assert_eq!(parse_duration("1000ms"), Some(Duration::from_millis(1000)));
145 }
146
147 #[test]
148 fn test_parse_duration_seconds() {
149 assert_eq!(parse_duration("30s"), Some(Duration::from_secs(30)));
150 assert_eq!(parse_duration("60s"), Some(Duration::from_secs(60)));
151 }
152
153 #[test]
154 fn test_parse_duration_minutes() {
155 assert_eq!(parse_duration("5m"), Some(Duration::from_secs(300)));
156 assert_eq!(parse_duration("10m"), Some(Duration::from_secs(600)));
157 }
158
159 #[test]
160 fn test_parse_duration_hours() {
161 assert_eq!(parse_duration("1h"), Some(Duration::from_secs(3600)));
162 assert_eq!(parse_duration("24h"), Some(Duration::from_secs(86400)));
163 }
164
165 #[test]
166 fn test_parse_duration_days() {
167 assert_eq!(parse_duration("1d"), Some(Duration::from_secs(86400)));
168 assert_eq!(parse_duration("7d"), Some(Duration::from_secs(604800)));
169 }
170
171 #[test]
172 fn test_parse_duration_bare_number() {
173 assert_eq!(parse_duration("60"), Some(Duration::from_secs(60)));
174 assert_eq!(parse_duration("3600"), Some(Duration::from_secs(3600)));
175 }
176
177 #[test]
178 fn test_parse_duration_whitespace() {
179 assert_eq!(parse_duration(" 30s "), Some(Duration::from_secs(30)));
180 }
181
182 #[test]
183 fn test_parse_duration_invalid() {
184 assert_eq!(parse_duration("invalid"), None);
185 assert_eq!(parse_duration("abc123"), None);
186 assert_eq!(parse_duration(""), None);
187 }
188
189 #[test]
190 fn test_parse_size_kilobytes() {
191 assert_eq!(parse_size("100kb"), Some(100 * 1024));
192 assert_eq!(parse_size("512KB"), Some(512 * 1024));
193 }
194
195 #[test]
196 fn test_parse_size_megabytes() {
197 assert_eq!(parse_size("20mb"), Some(20 * 1024 * 1024));
198 assert_eq!(parse_size("100MB"), Some(100 * 1024 * 1024));
199 }
200
201 #[test]
202 fn test_parse_size_gigabytes() {
203 assert_eq!(parse_size("1gb"), Some(1024 * 1024 * 1024));
204 assert_eq!(parse_size("2GB"), Some(2 * 1024 * 1024 * 1024));
205 }
206
207 #[test]
208 fn test_parse_size_bytes() {
209 assert_eq!(parse_size("1024b"), Some(1024));
210 assert_eq!(parse_size("0b"), Some(0));
211 }
212
213 #[test]
214 fn test_parse_size_bare_number() {
215 assert_eq!(parse_size("1048576"), Some(1048576));
216 }
217
218 #[test]
219 fn test_parse_size_whitespace() {
220 assert_eq!(parse_size(" 20mb "), Some(20 * 1024 * 1024));
221 }
222
223 #[test]
224 fn test_parse_size_invalid() {
225 assert_eq!(parse_size("invalid"), None);
226 assert_eq!(parse_size("abc123"), None);
227 assert_eq!(parse_size(""), None);
228 }
229
230 #[test]
231 fn test_to_snake_case() {
232 assert_eq!(to_snake_case("GetUser"), "get_user");
233 assert_eq!(to_snake_case("ListAllProjects"), "list_all_projects");
234 assert_eq!(to_snake_case("Simple"), "simple");
235 assert_eq!(to_snake_case("ProjectStatus"), "project_status");
236 assert_eq!(to_snake_case("HTTPServer"), "http_server");
237 assert_eq!(to_snake_case("XMLParser"), "xml_parser");
238 assert_eq!(to_snake_case("listInvoices"), "list_invoices");
239 assert_eq!(to_snake_case("foo2Bar"), "foo2_bar");
240 assert_eq!(to_snake_case("already_snake"), "already_snake");
241 }
242
243 #[test]
244 fn test_to_pascal_case() {
245 assert_eq!(to_pascal_case("get_user"), "GetUser");
246 assert_eq!(to_pascal_case("list_all_projects"), "ListAllProjects");
247 assert_eq!(to_pascal_case("simple"), "Simple");
248 }
249
250 #[test]
251 fn test_pluralize() {
252 assert_eq!(pluralize("user"), "users");
253 assert_eq!(pluralize("bus"), "buses");
254 assert_eq!(pluralize("quiz"), "quizzes");
255 assert_eq!(pluralize("index"), "indexes");
256 assert_eq!(pluralize("match"), "matches");
257 assert_eq!(pluralize("wish"), "wishes");
258 assert_eq!(pluralize("box"), "boxes");
259 assert_eq!(pluralize("class"), "classes");
260 assert_eq!(pluralize("buzz"), "buzzes");
261 assert_eq!(pluralize("policy"), "policies");
262 assert_eq!(pluralize("key"), "keys");
263 assert_eq!(pluralize("day"), "days");
264 }
265
266 #[test]
267 fn test_to_camel_case() {
268 assert_eq!(to_camel_case("get_user"), "getUser");
269 assert_eq!(to_camel_case("list_all_projects"), "listAllProjects");
270 assert_eq!(to_camel_case("simple"), "simple");
271 }
272}