string_view/lib.rs
1#![doc = include_str!("../README.md")]
2
3#![no_std]
4
5use core::error::Error;
6use core::fmt::{Debug, Display};
7use core::ops::{Deref, DerefMut};
8
9/// View into [`str`] slice.
10///
11/// Holds parent `str` info which allows to safely extend this view with parent
12/// size in mind.
13///
14/// ```rust
15/// use string_view::StrExt;
16///
17/// let text = "Hello World";
18/// let mut view = text.view_part(6, 11);
19/// assert_eq!(view.as_str(), "World");
20///
21/// view.extend_left(6);
22/// assert_eq!(view.as_str(), "Hello World");
23///
24/// view.reduce_right_while(char::is_alphabetic);
25/// assert_eq!(view.as_str(), "Hello ");
26/// ```
27pub struct StringView<'a> {
28 pub base: &'a str,
29 /// byte idx of view start inside [`StringView::base`].
30 view_start: usize,
31 /// byte len of view inside [`StringView::base`].
32 view_len: usize,
33}
34
35impl<'a> StringView<'a> {
36 /// Creates [`StringView`] of a whole [`str`] slice.
37 ///
38 /// see [`StringView::new_part`] to view part of [`str`] slice.
39 pub fn new(base: &'a str) -> Self {
40 Self {
41 base,
42 view_start: 0,
43 view_len: base.len(),
44 }
45 }
46
47 /// Creates [`StringView`] of a part of [`str`] slice using 2 byte indexes.
48 ///
49 /// ```rust
50 /// use string_view::StringView;
51 ///
52 /// let text = "Hello World";
53 /// let view = StringView::new_part(text, 6, 11);
54 ///
55 /// assert_eq!(view.as_str(), "World");
56 /// ```
57 ///
58 /// Or using [`StrExt`] extension trait:
59 ///
60 /// ```rust
61 /// use string_view::StrExt;
62 ///
63 /// let text = "Hello World";
64 /// let view = text.view_part(6, 11);
65 ///
66 /// assert_eq!(view.as_str(), "World");
67 /// ```
68 pub fn new_part(base: &'a str, view_start: usize, view_end: usize) -> Self {
69 assert!(
70 view_end >= view_start,
71 "View end index cannot be less then start index"
72 );
73 Self {
74 base,
75 view_start,
76 view_len: view_end - view_start,
77 }
78 }
79
80 /// Byte index of view start inside [`StringView::base`].
81 pub fn start(&self) -> usize {
82 self.view_start
83 }
84
85 /// Byte index of view end inside [`StringView::base`].
86 pub fn end(&self) -> usize {
87 self.view_start + self.view_len
88 }
89
90 pub fn as_str(&self) -> &'a str {
91 &self.base[self.view_start..self.view_start + self.view_len]
92 }
93
94 /// Shrinks this view from the left to current right edge with length zero.
95 ///
96 /// ```toml,ignore
97 /// [ str [ view ] ]
98 /// [ str -> [] ]
99 /// ```
100 ///
101 /// #### Example:
102 ///
103 /// ```rust
104 /// use string_view::StrExt;
105 ///
106 /// let text = "Hello World";
107 /// let mut view = text.view();
108 /// assert_eq!(view.as_str(), "Hello World");
109 ///
110 /// view.shrink_to_right();
111 /// view.extend_left_while(char::is_alphabetic);
112 /// assert_eq!(view.as_str(), "World");
113 /// ```
114 pub fn shrink_to_right(&mut self) {
115 self.view_start += self.view_len;
116 self.view_len = 0;
117 }
118
119 /// Shrinks this view from the right to current left edge with length zero.
120 ///
121 /// ```toml,ignore
122 /// [ str [ view ] ]
123 /// [ str [] <- ]
124 /// ```
125 ///
126 /// #### Example:
127 ///
128 /// ```rust
129 /// use string_view::StrExt;
130 ///
131 /// let text = "Hello World";
132 /// let mut view = text.view();
133 /// assert_eq!(view.as_str(), "Hello World");
134 ///
135 /// view.shrink_to_left();
136 /// view.extend_right_while(char::is_alphabetic);
137 /// assert_eq!(view.as_str(), "Hello");
138 /// ```
139 pub fn shrink_to_left(&mut self) {
140 self.view_len = 0;
141 }
142
143 /// Extend string view to the right by `n` characters.
144 ///
145 /// ```toml,ignore
146 /// [ str [ view ] ]
147 /// [ str [ view -> n ] ]
148 /// ```
149 ///
150 /// panics if there is not enough characters in base string to the right of this view.
151 ///
152 /// see [`StringView::try_extend_right`] for fallible version.
153 ///
154 /// ```rust
155 /// use string_view::StrExt;
156 ///
157 /// let text = "Hello World";
158 /// let mut view = text.view_part(0, 5);
159 /// assert_eq!(view.as_str(), "Hello");
160 ///
161 /// view.extend_right(6);
162 /// assert_eq!(view.as_str(), "Hello World");
163 /// ```
164 pub fn extend_right(&mut self, n: usize) {
165 self.try_extend_right(n)
166 .expect("Unable to extend string view to the right")
167 }
168
169 /// Try to extend string view to the right by `n` characters.
170 ///
171 /// ```toml,ignore
172 /// [ str [ view ] ]
173 /// [ str [ view -> n ] ]
174 /// ```
175 ///
176 /// returns [`Err`] if there is not enough characters in base string to the right of this view.
177 ///
178 /// ```rust
179 /// use string_view::StrExt;
180 ///
181 /// let text = "Hello World";
182 ///
183 /// let mut view = text.view_part(0, 5);
184 /// assert_eq!(view.as_str(), "Hello");
185 ///
186 /// let err = view.try_extend_right(4);
187 /// assert!(err.is_ok());
188 /// assert_eq!(view.as_str(), "Hello Wor");
189 ///
190 /// let err = view.try_extend_right(10);
191 /// assert!(matches!(err, Err(BaseStringIsTooShort)));
192 /// assert_eq!(view.as_str(), "Hello Wor");
193 /// ```
194 pub fn try_extend_right(&mut self, n: usize) -> Result<(), BaseStringIsTooShort<RIGHT>> {
195 let mut combined_len = 0;
196 let mut char_iter = self.base[self.end()..].chars();
197 for _ in 0..n {
198 combined_len += char_iter.next().ok_or(BaseStringIsTooShort)?.len_utf8();
199 }
200 self.view_len += combined_len;
201 Ok(())
202 }
203
204 /// Extend string view to the right while `func` returns `true`.
205 ///
206 /// ```toml,ignore
207 /// [ str [ view ] ]
208 /// [ str [ view -> n ] ]
209 /// ```
210 ///
211 /// ### Example:
212 ///
213 /// ```rust
214 /// use string_view::StrExt;
215 ///
216 /// let text = "Hello World !!!";
217 ///
218 /// let mut view = text.view_part(0, 2);
219 /// assert_eq!(view.as_str(), "He");
220 ///
221 /// view.extend_right_while(|ch| ch != ' ');
222 /// assert_eq!(view.as_str(), "Hello");
223 ///
224 /// view.extend_right(1);
225 /// view.extend_right_while(|ch| ch != ' ');
226 /// assert_eq!(view.as_str(), "Hello World");
227 /// ```
228 pub fn extend_right_while<F>(&mut self, mut func: F)
229 where
230 F: FnMut(char) -> bool,
231 {
232 let mut combined_len = 0;
233
234 for ch in self.base[self.end()..].chars() {
235 if func(ch) {
236 combined_len += ch.len_utf8();
237 }
238 else {
239 break;
240 }
241 }
242 self.view_len += combined_len;
243 }
244
245 /// Reduce string view from the right by `n` characters.
246 ///
247 /// ```toml,ignore
248 /// [ str [ view ] ]
249 /// [ str [ view ] <- n ]
250 /// ```
251 ///
252 /// panics if there is not enough characters in current string view.
253 ///
254 /// ```rust
255 /// use string_view::StrExt;
256 ///
257 /// let text = "Hello World";
258 ///
259 /// let mut view = text.view();
260 /// assert_eq!(view.as_str(), "Hello World");
261 ///
262 /// view.reduce_right(6);
263 /// assert_eq!(view.as_str(), "Hello");
264 /// ```
265 pub fn reduce_right(&mut self, n: usize) {
266 self.try_reduce_right(n)
267 .expect("Unable to reduce string view from the right")
268 }
269
270 /// Try to reduce string view from the right by `n` characters.
271 ///
272 /// ```toml,ignore
273 /// [ str [ view ] ]
274 /// [ str [ view ] <- n ]
275 /// ```
276 ///
277 /// returns [`Err`] if there is not enough characters in current string view.
278 ///
279 /// ```rust
280 /// use string_view::StrExt;
281 ///
282 /// let text = "One and only Hello World";
283 ///
284 /// let mut view = text.view_part(13, 24);
285 /// assert_eq!(view.as_str(), "Hello World");
286 ///
287 /// let result = view.try_reduce_right(6);
288 /// assert!(result.is_ok());
289 /// assert_eq!(view.as_str(), "Hello");
290 ///
291 /// let result = view.try_reduce_right(10);
292 /// assert!(matches!(result, Err(ViewIsTooShort)));
293 /// assert_eq!(view.as_str(), "Hello");
294 /// ```
295 pub fn try_reduce_right(&mut self, n: usize) -> Result<(), ViewIsTooShort<RIGHT>> {
296 let mut combined_len = 0;
297 let mut char_iter = self.base[self.start()..self.end()].chars().rev();
298 for _ in 0..n {
299 combined_len += char_iter.next().ok_or(ViewIsTooShort)?.len_utf8();
300 }
301 self.view_len -= combined_len;
302 Ok(())
303 }
304
305 /// Reduce string view from the right while `func` returns `true`.
306 ///
307 /// ```toml,ignore
308 /// [ str [ view ] ]
309 /// [ str [ view ] <- n ]
310 /// ```
311 ///
312 /// ```rust
313 /// use string_view::StrExt;
314 ///
315 /// let text = "Hello World !!!";
316 ///
317 /// let mut view = text.view();
318 /// assert_eq!(view.as_str(), "Hello World !!!");
319 ///
320 /// view.reduce_right_while(|ch| ch != ' ');
321 /// assert_eq!(view.as_str(), "Hello World ");
322 ///
323 /// view.reduce_right(1);
324 /// view.reduce_right_while(|ch| ch != ' ');
325 /// assert_eq!(view.as_str(), "Hello ");
326 /// ```
327 pub fn reduce_right_while<F>(&mut self, mut func: F)
328 where
329 F: FnMut(char) -> bool,
330 {
331 let mut combined_len = 0;
332 for ch in self.base[self.start()..self.end()].chars().rev() {
333 if func(ch) {
334 combined_len += ch.len_utf8();
335 }
336 else {
337 break;
338 }
339 }
340 self.view_len -= combined_len;
341 }
342
343 /// Extend string view to the left by `n` characters.
344 ///
345 /// ```toml,ignore
346 /// [ str [ view ] ]
347 /// [ str [ n <- view ] ]
348 /// ```
349 ///
350 /// panics if there is not enough characters in base string to the left of this view.
351 ///
352 /// see [`StringView::try_extend_left`] for fallible version.
353 ///
354 /// ```rust
355 /// use string_view::StrExt;
356 ///
357 /// let text = "Hello World";
358 /// let mut view = text.view_part(6, 11);
359 /// assert_eq!(view.as_str(), "World");
360 ///
361 /// view.extend_left(6);
362 /// assert_eq!(view.as_str(), "Hello World");
363 /// ```
364 pub fn extend_left(&mut self, n: usize) {
365 self.try_extend_left(n)
366 .expect("Unable to extend string view to the left")
367 }
368
369 /// Try to extend string view to the left by `n` characters.
370 ///
371 /// ```toml,ignore
372 /// [ str [ view ] ]
373 /// [ str [ n <- view ] ]
374 /// ```
375 ///
376 /// returns [`Err`] if there is not enough characters in base string to the right of this view.
377 ///
378 /// ```rust
379 /// use string_view::StrExt;
380 ///
381 /// let text = "Hello World";
382 ///
383 /// let mut view = text.view_part(6, 11);
384 /// assert_eq!(view.as_str(), "World");
385 ///
386 /// let err = view.try_extend_left(4);
387 /// assert!(err.is_ok());
388 /// assert_eq!(view.as_str(), "llo World");
389 ///
390 /// let err = view.try_extend_left(10);
391 /// assert!(matches!(err, Err(BaseStringIsTooShort)));
392 /// assert_eq!(view.as_str(), "llo World");
393 /// ```
394 pub fn try_extend_left(&mut self, n: usize) -> Result<(), BaseStringIsTooShort<LEFT>> {
395 let mut combined_len = 0;
396 let mut char_iter = self.base[..self.start()].chars().rev();
397 for _ in 0..n {
398 combined_len += char_iter.next().ok_or(BaseStringIsTooShort)?.len_utf8();
399 }
400 self.view_start -= combined_len;
401 self.view_len += combined_len;
402 Ok(())
403 }
404
405 /// Extend string view to the left while `func` returns `true`.
406 ///
407 /// ```toml,ignore
408 /// [ str [ view ] ]
409 /// [ str [ n <- view ] ]
410 /// ```
411 ///
412 /// #### Example:
413 ///
414 /// ```rust
415 /// use string_view::StrExt;
416 ///
417 /// let text = "Hello World !!!";
418 ///
419 /// let mut view = text.view_part(14, 15);
420 /// assert_eq!(view.as_str(), "!");
421 ///
422 /// view.extend_left_while(|ch| ch != ' ');
423 /// assert_eq!(view.as_str(), "!!!");
424 ///
425 /// view.extend_left(1);
426 /// view.extend_left_while(|ch| ch != ' ');
427 /// assert_eq!(view.as_str(), "World !!!");
428 /// ```
429 pub fn extend_left_while<F>(&mut self, mut func: F)
430 where
431 F: FnMut(char) -> bool,
432 {
433 let mut combined_len = 0;
434
435 for ch in self.base[..self.start()].chars().rev() {
436 if func(ch) {
437 combined_len += ch.len_utf8();
438 }
439 else {
440 break;
441 }
442 }
443 self.view_start -= combined_len;
444 self.view_len += combined_len;
445 }
446
447 /// Reduce string view from the left by `n` characters.
448 ///
449 /// ```toml,ignore
450 /// [ str [ view ] ]
451 /// [ str n -> [ view ] ]
452 /// ```
453 ///
454 /// panics if there is not enough characters in current string view.
455 ///
456 /// see [`StringView::try_reduce_left`] for fallible version.
457 ///
458 /// ```rust
459 /// use string_view::StrExt;
460 ///
461 /// let text = "Hello World";
462 ///
463 /// let mut view = text.view();
464 /// assert_eq!(view.as_str(), "Hello World");
465 ///
466 /// view.reduce_left(6);
467 /// assert_eq!(view.as_str(), "World");
468 /// ```
469 pub fn reduce_left(&mut self, n: usize) {
470 self.try_reduce_left(n)
471 .expect("Unable to reduce string view from the left")
472 }
473
474 /// Try to reduce string view from the left by `n` characters.
475 ///
476 /// ```toml,ignore
477 /// [ str [ view ] ]
478 /// [ str n -> [ view ] ]
479 /// ```
480 ///
481 /// returns [`Err`] if there is not enough characters in current string view.
482 ///
483 /// ```rust
484 /// use string_view::StrExt;
485 ///
486 /// let text = "One and only Hello World";
487 ///
488 /// let mut view = text.view_part(13, 24);
489 /// assert_eq!(view.as_str(), "Hello World");
490 ///
491 /// let result = view.try_reduce_left(6);
492 /// assert!(result.is_ok());
493 /// assert_eq!(view.as_str(), "World");
494 ///
495 /// let result = view.try_reduce_left(10);
496 /// assert!(matches!(result, Err(ViewIsTooShort)));
497 /// assert_eq!(view.as_str(), "World");
498 /// ```
499 pub fn try_reduce_left(&mut self, n: usize) -> Result<(), ViewIsTooShort<LEFT>> {
500 let mut combined_len = 0;
501 let mut char_iter = self.base[self.start()..self.end()].chars();
502 for _ in 0..n {
503 combined_len += char_iter.next().ok_or(ViewIsTooShort)?.len_utf8();
504 }
505 self.view_start += combined_len;
506 self.view_len -= combined_len;
507 Ok(())
508 }
509
510 /// Reduce string view from the left while `func` returns `true`.
511 ///
512 /// ```toml,ignore
513 /// [ str [ view ] ]
514 /// [ str n -> [ view ] ]
515 /// ```
516 ///
517 /// #### Example:
518 ///
519 /// ```rust
520 /// use string_view::StrExt;
521 ///
522 /// let text = "Hello World !!!";
523 ///
524 /// let mut view = text.view();
525 /// assert_eq!(view.as_str(), "Hello World !!!");
526 ///
527 /// view.reduce_left_while(|ch| ch != ' ');
528 /// assert_eq!(view.as_str(), " World !!!");
529 ///
530 /// view.reduce_left(1);
531 /// view.reduce_left_while(|ch| ch != ' ');
532 /// assert_eq!(view.as_str(), " !!!");
533 /// ```
534 pub fn reduce_left_while<F>(&mut self, mut func: F)
535 where
536 F: FnMut(char) -> bool,
537 {
538 let mut combined_len = 0;
539 for ch in self.base[self.start()..self.end()].chars() {
540 if func(ch) {
541 combined_len += ch.len_utf8();
542 }
543 else {
544 break;
545 }
546 }
547 self.view_start += combined_len;
548 self.view_len -= combined_len;
549 }
550}
551
552impl Deref for StringView<'_> {
553 type Target = str;
554
555 fn deref(&self) -> &Self::Target {
556 self.as_str()
557 }
558}
559
560impl Debug for StringView<'_> {
561 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
562 Debug::fmt(self.as_str(), f)
563 }
564}
565
566impl Display for StringView<'_> {
567 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
568 Display::fmt(self.as_str(), f)
569 }
570}
571
572type Side = bool;
573const RIGHT: bool = true;
574const LEFT: bool = false;
575
576/// The only error case in [`StringView::try_extend_right`].
577pub struct BaseStringIsTooShort<const SIDE: Side>;
578
579impl<const SIDE: Side> Debug for BaseStringIsTooShort<SIDE> {
580 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
581 write!(
582 f,
583 "Base String contains less characters than `n` to the {} of the view",
584 if SIDE == RIGHT { "right" } else { "left" }
585 )
586 }
587}
588
589impl<const SIDE: Side> Display for BaseStringIsTooShort<SIDE> {
590 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
591 Debug::fmt(&self, f)
592 }
593}
594
595impl<const SIDE: Side> Error for BaseStringIsTooShort<SIDE> {}
596
597/// The only error case in [`StringView::try_reduce_right`].
598pub struct ViewIsTooShort<const SIDE: Side>;
599
600impl<const SIDE: Side> Debug for ViewIsTooShort<SIDE> {
601 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
602 write!(
603 f,
604 "View contains less characters than `n` to the {} of the view",
605 if SIDE == RIGHT { "right" } else { "left" }
606 )
607 }
608}
609
610impl<const SIDE: Side> Display for ViewIsTooShort<SIDE> {
611 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
612 Debug::fmt(&self, f)
613 }
614}
615
616impl<const SIDE: Side> Error for ViewIsTooShort<SIDE> {}
617
618/// In-place character representation inside str slice
619///
620/// Under the hood is &str with single character
621///
622/// ```rust
623/// use string_view::Char;
624///
625/// let ch = Char::new("A");
626/// ```
627///
628/// ```rust,should_panic
629/// use string_view::Char;
630///
631/// let ch = Char::new(""); // panics
632/// let ch = Char::new("Hello"); // panics
633/// ```
634pub struct Char<'a>(&'a str);
635
636impl Char<'_> {
637 pub fn new(ch: &str) -> Char<'_> {
638 let char_len = ch
639 .chars()
640 .next()
641 .expect("Unable to create Char from empty string")
642 .len_utf8();
643
644 assert_eq!(
645 char_len,
646 ch.len(),
647 "Char can only be constructed from single character string"
648 );
649
650 Char(ch)
651 }
652
653 pub fn char(&self) -> char {
654 self.0.chars().next().unwrap()
655 }
656
657 pub fn as_str(&self) -> &str {
658 self.0
659 }
660}
661
662impl<'a> Deref for Char<'a> {
663 type Target = str;
664
665 fn deref(&self) -> &str {
666 self.0
667 }
668}
669
670impl Debug for Char<'_> {
671 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
672 Debug::fmt(&self.0, f)
673 }
674}
675
676impl Display for Char<'_> {
677 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
678 Display::fmt(&self.0, f)
679 }
680}
681
682/// Iterator of chars in-place
683pub struct CharsInPlace<'a>(&'a str);
684
685impl<'a> Iterator for CharsInPlace<'a> {
686 type Item = Char<'a>;
687
688 fn next(&mut self) -> Option<Self::Item> {
689 let next_char_len = self.0.chars().next()?.len_utf8();
690
691 let (this, rest) = self.0.split_at(next_char_len);
692 self.0 = rest;
693
694 Some(Char(this))
695 }
696}
697
698/// In-place character representation inside mutable str slice
699///
700/// Convert to [`Char`] using [`CharMut::as_char`].
701pub struct CharMut<'a>(&'a mut str);
702
703impl CharMut<'_> {
704 pub fn new(ch: &mut str) -> CharMut<'_> {
705 let char_len = ch
706 .chars()
707 .next()
708 .expect("Unable to create CharMut from empty string")
709 .len_utf8();
710
711 assert_eq!(
712 char_len,
713 ch.len(),
714 "CharMut can only be constructed from single character string"
715 );
716
717 CharMut(ch)
718 }
719
720 pub fn char(&self) -> char {
721 self.0.chars().next().unwrap()
722 }
723
724 pub fn as_str(&self) -> &str {
725 self.0
726 }
727
728 pub fn as_str_mut(&mut self) -> &mut str {
729 self.0
730 }
731
732 pub fn as_char(&self) -> Char<'_> {
733 Char(self.0)
734 }
735
736 pub fn is_same_size(&self, ch: char) -> bool {
737 self.char().len_utf8() == ch.len_utf8()
738 }
739
740 pub fn replace(&mut self, ch: char) -> Result<(), CharsHaveDifferentSizes> {
741 if !self.is_same_size(ch) {
742 return Err(CharsHaveDifferentSizes);
743 }
744 let mut buf: [u8; 4] = [0; 4];
745 let subslice: &[u8] = ch.encode_utf8(&mut buf).as_bytes();
746
747 // Safety: self and subslice have the same number of bytes
748 unsafe {
749 for (idx, byte) in self.as_bytes_mut().iter_mut().enumerate() {
750 *byte = subslice[idx];
751 }
752 }
753 Ok(())
754 }
755
756 /// Makes [`CharMut`] uppercase in-place.
757 ///
758 /// returns [`Err`] if uppercase variant has different size.
759 ///
760 /// ```rust
761 /// # extern crate std;
762 /// # use std::string::String;
763 ///
764 /// use string_view::StrExt;
765 ///
766 /// let text: &mut str = &mut String::from("Hello World");
767 /// text.chars_in_place_mut().for_each(|mut ch| ch.make_uppercase().unwrap());
768 ///
769 /// assert_eq!(text, "HELLO WORLD");
770 /// ```
771 pub fn make_uppercase(&mut self) -> Result<(), CharsHaveDifferentSizes> {
772 let this_char = self.char();
773 let mut upper_chars = this_char.to_uppercase();
774 let this_upper = upper_chars.next().unwrap();
775
776 if upper_chars.next().is_some() {
777 return Err(CharsHaveDifferentSizes);
778 };
779 self.replace(this_upper)
780 }
781
782 /// Makes [`CharMut`] uppercase in-place.
783 ///
784 /// returns [`Err`] if uppercase variant has different size.
785 ///
786 /// ```rust
787 /// # extern crate std;
788 /// # use std::string::String;
789 ///
790 /// use string_view::StrExt;
791 ///
792 /// let text: &mut str = &mut String::from("Hello World");
793 /// text.chars_in_place_mut().for_each(|mut ch| ch.make_lowercase().unwrap());
794 ///
795 /// assert_eq!(text, "hello world");
796 /// ```
797 pub fn make_lowercase(&mut self) -> Result<(), CharsHaveDifferentSizes> {
798 let this_char = self.char();
799 let mut lower_chars = this_char.to_lowercase();
800 let this_lower = lower_chars.next().unwrap();
801
802 if lower_chars.next().is_some() {
803 return Err(CharsHaveDifferentSizes);
804 };
805 self.replace(this_lower)
806 }
807}
808
809impl<'a> Deref for CharMut<'a> {
810 type Target = str;
811
812 fn deref(&self) -> &str {
813 self.0
814 }
815}
816
817impl<'a> DerefMut for CharMut<'a> {
818 fn deref_mut(&mut self) -> &mut Self::Target {
819 self.0
820 }
821}
822
823impl Debug for CharMut<'_> {
824 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
825 Debug::fmt(&self.0, f)
826 }
827}
828
829impl Display for CharMut<'_> {
830 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
831 Display::fmt(&self.0, f)
832 }
833}
834
835/// Mutable iterator of chars in-place
836pub struct CharsInPlaceMut<'a>(&'a mut str);
837
838impl<'a> Iterator for CharsInPlaceMut<'a> {
839 type Item = CharMut<'a>;
840
841 fn next(&mut self) -> Option<Self::Item> {
842 let next_char_len = self.0.chars().next()?.len_utf8();
843
844 let this: &mut str = core::mem::take(&mut self.0);
845 let (this, rest) = this.split_at_mut(next_char_len);
846 self.0 = rest;
847
848 Some(CharMut(this))
849 }
850}
851
852/// Common error case while working with chars in-place
853pub struct CharsHaveDifferentSizes;
854
855impl Debug for CharsHaveDifferentSizes {
856 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
857 write!(
858 f,
859 "Unable to replace character because they have different sizes.\
860 Characters have to have the same size for in-place modification."
861 )
862 }
863}
864
865impl Display for CharsHaveDifferentSizes {
866 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
867 Debug::fmt(&self, f)
868 }
869}
870
871impl Error for CharsHaveDifferentSizes {}
872
873pub trait StrExt {
874 fn view(&self) -> StringView<'_>;
875 fn view_part(&self, start_idx: usize, end_idx: usize) -> StringView<'_>;
876
877 /// Byte index of this [`Char`] start & end inside base [`str`].
878 ///
879 /// ```rust
880 /// use string_view::StrExt;
881 ///
882 /// let text = "Hello World";
883 /// let mut chars = text.chars_in_place();
884 ///
885 /// let first_char = chars.next().unwrap();
886 /// assert_eq!(first_char.as_str(), "H");
887 /// assert_eq!(text.char_idx(first_char), (0, 1));
888 ///
889 /// let second_char = chars.next().unwrap();
890 /// assert_eq!(second_char.as_str(), "e");
891 /// assert_eq!(text.char_idx(second_char), (1, 2));
892 /// ```
893 fn char_idx(&self, ch: Char) -> (usize, usize);
894
895 fn chars_in_place(&self) -> CharsInPlace<'_>;
896 fn chars_in_place_mut(&mut self) -> CharsInPlaceMut<'_>;
897
898 /// Makes str characters lowercase in-place where appropriate
899 fn make_lowercase(&mut self);
900
901 /// Makes str characters uppercase in-place where appropriate
902 fn make_uppercase(&mut self);
903}
904
905impl StrExt for str {
906 fn view(&self) -> StringView<'_> {
907 StringView::new(self)
908 }
909 fn view_part(&self, start_idx: usize, end_idx: usize) -> StringView<'_> {
910 StringView::new_part(self, start_idx, end_idx)
911 }
912 fn chars_in_place(&self) -> CharsInPlace<'_> {
913 CharsInPlace(self)
914 }
915 fn chars_in_place_mut(&mut self) -> CharsInPlaceMut<'_> {
916 CharsInPlaceMut(self)
917 }
918
919 /// Byte index of this [`Char`] start & end inside base [`str`].
920 ///
921 /// ```rust
922 /// use string_view::StrExt;
923 ///
924 /// let text = "Hello World";
925 /// let mut chars = text.chars_in_place();
926 ///
927 /// let first_char = chars.next().unwrap();
928 /// assert_eq!(first_char.as_str(), "H");
929 /// assert_eq!(text.char_idx(first_char), (0, 1));
930 ///
931 /// let second_char = chars.next().unwrap();
932 /// assert_eq!(second_char.as_str(), "e");
933 /// assert_eq!(text.char_idx(second_char), (1, 2));
934 /// ```
935 fn char_idx(&self, ch: Char) -> (usize, usize) {
936 let str_start = self.as_ptr() as usize;
937 let str_end = str_start + self.len();
938 let ch_start = ch.as_ptr() as usize;
939 let ch_end = ch_start + ch.len();
940
941 let range = str_start..str_end;
942 assert!(
943 range.contains(&ch_start),
944 "Char has to be inside this string to get its index"
945 );
946 assert!(
947 range.contains(&ch_end),
948 "Char has to be inside this string to get its index"
949 );
950
951 (ch_start - str_start, ch_end - str_start)
952 }
953
954 /// Makes [`str`] characters lowercase in-place where appropriate
955 ///
956 /// ```rust
957 /// # extern crate std;
958 /// # use std::string::String;
959 ///
960 /// use string_view::StrExt;
961 ///
962 /// let text: &mut str = &mut String::from("HELLO World");
963 /// text.make_lowercase();
964 /// assert_eq!(text, "hello world");
965 ///
966 /// let text: &mut str = &mut String::from("ПРИВЕТ Мир");
967 /// text.make_lowercase();
968 /// assert_eq!(text, "привет мир");
969 /// ```
970 fn make_lowercase(&mut self) {
971 self.chars_in_place_mut().for_each(|mut ch| {
972 let _ = ch.make_lowercase();
973 });
974 }
975
976 /// Makes [`str`] characters uppercase in-place where appropriate
977 ///
978 /// ```rust
979 /// # extern crate std;
980 /// # use std::string::String;
981 ///
982 /// use string_view::StrExt;
983 ///
984 /// let text: &mut str = &mut String::from("Hello World");
985 /// text.make_uppercase();
986 /// assert_eq!(text, "HELLO WORLD");
987 ///
988 /// let text: &mut str = &mut String::from("Привет мир");
989 /// text.make_uppercase();
990 /// assert_eq!(text, "ПРИВЕТ МИР");
991 /// ```
992 fn make_uppercase(&mut self) {
993 self.chars_in_place_mut().for_each(|mut ch| {
994 let _ = ch.make_uppercase();
995 });
996 }
997}
998
999#[cfg(test)]
1000mod test;