1//! A few wrappers for the `fmt::Write` objects that efficiently appends and remove
2//! common indentation after every newline
3//!
4//! # Setup
5//!
6//! Add this to your `Cargo.toml`:
7//!
8//! ```toml
9//! [dependencies]
10//! indenter = "0.2"
11//! ```
12//!
13//! # Examples
14//!
15//! ## Indentation only
16//!
17//! This type is intended primarily for writing error reporters that gracefully
18//! format error messages that span multiple lines.
19//!
20//! ```rust
21//! use std::error::Error;
22//! use core::fmt::{self, Write};
23//! use indenter::indented;
24//!
25//! struct ErrorReporter<'a>(&'a dyn Error);
26//!
27//! impl fmt::Debug for ErrorReporter<'_> {
28//! fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29//! let mut source = Some(self.0);
30//! let mut i = 0;
31//!
32//! while let Some(error) = source {
33//! writeln!(f)?;
34//! write!(indented(f).ind(i), "{}", error)?;
35//!
36//! source = error.source();
37//! i += 1;
38//! }
39//!
40//! Ok(())
41//! }
42//! }
43//! ```
44//!
45//! ## "Dedenting" (removing common leading indendation)
46//!
47//! This type is intended primarily for formatting source code. For example, when
48//! generating code.
49//!
50//! This type requires the feature `std`.
51//!
52//! ```rust
53//! # #[cfg(feature = "std")]
54//! # fn main() {
55//! use std::error::Error;
56//! use core::fmt::{self, Write};
57//! use indenter::CodeFormatter;
58//!
59//! let mut output = String::new();
60//! let mut f = CodeFormatter::new(&mut output, " ");
61//!
62//! write!(
63//! f,
64//! r#"
65//! Hello
66//! World
67//! "#,
68//! );
69//!
70//! assert_eq!(output, "Hello\n World\n");
71//!
72//! let mut output = String::new();
73//! let mut f = CodeFormatter::new(&mut output, " ");
74//!
75//! // it can also indent...
76//! f.indent(2);
77//!
78//! write!(
79//! f,
80//! r#"
81//! Hello
82//! World
83//! "#,
84//! );
85//!
86//! assert_eq!(output, " Hello\n World\n");
87//! # }
88//! # #[cfg(not(feature = "std"))]
89//! # fn main() {
90//! # }
91//! ```
92#![cfg_attr(not(feature = "std"), no_std)]
93#![doc(html_root_url = "https://docs.rs/indenter/0.3.3")]
94#![warn(
95 missing_debug_implementations,
96 missing_docs,
97 missing_doc_code_examples,
98 rust_2018_idioms,
99 unreachable_pub,
100 bad_style,
101 const_err,
102 dead_code,
103 improper_ctypes,
104 non_shorthand_field_patterns,
105 no_mangle_generic_items,
106 overflowing_literals,
107 path_statements,
108 patterns_in_fns_without_body,
109 private_in_public,
110 unconditional_recursion,
111 unused,
112 unused_allocation,
113 unused_comparisons,
114 unused_parens,
115 while_true
116)]
117use core::fmt;
118
119/// The set of supported formats for indentation
120#[allow(missing_debug_implementations)]
121pub enum Format<'a> {
122 /// Insert uniform indentation before every line
123 ///
124 /// This format takes a static string as input and inserts it after every newline
125 Uniform {
126 /// The string to insert as indentation
127 indentation: &'static str,
128 },
129 /// Inserts a number before the first line
130 ///
131 /// This format hard codes the indentation level to match the indentation from
132 /// `core::backtrace::Backtrace`
133 Numbered {
134 /// The index to insert before the first line of output
135 ind: usize,
136 },
137 /// A custom indenter which is executed after every newline
138 ///
139 /// Custom indenters are passed the current line number and the buffer to be written to as args
140 Custom {
141 /// The custom indenter
142 inserter: &'a mut Inserter,
143 },
144}
145
146/// Helper struct for efficiently indenting multi line display implementations
147///
148/// # Explanation
149///
150/// This type will never allocate a string to handle inserting indentation. It instead leverages
151/// the `write_str` function that serves as the foundation of the `core::fmt::Write` trait. This
152/// lets it intercept each piece of output as its being written to the output buffer. It then
153/// splits on newlines giving slices into the original string. Finally we alternate writing these
154/// lines and the specified indentation to the output buffer.
155#[allow(missing_debug_implementations)]
156pub struct Indented<'a, D: ?Sized> {
157 inner: &'a mut D,
158 needs_indent: bool,
159 format: Format<'a>,
160}
161
162/// A callback for `Format::Custom` used to insert indenation after a new line
163///
164/// The first argument is the line number within the output, starting from 0
165pub type Inserter = dyn FnMut(usize, &mut dyn fmt::Write) -> fmt::Result;
166
167impl Format<'_> {
168 fn insert_indentation(&mut self, line: usize, f: &mut dyn fmt::Write) -> fmt::Result {
169 match self {
170 Format::Uniform { indentation: &mut &str } => write!(f, "{}", indentation),
171 Format::Numbered { ind: &mut usize } => {
172 if line == 0 {
173 write!(f, "{: >4}: ", ind)
174 } else {
175 write!(f, " ")
176 }
177 }
178 Format::Custom { inserter: &mut &mut dyn FnMut(usize, …) -> … } => inserter(line, f),
179 }
180 }
181}
182
183impl<'a, D> Indented<'a, D> {
184 /// Sets the format to `Format::Numbered` with the provided index
185 pub fn ind(self, ind: usize) -> Self {
186 self.with_format(Format::Numbered { ind })
187 }
188
189 /// Sets the format to `Format::Uniform` with the provided static string
190 pub fn with_str(self, indentation: &'static str) -> Self {
191 self.with_format(Format::Uniform { indentation })
192 }
193
194 /// Construct an indenter with a user defined format
195 pub fn with_format(mut self, format: Format<'a>) -> Self {
196 self.format = format;
197 self
198 }
199}
200
201impl<T> fmt::Write for Indented<'_, T>
202where
203 T: fmt::Write + ?Sized,
204{
205 fn write_str(&mut self, s: &str) -> fmt::Result {
206 for (ind: usize, line: &str) in s.split('\n').enumerate() {
207 if ind > 0 {
208 self.inner.write_char('\n')?;
209 self.needs_indent = true;
210 }
211
212 if self.needs_indent {
213 // Don't render the line unless its actually got text on it
214 if line.is_empty() {
215 continue;
216 }
217
218 self.format.insert_indentation(line:ind, &mut self.inner)?;
219 self.needs_indent = false;
220 }
221
222 self.inner.write_fmt(format_args!("{}", line))?;
223 }
224
225 Ok(())
226 }
227}
228
229/// Helper function for creating a default indenter
230pub fn indented<D: ?Sized>(f: &mut D) -> Indented<'_, D> {
231 Indented {
232 inner: f,
233 needs_indent: true,
234 format: Format::Uniform {
235 indentation: " ",
236 },
237 }
238}
239
240/// Helper struct for efficiently dedent and indent multi line display implementations
241///
242/// # Explanation
243///
244/// This type allocates a string once to get the formatted result and then uses the internal
245/// formatter efficiently to: first dedent the output, then re-indent to the desired level.
246#[cfg(feature = "std")]
247#[allow(missing_debug_implementations)]
248pub struct CodeFormatter<'a, T> {
249 f: &'a mut T,
250 level: u32,
251 indentation: String,
252}
253
254#[cfg(feature = "std")]
255impl<'a, T: fmt::Write> fmt::Write for CodeFormatter<'a, T> {
256 fn write_str(&mut self, input: &str) -> fmt::Result {
257 let input = match input.chars().next() {
258 Some('\n') => &input[1..],
259 _ => return self.f.write_str(input),
260 };
261
262 let min = input
263 .split('\n')
264 .map(|line| line.chars().take_while(char::is_ascii_whitespace).count())
265 .filter(|count| *count > 0)
266 .min()
267 .unwrap_or_default();
268
269 let input = input.trim_end_matches(|c| char::is_ascii_whitespace(&c));
270
271 for line in input.split('\n') {
272 if line.len().saturating_sub(min) > 0 {
273 for _ in 0..self.level {
274 self.f.write_str(&self.indentation)?;
275 }
276 }
277
278 if line.len() >= min {
279 self.f.write_str(&line[min..])?;
280 } else {
281 self.f.write_str(&line)?;
282 }
283 self.f.write_char('\n')?;
284 }
285
286 Ok(())
287 }
288
289 fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
290 self.write_str(&args.to_string())
291 }
292}
293
294#[cfg(feature = "std")]
295impl<'a, T: fmt::Write> CodeFormatter<'a, T> {
296 /// Wrap the formatter `f`, use `indentation` as base string indentation and return a new
297 /// formatter that implements `std::fmt::Write` that can be used with the macro `write!()`
298 pub fn new<S: Into<String>>(f: &'a mut T, indentation: S) -> Self {
299 Self {
300 f,
301 level: 0,
302 indentation: indentation.into(),
303 }
304 }
305
306 /// Set the indentation level to a specific value
307 pub fn set_level(&mut self, level: u32) {
308 self.level = level;
309 }
310
311 /// Increase the indentation level by `inc`
312 pub fn indent(&mut self, inc: u32) {
313 self.level = self.level.saturating_add(inc);
314 }
315
316 /// Decrease the indentation level by `inc`
317 pub fn dedent(&mut self, inc: u32) {
318 self.level = self.level.saturating_sub(inc);
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 extern crate alloc;
325
326 use super::*;
327 use alloc::string::String;
328 use core::fmt::Write as _;
329
330 #[test]
331 fn one_digit() {
332 let input = "verify\nthis";
333 let expected = " 2: verify\n this";
334 let mut output = String::new();
335
336 indented(&mut output).ind(2).write_str(input).unwrap();
337
338 assert_eq!(expected, output);
339 }
340
341 #[test]
342 fn two_digits() {
343 let input = "verify\nthis";
344 let expected = " 12: verify\n this";
345 let mut output = String::new();
346
347 indented(&mut output).ind(12).write_str(input).unwrap();
348
349 assert_eq!(expected, output);
350 }
351
352 #[test]
353 fn no_digits() {
354 let input = "verify\nthis";
355 let expected = " verify\n this";
356 let mut output = String::new();
357
358 indented(&mut output).write_str(input).unwrap();
359
360 assert_eq!(expected, output);
361 }
362
363 #[test]
364 fn with_str() {
365 let input = "verify\nthis";
366 let expected = "...verify\n...this";
367 let mut output = String::new();
368
369 indented(&mut output)
370 .with_str("...")
371 .write_str(input)
372 .unwrap();
373
374 assert_eq!(expected, output);
375 }
376
377 #[test]
378 fn dyn_write() {
379 let input = "verify\nthis";
380 let expected = " verify\n this";
381 let mut output = String::new();
382 let writer: &mut dyn core::fmt::Write = &mut output;
383
384 indented(writer).write_str(input).unwrap();
385
386 assert_eq!(expected, output);
387 }
388
389 #[test]
390 fn nice_api() {
391 let input = "verify\nthis";
392 let expected = " 1: verify\n this";
393 let output = &mut String::new();
394 let n = 1;
395
396 write!(
397 indented(output).with_format(Format::Custom {
398 inserter: &mut move |line_no, f| {
399 if line_no == 0 {
400 write!(f, "{: >4}: ", n)
401 } else {
402 write!(f, " ")
403 }
404 }
405 }),
406 "{}",
407 input
408 )
409 .unwrap();
410
411 assert_eq!(expected, output);
412 }
413
414 #[test]
415 fn nice_api_2() {
416 let input = "verify\nthis";
417 let expected = " verify\n this";
418 let output = &mut String::new();
419
420 write!(
421 indented(output).with_format(Format::Uniform { indentation: " " }),
422 "{}",
423 input
424 )
425 .unwrap();
426
427 assert_eq!(expected, output);
428 }
429
430 #[test]
431 fn trailing_newlines() {
432 let input = "verify\nthis\n";
433 let expected = " verify\n this\n";
434 let output = &mut String::new();
435
436 write!(indented(output).with_str(" "), "{}", input).unwrap();
437
438 assert_eq!(expected, output);
439 }
440
441 #[test]
442 fn several_interpolations() {
443 let input = "verify\nthis\n";
444 let expected = " verify\n this\n and verify\n this\n";
445 let output = &mut String::new();
446
447 write!(indented(output).with_str(" "), "{} and {}", input, input).unwrap();
448
449 assert_eq!(expected, output);
450 }
451}
452
453#[cfg(all(test, feature = "std"))]
454mod tests_std {
455 use super::*;
456 use core::fmt::Write as _;
457
458 #[test]
459 fn dedent() {
460 let mut s = String::new();
461 let mut f = CodeFormatter::new(&mut s, " ");
462 write!(
463 f,
464 r#"
465 struct Foo;
466
467 impl Foo {{
468 fn foo() {{
469 todo!()
470 }}
471 }}
472 "#,
473 )
474 .unwrap();
475 assert_eq!(
476 s,
477 "struct Foo;\n\nimpl Foo {\n fn foo() {\n todo!()\n }\n}\n"
478 );
479
480 let mut s = String::new();
481 let mut f = CodeFormatter::new(&mut s, " ");
482 write!(
483 f,
484 r#"
485 struct Foo;
486
487 impl Foo {{
488 fn foo() {{
489 todo!()
490 }}
491 }}"#,
492 )
493 .unwrap();
494 assert_eq!(
495 s,
496 "struct Foo;\n\nimpl Foo {\n fn foo() {\n todo!()\n }\n}\n"
497 );
498 }
499
500 #[test]
501 fn indent() {
502 let mut s = String::new();
503 let mut f = CodeFormatter::new(&mut s, " ");
504 f.indent(1);
505 write!(
506 f,
507 r#"
508 struct Foo;
509
510 impl Foo {{
511 fn foo() {{
512 todo!()
513 }}
514 }}
515 "#,
516 )
517 .unwrap();
518 assert_eq!(s, " struct Foo;\n\n impl Foo {\n fn foo() {\n todo!()\n }\n }\n");
519 }
520
521 #[test]
522 fn inline() {
523 let mut s = String::new();
524 let mut f = CodeFormatter::new(&mut s, " ");
525 write!(
526 f,
527 r#"struct Foo;
528 fn foo() {{
529 }}"#,
530 )
531 .unwrap();
532 assert_eq!(s, "struct Foo;\n fn foo() {\n }");
533 }
534
535 #[test]
536 fn split_prefix() {
537 let mut s = String::new();
538 let mut f = CodeFormatter::new(&mut s, " ");
539 writeln!(f).unwrap();
540 assert_eq!(s, "\n");
541 }
542}
543