core_extensions/strings/mod.rs
1//! Extension trait for string types.
2
3use std_::borrow::Borrow;
4use std_::fmt;
5use std_::str::CharIndices;
6
7#[cfg(feature = "alloc")]
8use alloc::string::String;
9
10mod iterators;
11
12pub use self::iterators::{CharIndicesFrom, KeyStr, RSplitWhile, SplitWhile};
13
14/// Extension trait for strings (any type that borrows as `str`).
15pub trait StringExt: Borrow<str> {
16 /// Returns the previous character boundary, stopping at 0.
17 ///
18 /// if `index > self.len()`, returns `self.len()`.
19 ///
20 /// # Example
21 ///
22 /// ```
23 /// use core_extensions::StringExt;
24 ///
25 /// let word = "foo速度惊人";
26 ///
27 /// assert_eq!(word.previous_char_boundary(0), 0);
28 /// assert_eq!(word.previous_char_boundary(1), 0);
29 /// assert_eq!(word.previous_char_boundary(2), 1);
30 /// assert_eq!(word.previous_char_boundary(3), 2);
31 ///
32 /// // This input index is inside of '速'
33 /// assert_eq!(word.previous_char_boundary(4), 3);
34 /// assert_eq!(word.previous_char_boundary(5), 3);
35 /// assert_eq!(word.previous_char_boundary(6), 3);
36 ///
37 /// // This input index is inside of '度'
38 /// assert_eq!(word.previous_char_boundary(7), 6);
39 ///
40 /// assert_eq!(word.previous_char_boundary(10000), word.len());
41 ///
42 /// ```
43 ///
44 fn previous_char_boundary(&self, mut index: usize) -> usize {
45 let this = self.borrow();
46 if index > this.len() {
47 return this.len();
48 }
49 index = index.saturating_sub(1);
50 while !this.is_char_boundary(index) {
51 index -= 1;
52 }
53 index
54 }
55 /// Returns the next character boundary.
56 ///
57 /// If `index > self.len()`, returns `self.len()`
58 ///
59 /// # Example
60 ///
61 /// ```
62 /// use core_extensions::StringExt;
63 ///
64 /// let word = "foo誰もが";
65 ///
66 /// assert_eq!(word.next_char_boundary(0), 1);
67 /// assert_eq!(word.next_char_boundary(1), 2);
68 /// assert_eq!(word.next_char_boundary(2), 3);
69 ///
70 /// // This input index is inside of '誰'
71 /// assert_eq!(word.next_char_boundary(3), 6);
72 /// assert_eq!(word.next_char_boundary(4), 6);
73 /// assert_eq!(word.next_char_boundary(5), 6);
74 ///
75 /// // This input index is inside of 'も'
76 /// assert_eq!(word.next_char_boundary(6), 9);
77 ///
78 /// assert_eq!(word.next_char_boundary(10000), word.len());
79 ///
80 /// ```
81 ///
82 fn next_char_boundary(&self, mut index: usize) -> usize {
83 let this = self.borrow();
84 if index >= this.len() {
85 return this.len();
86 }
87 index += 1;
88 while !this.is_char_boundary(index) {
89 index += 1;
90 }
91 index
92 }
93 /// Returns the closest characted boundary left of `index`(including `index`).
94 ///
95 /// if `index > self.len()`, returns `self.len()`
96 ///
97 /// # Example
98 ///
99 /// ```
100 /// use core_extensions::StringExt;
101 ///
102 /// let word = "barЯзык";
103 ///
104 /// assert_eq!(word.left_char_boundary(0), 0);
105 /// assert_eq!(word.left_char_boundary(1), 1);
106 /// assert_eq!(word.left_char_boundary(2), 2);
107 ///
108 /// // The input index is inside of 'Я'
109 /// assert_eq!(word.left_char_boundary(3), 3);
110 /// assert_eq!(word.left_char_boundary(4), 3);
111 ///
112 /// // The input index is inside of 'з'
113 /// assert_eq!(word.left_char_boundary(5), 5);
114 /// assert_eq!(word.left_char_boundary(6), 5);
115 ///
116 /// assert_eq!(word.left_char_boundary(10000), word.len());
117 ///
118 /// ```
119 fn left_char_boundary(&self, mut index: usize) -> usize {
120 let this = self.borrow();
121 if index > this.len() {
122 return this.len();
123 }
124 while !this.is_char_boundary(index) {
125 index -= 1;
126 }
127 index
128 }
129 /// Returns the closest characted boundary right of `index`(including `index`).
130 ///
131 /// if `index > self.len()`, returns `self.len()`
132 ///
133 /// # Example
134 ///
135 /// ```
136 /// use core_extensions::StringExt;
137 ///
138 /// let word = "rápido";
139 ///
140 /// assert_eq!(word.right_char_boundary(0),0);
141 ///
142 /// // The input index is inside of 'á'
143 /// assert_eq!(word.right_char_boundary(1), 1);
144 /// assert_eq!(word.right_char_boundary(2), 3);
145 ///
146 /// assert_eq!(word.right_char_boundary(3), 3);
147 /// assert_eq!(word.right_char_boundary(4), 4);
148 ///
149 /// assert_eq!(word.right_char_boundary(10000), word.len());
150 ///
151 /// ```
152 fn right_char_boundary(&self, mut index: usize) -> usize {
153 let this = self.borrow();
154 if index >= this.len() {
155 return this.len();
156 }
157 while !this.is_char_boundary(index) {
158 index += 1;
159 }
160 index
161 }
162 /// Returns an iterator over substrings whose characters were mapped to
163 /// the same key by `mapper`.
164 ///
165 /// The returned type implements
166 /// `DoubleEndedIterator<Item =`[KeyStr](./struct.KeyStr.html)`<T>>`.
167 ///
168 /// # Example
169 /// ```
170 /// use core_extensions::strings::{StringExt, KeyStr};
171 ///
172 /// assert_eq!(
173 /// "Hello, world!".split_while(|c| c.is_alphanumeric()).collect::<Vec<_>>(),
174 /// vec![
175 /// KeyStr{key: true, str: "Hello"},
176 /// KeyStr{key: false, str: ", "},
177 /// KeyStr{key: true, str: "world"},
178 /// KeyStr{key: false, str: "!"},
179 /// ]
180 /// );
181 /// assert_eq!(
182 /// "aaabbbccc".split_while(|c| c).collect::<Vec<_>>(),
183 /// vec![
184 /// KeyStr{key: 'a', str: "aaa"},
185 /// KeyStr{key: 'b', str: "bbb"},
186 /// KeyStr{key: 'c', str: "ccc"},
187 /// ]
188 /// );
189 ///
190 /// ```
191 fn split_while<'a, P, T: Eq + Clone>(&'a self, mut mapper: P) -> SplitWhile<'a, P, T>
192 where
193 P: FnMut(char) -> T,
194 {
195 let this = self.borrow();
196 let mut c = this.chars();
197 SplitWhile {
198 last_left: mapper(c.next().unwrap_or(' ')),
199 last_right: mapper(c.next_back().unwrap_or(' ')),
200 mapper,
201 s: this,
202 }
203 }
204 /// A variation of [`split_while`](#method.split_while) that iterates
205 /// from the right(the order of substrings is reversed).
206 ///
207 /// The returned type implements
208 /// `DoubleEndedIterator<Item =`[KeyStr](./struct.KeyStr.html)`<T>>`.
209 ///
210 /// # Example
211 /// ```
212 /// use core_extensions::strings::{StringExt, KeyStr};
213 ///
214 /// assert_eq!(
215 /// "Hello, world!".rsplit_while(|c| c.is_alphanumeric()).collect::<Vec<_>>(),
216 /// vec![
217 /// KeyStr{key: false, str: "!"},
218 /// KeyStr{key: true, str: "world"},
219 /// KeyStr{key: false, str: ", "},
220 /// KeyStr{key: true, str: "Hello"},
221 /// ]
222 /// );
223 /// assert_eq!(
224 /// "aaabbbccc".rsplit_while(|c| c).collect::<Vec<_>>(),
225 /// vec![
226 /// KeyStr{key: 'c', str: "ccc"},
227 /// KeyStr{key: 'b', str: "bbb"},
228 /// KeyStr{key: 'a', str: "aaa"},
229 /// ]
230 /// );
231 ///
232 /// ```
233 fn rsplit_while<'a, P, T: Eq + Clone>(&'a self, mut mapper: P) -> RSplitWhile<'a, P, T>
234 where
235 P: FnMut(char) -> T,
236 {
237 let this = self.borrow();
238 let mut c = this.chars();
239 RSplitWhile {
240 last_left: mapper(c.next().unwrap_or(' ')),
241 last_right: mapper(c.next_back().unwrap_or(' ')),
242 mapper,
243 s: this,
244 }
245 }
246 /// The byte index of the `nth` character
247 ///
248 /// If there is no `nth` character, this returns `None`.
249 ///
250 /// This operation takes `O(n)` time, where `n` is `self.len()`.
251 ///
252 /// # Example
253 /// ```
254 /// use core_extensions::StringExt;
255 ///
256 /// let word = "fooпозволяющий";
257 ///
258 /// assert_eq!(word.get_nth_char_index(0), Some(0));
259 /// assert_eq!(word.get_nth_char_index(1), Some(1));
260 /// assert_eq!(word.get_nth_char_index(2), Some(2));
261 /// assert_eq!(word.get_nth_char_index(3), Some(3));
262 /// assert_eq!(word.get_nth_char_index(4), Some(5));
263 /// assert_eq!(word.get_nth_char_index(5), Some(7));
264 ///
265 /// assert_eq!(word.get_nth_char_index(13), Some(23));
266 /// assert_eq!(word.get_nth_char_index(14), None);
267 /// ```
268 fn get_nth_char_index(&self, nth: usize) -> Option<usize> {
269 self.borrow().char_indices().nth(nth).map(|(i, _)| i)
270 }
271
272 /// The byte index of the `nth` character
273 ///
274 /// If there is no `nth` character, this returns `self.len()`.
275 ///
276 /// This operation takes `O(n)` time, where `n` is `self.len()`.
277 ///
278 /// # Example
279 /// ```
280 /// use core_extensions::StringExt;
281 ///
282 /// let word = "fooпозволяющий";
283 ///
284 /// assert_eq!(word.nth_char_index(0), 0);
285 /// assert_eq!(word.nth_char_index(1), 1);
286 /// assert_eq!(word.nth_char_index(2), 2);
287 /// assert_eq!(word.nth_char_index(3), 3);
288 /// assert_eq!(word.nth_char_index(4), 5);
289 /// assert_eq!(word.nth_char_index(5), 7);
290 ///
291 /// assert_eq!(word.nth_char_index(13), 23);
292 /// assert_eq!(word.nth_char_index(14), word.len());
293 /// ```
294 fn nth_char_index(&self, nth: usize) -> usize {
295 let this = self.borrow();
296 this.char_indices()
297 .nth(nth)
298 .map_or(this.len(), |(i, _)| i)
299 }
300
301 /// Returns the `nth` character in the str.
302 ///
303 /// This operation takes `O(n)` time, where `n` is `self.len()`.
304 ///
305 /// # Example
306 /// ```
307 /// use core_extensions::StringExt;
308 ///
309 /// let word = "débuter";
310 ///
311 /// assert_eq!(word.nth_char(0), Some('d'));
312 /// assert_eq!(word.nth_char(1), Some('é'));
313 /// assert_eq!(word.nth_char(2), Some('b'));
314 /// assert_eq!(word.nth_char(3), Some('u'));
315 /// assert_eq!(word.nth_char(4), Some('t'));
316 /// assert_eq!(word.nth_char(5), Some('e'));
317 /// assert_eq!(word.nth_char(6), Some('r'));
318 /// assert_eq!(word.nth_char(7), None);
319 /// ```
320 fn nth_char(&self, nth: usize) -> Option<char> {
321 self.borrow().chars().nth(nth)
322 }
323
324 /// Returns a string containing the first `n` chars.
325 ///
326 /// if `n` is greater than the amount of chars, this returns the entire string.
327 ///
328 /// # Example
329 /// ```
330 /// use core_extensions::StringExt;
331 ///
332 /// let word = "сине";
333 ///
334 /// assert_eq!(word.first_chars(0),"");
335 /// assert_eq!(word.first_chars(1),"с");
336 /// assert_eq!(word.first_chars(2),"си");
337 /// assert_eq!(word.first_chars(3),"син");
338 /// assert_eq!(word.first_chars(4),"сине");
339 /// assert_eq!(word.first_chars(5),"сине");
340 /// ```
341 fn first_chars(&self, n: usize) -> &str {
342 let this = self.borrow();
343 &this[..this.nth_char_index(n)]
344 }
345 /// Returns a string containing the last `n` chars
346 ///
347 /// if `n` is greater than the amount of chars, this returns the entire string.
348 ///
349 /// # Example
350 /// ```
351 /// use core_extensions::StringExt;
352 ///
353 /// let word = "сине";
354 ///
355 /// assert_eq!(word.last_chars(0),"");
356 /// assert_eq!(word.last_chars(1),"е");
357 /// assert_eq!(word.last_chars(2),"не");
358 /// assert_eq!(word.last_chars(3),"ине");
359 /// assert_eq!(word.last_chars(4),"сине");
360 /// assert_eq!(word.last_chars(5),"сине");
361 /// ```
362 fn last_chars(&self, n: usize) -> &str {
363 let this = self.borrow();
364 // keeps the property of this being a slice of the same region of memory
365 if n == 0 {
366 return &this[..0];
367 }
368 let index = this
369 .char_indices()
370 .rev()
371 .nth(n - 1)
372 .map_or(0, |(i, _)| i);
373 &this[index..]
374 }
375 /// Returns the string from the `n`th character
376 ///
377 /// if `n` is greater than the amount of chars, this returns an empty string.
378 ///
379 /// # Example
380 ///
381 /// ```
382 /// use core_extensions::StringExt;
383 ///
384 /// let word = "υιός";
385 ///
386 /// assert_eq!(word.from_nth_char(0), "υιός");
387 /// assert_eq!(word.from_nth_char(1), "ιός");
388 /// assert_eq!(word.from_nth_char(2), "ός");
389 /// assert_eq!(word.from_nth_char(3), "ς");
390 /// assert_eq!(word.from_nth_char(4), "");
391 /// assert_eq!(word.from_nth_char(5), "");
392 /// assert_eq!(word.from_nth_char(6), "");
393 /// ```
394 #[allow(clippy::wrong_self_convention)]
395 fn from_nth_char(&self, n: usize) -> &str {
396 let this = self.borrow();
397 &this[this.nth_char_index(n)..]
398 }
399
400 /// Returns the length of the string in utf16
401 ///
402 /// # Warning
403 ///
404 /// This is calculated every time the function is called.
405 ///
406 /// # Example
407 /// ```
408 /// use core_extensions::StringExt;
409 ///
410 /// assert_eq!("foo".calc_len_utf16(), 3);
411 /// assert_eq!("υιός".calc_len_utf16(), 4);
412 /// assert_eq!("👪".calc_len_utf16(), 2);
413 ///
414 /// ```
415 fn calc_len_utf16(&self) -> usize {
416 self.borrow()
417 .chars()
418 .fold(0, |accum, c| accum + c.len_utf16())
419 }
420 /// Returns the character at the `at_byte` index inside of the string,
421 /// returning `None` if the index is outside the string.
422 ///
423 /// If the index is between char boundaries,
424 /// this returns the char at the previous char boundary.
425 ///
426 /// If `self.len() <= index`, this returns none.
427 ///
428 /// # Example
429 /// ```
430 /// use core_extensions::StringExt;
431 ///
432 /// let word = "foo 効 门";
433 ///
434 /// assert_eq!(word.get_char_at(0), Some('f'));
435 /// assert_eq!(word.get_char_at(1), Some('o'));
436 /// assert_eq!(word.get_char_at(2), Some('o'));
437 /// assert_eq!(word.get_char_at(3), Some(' '));
438 /// assert_eq!(word.get_char_at(4), Some('効'));
439 /// assert_eq!(word.get_char_at(5), Some('効'));
440 /// assert_eq!(word.get_char_at(6), Some('効'));
441 /// assert_eq!(word.get_char_at(7), Some(' '));
442 /// assert_eq!(word.get_char_at(8), Some('门'));
443 /// assert_eq!(word.get_char_at(9), Some('门'));
444 /// assert_eq!(word.get_char_at(10), Some('门'));
445 /// assert_eq!(word.get_char_at(11), None);
446 ///
447 /// ```
448 ///
449 fn get_char_at(&self, at_byte: usize) -> Option<char> {
450 let this = self.borrow();
451 if at_byte >= this.len() {
452 return None;
453 }
454 let start = this.left_char_boundary(at_byte);
455 this[start..].chars().nth(0)
456 }
457
458 /// Returns an iterator over (index,char) pairs up to
459 /// (but not including) the char at the `to` byte.
460 ///
461 /// IF the index is between char boundaries,
462 /// it doesn't include the char that index is inside of.
463 ///
464 /// if `index > self.len()`, returns an iterator over the entire string.
465 ///
466 /// # Example
467 /// ```
468 /// use core_extensions::StringExt;
469 ///
470 /// let word = "foo 効 ";
471 ///
472 /// assert_eq!(word.char_indices_to(0).collect::<Vec<_>>(), vec![]);
473 /// assert_eq!(word.char_indices_to(1).collect::<Vec<_>>(), vec![(0, 'f')]);
474 ///
475 /// let expected_a = vec![(0, 'f'), (1, 'o'), (2, 'o'), (3, ' ')];
476 /// assert_eq!(word.char_indices_to(4).collect::<Vec<_>>(), expected_a);
477 /// assert_eq!(word.char_indices_to(5).collect::<Vec<_>>(), expected_a);
478 /// assert_eq!(word.char_indices_to(6).collect::<Vec<_>>(), expected_a);
479 ///
480 /// let expected_b = vec![(0, 'f'), (1, 'o'), (2, 'o'), (3, ' '), (4, '効')];
481 /// assert_eq!(word.char_indices_to(7).collect::<Vec<_>>(), expected_b);
482 ///
483 /// let expected_c = vec![(0, 'f'), (1, 'o'), (2, 'o'), (3, ' '), (4, '効'), (7, ' ')];
484 /// assert_eq!(word.char_indices_to(8).collect::<Vec<_>>(), expected_c);
485 /// assert_eq!(word.char_indices_to(100).collect::<Vec<_>>(), expected_c);
486 /// ```
487 ///
488 fn char_indices_to(&self, to: usize) -> CharIndices<'_> {
489 let this = self.borrow();
490 let to = this.left_char_boundary(to);
491 this[..to].char_indices()
492 }
493
494 /// Returns an iterator over (index, char) pairs, starting from the `from` byte.
495 ///
496 /// If the index is between char boundaries,
497 /// this starts from the char at the previous char boundary.
498 ///
499 /// if `index > self.len()`, returns an empty iterator.
500 ///
501 /// # Example
502 /// ```
503 /// use core_extensions::StringExt;
504 ///
505 /// let word = "foo 効 ";
506 ///
507 /// let expected_a = vec![(0, 'f'), (1, 'o'), (2, 'o'), (3, ' '), (4, '効'), (7, ' ')];
508 /// assert_eq!(word.char_indices_from(0).collect::<Vec<_>>(), expected_a);
509 ///
510 /// let expected_b = vec![(1, 'o'), (2, 'o'), (3, ' '), (4, '効'), (7, ' ')];
511 /// assert_eq!(word.char_indices_from(1).collect::<Vec<_>>(), expected_b);
512 ///
513 /// let expected_c = vec![(3, ' '), (4, '効'), (7, ' ')];
514 /// assert_eq!(word.char_indices_from(3).collect::<Vec<_>>(), expected_c);
515 ///
516 /// let expected_c = vec![(4, '効'), (7, ' ')];
517 /// assert_eq!(word.char_indices_from(4).collect::<Vec<_>>(), expected_c);
518 /// assert_eq!(word.char_indices_from(5).collect::<Vec<_>>(), expected_c);
519 /// assert_eq!(word.char_indices_from(6).collect::<Vec<_>>(), expected_c);
520 ///
521 /// assert_eq!(word.char_indices_from(7).collect::<Vec<_>>(), vec![(7, ' ')]);
522 ///
523 /// assert_eq!(word.char_indices_from(8).collect::<Vec<_>>(), vec![]);
524 ///
525 /// assert_eq!(word.char_indices_from(9).collect::<Vec<_>>(), vec![]);
526 ///
527 /// ```
528 fn char_indices_from(&self, from: usize) -> CharIndicesFrom<'_> {
529 let this = self.borrow();
530 let from = this.left_char_boundary(from);
531 CharIndicesFrom {
532 offset: from,
533 iter: this[from..].char_indices(),
534 }
535 }
536
537 /// Pads the string on the left with `how_much` additional spaces.
538 ///
539 /// # Example
540 ///
541 /// ```
542 /// use core_extensions::StringExt;
543 ///
544 /// assert_eq!(
545 /// "what\n the\n hall".left_pad(4),
546 /// " what\n the\n hall"
547 /// );
548 /// ```
549 ///
550 #[cfg(feature = "alloc")]
551 #[cfg_attr(feature = "docsrs", doc(cfg(feature = "alloc")))]
552 fn left_pad(&self, how_much: usize) -> String {
553 use alloc::string::ToString;
554 self.left_padder(how_much).to_string()
555 }
556 /// Returns a value that pads the string on the left with `how_much` additional
557 /// spaces in its `Display` impl.
558 ///
559 /// Use this to avoid allocating an extra string.
560 ///
561 /// # Example
562 ///
563 #[cfg_attr(not(feature = "alloc"), doc = " ```ignore")]
564 #[cfg_attr(feature = "alloc", doc = " ```rust")]
565 /// use core_extensions::StringExt;
566 ///
567 /// assert_eq!(
568 /// "what\n the\n hall".left_pad(4).to_string(),
569 /// " what\n the\n hall"
570 /// );
571 /// ```
572 ///
573 fn left_padder<'a>(&'a self, how_much: usize) -> LeftPadder<'a> {
574 LeftPadder::new(self.borrow(), how_much)
575 }
576 /// The indentation of the first line.
577 ///
578 /// This considers lines that only contains whitespace to have as
579 /// much indentation as they're long.
580 ///
581 /// # Example
582 ///
583 /// ```
584 /// use core_extensions::StringExt;
585 ///
586 /// assert_eq!("".line_indentation(), 0);
587 /// assert_eq!(" ".line_indentation(), 4);
588 /// assert_eq!(" \n word".line_indentation(), 4);
589 /// assert_eq!(" \nword".line_indentation(), 4);
590 ///
591 /// ```
592 ///
593 #[cfg(feature = "alloc")]
594 fn line_indentation(&self) -> usize {
595 let this = self.borrow().lines().next().unwrap_or("");
596 this.len() - this.trim_start().len()
597 }
598
599 /// The minimum indentation of the string, ignoring lines that only contain whitespace.
600 ///
601 /// # Example
602 ///
603 /// ```
604 /// use core_extensions::StringExt;
605 ///
606 /// assert_eq!("".min_indentation(), 0);
607 /// assert_eq!(" ".min_indentation(), 0);
608 /// assert_eq!(" \nf".min_indentation(), 0);
609 /// assert_eq!(" \n word".min_indentation(), 6);
610 /// assert_eq!(" \n word".min_indentation(), 1);
611 /// assert_eq!(" \n\nword".min_indentation(), 0);
612 ///
613 /// ```
614 ///
615 #[cfg(feature = "alloc")]
616 fn min_indentation(&self) -> usize {
617 self.borrow()
618 .lines()
619 .filter(|l| !l.trim_start().is_empty())
620 .map(|v| v.line_indentation())
621 .min()
622 .unwrap_or(0)
623 }
624 /// The maximum indentation of the string, ignoring lines that only contain whitespace.
625 ///
626 /// # Example
627 ///
628 /// ```
629 /// use core_extensions::StringExt;
630 ///
631 /// assert_eq!("".max_indentation(), 0);
632 /// assert_eq!(" ".max_indentation(), 0);
633 /// assert_eq!(" \n word".max_indentation(), 6);
634 /// assert_eq!(" \n word".max_indentation(), 2);
635 ///
636 /// ```
637 ///
638 #[cfg(feature = "alloc")]
639 fn max_indentation(&self) -> usize {
640 self.borrow()
641 .lines()
642 .filter(|l| !l.trim_start().is_empty())
643 .map(|v| v.line_indentation())
644 .max()
645 .unwrap_or(0)
646 }
647}
648
649impl<T: ?Sized> StringExt for T where T: Borrow<str> {}
650
651//----------------------------------------------------------------------------------------
652
653/// Add padding to a string in its `Display` impl.
654///
655/// # Example
656///
657/// ```rust
658/// use core_extensions::strings::LeftPadder;
659///
660/// assert_eq!(LeftPadder::new("foo\n bar", 0).to_string(), "foo\n bar");
661/// assert_eq!(LeftPadder::new("foo\n bar", 1).to_string(), " foo\n bar");
662/// assert_eq!(LeftPadder::new("foo\n bar", 2).to_string(), " foo\n bar");
663/// assert_eq!(LeftPadder::new("foo\n bar", 4).to_string(), " foo\n bar");
664///
665///
666/// ```
667#[derive(Clone, Copy, Debug)]
668pub struct LeftPadder<'a> {
669 string: &'a str,
670 padding: usize,
671}
672
673impl<'a> LeftPadder<'a> {
674 /// Constructs a LeftPadder
675 pub fn new(string: &'a str, padding: usize) -> Self {
676 Self { string, padding }
677 }
678}
679
680impl<'a> fmt::Display for LeftPadder<'a> {
681 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
682 let mut first = true;
683 use std_::fmt::Write;
684 for line in self.string.lines() {
685 if !first {
686 f.write_char('\n')?;
687 }
688 const SPACES: &str = " ";
689
690 let has_non_whitespace = line.contains(|c: char| !c.is_whitespace());
691 let mut pad = if has_non_whitespace { self.padding } else { 0 };
692
693 while let Some(next) = pad.checked_sub(SPACES.len()) {
694 f.write_str(SPACES)?;
695 pad = next;
696 }
697 f.write_str(&SPACES[..pad])?;
698
699 fmt::Display::fmt(line, f)?;
700 first = false;
701 }
702 Ok(())
703 }
704}
705
706//---------------------------------------------------------------------------------------
707
708#[cfg(test)]
709mod tests {
710 use super::*;
711
712 #[test]
713 #[cfg(feature = "alloc")]
714 fn test_left_pad() {
715 let s = "what\n the\n hall";
716 assert_eq!(s.left_pad(0), s);
717
718 assert_eq!(
719 "what\n the\n hall".left_pad(4),
720 " what\n the\n hall"
721 );
722
723 assert_eq!("\n\nfoo".left_pad(4), "\n\n foo");
724 }
725
726 #[test]
727 fn test_right_char_boundary() {
728 let word = "niño";
729 assert_eq!(word.right_char_boundary(0), 0);
730 assert_eq!(word.right_char_boundary(1), 1);
731 assert_eq!(word.right_char_boundary(2), 2);
732 // This index is inside of 'ñ'
733 assert_eq!(word.right_char_boundary(3), 4);
734 assert_eq!(word.right_char_boundary(4), 4);
735 assert_eq!(word.right_char_boundary(5), 5);
736 assert_eq!(word.right_char_boundary(6), 5);
737 assert_eq!(word.right_char_boundary(7), 5);
738 }
739
740 #[test]
741 #[cfg(feature = "alloc")]
742 fn test_char_indices_to() {
743 let word = "niño";
744 assert_eq!(
745 word.char_indices_to(0).map(|(_, c)| c).collect::<String>(),
746 ""
747 );
748 assert_eq!(
749 word.char_indices_to(1).map(|(_, c)| c).collect::<String>(),
750 "n"
751 );
752 assert_eq!(
753 word.char_indices_to(2).map(|(_, c)| c).collect::<String>(),
754 "ni"
755 );
756 assert_eq!(
757 word.char_indices_to(3).map(|(_, c)| c).collect::<String>(),
758 "ni"
759 );
760 assert_eq!(
761 word.char_indices_to(4).map(|(_, c)| c).collect::<String>(),
762 "niñ"
763 );
764 assert_eq!(
765 word.char_indices_to(5).map(|(_, c)| c).collect::<String>(),
766 "niño"
767 );
768 assert_eq!(
769 word.char_indices_to(6).map(|(_, c)| c).collect::<String>(),
770 "niño"
771 );
772 assert_eq!(
773 word.char_indices_to(7).map(|(_, c)| c).collect::<String>(),
774 "niño"
775 );
776 }
777}