/rust/registry/src/index.crates.io-6f17d22bba15001f/textwrap-0.11.0/src/indentation.rs
Line | Count | Source (jump to first uncovered line) |
1 | | //! Functions related to adding and removing indentation from lines of |
2 | | //! text. |
3 | | //! |
4 | | //! The functions here can be used to uniformly indent or dedent |
5 | | //! (unindent) word wrapped lines of text. |
6 | | |
7 | | /// Add prefix to each non-empty line. |
8 | | /// |
9 | | /// ``` |
10 | | /// use textwrap::indent; |
11 | | /// |
12 | | /// assert_eq!(indent(" |
13 | | /// Foo |
14 | | /// Bar |
15 | | /// ", " "), " |
16 | | /// Foo |
17 | | /// Bar |
18 | | /// "); |
19 | | /// ``` |
20 | | /// |
21 | | /// Empty lines (lines consisting only of whitespace) are not indented |
22 | | /// and the whitespace is replaced by a single newline (`\n`): |
23 | | /// |
24 | | /// ``` |
25 | | /// use textwrap::indent; |
26 | | /// |
27 | | /// assert_eq!(indent(" |
28 | | /// Foo |
29 | | /// |
30 | | /// Bar |
31 | | /// \t |
32 | | /// Baz |
33 | | /// ", "->"), " |
34 | | /// ->Foo |
35 | | /// |
36 | | /// ->Bar |
37 | | /// |
38 | | /// ->Baz |
39 | | /// "); |
40 | | /// ``` |
41 | | /// |
42 | | /// Leading and trailing whitespace on non-empty lines is kept |
43 | | /// unchanged: |
44 | | /// |
45 | | /// ``` |
46 | | /// use textwrap::indent; |
47 | | /// |
48 | | /// assert_eq!(indent(" \t Foo ", "->"), "-> \t Foo \n"); |
49 | | /// ``` |
50 | 0 | pub fn indent(s: &str, prefix: &str) -> String { |
51 | 0 | let mut result = String::new(); |
52 | 0 | for line in s.lines() { |
53 | 0 | if line.chars().any(|c| !c.is_whitespace()) { |
54 | 0 | result.push_str(prefix); |
55 | 0 | result.push_str(line); |
56 | 0 | } |
57 | 0 | result.push('\n'); |
58 | | } |
59 | 0 | result |
60 | 0 | } |
61 | | |
62 | | /// Removes common leading whitespace from each line. |
63 | | /// |
64 | | /// This function will look at each non-empty line and determine the |
65 | | /// maximum amount of whitespace that can be removed from all lines: |
66 | | /// |
67 | | /// ``` |
68 | | /// use textwrap::dedent; |
69 | | /// |
70 | | /// assert_eq!(dedent(" |
71 | | /// 1st line |
72 | | /// 2nd line |
73 | | /// 3rd line |
74 | | /// "), " |
75 | | /// 1st line |
76 | | /// 2nd line |
77 | | /// 3rd line |
78 | | /// "); |
79 | | /// ``` |
80 | 81 | pub fn dedent(s: &str) -> String { |
81 | 81 | let mut prefix = ""; |
82 | 81 | let mut lines = s.lines(); |
83 | | |
84 | | // We first search for a non-empty line to find a prefix. |
85 | 81 | for line in &mut lines { |
86 | 81 | let mut whitespace_idx = line.len(); |
87 | 162 | for (idx, ch) in line.char_indices() { |
88 | 162 | if !ch.is_whitespace() { |
89 | 81 | whitespace_idx = idx; |
90 | 81 | break; |
91 | 81 | } |
92 | | } |
93 | | |
94 | | // Check if the line had anything but whitespace |
95 | 81 | if whitespace_idx < line.len() { |
96 | 81 | prefix = &line[..whitespace_idx]; |
97 | 81 | break; |
98 | 0 | } |
99 | | } |
100 | | |
101 | | // We then continue looking through the remaining lines to |
102 | | // possibly shorten the prefix. |
103 | 1.14k | for line in &mut lines { |
104 | 1.05k | let mut whitespace_idx = line.len(); |
105 | 1.05k | for ((idx, a), b) in line.char_indices().zip(prefix.chars()) { |
106 | 921 | if a != b { |
107 | 0 | whitespace_idx = idx; |
108 | 0 | break; |
109 | 921 | } |
110 | | } |
111 | | |
112 | | // Check if the line had anything but whitespace and if we |
113 | | // have found a shorter prefix |
114 | 1.05k | if whitespace_idx < line.len() && whitespace_idx < prefix.len() { |
115 | 0 | prefix = &line[..whitespace_idx]; |
116 | 1.05k | } |
117 | | } |
118 | | |
119 | | // We now go over the lines a second time to build the result. |
120 | 81 | let mut result = String::new(); |
121 | 1.14k | for line in s.lines() { |
122 | 2.00k | if line.starts_with(&prefix) && line.chars().any(|c| !c.is_whitespace()) { |
123 | 1.00k | let (_, tail) = line.split_at(prefix.len()); |
124 | 1.00k | result.push_str(tail); |
125 | 1.00k | } |
126 | 1.14k | result.push('\n'); |
127 | | } |
128 | | |
129 | 81 | if result.ends_with('\n') && !s.ends_with('\n') { |
130 | 81 | let new_len = result.len() - 1; |
131 | 81 | result.truncate(new_len); |
132 | 81 | } |
133 | | |
134 | 81 | result |
135 | 81 | } |
136 | | |
137 | | #[cfg(test)] |
138 | | mod tests { |
139 | | use super::*; |
140 | | |
141 | | /// Add newlines. Ensures that the final line in the vector also |
142 | | /// has a newline. |
143 | | fn add_nl(lines: &[&str]) -> String { |
144 | | lines.join("\n") + "\n" |
145 | | } |
146 | | |
147 | | #[test] |
148 | | fn indent_empty() { |
149 | | assert_eq!(indent("\n", " "), "\n"); |
150 | | } |
151 | | |
152 | | #[test] |
153 | | #[cfg_attr(rustfmt, rustfmt_skip)] |
154 | | fn indent_nonempty() { |
155 | | let x = vec![" foo", |
156 | | "bar", |
157 | | " baz"]; |
158 | | let y = vec!["// foo", |
159 | | "//bar", |
160 | | "// baz"]; |
161 | | assert_eq!(indent(&add_nl(&x), "//"), add_nl(&y)); |
162 | | } |
163 | | |
164 | | #[test] |
165 | | #[cfg_attr(rustfmt, rustfmt_skip)] |
166 | | fn indent_empty_line() { |
167 | | let x = vec![" foo", |
168 | | "bar", |
169 | | "", |
170 | | " baz"]; |
171 | | let y = vec!["// foo", |
172 | | "//bar", |
173 | | "", |
174 | | "// baz"]; |
175 | | assert_eq!(indent(&add_nl(&x), "//"), add_nl(&y)); |
176 | | } |
177 | | |
178 | | #[test] |
179 | | fn dedent_empty() { |
180 | | assert_eq!(dedent(""), ""); |
181 | | } |
182 | | |
183 | | #[test] |
184 | | #[cfg_attr(rustfmt, rustfmt_skip)] |
185 | | fn dedent_multi_line() { |
186 | | let x = vec![" foo", |
187 | | " bar", |
188 | | " baz"]; |
189 | | let y = vec![" foo", |
190 | | "bar", |
191 | | " baz"]; |
192 | | assert_eq!(dedent(&add_nl(&x)), add_nl(&y)); |
193 | | } |
194 | | |
195 | | #[test] |
196 | | #[cfg_attr(rustfmt, rustfmt_skip)] |
197 | | fn dedent_empty_line() { |
198 | | let x = vec![" foo", |
199 | | " bar", |
200 | | " ", |
201 | | " baz"]; |
202 | | let y = vec![" foo", |
203 | | "bar", |
204 | | "", |
205 | | " baz"]; |
206 | | assert_eq!(dedent(&add_nl(&x)), add_nl(&y)); |
207 | | } |
208 | | |
209 | | #[test] |
210 | | #[cfg_attr(rustfmt, rustfmt_skip)] |
211 | | fn dedent_blank_line() { |
212 | | let x = vec![" foo", |
213 | | "", |
214 | | " bar", |
215 | | " foo", |
216 | | " bar", |
217 | | " baz"]; |
218 | | let y = vec!["foo", |
219 | | "", |
220 | | " bar", |
221 | | " foo", |
222 | | " bar", |
223 | | " baz"]; |
224 | | assert_eq!(dedent(&add_nl(&x)), add_nl(&y)); |
225 | | } |
226 | | |
227 | | #[test] |
228 | | #[cfg_attr(rustfmt, rustfmt_skip)] |
229 | | fn dedent_whitespace_line() { |
230 | | let x = vec![" foo", |
231 | | " ", |
232 | | " bar", |
233 | | " foo", |
234 | | " bar", |
235 | | " baz"]; |
236 | | let y = vec!["foo", |
237 | | "", |
238 | | " bar", |
239 | | " foo", |
240 | | " bar", |
241 | | " baz"]; |
242 | | assert_eq!(dedent(&add_nl(&x)), add_nl(&y)); |
243 | | } |
244 | | |
245 | | #[test] |
246 | | #[cfg_attr(rustfmt, rustfmt_skip)] |
247 | | fn dedent_mixed_whitespace() { |
248 | | let x = vec!["\tfoo", |
249 | | " bar"]; |
250 | | let y = vec!["\tfoo", |
251 | | " bar"]; |
252 | | assert_eq!(dedent(&add_nl(&x)), add_nl(&y)); |
253 | | } |
254 | | |
255 | | #[test] |
256 | | #[cfg_attr(rustfmt, rustfmt_skip)] |
257 | | fn dedent_tabbed_whitespace() { |
258 | | let x = vec!["\t\tfoo", |
259 | | "\t\t\tbar"]; |
260 | | let y = vec!["foo", |
261 | | "\tbar"]; |
262 | | assert_eq!(dedent(&add_nl(&x)), add_nl(&y)); |
263 | | } |
264 | | |
265 | | #[test] |
266 | | #[cfg_attr(rustfmt, rustfmt_skip)] |
267 | | fn dedent_mixed_tabbed_whitespace() { |
268 | | let x = vec!["\t \tfoo", |
269 | | "\t \t\tbar"]; |
270 | | let y = vec!["foo", |
271 | | "\tbar"]; |
272 | | assert_eq!(dedent(&add_nl(&x)), add_nl(&y)); |
273 | | } |
274 | | |
275 | | #[test] |
276 | | #[cfg_attr(rustfmt, rustfmt_skip)] |
277 | | fn dedent_mixed_tabbed_whitespace2() { |
278 | | let x = vec!["\t \tfoo", |
279 | | "\t \tbar"]; |
280 | | let y = vec!["\tfoo", |
281 | | " \tbar"]; |
282 | | assert_eq!(dedent(&add_nl(&x)), add_nl(&y)); |
283 | | } |
284 | | |
285 | | #[test] |
286 | | #[cfg_attr(rustfmt, rustfmt_skip)] |
287 | | fn dedent_preserve_no_terminating_newline() { |
288 | | let x = vec![" foo", |
289 | | " bar"].join("\n"); |
290 | | let y = vec!["foo", |
291 | | " bar"].join("\n"); |
292 | | assert_eq!(dedent(&x), y); |
293 | | } |
294 | | } |