stylish_stringlike/text/
replaceable.rs

1use super::{Expandable, Pushable, RawText, Sliceable};
2use regex::Regex;
3/// Replacing text in text-like objects.
4///
5/// This is implemented for [`String`] by default.
6pub trait Replaceable<T> {
7    /// Perform literal string replacement.
8    ///
9    /// # Example
10    /// ```rust
11    /// use stylish_stringlike::text::*;
12    /// let foo = String::from("foo");
13    /// let bar = Replaceable::<&String>::replace(&foo, "foo", &String::from("bar"));
14    /// assert_eq!(String::from("bar"), bar);
15    /// ```
16    fn replace(&self, from: &str, replacer: T) -> Self;
17    /// Perform regex string replacement.
18    ///
19    /// # Example
20    /// ```rust
21    /// use regex::Regex;
22    /// use stylish_stringlike::text::*;
23    /// let foooo = String::from("foooo");
24    /// let re = Regex::new("fo+").unwrap();
25    /// let bar = Replaceable::<&String>::replace_regex(&foooo, &re, &String::from("bar"));
26    /// assert_eq!(bar, String::from("bar"));
27    /// ```
28    fn replace_regex(&self, searcher: &Regex, replacer: T) -> Self;
29}
30
31impl<'a, T> Replaceable<&'a T> for T
32where
33    T: Default + RawText + Sliceable + Pushable<T> + Expandable,
34{
35    fn replace(&self, from: &str, replacer: &'a T) -> Self {
36        let mut result: T = Default::default();
37        let mut last_end = 0;
38        for (start, part) in self.raw_ref().match_indices(from) {
39            eprintln!("start: {}, part: {}", start, part);
40            match self.slice(last_end..start) {
41                Some(slice) if !slice.raw_ref().is_empty() => {
42                    result.push(&slice);
43                }
44                _ => {}
45            }
46            result.push(replacer);
47            last_end = start + part.len();
48        }
49        match self.slice(last_end..) {
50            Some(slice) if !slice.raw_ref().is_empty() => {
51                result.push(&slice);
52            }
53            _ => {}
54        }
55        result
56    }
57    fn replace_regex(&self, searcher: &Regex, replacer: &'a T) -> Self {
58        let mut result: T = Default::default();
59        let mut last_end = 0;
60        let captures = searcher.captures_iter(self.raw_ref());
61        for capture in captures {
62            let mat = capture
63                .get(0)
64                .expect("Captures are always supposed to have at least one match");
65            if let Some(slice) = self.slice(last_end..mat.start()) {
66                result.push(&slice);
67                if let Some(_original) = self.slice(mat.start()..mat.end()) {
68                    let expanded = replacer.expand(&capture);
69                    result.push(&expanded);
70                }
71            }
72            last_end = mat.end();
73        }
74        if let Some(spans) = self.slice(last_end..) {
75            result.push(&spans);
76        }
77        result
78    }
79}
80
81#[cfg(test)]
82mod test {
83    use super::*;
84    #[test]
85    fn test_string_replace() {
86        let foo = String::from("foo");
87        let bar = Replaceable::<&String>::replace(&foo, "foo", &String::from("bar"));
88        eprintln!("bar: {}", bar);
89        assert_eq!(bar, String::from("bar"));
90    }
91    #[test]
92    fn test_string_regex_replace() {
93        let foooo = String::from("foooo");
94        let re = Regex::new("fo+").unwrap();
95        let bar = Replaceable::<&String>::replace_regex(&foooo, &re, &String::from("bar"));
96        assert_eq!(bar, String::from("bar"));
97    }
98}