Skip to main content

str_utils/
pattern.rs

1use alloc::{borrow::Cow, string::String};
2
3mod sealed {
4    pub trait Sealed {}
5
6    impl Sealed for char {}
7    impl Sealed for &str {}
8    impl Sealed for &&str {}
9    impl Sealed for &[char] {}
10
11    impl<const N: usize> Sealed for [char; N] {}
12    impl<const N: usize> Sealed for &[char; N] {}
13
14    impl<F> Sealed for F where F: FnMut(char) -> bool {}
15}
16
17#[inline]
18fn char_eq_str(c: char, s: &str) -> bool {
19    let mut buffer = [0; 4];
20
21    c.encode_utf8(&mut buffer) == s
22}
23
24fn replace_chars<'a, F>(s: &'a str, mut from: F, to: &str, mut count: usize) -> Cow<'a, str>
25where
26    F: FnMut(char) -> bool, {
27    if count == 0 {
28        return Cow::Borrowed(s);
29    }
30
31    let mut new_s: Option<String> = None;
32    let mut start = 0;
33
34    for (p, c) in s.char_indices() {
35        if count == 0 {
36            break;
37        }
38
39        if from(c) {
40            let next = p + c.len_utf8();
41
42            if char_eq_str(c, to) {
43                count -= 1;
44
45                continue;
46            }
47
48            if let Some(new_s) = new_s.as_mut() {
49                new_s.push_str(&s[start..p]);
50                new_s.push_str(to);
51            } else {
52                let mut owned = String::with_capacity(s.len());
53
54                owned.push_str(&s[..p]);
55                owned.push_str(to);
56
57                new_s = Some(owned);
58            }
59
60            start = next;
61            count -= 1;
62        }
63    }
64
65    match new_s {
66        Some(mut new_s) => {
67            new_s.push_str(&s[start..]);
68
69            Cow::Owned(new_s)
70        },
71        None => Cow::Borrowed(s),
72    }
73}
74
75#[inline]
76fn replace_str<'a>(s: &'a str, from: &str, to: &str) -> Cow<'a, str> {
77    if from == to || !s.contains(from) {
78        Cow::Borrowed(s)
79    } else {
80        Cow::Owned(s.replace(from, to))
81    }
82}
83
84#[inline]
85fn replacen_str<'a>(s: &'a str, from: &str, to: &str, count: usize) -> Cow<'a, str> {
86    if count == 0 || from == to || !s.contains(from) {
87        Cow::Borrowed(s)
88    } else {
89        Cow::Owned(s.replacen(from, to, count))
90    }
91}
92
93/// A stable pattern type that can be used by `replace_cow` and `replacen_cow`.
94///
95/// This trait is local to this crate because the standard library `Pattern` trait is still unstable to name in public APIs.
96pub trait Pattern: sealed::Sealed {
97    /// Returns a `Cow<str>` after replacing all matches in the given string.
98    #[doc(hidden)]
99    fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str>;
100
101    /// Returns a `Cow<str>` after replacing at most `count` matches in the given string.
102    #[doc(hidden)]
103    fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str>;
104}
105
106impl Pattern for char {
107    #[inline]
108    fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
109        replace_chars(s, |c| c == self, to, usize::MAX)
110    }
111
112    #[inline]
113    fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
114        replace_chars(s, |c| c == self, to, count)
115    }
116}
117
118impl Pattern for &str {
119    #[inline]
120    fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
121        replace_str(s, self, to)
122    }
123
124    #[inline]
125    fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
126        replacen_str(s, self, to, count)
127    }
128}
129
130impl Pattern for &&str {
131    #[inline]
132    fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
133        (*self).replace_from(s, to)
134    }
135
136    #[inline]
137    fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
138        (*self).replacen_from(s, to, count)
139    }
140}
141
142impl Pattern for &[char] {
143    #[inline]
144    fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
145        replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, usize::MAX)
146    }
147
148    #[inline]
149    fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
150        replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, count)
151    }
152}
153
154impl<const N: usize> Pattern for [char; N] {
155    #[inline]
156    fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
157        replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, usize::MAX)
158    }
159
160    #[inline]
161    fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
162        replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, count)
163    }
164}
165
166impl<const N: usize> Pattern for &[char; N] {
167    #[inline]
168    fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
169        replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, usize::MAX)
170    }
171
172    #[inline]
173    fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
174        replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, count)
175    }
176}
177
178impl<F> Pattern for F
179where
180    F: FnMut(char) -> bool,
181{
182    #[inline]
183    fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
184        replace_chars(s, self, to, usize::MAX)
185    }
186
187    #[inline]
188    fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
189        replace_chars(s, self, to, count)
190    }
191}