indent/lib.rs
1// SPDX-FileCopyrightText: 2021 ilkecan <ilkecan@protonmail.com>
2//
3// SPDX-License-Identifier: MPL-2.0
4
5//! These functions are useful for inserting a multiline string into an already indented context in
6//! another string.
7
8use std::borrow::Cow;
9
10/// Indents every line that is not empty by the given number of spaces, starting from the second
11/// line.
12///
13/// The first line of the string is not indented so that it can be placed after an introduction
14/// sequence that has already begun the line.
15///
16/// # Examples
17/// ```rust
18/// assert_eq!(format!(" items: {}", indent::indent_by(2, "[\n foo,\n bar,\n]\n")),
19/// " items: [
20/// foo,
21/// bar,
22/// ]
23/// ")
24/// ```
25///
26/// For the version that also indents the first line, see [indent_all_by].
27pub fn indent_by<'a, S>(number_of_spaces: usize, input: S) -> String
28where
29 S: Into<Cow<'a, str>>,
30{
31 indent(" ".repeat(number_of_spaces), input, false)
32}
33
34/// Indents every line that is not empty with the given prefix, starting from the second line.
35///
36/// The first line of the string is not indented so that it can be placed after an introduction
37/// sequence that has already begun the line.
38///
39/// # Examples
40/// ```rust
41/// assert_eq!(format!("items:{}", indent::indent_with("- ", "\nfoo\nbar\n")),
42/// "items:
43/// - foo
44/// - bar
45/// ")
46/// ```
47///
48/// For the version that also indents the first line, see [indent_all_with].
49pub fn indent_with<'a, S, T>(prefix: S, input: T) -> String
50where
51 S: Into<Cow<'a, str>>,
52 T: Into<Cow<'a, str>>,
53{
54 indent(prefix, input, false)
55}
56
57/// Indents every line that is not empty by the given number of spaces.
58///
59/// # Examples
60/// ```rust
61/// assert_eq!(format!("items: [\n{}]\n", indent::indent_all_by(2, "foo,\nbar,\n")),
62/// "items: [
63/// foo,
64/// bar,
65/// ]
66/// ")
67/// ```
68///
69/// For the version that doesn't indent the first line, see [indent_by].
70pub fn indent_all_by<'a, S>(number_of_spaces: usize, input: S) -> String
71where
72 S: Into<Cow<'a, str>>,
73{
74 indent(" ".repeat(number_of_spaces), input, true)
75}
76
77/// Indents every line that is not empty with the given prefix.
78///
79/// # Examples
80/// ```rust
81/// assert_eq!(format!("items:\n{}", indent::indent_all_with("- ", "foo\nbar\n")),
82/// "items:
83/// - foo
84/// - bar
85/// ")
86/// ```
87///
88/// For the version that also indents the first line, see [indent_with].
89pub fn indent_all_with<'a, S, T>(prefix: S, input: T) -> String
90where
91 S: Into<Cow<'a, str>>,
92 T: Into<Cow<'a, str>>,
93{
94 indent(prefix, input, true)
95}
96
97fn indent<'a, S, T>(prefix: S, input: T, indent_all: bool) -> String
98where
99 S: Into<Cow<'a, str>>,
100 T: Into<Cow<'a, str>>,
101{
102 let prefix = prefix.into();
103 let input = input.into();
104 let length = input.len();
105 let mut output = String::with_capacity(length + length / 2);
106
107 for (i, line) in input.lines().enumerate() {
108 if i > 0 {
109 output.push('\n');
110
111 if !line.is_empty() {
112 output.push_str(&prefix);
113 }
114 } else if indent_all && !line.is_empty() {
115 output.push_str(&prefix);
116 }
117
118 output.push_str(line);
119 }
120
121 if input.ends_with('\n') {
122 output.push('\n');
123 }
124
125 output
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_single_line_without_newline() {
134 assert_eq!(indent_by(2, "foo"), "foo")
135 }
136
137 #[test]
138 fn test_single_line_with_newline() {
139 assert_eq!(indent_by(2, "foo\n"), "foo\n")
140 }
141
142 #[test]
143 fn test_multiline_without_newline() {
144 assert_eq!(
145 indent_by(
146 2, "
147foo
148bar"
149 ),
150 "
151 foo
152 bar"
153 )
154 }
155
156 #[test]
157 fn test_multiline_with_newline() {
158 assert_eq!(
159 indent_by(
160 2,
161 "
162foo
163bar
164"
165 ),
166 "
167 foo
168 bar
169"
170 )
171 }
172
173 #[test]
174 fn test_empty_line() {
175 assert_eq!(
176 indent_by(
177 2,
178 "
179foo
180
181bar
182"
183 ),
184 "
185 foo
186
187 bar
188"
189 )
190 }
191
192 #[test]
193 fn test_indent_all_by_empty_line() {
194 assert_eq!(
195 indent_all_by(
196 2,
197 "
198foo
199
200bar"
201 ),
202 "
203 foo
204
205 bar"
206 )
207 }
208
209 #[test]
210 fn test_indent_all_by() {
211 assert_eq!(
212 indent_all_by(
213 2, "foo
214
215bar"
216 ),
217 " foo
218
219 bar"
220 )
221 }
222
223 #[test]
224 fn test_indent_with() {
225 assert_eq!(
226 indent_with(
227 " ",
228 "
229foo
230
231bar
232"
233 ),
234 "
235 foo
236
237 bar
238"
239 )
240 }
241
242 #[test]
243 fn test_indent_all_with() {
244 assert_eq!(
245 indent_all_with(
246 " ", "foo
247
248bar"
249 ),
250 " foo
251
252 bar"
253 )
254 }
255}