Skip to main content

robinpath_modules/modules/
string_mod.rs

1use robinpath::{RobinPath, Value};
2
3pub fn register(rp: &mut RobinPath) {
4    rp.register_builtin("string.capitalize", |args, _| {
5        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
6        let mut chars = s.chars();
7        let result = match chars.next() {
8            None => String::new(),
9            Some(c) => c.to_uppercase().to_string() + chars.as_str(),
10        };
11        Ok(Value::String(result))
12    });
13
14    rp.register_builtin("string.camelCase", |args, _| {
15        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
16        let words = split_words(&s);
17        let result: String = words
18            .iter()
19            .enumerate()
20            .map(|(i, w)| {
21                if i == 0 {
22                    w.to_lowercase()
23                } else {
24                    capitalize_first(&w.to_lowercase())
25                }
26            })
27            .collect();
28        Ok(Value::String(result))
29    });
30
31    rp.register_builtin("string.snakeCase", |args, _| {
32        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
33        let words = split_words(&s);
34        let result: String = words.iter().map(|w| w.to_lowercase()).collect::<Vec<_>>().join("_");
35        Ok(Value::String(result))
36    });
37
38    rp.register_builtin("string.kebabCase", |args, _| {
39        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
40        let words = split_words(&s);
41        let result: String = words.iter().map(|w| w.to_lowercase()).collect::<Vec<_>>().join("-");
42        Ok(Value::String(result))
43    });
44
45    rp.register_builtin("string.pascalCase", |args, _| {
46        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
47        let words = split_words(&s);
48        let result: String = words.iter().map(|w| capitalize_first(&w.to_lowercase())).collect();
49        Ok(Value::String(result))
50    });
51
52    rp.register_builtin("string.titleCase", |args, _| {
53        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
54        let result: String = s
55            .split_whitespace()
56            .map(|w| capitalize_first(w))
57            .collect::<Vec<_>>()
58            .join(" ");
59        Ok(Value::String(result))
60    });
61
62    rp.register_builtin("string.slugify", |args, _| {
63        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
64        let slug: String = s
65            .to_lowercase()
66            .chars()
67            .map(|c| if c.is_alphanumeric() || c == ' ' || c == '-' { c } else { ' ' })
68            .collect::<String>()
69            .split_whitespace()
70            .collect::<Vec<_>>()
71            .join("-");
72        Ok(Value::String(slug))
73    });
74
75    rp.register_builtin("string.truncate", |args, _| {
76        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
77        let max_len = args.get(1).map(|v| v.to_number() as usize).unwrap_or(s.len());
78        let suffix = args.get(2).map(|v| v.to_display_string()).unwrap_or_else(|| "...".to_string());
79        if s.len() <= max_len {
80            Ok(Value::String(s))
81        } else {
82            let truncated = &s[..max_len.saturating_sub(suffix.len()).min(s.len())];
83            Ok(Value::String(format!("{}{}", truncated, suffix)))
84        }
85    });
86
87    rp.register_builtin("string.padStart", |args, _| {
88        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
89        let target_len = args.get(1).map(|v| v.to_number() as usize).unwrap_or(0);
90        let pad_char = args.get(2).map(|v| v.to_display_string()).unwrap_or_else(|| " ".to_string());
91        if s.len() >= target_len {
92            return Ok(Value::String(s));
93        }
94        let padding_needed = target_len - s.len();
95        let padding: String = pad_char.chars().cycle().take(padding_needed).collect();
96        Ok(Value::String(format!("{}{}", padding, s)))
97    });
98
99    rp.register_builtin("string.padEnd", |args, _| {
100        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
101        let target_len = args.get(1).map(|v| v.to_number() as usize).unwrap_or(0);
102        let pad_char = args.get(2).map(|v| v.to_display_string()).unwrap_or_else(|| " ".to_string());
103        if s.len() >= target_len {
104            return Ok(Value::String(s));
105        }
106        let padding_needed = target_len - s.len();
107        let padding: String = pad_char.chars().cycle().take(padding_needed).collect();
108        Ok(Value::String(format!("{}{}", s, padding)))
109    });
110
111    rp.register_builtin("string.reverse", |args, _| {
112        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
113        Ok(Value::String(s.chars().rev().collect()))
114    });
115
116    rp.register_builtin("string.wordCount", |args, _| {
117        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
118        Ok(Value::Number(s.split_whitespace().count() as f64))
119    });
120
121    rp.register_builtin("string.contains", |args, _| {
122        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
123        let sub = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
124        Ok(Value::Bool(s.contains(&sub)))
125    });
126
127    rp.register_builtin("string.repeat", |args, _| {
128        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
129        let count = args.get(1).map(|v| v.to_number() as usize).unwrap_or(1);
130        Ok(Value::String(s.repeat(count)))
131    });
132
133    rp.register_builtin("string.replaceAll", |args, _| {
134        let s = args.first().map(|v| v.to_display_string()).unwrap_or_default();
135        let search = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
136        let replacement = args.get(2).map(|v| v.to_display_string()).unwrap_or_default();
137        Ok(Value::String(s.replace(&search, &replacement)))
138    });
139}
140
141fn split_words(s: &str) -> Vec<String> {
142    let mut words = Vec::new();
143    let mut current = String::new();
144
145    for ch in s.chars() {
146        if ch == '_' || ch == '-' || ch == ' ' || ch == '.' {
147            if !current.is_empty() {
148                words.push(current.clone());
149                current.clear();
150            }
151        } else if ch.is_uppercase() && !current.is_empty() && current.chars().last().map_or(false, |c| c.is_lowercase()) {
152            words.push(current.clone());
153            current.clear();
154            current.push(ch);
155        } else {
156            current.push(ch);
157        }
158    }
159    if !current.is_empty() {
160        words.push(current);
161    }
162    words
163}
164
165fn capitalize_first(s: &str) -> String {
166    let mut chars = s.chars();
167    match chars.next() {
168        None => String::new(),
169        Some(c) => c.to_uppercase().to_string() + chars.as_str(),
170    }
171}