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;