Skip to main content

rok_utils/
str.rs

1#[derive(Debug, Clone, PartialEq, Eq)]
2pub struct Str {
3    inner: String,
4}
5
6unsafe impl Send for Str {}
7unsafe impl Sync for Str {}
8
9impl Str {
10    pub fn of(s: impl Into<String>) -> Self {
11        Self { inner: s.into() }
12    }
13
14    pub fn slug(mut self) -> Self {
15        self.inner = crate::string::slug(&self.inner, '-');
16        self
17    }
18
19    pub fn snake(mut self) -> Self {
20        self.inner = crate::string::to_snake_case(&self.inner);
21        self
22    }
23
24    pub fn camel(mut self) -> Self {
25        self.inner = crate::string::to_camel_case(&self.inner);
26        self
27    }
28
29    pub fn pascal(mut self) -> Self {
30        self.inner = crate::string::to_pascal_case(&self.inner);
31        self
32    }
33
34    pub fn kebab(mut self) -> Self {
35        self.inner = crate::string::to_kebab_case(&self.inner);
36        self
37    }
38
39    pub fn title(mut self) -> Self {
40        self.inner = crate::string::to_title_case(&self.inner);
41        self
42    }
43
44    pub fn upper(mut self) -> Self {
45        self.inner = crate::string::to_upper(&self.inner);
46        self
47    }
48
49    pub fn lower(mut self) -> Self {
50        self.inner = crate::string::to_lower(&self.inner);
51        self
52    }
53
54    pub fn trim(mut self) -> Self {
55        self.inner = self.inner.trim().to_string();
56        self
57    }
58
59    pub fn ltrim(mut self) -> Self {
60        self.inner = self.inner.trim_start().to_string();
61        self
62    }
63
64    pub fn rtrim(mut self) -> Self {
65        self.inner = self.inner.trim_end().to_string();
66        self
67    }
68
69    pub fn squish(mut self) -> Self {
70        self.inner = crate::string::squish(&self.inner);
71        self
72    }
73
74    pub fn truncate(mut self, limit: usize) -> Self {
75        self.inner = crate::string::truncate(&self.inner, limit);
76        self
77    }
78
79    pub fn reverse(mut self) -> Self {
80        self.inner = crate::string::reverse(&self.inner);
81        self
82    }
83
84    pub fn repeat(mut self, times: usize) -> Self {
85        self.inner = crate::string::repeat(&self.inner, times);
86        self
87    }
88
89    pub fn append(mut self, s: &str) -> Self {
90        self.inner.push_str(s);
91        self
92    }
93
94    pub fn prepend(mut self, s: &str) -> Self {
95        self.inner = format!("{}{}", s, self.inner);
96        self
97    }
98
99    pub fn replace(mut self, from: &str, to: &str) -> Self {
100        self.inner = self.inner.replace(from, to);
101        self
102    }
103
104    pub fn replace_first(mut self, from: &str, to: &str) -> Self {
105        self.inner = crate::string::replace_first(&self.inner, from, to);
106        self
107    }
108
109    pub fn replace_last(mut self, from: &str, to: &str) -> Self {
110        self.inner = crate::string::replace_last(&self.inner, from, to);
111        self
112    }
113
114    pub fn finish(mut self, cap: &str) -> Self {
115        self.inner = crate::string::finish(&self.inner, cap);
116        self
117    }
118
119    pub fn ensure_start(mut self, prefix: &str) -> Self {
120        self.inner = crate::string::ensure_start(&self.inner, prefix);
121        self
122    }
123
124    pub fn wrap(mut self, before: &str, after: &str) -> Self {
125        self.inner = crate::string::wrap(&self.inner, before, after);
126        self
127    }
128
129    pub fn pad_left(mut self, n: usize) -> Self {
130        self.inner = crate::string::pad_left(&self.inner, n, ' ');
131        self
132    }
133
134    pub fn pad_right(mut self, n: usize) -> Self {
135        self.inner = crate::string::pad_right(&self.inner, n, ' ');
136        self
137    }
138
139    pub fn pad_both(mut self, n: usize) -> Self {
140        self.inner = crate::string::pad_both(&self.inner, n, ' ');
141        self
142    }
143
144    pub fn mask(mut self, mask_char: char, from: usize) -> Self {
145        self.inner = crate::string::mask(&self.inner, mask_char, from);
146        self
147    }
148
149    pub fn escape_html(mut self) -> Self {
150        self.inner = escape_html_impl(&self.inner);
151        self
152    }
153
154    pub fn when(self, condition: bool, f: impl FnOnce(Self) -> Self) -> Self {
155        if condition {
156            f(self)
157        } else {
158            self
159        }
160    }
161
162    pub fn when_empty(self, f: impl FnOnce(Self) -> Self) -> Self {
163        if self.inner.is_empty() {
164            f(self)
165        } else {
166            self
167        }
168    }
169
170    pub fn when_not_empty(self, f: impl FnOnce(Self) -> Self) -> Self {
171        if !self.inner.is_empty() {
172            f(self)
173        } else {
174            self
175        }
176    }
177
178    pub fn when_contains(self, needle: &str, f: impl FnOnce(Self) -> Self) -> Self {
179        if self.inner.contains(needle) {
180            f(self)
181        } else {
182            self
183        }
184    }
185
186    pub fn when_starts_with(self, prefix: &str, f: impl FnOnce(Self) -> Self) -> Self {
187        if self.inner.starts_with(prefix) {
188            f(self)
189        } else {
190            self
191        }
192    }
193
194    pub fn when_ends_with(self, suffix: &str, f: impl FnOnce(Self) -> Self) -> Self {
195        if self.inner.ends_with(suffix) {
196            f(self)
197        } else {
198            self
199        }
200    }
201
202    pub fn tap(self, f: impl FnOnce(&str)) -> Self {
203        f(&self.inner);
204        self
205    }
206
207    pub fn pipe<F: FnOnce(String) -> String>(self, f: F) -> Self {
208        Self {
209            inner: f(self.inner),
210        }
211    }
212
213    #[allow(clippy::inherent_to_string)]
214    pub fn to_string(self) -> String {
215        self.inner
216    }
217
218    pub fn len(&self) -> usize {
219        self.inner.len()
220    }
221
222    pub fn is_empty(&self) -> bool {
223        self.inner.is_empty()
224    }
225
226    pub fn contains(&self, needle: &str) -> bool {
227        self.inner.contains(needle)
228    }
229
230    pub fn starts_with(&self, prefix: &str) -> bool {
231        self.inner.starts_with(prefix)
232    }
233
234    pub fn ends_with(&self, suffix: &str) -> bool {
235        self.inner.ends_with(suffix)
236    }
237
238    pub fn word_count(&self) -> usize {
239        crate::string::word_count(&self.inner)
240    }
241
242    pub fn to_base64(self) -> String {
243        to_base64(&self.inner)
244    }
245
246    pub fn split(self, delimiter: &str) -> Vec<String> {
247        self.inner.split(delimiter).map(|s| s.to_string()).collect()
248    }
249
250    pub fn exactly(&self, other: &str) -> bool {
251        self.inner == other
252    }
253
254    pub fn value(self) -> String {
255        self.inner
256    }
257}
258
259fn escape_html_impl(s: &str) -> String {
260    s.replace('&', "&amp;")
261        .replace('<', "&lt;")
262        .replace('>', "&gt;")
263        .replace('"', "&quot;")
264        .replace('\'', "&#39;")
265}
266
267pub fn to_base64(s: &str) -> String {
268    use base64::Engine;
269    base64::engine::general_purpose::STANDARD.encode(s.as_bytes())
270}
271
272#[cfg(feature = "json")]
273pub fn escape_html(s: &str) -> String {
274    escape_html_impl(s)
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    #[test]
282    fn fluent_basic() {
283        let result = Str::of("hello world").trim().snake().value();
284        assert_eq!(result, "hello_world");
285    }
286
287    #[test]
288    fn fluent_when() {
289        let result = Str::of("hello").when(false, |s| s.append(" world")).value();
290        assert_eq!(result, "hello");
291
292        let result = Str::of("hello").when(true, |s| s.append(" world")).value();
293        assert_eq!(result, "hello world");
294    }
295
296    #[test]
297    fn fluent_when_empty() {
298        let result = Str::of("").when_empty(|s| s.append("default")).value();
299        assert_eq!(result, "default");
300
301        let result = Str::of("hello").when_empty(|s| s.append("default")).value();
302        assert_eq!(result, "hello");
303    }
304
305    #[test]
306    fn fluent_tap() {
307        let mut seen = String::new();
308        let _ = Str::of("test").tap(|s| seen.push_str(s));
309        assert_eq!(seen, "test");
310    }
311
312    #[test]
313    fn fluent_pipe() {
314        let result = Str::of("hello").pipe(|s| s.to_uppercase()).value();
315        assert_eq!(result, "HELLO");
316    }
317
318    #[test]
319    fn to_base64_basic() {
320        assert_eq!(to_base64("hello"), "aGVsbG8=");
321    }
322
323    #[test]
324    fn escape_html_basic() {
325        #[cfg(feature = "json")]
326        {
327            assert_eq!(escape_html("<div>"), "&lt;div&gt;");
328        }
329    }
330}