fluent_string/
lib.rs

1//! A trait that provides fluent versions of `String` mutation
2//! methods, allowing for a fluent construction of strings.
3//!
4//! It also implements two conditional push combinators
5//! `f_push_if` and `f_push_str_if` to help with such things
6//! as comma-separated token construction.
7//!
8//! # Examples
9//! ```
10//! use fluent_string::*;
11//!
12//! assert_eq!("my string".to_owned()
13//!     .f_push_str(" is a bit longer now")
14//!     .f_insert_str(12,", maybe,")
15//!     .f_truncate(33),
16//!     "my string is, maybe, a bit longer");
17//! ```
18use std::{collections::TryReserveError, ops::RangeBounds};
19pub trait FluentString: Sized {
20    /// As `String::clear` except returns `self`
21    #[must_use]
22    fn f_clear(self) -> Self;
23    /// As `String::insert` except returns `self`
24    #[must_use]
25    fn f_insert(self, idx: usize, ch: char) -> Self;
26    /// As `String::insert_str` except returns `self`
27    #[must_use]
28    fn f_insert_str(self, idx: usize, string: &str) -> Self;
29    /// As `String::push` except returns `self`
30    #[must_use]
31    fn f_push(self, ch: char) -> Self;
32    /// As `String::push_str` except returns `self`
33    #[must_use]
34    fn f_push_str(self, string: &str) -> Self;
35    /// As `String::replace_range` except returns `self`
36    #[must_use]
37    fn f_replace_range<R>(self, range: R, replace_with: &str) -> Self
38    where
39        R: RangeBounds<usize>;
40    /// As `String::reserve` except returns `self`
41    #[must_use]
42    fn f_reserve(self, additional: usize) -> Self;
43    /// As `String::reserve_exact` except returns `self`
44    #[must_use]
45    fn f_reserve_exact(self, additional: usize) -> Self;
46    /// As `String::retain` except returns `self`
47    #[must_use]
48    fn f_retain<F>(self, f: F) -> Self
49    where
50        F: FnMut(char) -> bool;
51    /// As `String::shrink_to` except returns `self`
52    #[must_use]
53    fn f_shrink_to(self, min_capacity: usize) -> Self;
54    /// As `String::shrink_to_fit` except returns `self`
55    #[must_use]
56    fn f_shrink_to_fit(self) -> Self;
57    /// As `String::truncate` except returns `self`
58    #[must_use]
59    fn f_truncate(self, new_len: usize) -> Self;
60    /// As `String::try_reserve` except returns `Result<Self, TryReserveError>`
61    /// # Errors
62    /// See `String::try_reserve_exact`
63    fn f_try_reserve(self, additional: usize) -> Result<Self, TryReserveError>;
64    /// As `String::try_reserve_exact` except returns `Result<Self, TryReserveError>`
65    /// # Errors
66    /// See `String::try_reserve_exact`
67    fn f_try_reserve_exact(self, additional: usize) -> Result<Self, TryReserveError>;
68
69    /// As `FluentString::f_push` except only if `f` returns true
70    #[must_use]
71    fn f_push_if<F>(self, ch: char, f: F) -> Self
72    where
73        F: Fn(&Self, char) -> bool,
74    {
75        if f(&self, ch) {
76            self.f_push(ch)
77        } else {
78            self
79        }
80    }
81
82    /// As `FluentString::f_push_str` except only if `f` returns true
83    #[must_use]
84    fn f_push_str_if<F>(self, string: &str, f: F) -> Self
85    where
86        F: Fn(&Self, &str) -> bool,
87    {
88        if f(&self, string) {
89            self.f_push_str(string)
90        } else {
91            self
92        }
93    }
94
95    /// As `FluentString::f_truncate` except only if `f` returns Some(usize)
96    #[must_use]
97    fn f_truncate_if<F>(self, f: F) -> Self
98    where
99        F: Fn(&Self) -> Option<usize>,
100    {
101        match f(&self) {
102            Some(l) => self.f_truncate(l),
103            None => self
104        } 
105    }
106}
107
108/// Fluent versions of all `std::string:String` mutation methods that
109/// otherwise return nothing.
110impl FluentString for String {
111    fn f_clear(mut self) -> Self {
112        self.clear();
113        self
114    }
115
116    fn f_insert(mut self, idx: usize, ch: char) -> Self {
117        self.insert(idx, ch);
118        self
119    }
120
121    fn f_insert_str(mut self, idx: usize, string: &str) -> Self {
122        self.insert_str(idx, string);
123        self
124    }
125
126    fn f_push(mut self, ch: char) -> Self {
127        self.push(ch);
128        self
129    }
130
131    fn f_push_str(mut self, string: &str) -> Self {
132        self.push_str(string);
133        self
134    }
135
136    fn f_replace_range<R>(mut self, range: R, replace_with: &str) -> Self
137    where
138        R: RangeBounds<usize>,
139    {
140        self.replace_range(range, replace_with);
141        self
142    }
143
144    fn f_reserve(mut self, additional: usize) -> Self {
145        self.reserve(additional);
146        self
147    }
148
149    fn f_reserve_exact(mut self, additional: usize) -> Self {
150        self.reserve_exact(additional);
151        self
152    }
153
154    fn f_retain<F>(mut self, f: F) -> Self
155    where
156        F: FnMut(char) -> bool,
157    {
158        self.retain(f);
159        self
160    }
161
162    fn f_shrink_to(mut self, min_capacity: usize) -> Self {
163        self.shrink_to(min_capacity);
164        self
165    }
166
167    fn f_shrink_to_fit(mut self) -> Self {
168        self.shrink_to_fit();
169        self
170    }
171
172    fn f_truncate(mut self, new_len: usize) -> Self {
173        self.truncate(new_len);
174        self
175    }
176
177    fn f_try_reserve(mut self, additional: usize) -> Result<Self, TryReserveError> {
178        self.try_reserve(additional).map(|()| self)
179    }
180
181    fn f_try_reserve_exact(mut self, additional: usize) -> Result<Self, TryReserveError> {
182        self.try_reserve_exact(additional).map(|()| self)
183    }
184}
185
186/// Fluent versions of all `&mut std::string:String` mutation methods that
187/// otherwise return nothing.
188impl FluentString for &mut String {
189    fn f_clear(self) -> Self {
190        self.clear();
191        self
192    }
193
194    fn f_insert(self, idx: usize, ch: char) -> Self {
195        self.insert(idx, ch);
196        self
197    }
198
199    fn f_insert_str(self, idx: usize, string: &str) -> Self {
200        self.insert_str(idx, string);
201        self
202    }
203
204    fn f_push(self, ch: char) -> Self {
205        self.push(ch);
206        self
207    }
208
209    fn f_push_str(self, string: &str) -> Self {
210        self.push_str(string);
211        self
212    }
213
214    fn f_replace_range<R>(self, range: R, replace_with: &str) -> Self
215    where
216        R: RangeBounds<usize>,
217    {
218        self.replace_range(range, replace_with);
219        self
220    }
221
222    fn f_reserve(self, additional: usize) -> Self {
223        self.reserve(additional);
224        self
225    }
226
227    fn f_reserve_exact(self, additional: usize) -> Self {
228        self.reserve_exact(additional);
229        self
230    }
231
232    fn f_retain<F>(self, f: F) -> Self
233    where
234        F: FnMut(char) -> bool,
235    {
236        self.retain(f);
237        self
238    }
239
240    fn f_shrink_to(self, min_capacity: usize) -> Self {
241        self.shrink_to(min_capacity);
242        self
243    }
244
245    fn f_shrink_to_fit(self) -> Self {
246        self.shrink_to_fit();
247        self
248    }
249
250    fn f_truncate(self, new_len: usize) -> Self {
251        self.truncate(new_len);
252        self
253    }
254
255    fn f_try_reserve(self, additional: usize) -> Result<Self, TryReserveError> {
256        self.try_reserve(additional).map(|()| self)
257    }
258
259    fn f_try_reserve_exact(self, additional: usize) -> Result<Self, TryReserveError> {
260        self.try_reserve_exact(additional).map(|()| self)
261    }
262}
263
264#[cfg(test)]
265mod tests {
266    use super::*;
267
268    // Owned String tests
269    #[test]
270    fn test_owned_clear() {
271        assert_eq!("this is a string".to_string().f_clear().len(), 0);
272    }
273    #[test]
274    fn test_owned_insert() {
275        assert!("this is a string"
276            .to_string()
277            .f_insert(5, 'b')
278            .eq_ignore_ascii_case("THIS BIS A STRING"));
279    }
280    #[test]
281    fn test_owned_insert_str() {
282        assert!("this is a string"
283            .to_string()
284            .f_insert_str(8, "not ")
285            .eq_ignore_ascii_case("THIS IS NOT A STRING"));
286    }
287    #[test]
288    fn test_owned_push() {
289        assert!("this is a string"
290            .to_string()
291            .f_push('P')
292            .eq_ignore_ascii_case("THIS IS A STRINGP"));
293    }
294    #[test]
295    fn test_owned_push_str() {
296        assert!("this is a string"
297            .to_string()
298            .f_push_str("PUP")
299            .eq_ignore_ascii_case("THIS IS A STRINGPUP"));
300    }
301    #[test]
302    fn test_owned_replace_range() {
303        assert!("this is a string"
304            .to_string()
305            .f_replace_range(7..9, " not your")
306            .eq_ignore_ascii_case("THIS IS NOT YOUR STRING"));
307    }
308    #[test]
309    fn test_owned_reserve() {
310        assert_eq!(String::with_capacity(10).f_reserve(20).capacity(), 20);
311    }
312    #[test]
313    fn test_owned_reserve_exact() {
314        assert_eq!(String::with_capacity(10).f_reserve_exact(20).capacity(), 20);
315    }
316    #[test]
317    fn test_owned_retain() {
318        assert!("this is a string"
319            .to_string()
320            .f_retain(|c| c != 't')
321            .eq_ignore_ascii_case("HIS IS A SRING"));
322    }
323    #[test]
324    fn test_owned_shrink_to() {
325        assert_eq!(
326            String::with_capacity(30)
327                .f_push_str("this is a string")
328                .f_shrink_to(20)
329                .capacity(),
330            20
331        );
332    }
333    #[test]
334    fn test_owned_shrink_to_fit() {
335        assert_eq!(
336            String::with_capacity(30)
337                .f_push_str("this is a string")
338                .f_shrink_to_fit()
339                .capacity(),
340            16
341        );
342    }
343    #[test]
344    fn test_owned_truncate() {
345        assert!("this is a string"
346            .to_string()
347            .f_truncate(4)
348            .eq_ignore_ascii_case("THIS"));
349    }
350    #[test]
351    fn test_owned_try_reserve() {
352        assert_eq!(
353            String::with_capacity(10)
354                .f_try_reserve(20)
355                .unwrap()
356                .capacity(),
357            20
358        );
359    }
360    #[test]
361    fn test_owned_try_reserve_exact() {
362        assert_eq!(
363            String::with_capacity(10)
364                .f_try_reserve_exact(20)
365                .unwrap()
366                .capacity(),
367            20
368        );
369    }
370
371    #[test]
372    fn test_owned_push_if_false() {
373        assert_eq!(String::new().f_push_if(',', |s, _c| !s.is_empty()), "");
374    }
375
376    #[test]
377    fn test_owned_push_if_true() {
378        assert_eq!(
379            "hey".to_string().f_push_if(',', |s, _c| !s.is_empty()),
380            "hey,"
381        );
382    }
383
384    #[test]
385    fn test_owned_push_str_if_empty_self() {
386        assert_eq!(
387            String::new().f_push_str_if(",more", |s, s1| !(s.is_empty() || s1.is_empty())),
388            ""
389        );
390    }
391
392    #[test]
393    fn test_owned_push_str_if_empty_str() {
394        assert_eq!(
395            "hey"
396                .to_string()
397                .f_push_str_if("", |s, s1| !(s.is_empty() || s1.is_empty())),
398            "hey"
399        );
400    }
401
402    #[test]
403    fn test_owned_push_str_if_true() {
404        assert_eq!(
405            "hey"
406                .to_string()
407                .f_push_str_if(",more", |s, s1| !(s.is_empty() || s1.is_empty())),
408            "hey,more"
409        );
410    }
411
412    #[test]
413    fn test_owned_truncate_if_some() {
414        assert_eq!(
415            "hey you"
416                .to_string()
417                .f_truncate_if(|s| if s.ends_with(" you") {Some(s.len() - 4)} else {None}),
418            "hey"
419        );
420    }
421
422    #[test]
423    fn test_owned_truncate_if_none() {
424        assert_eq!(
425            "hey you"
426                .to_string()
427                .f_truncate_if(|s| if s.ends_with(" ble") {Some(s.len() - 4)} else {None}),
428            "hey you"
429        );
430    }
431
432    // String ref tests
433    #[test]
434    fn test_ref_clear() {
435        let mut s = "this is a string".to_string();
436        let s = &mut s;
437        assert_eq!(s.f_clear().len(), 0);
438    }
439    #[test]
440    fn test_ref_insert() {
441        let mut s = "this is a string".to_string();
442        let s = &mut s;
443        assert!(s.f_insert(5, 'b').eq_ignore_ascii_case("THIS BIS A STRING"));
444    }
445    #[test]
446    fn test_ref_insert_str() {
447        let mut s = "this is a string".to_string();
448        let s = &mut s;
449        assert!(s
450            .f_insert_str(8, "not ")
451            .eq_ignore_ascii_case("THIS IS NOT A STRING"));
452    }
453    #[test]
454    fn test_ref_push() {
455        let mut s = "this is a string".to_string();
456        let s = &mut s;
457        assert!(s.f_push('P').eq_ignore_ascii_case("THIS IS A STRINGP"));
458    }
459    #[test]
460    fn test_ref_push_str() {
461        let mut s = "this is a string".to_string();
462        let s = &mut s;
463        assert!(s
464            .f_push_str("PUP")
465            .eq_ignore_ascii_case("THIS IS A STRINGPUP"));
466    }
467    #[test]
468    fn test_ref_replace_range() {
469        let mut s = "this is a string".to_string();
470        let s = &mut s;
471        assert!(s
472            .f_replace_range(7..9, " not your")
473            .eq_ignore_ascii_case("THIS IS NOT YOUR STRING"));
474    }
475    #[test]
476    fn test_ref_reserve() {
477        let mut s = String::with_capacity(10);
478        let s = &mut s;
479        assert_eq!(s.f_reserve(20).capacity(), 20);
480    }
481    #[test]
482    fn test_ref_reserve_exact() {
483        let mut s = String::with_capacity(10);
484        let s = &mut s;
485        assert_eq!(s.f_reserve_exact(20).capacity(), 20);
486    }
487    #[test]
488    fn test_ref_retain() {
489        let mut s = "this is a string".to_string();
490        let s = &mut s;
491        assert!(s
492            .f_retain(|c| c != 't')
493            .eq_ignore_ascii_case("HIS IS A SRING"));
494    }
495    #[test]
496    fn test_ref_shrink_to() {
497        let mut s = String::with_capacity(30);
498        let s = &mut s;
499        assert_eq!(
500            s.f_push_str("this is a string").f_shrink_to(20).capacity(),
501            20
502        );
503    }
504    #[test]
505    fn test_ref_shrink_to_fit() {
506        let mut s = String::with_capacity(30);
507        let s = &mut s;
508        assert_eq!(
509            s.f_push_str("this is a string")
510                .f_shrink_to_fit()
511                .capacity(),
512            16
513        );
514    }
515    #[test]
516    fn test_ref_truncate() {
517        let mut s = "this is a string".to_string();
518        let s = &mut s;
519        assert!(s.f_truncate(4).eq_ignore_ascii_case("THIS"));
520    }
521    #[test]
522    fn test_ref_try_reserve() {
523        let mut s = String::with_capacity(10);
524        let s = &mut s;
525        assert_eq!(s.f_try_reserve(20).unwrap().capacity(), 20);
526    }
527    #[test]
528    fn test_ref_try_reserve_exact() {
529        let mut s = String::with_capacity(10);
530        let s = &mut s;
531        assert_eq!(s.f_try_reserve_exact(20).unwrap().capacity(), 20);
532    }
533    #[test]
534    fn test_ref_push_if_false() {
535        let mut s = String::new();
536        let s = &mut s;
537        assert_eq!(s.f_push_if(',', |s, _c| !s.is_empty()), "");
538    }
539
540    #[test]
541    fn test_ref_push_if_true() {
542        let mut s = "hey".to_string();
543        let s = &mut s;
544        assert_eq!(s.f_push_if(',', |s, _c| !s.is_empty()), "hey,");
545    }
546
547    #[test]
548    fn test_ref_push_str_if_empty_self() {
549        let mut s = String::new();
550        let s = &mut s;
551        assert_eq!(
552            s.f_push_str_if(",more", |s, s1| !(s.is_empty() || s1.is_empty())),
553            ""
554        );
555    }
556
557    #[test]
558    fn test_ref_push_str_if_empty_str() {
559        let mut s = "hey".to_string();
560        let s = &mut s;
561        assert_eq!(
562            s.f_push_str_if("", |s, s1| !(s.is_empty() || s1.is_empty())),
563            "hey"
564        );
565    }
566
567    #[test]
568    fn test_ref_push_str_if_true() {
569        let mut s = "hey".to_string();
570        let s = &mut s;
571        assert_eq!(
572            s.f_push_str_if(",more", |s, s1| !(s.is_empty() || s1.is_empty())),
573            "hey,more"
574        );
575    }
576
577    #[test]
578    fn test_ref_truncate_if_some() {
579        let mut s = "hey you".to_string();
580        let s = &mut s;
581        assert_eq!(
582            s.f_truncate_if(|s| if s.ends_with(" you") {Some(s.len() - 4)} else {None}),
583            "hey"
584        );
585    }
586
587    #[test]
588    fn test_ref_truncate_if_none() {
589        let mut s = "hey you".to_string();
590        let s = &mut s;
591        assert_eq!(
592            s.f_truncate_if(|s| if s.ends_with(" ble") {Some(s.len() - 4)} else {None}),
593            "hey you"
594        );
595    }
596}