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 remove_matches<'a, I>(s: &'a str, matches: I) -> Cow<'a, str>
25where
26 I: Iterator<Item = (usize, usize)>, {
27 let mut borrowed_start = 0;
28 let mut pending: Option<(usize, usize)> = None;
29 let mut new_s: Option<String> = None;
30 let mut start = 0;
31
32 for (match_start, match_end) in matches {
33 if let Some(new_s) = new_s.as_mut() {
34 new_s.push_str(&s[start..match_start]);
35 start = match_end;
36
37 continue;
38 }
39
40 if let Some((pending_start, pending_end)) = pending.as_mut() {
41 if match_start == *pending_end {
42 *pending_end = match_end;
43
44 continue;
45 }
46
47 let mut owned = String::with_capacity(s.len());
49
50 owned.push_str(&s[borrowed_start..*pending_start]);
51 owned.push_str(&s[*pending_end..match_start]);
52
53 new_s = Some(owned);
54 start = match_end;
55
56 continue;
57 }
58
59 if match_start == borrowed_start {
60 borrowed_start = match_end;
61 } else {
62 pending = Some((match_start, match_end));
63 }
64 }
65
66 if let Some(mut new_s) = new_s {
67 new_s.push_str(&s[start..]);
68
69 return Cow::Owned(new_s);
70 }
71
72 match pending {
73 Some((pending_start, pending_end)) if pending_end == s.len() => {
74 Cow::Borrowed(&s[borrowed_start..pending_start])
75 },
76 Some((pending_start, pending_end)) => {
77 let mut new_s = String::with_capacity(s.len());
78
79 new_s.push_str(&s[borrowed_start..pending_start]);
80 new_s.push_str(&s[pending_end..]);
81
82 Cow::Owned(new_s)
83 },
84 None => Cow::Borrowed(&s[borrowed_start..]),
85 }
86}
87
88fn replace_chars<'a, F>(s: &'a str, mut from: F, to: &str, mut count: usize) -> Cow<'a, str>
89where
90 F: FnMut(char) -> bool, {
91 if count == 0 {
92 return Cow::Borrowed(s);
93 }
94
95 if to.is_empty() {
96 let matches =
97 s.char_indices().filter_map(
98 |(p, c)| {
99 if from(c) {
100 Some((p, p + c.len_utf8()))
101 } else {
102 None
103 }
104 },
105 );
106
107 return remove_matches(s, matches.take(count));
108 }
109
110 let mut new_s: Option<String> = None;
111 let mut start = 0;
112
113 for (p, c) in s.char_indices() {
114 if count == 0 {
115 break;
116 }
117
118 if from(c) {
119 let next = p + c.len_utf8();
120
121 if char_eq_str(c, to) {
122 count -= 1;
123
124 continue;
125 }
126
127 if let Some(new_s) = new_s.as_mut() {
128 new_s.push_str(&s[start..p]);
129 new_s.push_str(to);
130 } else {
131 let mut owned = String::with_capacity(s.len());
132
133 owned.push_str(&s[..p]);
134 owned.push_str(to);
135
136 new_s = Some(owned);
137 }
138
139 start = next;
140 count -= 1;
141 }
142 }
143
144 match new_s {
145 Some(mut new_s) => {
146 new_s.push_str(&s[start..]);
147
148 Cow::Owned(new_s)
149 },
150 None => Cow::Borrowed(s),
151 }
152}
153
154#[inline]
155fn replace_str<'a>(s: &'a str, from: &str, to: &str) -> Cow<'a, str> {
156 if from == to {
157 Cow::Borrowed(s)
158 } else if to.is_empty() {
159 remove_matches(s, s.match_indices(from).map(|(p, from)| (p, p + from.len())))
160 } else if !s.contains(from) {
161 Cow::Borrowed(s)
162 } else {
163 Cow::Owned(s.replace(from, to))
164 }
165}
166
167#[inline]
168fn replacen_str<'a>(s: &'a str, from: &str, to: &str, count: usize) -> Cow<'a, str> {
169 if count == 0 || from == to {
170 Cow::Borrowed(s)
171 } else if to.is_empty() {
172 remove_matches(s, s.match_indices(from).map(|(p, from)| (p, p + from.len())).take(count))
173 } else if !s.contains(from) {
174 Cow::Borrowed(s)
175 } else {
176 Cow::Owned(s.replacen(from, to, count))
177 }
178}
179
180pub trait Pattern: sealed::Sealed {
184 #[doc(hidden)]
186 fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str>;
187
188 #[doc(hidden)]
190 fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str>;
191
192 #[doc(hidden)]
194 fn trim_matches_from(self, s: &str) -> &str;
195
196 #[doc(hidden)]
198 fn trim_start_matches_from(self, s: &str) -> &str;
199
200 #[doc(hidden)]
202 fn trim_end_matches_from(self, s: &str) -> &str;
203}
204
205impl Pattern for char {
206 #[inline]
207 fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
208 replace_chars(s, |c| c == self, to, usize::MAX)
209 }
210
211 #[inline]
212 fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
213 replace_chars(s, |c| c == self, to, count)
214 }
215
216 #[inline]
217 fn trim_matches_from(self, s: &str) -> &str {
218 s.trim_matches(self)
219 }
220
221 #[inline]
222 fn trim_start_matches_from(self, s: &str) -> &str {
223 s.trim_start_matches(self)
224 }
225
226 #[inline]
227 fn trim_end_matches_from(self, s: &str) -> &str {
228 s.trim_end_matches(self)
229 }
230}
231
232impl Pattern for &str {
233 #[inline]
234 fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
235 replace_str(s, self, to)
236 }
237
238 #[inline]
239 fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
240 replacen_str(s, self, to, count)
241 }
242
243 #[inline]
244 fn trim_matches_from(self, s: &str) -> &str {
245 s.trim_start_matches(self).trim_end_matches(self)
246 }
247
248 #[inline]
249 fn trim_start_matches_from(self, s: &str) -> &str {
250 s.trim_start_matches(self)
251 }
252
253 #[inline]
254 fn trim_end_matches_from(self, s: &str) -> &str {
255 s.trim_end_matches(self)
256 }
257}
258
259impl Pattern for &&str {
260 #[inline]
261 fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
262 (*self).replace_from(s, to)
263 }
264
265 #[inline]
266 fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
267 (*self).replacen_from(s, to, count)
268 }
269
270 #[inline]
271 fn trim_matches_from(self, s: &str) -> &str {
272 (*self).trim_matches_from(s)
273 }
274
275 #[inline]
276 fn trim_start_matches_from(self, s: &str) -> &str {
277 (*self).trim_start_matches_from(s)
278 }
279
280 #[inline]
281 fn trim_end_matches_from(self, s: &str) -> &str {
282 (*self).trim_end_matches_from(s)
283 }
284}
285
286impl Pattern for &[char] {
287 #[inline]
288 fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
289 replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, usize::MAX)
290 }
291
292 #[inline]
293 fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
294 replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, count)
295 }
296
297 #[inline]
298 fn trim_matches_from(self, s: &str) -> &str {
299 s.trim_matches(|c: char| self.iter().any(|from| c.eq(from)))
300 }
301
302 #[inline]
303 fn trim_start_matches_from(self, s: &str) -> &str {
304 s.trim_start_matches(|c: char| self.iter().any(|from| c.eq(from)))
305 }
306
307 #[inline]
308 fn trim_end_matches_from(self, s: &str) -> &str {
309 s.trim_end_matches(|c: char| self.iter().any(|from| c.eq(from)))
310 }
311}
312
313impl<const N: usize> Pattern for [char; N] {
314 #[inline]
315 fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
316 replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, usize::MAX)
317 }
318
319 #[inline]
320 fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
321 replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, count)
322 }
323
324 #[inline]
325 fn trim_matches_from(self, s: &str) -> &str {
326 s.trim_matches(|c: char| self.iter().any(|from| c.eq(from)))
327 }
328
329 #[inline]
330 fn trim_start_matches_from(self, s: &str) -> &str {
331 s.trim_start_matches(|c: char| self.iter().any(|from| c.eq(from)))
332 }
333
334 #[inline]
335 fn trim_end_matches_from(self, s: &str) -> &str {
336 s.trim_end_matches(|c: char| self.iter().any(|from| c.eq(from)))
337 }
338}
339
340impl<const N: usize> Pattern for &[char; N] {
341 #[inline]
342 fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
343 replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, usize::MAX)
344 }
345
346 #[inline]
347 fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
348 replace_chars(s, |c| self.iter().any(|from| c.eq(from)), to, count)
349 }
350
351 #[inline]
352 fn trim_matches_from(self, s: &str) -> &str {
353 s.trim_matches(|c: char| self.iter().any(|from| c.eq(from)))
354 }
355
356 #[inline]
357 fn trim_start_matches_from(self, s: &str) -> &str {
358 s.trim_start_matches(|c: char| self.iter().any(|from| c.eq(from)))
359 }
360
361 #[inline]
362 fn trim_end_matches_from(self, s: &str) -> &str {
363 s.trim_end_matches(|c: char| self.iter().any(|from| c.eq(from)))
364 }
365}
366
367impl<F> Pattern for F
368where
369 F: FnMut(char) -> bool,
370{
371 #[inline]
372 fn replace_from<'a>(self, s: &'a str, to: &str) -> Cow<'a, str> {
373 replace_chars(s, self, to, usize::MAX)
374 }
375
376 #[inline]
377 fn replacen_from<'a>(self, s: &'a str, to: &str, count: usize) -> Cow<'a, str> {
378 replace_chars(s, self, to, count)
379 }
380
381 #[inline]
382 fn trim_matches_from(self, s: &str) -> &str {
383 s.trim_matches(self)
384 }
385
386 #[inline]
387 fn trim_start_matches_from(self, s: &str) -> &str {
388 s.trim_start_matches(self)
389 }
390
391 #[inline]
392 fn trim_end_matches_from(self, s: &str) -> &str {
393 s.trim_end_matches(self)
394 }
395}