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}