indenter/
lib.rs

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 } => write!(f, "{}", indentation),
171            Format::Numbered { ind } => {
172                if line == 0 {
173                    write!(f, "{: >4}: ", ind)
174                } else {
175                    write!(f, "      ")
176                }
177            }
178            Format::Custom { inserter } => 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, line) 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(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}