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.4")]
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    dead_code,
102    improper_ctypes,
103    non_shorthand_field_patterns,
104    no_mangle_generic_items,
105    overflowing_literals,
106    path_statements,
107    patterns_in_fns_without_body,
108    unconditional_recursion,
109    unused,
110    unused_allocation,
111    unused_comparisons,
112    unused_parens,
113    while_true
114)]
115
116use core::fmt;
117
118/// The set of supported formats for indentation
119#[allow(missing_debug_implementations)]
120pub enum Format<'a> {
121    /// Insert uniform indentation before every line
122    ///
123    /// This format takes a static string as input and inserts it after every newline
124    Uniform {
125        /// The string to insert as indentation
126        indentation: &'static str,
127    },
128    /// Inserts a number before the first line
129    ///
130    /// This format hard codes the indentation level to match the indentation from
131    /// `core::backtrace::Backtrace`
132    Numbered {
133        /// The index to insert before the first line of output
134        ind: usize,
135    },
136    /// A custom indenter which is executed after every newline
137    ///
138    /// Custom indenters are passed the current line number and the buffer to be written to as args
139    Custom {
140        /// The custom indenter
141        inserter: &'a mut Inserter,
142    },
143}
144
145/// Helper struct for efficiently indenting multi line display implementations
146///
147/// # Explanation
148///
149/// This type will never allocate a string to handle inserting indentation. It instead leverages
150/// the `write_str` function that serves as the foundation of the `core::fmt::Write` trait. This
151/// lets it intercept each piece of output as its being written to the output buffer. It then
152/// splits on newlines giving slices into the original string. Finally we alternate writing these
153/// lines and the specified indentation to the output buffer.
154#[allow(missing_debug_implementations)]
155pub struct Indented<'a, D: ?Sized> {
156    inner: &'a mut D,
157    line_number: usize,
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 (i, line) in s.split('\n').enumerate() {
207            if i > 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 it actually has text on it
214                if line.is_empty() {
215                    continue;
216                }
217
218                self.format
219                    .insert_indentation(self.line_number, &mut self.inner)?;
220                self.line_number += 1;
221                self.needs_indent = false;
222            }
223
224            self.inner.write_str(line)?;
225        }
226
227        Ok(())
228    }
229}
230
231/// Helper function for creating a default indenter
232pub fn indented<D: ?Sized>(f: &mut D) -> Indented<'_, D> {
233    Indented {
234        inner: f,
235        line_number: 0,
236        needs_indent: true,
237        format: Format::Uniform {
238            indentation: "    ",
239        },
240    }
241}
242
243/// Helper struct for efficiently dedent and indent multi line display implementations
244///
245/// # Explanation
246///
247/// This type allocates a string once to get the formatted result and then uses the internal
248/// formatter efficiently to: first dedent the output, then re-indent to the desired level.
249#[cfg(feature = "std")]
250#[allow(missing_debug_implementations)]
251pub struct CodeFormatter<'a, T> {
252    f: &'a mut T,
253    level: u32,
254    indentation: String,
255}
256
257#[cfg(feature = "std")]
258impl<'a, T: fmt::Write> fmt::Write for CodeFormatter<'a, T> {
259    fn write_str(&mut self, input: &str) -> fmt::Result {
260        let input = match input.chars().next() {
261            Some('\n') => &input[1..],
262            _ => return self.f.write_str(input),
263        };
264
265        let min = input
266            .split('\n')
267            .map(|line| line.chars().take_while(char::is_ascii_whitespace).count())
268            .filter(|count| *count > 0)
269            .min()
270            .unwrap_or_default();
271
272        let input = input.trim_end_matches(|c| char::is_ascii_whitespace(&c));
273
274        for line in input.split('\n') {
275            if line.len().saturating_sub(min) > 0 {
276                for _ in 0..self.level {
277                    self.f.write_str(&self.indentation)?;
278                }
279            }
280
281            if line.len() >= min {
282                self.f.write_str(&line[min..])?;
283            } else {
284                self.f.write_str(&line)?;
285            }
286            self.f.write_char('\n')?;
287        }
288
289        Ok(())
290    }
291
292    fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
293        self.write_str(&args.to_string())
294    }
295}
296
297#[cfg(feature = "std")]
298impl<'a, T: fmt::Write> CodeFormatter<'a, T> {
299    /// Wrap the formatter `f`, use `indentation` as base string indentation and return a new
300    /// formatter that implements `std::fmt::Write` that can be used with the macro `write!()`
301    pub fn new<S: Into<String>>(f: &'a mut T, indentation: S) -> Self {
302        Self {
303            f,
304            level: 0,
305            indentation: indentation.into(),
306        }
307    }
308
309    /// Set the indentation level to a specific value
310    pub fn set_level(&mut self, level: u32) {
311        self.level = level;
312    }
313
314    /// Increase the indentation level by `inc`
315    pub fn indent(&mut self, inc: u32) {
316        self.level = self.level.saturating_add(inc);
317    }
318
319    /// Decrease the indentation level by `inc`
320    pub fn dedent(&mut self, inc: u32) {
321        self.level = self.level.saturating_sub(inc);
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    extern crate alloc;
328
329    use super::*;
330    use alloc::string::String;
331    use core::fmt::Write as _;
332
333    #[test]
334    fn one_digit() {
335        let input = "verify\nthis";
336        let expected = "   2: verify\n      this";
337        let mut output = String::new();
338
339        indented(&mut output).ind(2).write_str(input).unwrap();
340
341        assert_eq!(expected, output);
342    }
343
344    #[test]
345    fn two_digits() {
346        let input = "verify\nthis";
347        let expected = "  12: verify\n      this";
348        let mut output = String::new();
349
350        indented(&mut output).ind(12).write_str(input).unwrap();
351
352        assert_eq!(expected, output);
353    }
354
355    #[test]
356    fn no_digits() {
357        let input = "verify\nthis";
358        let expected = "    verify\n    this";
359        let mut output = String::new();
360
361        indented(&mut output).write_str(input).unwrap();
362
363        assert_eq!(expected, output);
364    }
365
366    #[test]
367    fn with_str() {
368        let input = "verify\nthis";
369        let expected = "...verify\n...this";
370        let mut output = String::new();
371
372        indented(&mut output)
373            .with_str("...")
374            .write_str(input)
375            .unwrap();
376
377        assert_eq!(expected, output);
378    }
379
380    #[test]
381    fn dyn_write() {
382        let input = "verify\nthis";
383        let expected = "    verify\n    this";
384        let mut output = String::new();
385        let writer: &mut dyn core::fmt::Write = &mut output;
386
387        indented(writer).write_str(input).unwrap();
388
389        assert_eq!(expected, output);
390    }
391
392    #[test]
393    fn nice_api() {
394        let input = "verify\nthis";
395        let expected = "   1: verify\n       this";
396        let output = &mut String::new();
397        let n = 1;
398
399        write!(
400            indented(output).with_format(Format::Custom {
401                inserter: &mut move |line_no, f| {
402                    if line_no == 0 {
403                        write!(f, "{: >4}: ", n)
404                    } else {
405                        write!(f, "       ")
406                    }
407                }
408            }),
409            "{}",
410            input
411        )
412        .unwrap();
413
414        assert_eq!(expected, output);
415    }
416
417    #[test]
418    fn nice_api_2() {
419        let input = "verify\nthis";
420        let expected = "  verify\n  this";
421        let output = &mut String::new();
422
423        write!(
424            indented(output).with_format(Format::Uniform { indentation: "  " }),
425            "{}",
426            input
427        )
428        .unwrap();
429
430        assert_eq!(expected, output);
431    }
432
433    #[test]
434    fn empty_lines() {
435        let input = "\n\n\nverify\n\nthis";
436        let expected = "\n\n\n  verify\n\n  this";
437        let output = &mut String::new();
438
439        write!(indented(output).with_str("  "), "{}", input).unwrap();
440
441        assert_eq!(expected, output);
442    }
443
444    #[test]
445    fn trailing_newlines() {
446        let input = "verify\nthis\n";
447        let expected = "  verify\n  this\n";
448        let output = &mut String::new();
449
450        write!(indented(output).with_str("  "), "{}", input).unwrap();
451
452        assert_eq!(expected, output);
453    }
454
455    #[test]
456    fn several_interpolations() {
457        let input = "verify\nthis\n";
458        let expected = "  verify\n  this\n   and verify\n  this\n";
459        let output = &mut String::new();
460
461        write!(indented(output).with_str("  "), "{} and {}", input, input).unwrap();
462
463        assert_eq!(expected, output);
464    }
465
466    #[test]
467    fn several_interpolations_keep_monotonic_line_numbers() {
468        let input = '\n';
469        let expected = "verify\n this\n  and this";
470        let output = &mut String::new();
471
472        write!(
473            indented(output).with_format(Format::Custom {
474                inserter: &mut move |line_no, f| { write!(f, "{:spaces$}", "", spaces = line_no) }
475            }),
476            "verify{}this{0}and this",
477            input
478        )
479        .unwrap();
480
481        assert_eq!(expected, output);
482    }
483}
484
485#[cfg(all(test, feature = "std"))]
486mod tests_std {
487    use super::*;
488    use core::fmt::Write as _;
489
490    #[test]
491    fn dedent() {
492        let mut s = String::new();
493        let mut f = CodeFormatter::new(&mut s, "    ");
494        write!(
495            f,
496            r#"
497            struct Foo;
498
499            impl Foo {{
500                fn foo() {{
501                    todo!()
502                }}
503            }}
504            "#,
505        )
506        .unwrap();
507        assert_eq!(
508            s,
509            "struct Foo;\n\nimpl Foo {\n    fn foo() {\n        todo!()\n    }\n}\n"
510        );
511
512        let mut s = String::new();
513        let mut f = CodeFormatter::new(&mut s, "    ");
514        write!(
515            f,
516            r#"
517            struct Foo;
518
519            impl Foo {{
520                fn foo() {{
521                    todo!()
522                }}
523            }}"#,
524        )
525        .unwrap();
526        assert_eq!(
527            s,
528            "struct Foo;\n\nimpl Foo {\n    fn foo() {\n        todo!()\n    }\n}\n"
529        );
530    }
531
532    #[test]
533    fn indent() {
534        let mut s = String::new();
535        let mut f = CodeFormatter::new(&mut s, "    ");
536        f.indent(1);
537        write!(
538            f,
539            r#"
540            struct Foo;
541
542            impl Foo {{
543                fn foo() {{
544                    todo!()
545                }}
546            }}
547            "#,
548        )
549        .unwrap();
550        assert_eq!(s, "    struct Foo;\n\n    impl Foo {\n        fn foo() {\n            todo!()\n        }\n    }\n");
551    }
552
553    #[test]
554    fn inline() {
555        let mut s = String::new();
556        let mut f = CodeFormatter::new(&mut s, "    ");
557        write!(
558            f,
559            r#"struct Foo;
560            fn foo() {{
561            }}"#,
562        )
563        .unwrap();
564        assert_eq!(s, "struct Foo;\n            fn foo() {\n            }");
565    }
566
567    #[test]
568    fn split_prefix() {
569        let mut s = String::new();
570        let mut f = CodeFormatter::new(&mut s, "    ");
571        writeln!(f).unwrap();
572        assert_eq!(s, "\n");
573    }
574}