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
93pub trait Pattern: sealed::Sealed {
97 #[doc(hidden)]
99 fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str>;
100
101 #[doc(hidden)]
103 fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str>;
104
105 #[doc(hidden)]
107 fn trim_matches_from(self, s: &str) -> &str;
108
109 #[doc(hidden)]
111 fn trim_start_matches_from(self, s: &str) -> &str;
112
113 #[doc(hidden)]
115 fn trim_end_matches_from(self, s: &str) -> &str;
116}
117
118impl Pattern for char {
119 #[inline]
120 fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
121 replace_chars(s, |c| c == self, to, usize::MAX)
122 }
123
124 #[inline]
125 fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
126 replace_chars(s, |c| c == self, to, count)
127 }
128
129 #[inline]
130 fn trim_matches_from(self, s: &str) -> &str {
131 s.trim_matches(self)
132 }
133
134 #[inline]
135 fn trim_start_matches_from(self, s: &str) -> &str {
136 s.trim_start_matches(self)
137 }
138
139 #[inline]
140 fn trim_end_matches_from(self, s: &str) -> &str {
141 s.trim_end_matches(self)
142 }
143}
144
145impl Pattern for &str {
146 #[inline]
147 fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
148 replace_str(s, self, to)
149 }
150
151 #[inline]
152 fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
153 replacen_str(s, self, to, count)
154 }
155
156 #[inline]
157 fn trim_matches_from(self, s: &str) -> &str {
158 s.trim_start_matches(self).trim_end_matches(self)
159 }
160
161 #[inline]
162 fn trim_start_matches_from(self, s: &str) -> &str {
163 s.trim_start_matches(self)
164 }
165
166 #[inline]
167 fn trim_end_matches_from(self, s: &str) -> &str {
168 s.trim_end_matches(self)
169 }
170}
171
172impl Pattern for &&str {
173 #[inline]
174 fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
175 (*self).replace_from(s, to)
176 }
177
178 #[inline]
179 fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
180 (*self).replacen_from(s, to, count)
181 }
182
183 #[inline]
184 fn trim_matches_from(self, s: &str) -> &str {
185 (*self).trim_matches_from(s)
186 }
187
188 #[inline]
189 fn trim_start_matches_from(self, s: &str) -> &str {
190 (*self).trim_start_matches_from(s)
191 }
192
193 #[inline]
194 fn trim_end_matches_from(self, s: &str) -> &str {
195 (*self).trim_end_matches_from(s)
196 }
197}
198
199impl Pattern for &[char] {
200 #[inline]
201 fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
202 replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, usize::MAX)
203 }
204
205 #[inline]
206 fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
207 replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, count)
208 }
209
210 #[inline]
211 fn trim_matches_from(self, s: &str) -> &str {
212 s.trim_matches(|c: char| self.iter().any(|from| c.eq(from)))
213 }
214
215 #[inline]
216 fn trim_start_matches_from(self, s: &str) -> &str {
217 s.trim_start_matches(|c: char| self.iter().any(|from| c.eq(from)))
218 }
219
220 #[inline]
221 fn trim_end_matches_from(self, s: &str) -> &str {
222 s.trim_end_matches(|c: char| self.iter().any(|from| c.eq(from)))
223 }
224}
225
226impl<const N: usize> Pattern for [char; N] {
227 #[inline]
228 fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
229 replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, usize::MAX)
230 }
231
232 #[inline]
233 fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
234 replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, count)
235 }
236
237 #[inline]
238 fn trim_matches_from(self, s: &str) -> &str {
239 s.trim_matches(|c: char| self.iter().any(|from| c.eq(from)))
240 }
241
242 #[inline]
243 fn trim_start_matches_from(self, s: &str) -> &str {
244 s.trim_start_matches(|c: char| self.iter().any(|from| c.eq(from)))
245 }
246
247 #[inline]
248 fn trim_end_matches_from(self, s: &str) -> &str {
249 s.trim_end_matches(|c: char| self.iter().any(|from| c.eq(from)))
250 }
251}
252
253impl<const N: usize> Pattern for &[char; N] {
254 #[inline]
255 fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
256 replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, usize::MAX)
257 }
258
259 #[inline]
260 fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
261 replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, count)
262 }
263
264 #[inline]
265 fn trim_matches_from(self, s: &str) -> &str {
266 s.trim_matches(|c: char| self.iter().any(|from| c.eq(from)))
267 }
268
269 #[inline]
270 fn trim_start_matches_from(self, s: &str) -> &str {
271 s.trim_start_matches(|c: char| self.iter().any(|from| c.eq(from)))
272 }
273
274 #[inline]
275 fn trim_end_matches_from(self, s: &str) -> &str {
276 s.trim_end_matches(|c: char| self.iter().any(|from| c.eq(from)))
277 }
278}
279
280impl<F> Pattern for F
281where
282 F: FnMut(char) -> bool,
283{
284 #[inline]
285 fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
286 replace_chars(s, self, to, usize::MAX)
287 }
288
289 #[inline]
290 fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
291 replace_chars(s, self, to, count)
292 }
293
294 #[inline]
295 fn trim_matches_from(self, s: &str) -> &str {
296 s.trim_matches(self)
297 }
298
299 #[inline]
300 fn trim_start_matches_from(self, s: &str) -> &str {
301 s.trim_start_matches(self)
302 }
303
304 #[inline]
305 fn trim_end_matches_from(self, s: &str) -> &str {
306 s.trim_end_matches(self)
307 }
308}