indenter-0.3.3/.cargo_vcs_info.json0000644000000001121401502756000126770ustar { "git": { "sha1": "6dcbe9f1565218c1ee58f47e92a3f4a35c451a44" } } indenter-0.3.3/.github/workflows/ci.yml010066400017500001750000000047451377017450500162460ustar 00000000000000on: push: branches: - master pull_request: {} name: Continuous integration jobs: check: name: Check runs-on: ubuntu-latest strategy: matrix: rust: - stable steps: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} override: true - uses: actions-rs/cargo@v1 with: command: check test-versions: name: Test Suite runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: toolchain: stable override: true - uses: actions-rs/cargo@v1 with: command: test test-features: name: Test Suite runs-on: ubuntu-latest strategy: matrix: rust: - stable - beta - nightly - 1.39.0 steps: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} override: true - uses: actions-rs/cargo@v1 with: command: test args: --all-features - uses: actions-rs/cargo@v1 with: command: test args: --no-default-features test-os: name: Test Suite runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] steps: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal override: true - uses: actions-rs/cargo@v1 with: command: test fmt: name: Rustfmt runs-on: ubuntu-latest strategy: matrix: rust: - stable steps: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} override: true - run: rustup component add rustfmt - uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check clippy: name: Clippy runs-on: ubuntu-latest strategy: matrix: rust: - stable steps: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} override: true - run: rustup component add clippy - uses: actions-rs/cargo@v1 with: command: clippy args: -- -D warnings indenter-0.3.3/.gitignore010066400017500001750000000000231377017450500135040ustar 00000000000000/target Cargo.lock indenter-0.3.3/CHANGELOG.md010066400017500001750000000017261401502747400133330ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] - ReleaseDate ## [0.3.3] - 2021-02-22 ### Added - Implement new code dedenting / indenting formatter by cecton ## [0.3.2] - 2021-01-04 ### Fixed - Changed indentation logic to better support trailing newlines and improve overall formatting consistency ## [0.3.1] - 2020-12-21 ### Added - `with_str` helper method for indenting with static strings ### Changed - Relaxed `Sized` bound on inner writers [Unreleased]: https://github.com/yaahc/indenter/compare/v0.3.3...HEAD [0.3.3]: https://github.com/yaahc/indenter/compare/v0.3.2...v0.3.3 [0.3.2]: https://github.com/yaahc/indenter/compare/v0.3.1...v0.3.2 [0.3.1]: https://github.com/yaahc/indenter/releases/tag/v0.3.1 indenter-0.3.3/Cargo.lock0000644000000002131401502756000106540ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "indenter" version = "0.3.3" indenter-0.3.3/Cargo.toml0000644000000041621401502756000107060ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "indenter" version = "0.3.3" authors = ["Jane Lusby "] description = "A formatter wrapper that indents the text, designed for error display impls\n" homepage = "https://github.com/yaahc/indenter" documentation = "https://docs.rs/indenter" readme = "README.md" keywords = ["display", "fmt", "Formatter", "error"] license = "MIT OR Apache-2.0" repository = "https://github.com/yaahc/indenter" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [package.metadata.release] no-dev-version = true [[package.metadata.release.pre-release-replacements]] file = "CHANGELOG.md" replace = "{{version}}" search = "Unreleased" [[package.metadata.release.pre-release-replacements]] exactly = 1 file = "src/lib.rs" replace = "#![doc(html_root_url = \"https://docs.rs/{{crate_name}}/{{version}}\")]" search = "#!\\[doc\\(html_root_url.*" [[package.metadata.release.pre-release-replacements]] exactly = 1 file = "CHANGELOG.md" replace = "...{{tag_name}}" search = "\\.\\.\\.HEAD" [[package.metadata.release.pre-release-replacements]] file = "CHANGELOG.md" replace = "{{date}}" search = "ReleaseDate" [[package.metadata.release.pre-release-replacements]] exactly = 1 file = "CHANGELOG.md" replace = "\n\n## [Unreleased] - ReleaseDate" search = "" [[package.metadata.release.pre-release-replacements]] exactly = 1 file = "CHANGELOG.md" replace = "\n[Unreleased]: https://github.com/yaahc/{{crate_name}}/compare/{{tag_name}}...HEAD" search = "" [dependencies] [features] default = [] std = [] indenter-0.3.3/Cargo.toml.orig010066400017500001750000000032661401502747400144120ustar 00000000000000[package] name = "indenter" version = "0.3.3" authors = ["Jane Lusby "] edition = "2018" license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/yaahc/indenter" homepage = "https://github.com/yaahc/indenter" documentation = "https://docs.rs/indenter" keywords = ["display", "fmt", "Formatter", "error"] description = """ A formatter wrapper that indents the text, designed for error display impls """ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] default = [] std = [] [dependencies] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [package.metadata.release] no-dev-version = true [[package.metadata.release.pre-release-replacements]] file = "CHANGELOG.md" search = "Unreleased" replace="{{version}}" [[package.metadata.release.pre-release-replacements]] file = "src/lib.rs" search = "#!\\[doc\\(html_root_url.*" replace = "#![doc(html_root_url = \"https://docs.rs/{{crate_name}}/{{version}}\")]" exactly = 1 [[package.metadata.release.pre-release-replacements]] file = "CHANGELOG.md" search = "\\.\\.\\.HEAD" replace="...{{tag_name}}" exactly = 1 [[package.metadata.release.pre-release-replacements]] file = "CHANGELOG.md" search = "ReleaseDate" replace="{{date}}" [[package.metadata.release.pre-release-replacements]] file="CHANGELOG.md" search="" replace="\n\n## [Unreleased] - ReleaseDate" exactly=1 [[package.metadata.release.pre-release-replacements]] file="CHANGELOG.md" search="" replace="\n[Unreleased]: https://github.com/yaahc/{{crate_name}}/compare/{{tag_name}}...HEAD" exactly=1 indenter-0.3.3/README.md010066400017500001750000000050171401502731000127630ustar 00000000000000## indenter [![Build Status][actions-badge]][actions-url] [![Latest Version][version-badge]][version-url] [![Rust Documentation][docs-badge]][docs-url] [actions-badge]: https://github.com/yaahc/indenter/workflows/Continuous%20integration/badge.svg [actions-url]: https://github.com/yaahc/indenter/actions?query=workflow%3A%22Continuous+integration%22 [version-badge]: https://img.shields.io/crates/v/indenter.svg [version-url]: https://crates.io/crates/indenter [docs-badge]: https://img.shields.io/badge/docs-latest-blue.svg [docs-url]: https://docs.rs/indenter A few wrappers for the `fmt::Write` objects that efficiently appends and remove common indentation after every newline ## Setup Add this to your `Cargo.toml`: ```toml [dependencies] indenter = "0.2" ``` ## Examples ## Indentation only This type is intended primarily for writing error reporters that gracefully format error messages that span multiple lines. ```rust use std::error::Error; use std::fmt::{self, Write}; use indenter::indented; struct ErrorReporter<'a>(&'a dyn Error); impl fmt::Debug for ErrorReporter<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut source = Some(self.0); let mut i = 0; while let Some(error) = source { writeln!(f)?; write!(indented(f).ind(i), "{}", error)?; source = error.source(); i += 1; } Ok(()) } } ``` ## "Dedenting" (removing common leading indendation) This type is intended primarily for formatting source code. For example, when generating code. This type requires the feature `std`. ```rust use std::error::Error; use core::fmt::{self, Write}; use indenter::CodeFormatter; let mut output = String::new(); let mut f = CodeFormatter::new(&mut output, " "); write!( f, r#" Hello World "#, ); assert_eq!(output, "Hello\n World\n"); let mut output = String::new(); let mut f = CodeFormatter::new(&mut output, " "); // it can also indent... f.indent(2); write!( f, r#" Hello World "#, ); assert_eq!(output, " Hello\n World\n"); ``` #### License Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. indenter-0.3.3/examples/usage.rs010066400017500001750000000004111377017450500150050ustar 00000000000000use indenter::indented; use std::fmt::Write; fn main() { let input = "verify\nthis"; let mut output = String::new(); indented(&mut output).ind(12).write_str(input).unwrap(); println!("Before:\n{}\n", input); println!("After:\n{}", output); } indenter-0.3.3/src/lib.rs010066400017500001750000000337371401502747400134340ustar 00000000000000//! A few wrappers for the `fmt::Write` objects that efficiently appends and remove //! common indentation after every newline //! //! # Setup //! //! Add this to your `Cargo.toml`: //! //! ```toml //! [dependencies] //! indenter = "0.2" //! ``` //! //! # Examples //! //! ## Indentation only //! //! This type is intended primarily for writing error reporters that gracefully //! format error messages that span multiple lines. //! //! ```rust //! use std::error::Error; //! use core::fmt::{self, Write}; //! use indenter::indented; //! //! struct ErrorReporter<'a>(&'a dyn Error); //! //! impl fmt::Debug for ErrorReporter<'_> { //! fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { //! let mut source = Some(self.0); //! let mut i = 0; //! //! while let Some(error) = source { //! writeln!(f)?; //! write!(indented(f).ind(i), "{}", error)?; //! //! source = error.source(); //! i += 1; //! } //! //! Ok(()) //! } //! } //! ``` //! //! ## "Dedenting" (removing common leading indendation) //! //! This type is intended primarily for formatting source code. For example, when //! generating code. //! //! This type requires the feature `std`. //! //! ```rust //! # #[cfg(feature = "std")] //! # fn main() { //! use std::error::Error; //! use core::fmt::{self, Write}; //! use indenter::CodeFormatter; //! //! let mut output = String::new(); //! let mut f = CodeFormatter::new(&mut output, " "); //! //! write!( //! f, //! r#" //! Hello //! World //! "#, //! ); //! //! assert_eq!(output, "Hello\n World\n"); //! //! let mut output = String::new(); //! let mut f = CodeFormatter::new(&mut output, " "); //! //! // it can also indent... //! f.indent(2); //! //! write!( //! f, //! r#" //! Hello //! World //! "#, //! ); //! //! assert_eq!(output, " Hello\n World\n"); //! # } //! # #[cfg(not(feature = "std"))] //! # fn main() { //! # } //! ``` #![cfg_attr(not(feature = "std"), no_std)] #![doc(html_root_url = "https://docs.rs/indenter/0.3.3")] #![warn( missing_debug_implementations, missing_docs, missing_doc_code_examples, rust_2018_idioms, unreachable_pub, bad_style, const_err, dead_code, improper_ctypes, non_shorthand_field_patterns, no_mangle_generic_items, overflowing_literals, path_statements, patterns_in_fns_without_body, private_in_public, unconditional_recursion, unused, unused_allocation, unused_comparisons, unused_parens, while_true )] use core::fmt; /// The set of supported formats for indentation #[allow(missing_debug_implementations)] pub enum Format<'a> { /// Insert uniform indentation before every line /// /// This format takes a static string as input and inserts it after every newline Uniform { /// The string to insert as indentation indentation: &'static str, }, /// Inserts a number before the first line /// /// This format hard codes the indentation level to match the indentation from /// `core::backtrace::Backtrace` Numbered { /// The index to insert before the first line of output ind: usize, }, /// A custom indenter which is executed after every newline /// /// Custom indenters are passed the current line number and the buffer to be written to as args Custom { /// The custom indenter inserter: &'a mut Inserter, }, } /// Helper struct for efficiently indenting multi line display implementations /// /// # Explanation /// /// This type will never allocate a string to handle inserting indentation. It instead leverages /// the `write_str` function that serves as the foundation of the `core::fmt::Write` trait. This /// lets it intercept each piece of output as its being written to the output buffer. It then /// splits on newlines giving slices into the original string. Finally we alternate writing these /// lines and the specified indentation to the output buffer. #[allow(missing_debug_implementations)] pub struct Indented<'a, D: ?Sized> { inner: &'a mut D, needs_indent: bool, format: Format<'a>, } /// A callback for `Format::Custom` used to insert indenation after a new line /// /// The first argument is the line number within the output, starting from 0 pub type Inserter = dyn FnMut(usize, &mut dyn fmt::Write) -> fmt::Result; impl Format<'_> { fn insert_indentation(&mut self, line: usize, f: &mut dyn fmt::Write) -> fmt::Result { match self { Format::Uniform { indentation } => write!(f, "{}", indentation), Format::Numbered { ind } => { if line == 0 { write!(f, "{: >4}: ", ind) } else { write!(f, " ") } } Format::Custom { inserter } => inserter(line, f), } } } impl<'a, D> Indented<'a, D> { /// Sets the format to `Format::Numbered` with the provided index pub fn ind(self, ind: usize) -> Self { self.with_format(Format::Numbered { ind }) } /// Sets the format to `Format::Uniform` with the provided static string pub fn with_str(self, indentation: &'static str) -> Self { self.with_format(Format::Uniform { indentation }) } /// Construct an indenter with a user defined format pub fn with_format(mut self, format: Format<'a>) -> Self { self.format = format; self } } impl fmt::Write for Indented<'_, T> where T: fmt::Write + ?Sized, { fn write_str(&mut self, s: &str) -> fmt::Result { for (ind, line) in s.split('\n').enumerate() { if ind > 0 { self.inner.write_char('\n')?; self.needs_indent = true; } if self.needs_indent { // Don't render the line unless its actually got text on it if line.is_empty() { continue; } self.format.insert_indentation(ind, &mut self.inner)?; self.needs_indent = false; } self.inner.write_fmt(format_args!("{}", line))?; } Ok(()) } } /// Helper function for creating a default indenter pub fn indented(f: &mut D) -> Indented<'_, D> { Indented { inner: f, needs_indent: true, format: Format::Uniform { indentation: " ", }, } } /// Helper struct for efficiently dedent and indent multi line display implementations /// /// # Explanation /// /// This type allocates a string once to get the formatted result and then uses the internal /// formatter efficiently to: first dedent the output, then re-indent to the desired level. #[cfg(feature = "std")] #[allow(missing_debug_implementations)] pub struct CodeFormatter<'a, T> { f: &'a mut T, level: u32, indentation: String, } #[cfg(feature = "std")] impl<'a, T: fmt::Write> fmt::Write for CodeFormatter<'a, T> { fn write_str(&mut self, input: &str) -> fmt::Result { let input = match input.chars().next() { Some('\n') => &input[1..], _ => return self.f.write_str(input), }; let min = input .split('\n') .map(|line| line.chars().take_while(char::is_ascii_whitespace).count()) .filter(|count| *count > 0) .min() .unwrap_or_default(); let input = input.trim_end_matches(|c| char::is_ascii_whitespace(&c)); for line in input.split('\n') { if line.len().saturating_sub(min) > 0 { for _ in 0..self.level { self.f.write_str(&self.indentation)?; } } if line.len() >= min { self.f.write_str(&line[min..])?; } else { self.f.write_str(&line)?; } self.f.write_char('\n')?; } Ok(()) } fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result { self.write_str(&args.to_string()) } } #[cfg(feature = "std")] impl<'a, T: fmt::Write> CodeFormatter<'a, T> { /// Wrap the formatter `f`, use `indentation` as base string indentation and return a new /// formatter that implements `std::fmt::Write` that can be used with the macro `write!()` pub fn new>(f: &'a mut T, indentation: S) -> Self { Self { f, level: 0, indentation: indentation.into(), } } /// Set the indentation level to a specific value pub fn set_level(&mut self, level: u32) { self.level = level; } /// Increase the indentation level by `inc` pub fn indent(&mut self, inc: u32) { self.level = self.level.saturating_add(inc); } /// Decrease the indentation level by `inc` pub fn dedent(&mut self, inc: u32) { self.level = self.level.saturating_sub(inc); } } #[cfg(test)] mod tests { extern crate alloc; use super::*; use alloc::string::String; use core::fmt::Write as _; #[test] fn one_digit() { let input = "verify\nthis"; let expected = " 2: verify\n this"; let mut output = String::new(); indented(&mut output).ind(2).write_str(input).unwrap(); assert_eq!(expected, output); } #[test] fn two_digits() { let input = "verify\nthis"; let expected = " 12: verify\n this"; let mut output = String::new(); indented(&mut output).ind(12).write_str(input).unwrap(); assert_eq!(expected, output); } #[test] fn no_digits() { let input = "verify\nthis"; let expected = " verify\n this"; let mut output = String::new(); indented(&mut output).write_str(input).unwrap(); assert_eq!(expected, output); } #[test] fn with_str() { let input = "verify\nthis"; let expected = "...verify\n...this"; let mut output = String::new(); indented(&mut output) .with_str("...") .write_str(input) .unwrap(); assert_eq!(expected, output); } #[test] fn dyn_write() { let input = "verify\nthis"; let expected = " verify\n this"; let mut output = String::new(); let writer: &mut dyn core::fmt::Write = &mut output; indented(writer).write_str(input).unwrap(); assert_eq!(expected, output); } #[test] fn nice_api() { let input = "verify\nthis"; let expected = " 1: verify\n this"; let output = &mut String::new(); let n = 1; write!( indented(output).with_format(Format::Custom { inserter: &mut move |line_no, f| { if line_no == 0 { write!(f, "{: >4}: ", n) } else { write!(f, " ") } } }), "{}", input ) .unwrap(); assert_eq!(expected, output); } #[test] fn nice_api_2() { let input = "verify\nthis"; let expected = " verify\n this"; let output = &mut String::new(); write!( indented(output).with_format(Format::Uniform { indentation: " " }), "{}", input ) .unwrap(); assert_eq!(expected, output); } #[test] fn trailing_newlines() { let input = "verify\nthis\n"; let expected = " verify\n this\n"; let output = &mut String::new(); write!(indented(output).with_str(" "), "{}", input).unwrap(); assert_eq!(expected, output); } #[test] fn several_interpolations() { let input = "verify\nthis\n"; let expected = " verify\n this\n and verify\n this\n"; let output = &mut String::new(); write!(indented(output).with_str(" "), "{} and {}", input, input).unwrap(); assert_eq!(expected, output); } } #[cfg(all(test, feature = "std"))] mod tests_std { use super::*; use core::fmt::Write as _; #[test] fn dedent() { let mut s = String::new(); let mut f = CodeFormatter::new(&mut s, " "); write!( f, r#" struct Foo; impl Foo {{ fn foo() {{ todo!() }} }} "#, ) .unwrap(); assert_eq!( s, "struct Foo;\n\nimpl Foo {\n fn foo() {\n todo!()\n }\n}\n" ); let mut s = String::new(); let mut f = CodeFormatter::new(&mut s, " "); write!( f, r#" struct Foo; impl Foo {{ fn foo() {{ todo!() }} }}"#, ) .unwrap(); assert_eq!( s, "struct Foo;\n\nimpl Foo {\n fn foo() {\n todo!()\n }\n}\n" ); } #[test] fn indent() { let mut s = String::new(); let mut f = CodeFormatter::new(&mut s, " "); f.indent(1); write!( f, r#" struct Foo; impl Foo {{ fn foo() {{ todo!() }} }} "#, ) .unwrap(); assert_eq!(s, " struct Foo;\n\n impl Foo {\n fn foo() {\n todo!()\n }\n }\n"); } #[test] fn inline() { let mut s = String::new(); let mut f = CodeFormatter::new(&mut s, " "); write!( f, r#"struct Foo; fn foo() {{ }}"#, ) .unwrap(); assert_eq!(s, "struct Foo;\n fn foo() {\n }"); } #[test] fn split_prefix() { let mut s = String::new(); let mut f = CodeFormatter::new(&mut s, " "); writeln!(f).unwrap(); assert_eq!(s, "\n"); } }