1#![doc = include_str ! ("./../README.md")]
2#![forbid(unsafe_code)]
3
4pub mod prelude {
5 pub use crate::{
6 StringKeeperExt,
7 SubstringExt,
8 SubstringKeeperExt
9 };
10}
11
12pub trait SubstringExt {
13 fn substring<R: std::ops::RangeBounds<usize>>(&self, range: R) -> String;
14 fn try_substring<R: std::ops::RangeBounds<usize>>(&self, range: R) -> Option<String>;
15}
16
17pub trait StringKeeperExt<T> {
18 fn beginning_of_string(self) -> StringKeeper<T>;
19 fn end_of_string(self) -> StringKeeper<T>;
20 fn including_pattern(self) -> StringKeeper<T>;
21 fn excluding_pattern(self) -> StringKeeper<T>;
22 fn before_pattern(self) -> StringKeeper<T>;
23 fn after_pattern(self) -> StringKeeper<T>;
24}
25
26pub enum KeeperPeriod {
27 Start,
28 End,
29}
30
31pub enum KeeperCutoff {
32 After,
33 Before,
34}
35
36pub enum KeeperClusivity {
37 Including,
38 Excluding,
39}
40
41pub struct StringKeeper<T> {
42 pub to_parse: String,
43 pub pattern: T,
44 pub period: KeeperPeriod,
45 pub clusivity: KeeperClusivity,
46 pub cutoff: KeeperCutoff,
47}
48pub trait SubstringKeeperExt<T> {
49 fn keep(self, pattern: T) -> StringKeeper<T>;
50}
51
52impl SubstringKeeperExt<String> for String {
53 fn keep(self, pattern: String) -> StringKeeper<String> {
54 StringKeeper {
55 to_parse: self,
56 period: KeeperPeriod::Start,
57 cutoff: KeeperCutoff::After,
58 clusivity: KeeperClusivity::Including,
59 pattern,
60 }
61 }
62}
63
64impl SubstringKeeperExt<char> for String {
65 fn keep(self, pattern: char) -> StringKeeper<char> {
66 StringKeeper {
67 to_parse: self,
68 period: KeeperPeriod::Start,
69 cutoff: KeeperCutoff::After,
70 clusivity: KeeperClusivity::Including,
71 pattern,
72 }
73 }
74}
75
76impl StringKeeperExt<String> for StringKeeper<String> {
77 fn beginning_of_string(mut self) -> StringKeeper<String> {
78 self.period = KeeperPeriod::Start;
79 self
80 }
81
82 fn end_of_string(mut self) -> StringKeeper<String> {
83 self.period = KeeperPeriod::End;
84 self
85 }
86
87 fn including_pattern(mut self) -> StringKeeper<String> {
88 self.clusivity = KeeperClusivity::Including;
89 self
90 }
91
92 fn excluding_pattern(mut self) -> StringKeeper<String> {
93 self.clusivity = KeeperClusivity::Excluding;
94 self
95 }
96
97 fn before_pattern(mut self) -> StringKeeper<String> {
98 self.cutoff = KeeperCutoff::Before;
99 self
100 }
101
102 fn after_pattern(mut self) -> StringKeeper<String> {
103 self.cutoff = KeeperCutoff::After;
104 self
105 }
106}
107
108impl std::fmt::Display for StringKeeper<String> {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 let try_find = match self.period {
111 KeeperPeriod::Start => self.to_parse.find(&self.pattern),
112 KeeperPeriod::End => self.to_parse.rfind(&self.pattern),
113 };
114
115 let range = match try_find {
116 None => usize::MIN..usize::MIN,
117 Some(pos) => match self.clusivity {
118 KeeperClusivity::Including => match self.cutoff {
119 KeeperCutoff::After => pos..usize::MAX,
120 KeeperCutoff::Before => {
121 let offset = pos + self.pattern.chars().count();
122 usize::MIN..offset
123 }
124 },
125 KeeperClusivity::Excluding => match self.cutoff {
126 KeeperCutoff::After => {
127 let offset = pos + self.pattern.chars().count();
128 offset..usize::MAX
129 }
130 KeeperCutoff::Before => usize::MIN..pos,
131 },
132 },
133 };
134
135 write!(f, "{}", self.to_parse.substring(range))
136 }
137}
138
139impl StringKeeperExt<char> for StringKeeper<char> {
140 fn beginning_of_string(mut self) -> Self {
141 self.period = KeeperPeriod::Start;
142 self
143 }
144
145 fn end_of_string(mut self) -> Self {
146 self.period = KeeperPeriod::End;
147 self
148 }
149
150 fn including_pattern(mut self) -> Self {
151 self.clusivity = KeeperClusivity::Including;
152 self
153 }
154
155 fn excluding_pattern(mut self) -> Self {
156 self.clusivity = KeeperClusivity::Excluding;
157 self
158 }
159
160 fn before_pattern(mut self) -> Self {
161 self.cutoff = KeeperCutoff::Before;
162 self
163 }
164
165 fn after_pattern(mut self) -> Self {
166 self.cutoff = KeeperCutoff::After;
167 self
168 }
169
170}
171
172impl std::fmt::Display for StringKeeper<char> {
173 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174 let try_find = match self.period {
175 KeeperPeriod::Start => self.to_parse.find(self.pattern),
176 KeeperPeriod::End => self.to_parse.rfind(self.pattern),
177 };
178
179 let range = match try_find {
180 None => usize::MIN..usize::MIN,
181 Some(pos) => match self.clusivity {
182 KeeperClusivity::Including => match self.cutoff {
183 KeeperCutoff::After => pos..usize::MAX,
184 KeeperCutoff::Before => usize::MIN..(pos).saturating_add(1),
185 },
186 KeeperClusivity::Excluding => match self.cutoff {
187 KeeperCutoff::After => (pos).saturating_add(1)..usize::MAX,
188 KeeperCutoff::Before => usize::MIN..pos,
189 },
190 },
191 };
192
193 write!(f, "{}", self.to_parse.substring(range))
194 }
195}
196
197impl SubstringExt for str {
198 fn substring<R: std::ops::RangeBounds<usize>>(&self, range: R) -> String {
199 self.try_substring(range).unwrap_or_else(|| "".to_string())
200 }
201
202 fn try_substring<R: std::ops::RangeBounds<usize>>(&self, range: R) -> Option<String> {
203 let start_idx = match range.start_bound() {
204 std::collections::Bound::Included(v) => *v,
205 std::collections::Bound::Excluded(v) => v.saturating_add(1),
206 std::collections::Bound::Unbounded => usize::MIN,
207 };
208
209 let end_idx = match range.end_bound() {
210 std::collections::Bound::Included(v) => v.saturating_add(1),
211 std::collections::Bound::Excluded(v) => *v,
212 std::collections::Bound::Unbounded => usize::MAX,
213 };
214
215 if end_idx <= start_idx {
216 return Some("".to_string());
217 }
218
219 end_idx
220 .checked_sub(start_idx)
221 .map(|take_count| self.chars().skip(start_idx).take(take_count).collect())
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::prelude::*;
228
229 #[test]
230 fn try_substring() {
231 let some_text = "hello, world!";
232 let result = some_text.substring(7..12);
233 assert_eq!(result, "world");
234
235 let some_text = "42Hello, world!".to_string();
236 let result = some_text.try_substring(2..7).unwrap();
237 let expected = "Hello";
238 assert_eq!(result, expected);
239 }
240
241 #[test]
242 fn substring() {
243 let some_text = "42Hello, world!".to_string();
244
245 let result = some_text.substring(2..7);
246 let expected = "Hello";
247 assert_eq!(result, expected);
248
249 let result = some_text.substring(2..424242);
250 let expected = "Hello, world!";
251 assert_eq!(result, expected);
252 }
253
254 #[test]
255 fn test_substring() {
256 assert_eq!("foobar".substring(..3), "foo");
257 }
258
259 #[test]
260 fn test_out_of_bounds() {
261 assert_eq!("foobar".substring(..10), "foobar");
262 assert_eq!("foobar".substring(6..10), "");
263 }
264
265 #[test]
266 fn test_start_and_end_equal() {
267 assert_eq!("foobar".substring(3..3), "");
268 }
269
270 #[test]
271 fn test_multiple_byte_characters() {
272 assert_eq!("ã".substring(..1), "a"); assert_eq!("ã".substring(1..2), "\u{0303}");
274 assert_eq!("fõøbα®".substring(2..5), "øbα");
275 }
276
277 #[test]
278 fn mozilla_substring_cases() {
279 let any_string = "Mozilla";
280 assert_eq!(any_string.substring(..1), "M");
281 assert_eq!(any_string.substring(1..), "ozilla");
282 assert_eq!(any_string.substring(..6), "Mozill");
283 assert_eq!(any_string.substring(4..), "lla");
284 assert_eq!(any_string.substring(4..7), "lla");
285 assert_eq!(any_string.substring(..7), "Mozilla");
286 assert_eq!(any_string.substring(..10), "Mozilla");
287 assert_eq!(any_string.substring(any_string.len() - 4..), "illa");
288 assert_eq!(any_string.substring(any_string.len() - 5..), "zilla");
289 assert_eq!(any_string.substring(2..5), "zil");
290 assert_eq!(any_string.substring(..2), "Mo");
291 assert_eq!(any_string.substring(..), "Mozilla");
292 }
293
294 #[test]
295 fn test_keep_after_include_string() {
296 assert_eq!(
297 "this is karøbα it was"
298 .to_string()
299 .keep("karøbα".to_string())
300 .beginning_of_string() .after_pattern() .including_pattern() .to_string(),
304 "karøbα it was"
305 );
306 assert_eq!(
307 "this is karøbα it was"
308 .to_string()
309 .keep("karøbα".to_string())
310 .to_string(),
311 "karøbα it was"
312 );
313 assert_eq!(
314 "karøbα"
315 .to_string()
316 .keep("kar".to_string())
317 .after_pattern()
318 .including_pattern()
319 .to_string(),
320 "karøbα"
321 );
322 }
323
324 #[test]
325 fn test_keep_after_exclude_string() {
326 assert_eq!(
327 "this is karøbα it was"
328 .to_string()
329 .keep("karøbα".to_string())
330 .beginning_of_string()
331 .after_pattern()
332 .excluding_pattern()
333 .to_string(),
334 " it was"
335 );
336 assert_eq!(
337 "karøbα"
338 .to_string()
339 .keep("kar".to_string())
340 .after_pattern()
341 .excluding_pattern()
342 .to_string(),
343 "øbα"
344 );
345 }
346
347 #[test]
348 fn test_keep_after_include_char() {
349 assert_eq!(
350 "this is karøbα it was"
351 .to_string()
352 .keep('k')
353 .after_pattern()
354 .including_pattern()
355 .to_string(),
356 "karøbα it was"
357 );
358 assert_eq!(
359 "karøbα"
360 .to_string()
361 .keep('k')
362 .after_pattern()
363 .including_pattern()
364 .to_string(),
365 "karøbα"
366 );
367 }
368
369 #[test]
370 fn test_keep_after_exclude_char() {
371 assert_eq!(
372 "this is karøbα it was"
373 .to_string()
374 .keep('k')
375 .after_pattern()
376 .excluding_pattern()
377 .to_string(),
378 "arøbα it was"
379 );
380 assert_eq!(
381 "karøbα"
382 .to_string()
383 .keep('k')
384 .after_pattern()
385 .excluding_pattern()
386 .to_string(),
387 "arøbα"
388 );
389 }
390
391 #[test]
392 fn test_keep_before_include_string() {
393 assert_eq!(
394 "this is karøbα it was"
395 .to_string()
396 .keep("øbα".to_string())
397 .before_pattern()
398 .including_pattern()
399 .to_string(),
400 "this is karøbα"
401 );
402 assert_eq!(
403 "karøbα"
404 .to_string()
405 .keep("øbα".to_string())
406 .before_pattern()
407 .including_pattern()
408 .to_string(),
409 "karøbα"
410 );
411 }
412 #[test]
413 fn test_keep_before_include_char() {
414 assert_eq!(
415 "this is karøbα it was"
416 .to_string()
417 .keep('ø')
418 .before_pattern()
419 .including_pattern()
420 .to_string(),
421 "this is karø"
422 );
423 assert_eq!(
424 "karøbα"
425 .to_string()
426 .keep('ø')
427 .before_pattern()
428 .including_pattern()
429 .to_string(),
430 "karø"
431 );
432 }
433
434 #[test]
435 fn test_keep_before_exclude_string() {
436 assert_eq!(
437 "this is karøbα it was"
438 .to_string()
439 .keep("øbα".to_string())
440 .before_pattern()
441 .excluding_pattern()
442 .to_string(),
443 "this is kar"
444 );
445 assert_eq!(
446 "karøbα"
447 .to_string()
448 .keep("øbα".to_string())
449 .before_pattern()
450 .excluding_pattern()
451 .to_string(),
452 "kar"
453 );
454 }
455
456 #[test]
457 fn test_keep_before_exclude_char() {
458 assert_eq!(
459 "this is karøbα it was"
460 .to_string()
461 .keep('ø')
462 .before_pattern()
463 .excluding_pattern()
464 .to_string(),
465 "this is kar"
466 );
467 assert_eq!(
468 "karøbα"
469 .to_string()
470 .keep('ø')
471 .before_pattern()
472 .excluding_pattern()
473 .to_string(),
474 "kar"
475 );
476 }
477}