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;