string_view/
lib.rs

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