Coverage Report

Created: 2024-07-06 06:44

/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
}