| 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 | )] |
| 117 | use core::fmt; |
| 118 | |
| 119 | /// The set of supported formats for indentation |
| 120 | #[allow (missing_debug_implementations)] |
| 121 | pub 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)] |
| 156 | pub 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 |
| 165 | pub type Inserter = dyn FnMut(usize, &mut dyn fmt::Write) -> fmt::Result; |
| 166 | |
| 167 | impl Format<'_> { |
| 168 | fn insert_indentation(&mut self, line: usize, f: &mut dyn fmt::Write) -> fmt::Result { |
| 169 | match self { |
| 170 | Format::Uniform { indentation: &mut &'static 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, &mut (dyn Write + 'static)) -> … + 'static) } => inserter(line, f), |
| 179 | } |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | impl<'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 | |
| 201 | impl<T> fmt::Write for Indented<'_, T> |
| 202 | where |
| 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(args:format_args!(" {}" , line))?; |
| 223 | } |
| 224 | |
| 225 | Ok(()) |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | /// Helper function for creating a default indenter |
| 230 | pub 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)] |
| 248 | pub struct CodeFormatter<'a, T> { |
| 249 | f: &'a mut T, |
| 250 | level: u32, |
| 251 | indentation: String, |
| 252 | } |
| 253 | |
| 254 | #[cfg (feature = "std" )] |
| 255 | impl<'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" )] |
| 295 | impl<'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)] |
| 323 | mod 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" ))] |
| 454 | mod 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 | |