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