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}