1use std::slice::Split;
2
3pub fn unindent(s: &str) -> String {
4 let preserve_empty_first_line: bool = false;
5 do_unindent(s, preserve_empty_first_line)
6}
7
8// Compute the maximal number of spaces that can be removed from every line, and
9// remove them.
10pub fn unindent_bytes(s: &[u8]) -> Vec<u8> {
11 let preserve_empty_first_line: bool = false;
12 do_unindent_bytes(s, preserve_empty_first_line)
13}
14
15pub(crate) fn do_unindent(s: &str, preserve_empty_first_line: bool) -> String {
16 let bytes: &[u8] = s.as_bytes();
17 let unindented: Vec = do_unindent_bytes(s:bytes, preserve_empty_first_line);
18 String::from_utf8(vec:unindented).unwrap()
19}
20
21fn do_unindent_bytes(s: &[u8], preserve_empty_first_line: bool) -> Vec<u8> {
22 // Document may start either on the same line as opening quote or
23 // on the next line
24 let ignore_first_line =
25 !preserve_empty_first_line && (s.starts_with(b"\n") || s.starts_with(b"\r\n"));
26
27 // Largest number of spaces that can be removed from every
28 // non-whitespace-only line after the first
29 let spaces = s
30 .lines()
31 .skip(1)
32 .filter_map(count_spaces)
33 .min()
34 .unwrap_or(0);
35
36 let mut result = Vec::with_capacity(s.len());
37 for (i, line) in s.lines().enumerate() {
38 if i > 1 || (i == 1 && !ignore_first_line) {
39 result.push(b'\n');
40 }
41 if i == 0 {
42 // Do not un-indent anything on same line as opening quote
43 result.extend_from_slice(line);
44 } else if line.len() > spaces {
45 // Whitespace-only lines may have fewer than the number of spaces
46 // being removed
47 result.extend_from_slice(&line[spaces..]);
48 }
49 }
50 result
51}
52
53pub trait Unindent {
54 type Output;
55
56 fn unindent(&self) -> Self::Output;
57}
58
59impl Unindent for str {
60 type Output = String;
61
62 fn unindent(&self) -> Self::Output {
63 unindent(self)
64 }
65}
66
67impl Unindent for String {
68 type Output = String;
69
70 fn unindent(&self) -> Self::Output {
71 unindent(self)
72 }
73}
74
75impl Unindent for [u8] {
76 type Output = Vec<u8>;
77
78 fn unindent(&self) -> Self::Output {
79 unindent_bytes(self)
80 }
81}
82
83impl<'a, T: ?Sized + Unindent> Unindent for &'a T {
84 type Output = T::Output;
85
86 fn unindent(&self) -> Self::Output {
87 (**self).unindent()
88 }
89}
90
91// Number of leading spaces in the line, or None if the line is entirely spaces.
92fn count_spaces(line: &[u8]) -> Option<usize> {
93 for (i: usize, ch: &u8) in line.iter().enumerate() {
94 if *ch != b' ' && *ch != b'\t' {
95 return Some(i);
96 }
97 }
98 None
99}
100
101// Based on core::str::StrExt.
102trait BytesExt {
103 fn lines(&self) -> Split<u8, fn(&u8) -> bool>;
104}
105
106impl BytesExt for [u8] {
107 fn lines(&self) -> Split<u8, fn(&u8) -> bool> {
108 fn is_newline(b: &u8) -> bool {
109 *b == b'\n'
110 }
111 let bytestring: &[u8] = if self.starts_with(needle:b"\r\n") {
112 &self[1..]
113 } else {
114 self
115 };
116 bytestring.split(pred:is_newline as fn(&u8) -> bool)
117 }
118}
119