robinpath_modules/modules/
string_mod.rs1use 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}