Skip to main content

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