oxidite_utils/
string.rs

1//! String manipulation utilities
2
3use rand::Rng;
4
5/// Convert a string to a URL-friendly slug
6pub fn slugify(s: &str) -> String {
7    s.to_lowercase()
8        .chars()
9        .map(|c| {
10            if c.is_alphanumeric() {
11                c
12            } else if c.is_whitespace() || c == '-' || c == '_' {
13                '-'
14            } else {
15                '\0'
16            }
17        })
18        .filter(|c| *c != '\0')
19        .collect::<String>()
20        .split('-')
21        .filter(|s| !s.is_empty())
22        .collect::<Vec<_>>()
23        .join("-")
24}
25
26/// Truncate a string to a maximum length
27pub fn truncate(s: &str, max_len: usize) -> String {
28    if s.len() <= max_len {
29        s.to_string()
30    } else if max_len <= 3 {
31        s.chars().take(max_len).collect()
32    } else {
33        format!("{}...", s.chars().take(max_len - 3).collect::<String>())
34    }
35}
36
37/// Capitalize the first letter of a string
38pub fn capitalize(s: &str) -> String {
39    let mut chars = s.chars();
40    match chars.next() {
41        None => String::new(),
42        Some(first) => first.to_uppercase().chain(chars).collect(),
43    }
44}
45
46/// Generate a random string of specified length
47pub fn random_string(length: usize) -> String {
48    const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
49    let mut rng = rand::rng();
50    
51    (0..length)
52        .map(|_| {
53            let idx = rng.random_range(0..CHARSET.len());
54            CHARSET[idx] as char
55        })
56        .collect()
57}
58
59/// Convert to camelCase
60pub fn camel_case(s: &str) -> String {
61    let words: Vec<&str> = s.split(|c: char| c == '_' || c == '-' || c.is_whitespace())
62        .filter(|s| !s.is_empty())
63        .collect();
64    
65    if words.is_empty() {
66        return String::new();
67    }
68
69    let mut result = words[0].to_lowercase();
70    for word in &words[1..] {
71        result.push_str(&capitalize(&word.to_lowercase()));
72    }
73    result
74}
75
76/// Convert to snake_case
77pub fn snake_case(s: &str) -> String {
78    let mut result = String::new();
79    for (i, c) in s.chars().enumerate() {
80        if c.is_uppercase() {
81            if i > 0 {
82                result.push('_');
83            }
84            result.push(c.to_lowercase().next().unwrap());
85        } else if c == '-' || c.is_whitespace() {
86            result.push('_');
87        } else {
88            result.push(c);
89        }
90    }
91    result
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_slugify() {
100        assert_eq!(slugify("Hello World"), "hello-world");
101        assert_eq!(slugify("This is a Test!"), "this-is-a-test");
102        assert_eq!(slugify("  Multiple   Spaces  "), "multiple-spaces");
103    }
104
105    #[test]
106    fn test_truncate() {
107        assert_eq!(truncate("Hello World", 5), "He...");
108        assert_eq!(truncate("Hi", 5), "Hi");
109        assert_eq!(truncate("Hello", 5), "Hello");
110    }
111
112    #[test]
113    fn test_capitalize() {
114        assert_eq!(capitalize("hello"), "Hello");
115        assert_eq!(capitalize("HELLO"), "HELLO");
116        assert_eq!(capitalize(""), "");
117    }
118
119    #[test]
120    fn test_camel_case() {
121        assert_eq!(camel_case("hello_world"), "helloWorld");
122        assert_eq!(camel_case("user-name"), "userName");
123    }
124
125    #[test]
126    fn test_snake_case() {
127        assert_eq!(snake_case("helloWorld"), "hello_world");
128        assert_eq!(snake_case("UserName"), "user_name");
129    }
130}