Simplify sections features (#22)

* Simplify sections features

* cleanup docs a bit

* update docs and switch context to handler
This commit is contained in:
Jane Lusby 2020-06-02 18:26:02 -07:00 committed by GitHub
parent fe284a81b1
commit 5b6507d455
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 324 additions and 496 deletions

View File

@ -4,7 +4,7 @@ version = "0.3.2"
authors = ["Jane Lusby <jlusby@yaah.dev>"] authors = ["Jane Lusby <jlusby@yaah.dev>"]
edition = "2018" edition = "2018"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
description = "A custom context for the `eyre` crate for colorful error reports, suggestions, and `tracing-error` support." description = "An error report handler for panics and eyre::Reports for colorful, consistent, and well formatted error reports for all kinds of errors."
repository = "https://github.com/yaahc/color-eyre" repository = "https://github.com/yaahc/color-eyre"
documentation = "https://docs.rs/color-eyre" documentation = "https://docs.rs/color-eyre"
readme = "README.md" readme = "README.md"
@ -16,7 +16,7 @@ default = ["capture-spantrace"]
capture-spantrace = ["tracing-error", "color-spantrace"] capture-spantrace = ["tracing-error", "color-spantrace"]
[dependencies] [dependencies]
eyre = "0.4.2" eyre = "0.4.3"
tracing-error = { version = "0.1.2", optional = true } tracing-error = { version = "0.1.2", optional = true }
color-backtrace = "0.4.0" color-backtrace = "0.4.0"
backtrace = "0.3.48" backtrace = "0.3.48"

126
README.md
View File

@ -11,8 +11,8 @@
[docs-badge]: https://img.shields.io/badge/docs-latest-blue.svg [docs-badge]: https://img.shields.io/badge/docs-latest-blue.svg
[docs-url]: https://docs.rs/color-eyre [docs-url]: https://docs.rs/color-eyre
A custom context for the [`eyre`] crate for colorful error reports with suggestions, custom An error report handler for panics and the [`eyre`] crate for colorful, consistent, and well
sections, [`tracing-error`] support, and backtraces on stable. formatted error reports for all kinds of errors.
## TLDR ## TLDR
@ -82,53 +82,7 @@ opt-level = 3
### Multiple report format verbosity levels ### Multiple report format verbosity levels
`color-eyre` provides 3 different report formats for how it formats the captured `SpanTrace` `color-eyre` provides 3 different report formats for how it formats the captured `SpanTrace`
and `Backtrace`, minimal, short, and full. Take the following example, taken from and `Backtrace`, minimal, short, and full. Take the below screenshots of the output produced by [`examples/usage.rs`]:
[`examples/usage.rs`]:
```rust
use color_eyre::{Help, Report};
use eyre::WrapErr;
use tracing::{info, instrument};
#[instrument]
fn main() -> Result<(), Report> {
#[cfg(feature = "capture-spantrace")]
install_tracing();
Ok(read_config()?)
}
#[cfg(feature = "capture-spantrace")]
fn install_tracing() {
use tracing_error::ErrorLayer;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
let fmt_layer = fmt::layer().with_target(false);
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.with(ErrorLayer::default())
.init();
}
#[instrument]
fn read_file(path: &str) -> Result<(), Report> {
info!("Reading file");
Ok(std::fs::read_to_string(path).map(drop)?)
}
#[instrument]
fn read_config() -> Result<(), Report> {
read_file("fake_file")
.wrap_err("Unable to read config")
.suggestion("try using a file that exists next time")
}
```
--- ---
@ -154,9 +108,10 @@ error originated from, assuming it can find them on the disk.
### Custom `Section`s for error reports via [`Help`] trait ### Custom `Section`s for error reports via [`Help`] trait
The `section` module provides helpers for adding extra sections to error reports. Sections are The `section` module provides helpers for adding extra sections to error
disinct from error messages and are displayed independently from the chain of errors. Take this reports. Sections are disinct from error messages and are displayed
example of adding sections to contain `stderr` and `stdout` from a failed command, taken from independently from the chain of errors. Take this example of adding sections
to contain `stderr` and `stdout` from a failed command, taken from
[`examples/custom_section.rs`]: [`examples/custom_section.rs`]:
```rust ```rust
@ -179,16 +134,8 @@ impl Output for Command {
if !output.status.success() { if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
Err(eyre!("cmd exited with non-zero status code")) Err(eyre!("cmd exited with non-zero status code"))
.with_section(move || { .with_section(move || stdout.trim().to_string().header("Stdout:"))
"Stdout:" .with_section(move || stderr.trim().to_string().header("Stderr:"))
.skip_if(|| stdout.is_empty())
.body(stdout.trim().to_string())
})
.with_section(move || {
"Stderr:"
.skip_if(|| stderr.is_empty())
.body(stderr.trim().to_string())
})
} else { } else {
Ok(stdout.into()) Ok(stdout.into())
} }
@ -198,26 +145,25 @@ impl Output for Command {
--- ---
Here we have an function that, if the command exits unsuccessfully, creates a report indicating Here we have an function that, if the command exits unsuccessfully, creates a
the failure and attaches two sections, one for `stdout` and one for `stderr`. Each section report indicating the failure and attaches two sections, one for `stdout` and
includes a short header and a body that contains the actual output. Additionally these sections one for `stderr`.
use `skip_if` to tell the report not to include them if there was no output, preventing empty
sections from polluting the end report.
Running `cargo run --example custom_section` shows us how these sections are included in the Running `cargo run --example custom_section` shows us how these sections are
output: included in the output:
![custom section example](https://raw.githubusercontent.com/yaahc/color-eyre/master/pictures/custom_section.png) ![custom section example](https://raw.githubusercontent.com/yaahc/color-eyre/master/pictures/custom_section.png)
Only the `Stderr:` section actually gets included. The `cat` command fails, so stdout ends up Only the `Stderr:` section actually gets included. The `cat` command fails,
being empty and is skipped in the final report. This gives us a short and concise error report so stdout ends up being empty and is skipped in the final report. This gives
indicating exactly what was attempted and how it failed. us a short and concise error report indicating exactly what was attempted and
how it failed.
### Aggregating multiple errors into one report ### Aggregating multiple errors into one report
It's not uncommon for programs like batched task runners or parsers to want to It's not uncommon for programs like batched task runners or parsers to want
return an error with multiple sources. The current version of the error trait to return an error with multiple sources. The current version of the error
does not support this use case very well, though there is [work being trait does not support this use case very well, though there is [work being
done](https://github.com/rust-lang/rfcs/pull/2895) to improve this. done](https://github.com/rust-lang/rfcs/pull/2895) to improve this.
For now however one way to work around this is to compose errors outside the For now however one way to work around this is to compose errors outside the
@ -228,41 +174,43 @@ For an example of how to aggregate errors check out [`examples/multiple_errors.r
### Custom configuration for `color-backtrace` for setting custom filters and more ### Custom configuration for `color-backtrace` for setting custom filters and more
The pretty printing for backtraces and span traces isn't actually provided by `color-eyre`, but The pretty printing for backtraces and span traces isn't actually provided by
instead comes from its dependencies [`color-backtrace`] and [`color-spantrace`]. `color-eyre`, but instead comes from its dependencies [`color-backtrace`] and
`color-backtrace` in particular has many more features than are exported by `color-eyre`, such [`color-spantrace`]. `color-backtrace` in particular has many more features
as customized color schemes, panic hooks, and custom frame filters. The custom frame filters than are exported by `color-eyre`, such as customized color schemes, panic
are particularly useful when combined with `color-eyre`, so to enable their usage we provide hooks, and custom frame filters. The custom frame filters are particularly
the `install` fn for setting up a custom `BacktracePrinter` with custom filters installed. useful when combined with `color-eyre`, so to enable their usage we provide
the `install` fn for setting up a custom `BacktracePrinter` with custom
filters installed.
For an example of how to setup custom filters, check out [`examples/custom_filter.rs`]. For an example of how to setup custom filters, check out [`examples/custom_filter.rs`].
## Explanation ## Explanation
This crate works by defining a `Context` type which implements [`eyre::EyreContext`] This crate works by defining a `Handler` type which implements
and a pair of type aliases for setting this context type as the parameter of [`eyre::EyreHandler`] and a pair of type aliases for setting this handler
[`eyre::Report`]. type as the parameter of [`eyre::Report`].
```rust ```rust
use color_eyre::Context; use color_eyre::Handler;
pub type Report = eyre::Report<Context>; pub type Report = eyre::Report<Handler>;
pub type Result<T, E = Report> = core::result::Result<T, E>; pub type Result<T, E = Report> = core::result::Result<T, E>;
``` ```
Please refer to the [`Context`] type's docs for more details about its feature set. Please refer to the [`Handler`] type's docs for more details about its feature set.
[`eyre`]: https://docs.rs/eyre [`eyre`]: https://docs.rs/eyre
[`tracing-error`]: https://docs.rs/tracing-error [`tracing-error`]: https://docs.rs/tracing-error
[`color-backtrace`]: https://docs.rs/color-backtrace [`color-backtrace`]: https://docs.rs/color-backtrace
[`eyre::EyreContext`]: https://docs.rs/eyre/*/eyre/trait.EyreContext.html [`eyre::EyreHandler`]: https://docs.rs/eyre/*/eyre/trait.EyreHandler.html
[`backtrace::Backtrace`]: https://docs.rs/backtrace/*/backtrace/struct.Backtrace.html [`backtrace::Backtrace`]: https://docs.rs/backtrace/*/backtrace/struct.Backtrace.html
[`tracing_error::SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html [`tracing_error::SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html
[`color-spantrace`]: https://github.com/yaahc/color-spantrace [`color-spantrace`]: https://github.com/yaahc/color-spantrace
[`Help`]: https://docs.rs/color-eyre/*/color_eyre/trait.Help.html [`Help`]: https://docs.rs/color-eyre/*/color_eyre/trait.Help.html
[`eyre::Report`]: https://docs.rs/eyre/*/eyre/struct.Report.html [`eyre::Report`]: https://docs.rs/eyre/*/eyre/struct.Report.html
[`eyre::Result`]: https://docs.rs/eyre/*/eyre/type.Result.html [`eyre::Result`]: https://docs.rs/eyre/*/eyre/type.Result.html
[`Context`]: https://docs.rs/color-eyre/*/color_eyre/struct.Context.html [`Handler`]: https://docs.rs/color-eyre/*/color_eyre/struct.Handler.html
[`examples/usage.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/usage.rs [`examples/usage.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/usage.rs
[`examples/custom_filter.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/custom_filter.rs [`examples/custom_filter.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/custom_filter.rs
[`examples/custom_section.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/custom_section.rs [`examples/custom_section.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/custom_section.rs

View File

@ -17,16 +17,8 @@ impl Output for Command {
if !output.status.success() { if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
Err(eyre!("cmd exited with non-zero status code")) Err(eyre!("cmd exited with non-zero status code"))
.with_section(move || { .with_section(move || stdout.trim().to_string().header("Stdout:"))
"Stdout:" .with_section(move || stderr.trim().to_string().header("Stderr:"))
.skip_if(|| stdout.is_empty())
.body(stdout.trim().to_string())
})
.with_section(move || {
"Stderr:"
.skip_if(|| stderr.is_empty())
.body(stderr.trim().to_string())
})
} else { } else {
Ok(stdout.into()) Ok(stdout.into())
} }
@ -61,7 +53,7 @@ fn install_tracing() {
#[instrument] #[instrument]
fn read_file(path: &str) -> Result<String, Report> { fn read_file(path: &str) -> Result<String, Report> {
Command::new("cat").arg("fake_file").output2() Command::new("cat").arg(path).output2()
} }
#[instrument] #[instrument]

View File

@ -26,7 +26,7 @@ fn time_report_inner() {
.suggestion("try using a file that exists next time") .suggestion("try using a file that exists next time")
.unwrap_err(); .unwrap_err();
let _ = println!("Error: {:?}", report); println!("Error: {:?}", report);
drop(report); drop(report);
let end = std::time::Instant::now(); let end = std::time::Instant::now();

View File

@ -1,5 +1,5 @@
//! A custom context for the [`eyre`] crate for colorful error reports with suggestions, custom //! An error report handler for panics and the [`eyre`] crate for colorful, consistent, and well
//! sections, [`tracing-error`] support, and backtraces on stable. //! formatted error reports for all kinds of errors.
//! //!
//! ## TLDR //! ## TLDR
//! //!
@ -69,53 +69,7 @@
//! ### Multiple report format verbosity levels //! ### Multiple report format verbosity levels
//! //!
//! `color-eyre` provides 3 different report formats for how it formats the captured `SpanTrace` //! `color-eyre` provides 3 different report formats for how it formats the captured `SpanTrace`
//! and `Backtrace`, minimal, short, and full. Take the following example, taken from //! and `Backtrace`, minimal, short, and full. Take the below screenshots of the output produced by [`examples/usage.rs`]:
//! [`examples/usage.rs`]:
//!
//! ```rust,should_panic
//! use color_eyre::{Help, Report};
//! use eyre::WrapErr;
//! use tracing::{info, instrument};
//!
//! #[instrument]
//! fn main() -> Result<(), Report> {
//! #[cfg(feature = "capture-spantrace")]
//! install_tracing();
//!
//! Ok(read_config()?)
//! }
//!
//! #[cfg(feature = "capture-spantrace")]
//! fn install_tracing() {
//! use tracing_error::ErrorLayer;
//! use tracing_subscriber::prelude::*;
//! use tracing_subscriber::{fmt, EnvFilter};
//!
//! let fmt_layer = fmt::layer().with_target(false);
//! let filter_layer = EnvFilter::try_from_default_env()
//! .or_else(|_| EnvFilter::try_new("info"))
//! .unwrap();
//!
//! tracing_subscriber::registry()
//! .with(filter_layer)
//! .with(fmt_layer)
//! .with(ErrorLayer::default())
//! .init();
//! }
//!
//! #[instrument]
//! fn read_file(path: &str) -> Result<(), Report> {
//! info!("Reading file");
//! Ok(std::fs::read_to_string(path).map(drop)?)
//! }
//!
//! #[instrument]
//! fn read_config() -> Result<(), Report> {
//! read_file("fake_file")
//! .wrap_err("Unable to read config")
//! .suggestion("try using a file that exists next time")
//! }
//! ```
//! //!
//! --- //! ---
//! //!
@ -141,9 +95,10 @@
//! //!
//! ### Custom `Section`s for error reports via [`Help`] trait //! ### Custom `Section`s for error reports via [`Help`] trait
//! //!
//! The `section` module provides helpers for adding extra sections to error reports. Sections are //! The `section` module provides helpers for adding extra sections to error
//! disinct from error messages and are displayed independently from the chain of errors. Take this //! reports. Sections are disinct from error messages and are displayed
//! example of adding sections to contain `stderr` and `stdout` from a failed command, taken from //! independently from the chain of errors. Take this example of adding sections
//! to contain `stderr` and `stdout` from a failed command, taken from
//! [`examples/custom_section.rs`]: //! [`examples/custom_section.rs`]:
//! //!
//! ```rust //! ```rust
@ -166,16 +121,8 @@
//! if !output.status.success() { //! if !output.status.success() {
//! let stderr = String::from_utf8_lossy(&output.stderr); //! let stderr = String::from_utf8_lossy(&output.stderr);
//! Err(eyre!("cmd exited with non-zero status code")) //! Err(eyre!("cmd exited with non-zero status code"))
//! .with_section(move || { //! .with_section(move || stdout.trim().to_string().header("Stdout:"))
//! "Stdout:" //! .with_section(move || stderr.trim().to_string().header("Stderr:"))
//! .skip_if(|| stdout.is_empty())
//! .body(stdout.trim().to_string())
//! })
//! .with_section(move || {
//! "Stderr:"
//! .skip_if(|| stderr.is_empty())
//! .body(stderr.trim().to_string())
//! })
//! } else { //! } else {
//! Ok(stdout.into()) //! Ok(stdout.into())
//! } //! }
@ -185,26 +132,25 @@
//! //!
//! --- //! ---
//! //!
//! Here we have an function that, if the command exits unsuccessfully, creates a report indicating //! Here we have an function that, if the command exits unsuccessfully, creates a
//! the failure and attaches two sections, one for `stdout` and one for `stderr`. Each section //! report indicating the failure and attaches two sections, one for `stdout` and
//! includes a short header and a body that contains the actual output. Additionally these sections //! one for `stderr`.
//! use `skip_if` to tell the report not to include them if there was no output, preventing empty
//! sections from polluting the end report.
//! //!
//! Running `cargo run --example custom_section` shows us how these sections are included in the //! Running `cargo run --example custom_section` shows us how these sections are
//! output: //! included in the output:
//! //!
//! ![custom section example](https://raw.githubusercontent.com/yaahc/color-eyre/master/pictures/custom_section.png) //! ![custom section example](https://raw.githubusercontent.com/yaahc/color-eyre/master/pictures/custom_section.png)
//! //!
//! Only the `Stderr:` section actually gets included. The `cat` command fails, so stdout ends up //! Only the `Stderr:` section actually gets included. The `cat` command fails,
//! being empty and is skipped in the final report. This gives us a short and concise error report //! so stdout ends up being empty and is skipped in the final report. This gives
//! indicating exactly what was attempted and how it failed. //! us a short and concise error report indicating exactly what was attempted and
//! how it failed.
//! //!
//! ### Aggregating multiple errors into one report //! ### Aggregating multiple errors into one report
//! //!
//! It's not uncommon for programs like batched task runners or parsers to want to //! It's not uncommon for programs like batched task runners or parsers to want
//! return an error with multiple sources. The current version of the error trait //! to return an error with multiple sources. The current version of the error
//! does not support this use case very well, though there is [work being //! trait does not support this use case very well, though there is [work being
//! done](https://github.com/rust-lang/rfcs/pull/2895) to improve this. //! done](https://github.com/rust-lang/rfcs/pull/2895) to improve this.
//! //!
//! For now however one way to work around this is to compose errors outside the //! For now however one way to work around this is to compose errors outside the
@ -215,41 +161,43 @@
//! //!
//! ### Custom configuration for `color-backtrace` for setting custom filters and more //! ### Custom configuration for `color-backtrace` for setting custom filters and more
//! //!
//! The pretty printing for backtraces and span traces isn't actually provided by `color-eyre`, but //! The pretty printing for backtraces and span traces isn't actually provided by
//! instead comes from its dependencies [`color-backtrace`] and [`color-spantrace`]. //! `color-eyre`, but instead comes from its dependencies [`color-backtrace`] and
//! `color-backtrace` in particular has many more features than are exported by `color-eyre`, such //! [`color-spantrace`]. `color-backtrace` in particular has many more features
//! as customized color schemes, panic hooks, and custom frame filters. The custom frame filters //! than are exported by `color-eyre`, such as customized color schemes, panic
//! are particularly useful when combined with `color-eyre`, so to enable their usage we provide //! hooks, and custom frame filters. The custom frame filters are particularly
//! the `install` fn for setting up a custom `BacktracePrinter` with custom filters installed. //! useful when combined with `color-eyre`, so to enable their usage we provide
//! the `install` fn for setting up a custom `BacktracePrinter` with custom
//! filters installed.
//! //!
//! For an example of how to setup custom filters, check out [`examples/custom_filter.rs`]. //! For an example of how to setup custom filters, check out [`examples/custom_filter.rs`].
//! //!
//! ## Explanation //! ## Explanation
//! //!
//! This crate works by defining a `Context` type which implements [`eyre::EyreContext`] //! This crate works by defining a `Handler` type which implements
//! and a pair of type aliases for setting this context type as the parameter of //! [`eyre::EyreHandler`] and a pair of type aliases for setting this handler
//! [`eyre::Report`]. //! type as the parameter of [`eyre::Report`].
//! //!
//! ```rust //! ```rust
//! use color_eyre::Context; //! use color_eyre::Handler;
//! //!
//! pub type Report = eyre::Report<Context>; //! pub type Report = eyre::Report<Handler>;
//! pub type Result<T, E = Report> = core::result::Result<T, E>; //! pub type Result<T, E = Report> = core::result::Result<T, E>;
//! ``` //! ```
//! //!
//! Please refer to the [`Context`] type's docs for more details about its feature set. //! Please refer to the [`Handler`] type's docs for more details about its feature set.
//! //!
//! [`eyre`]: https://docs.rs/eyre //! [`eyre`]: https://docs.rs/eyre
//! [`tracing-error`]: https://docs.rs/tracing-error //! [`tracing-error`]: https://docs.rs/tracing-error
//! [`color-backtrace`]: https://docs.rs/color-backtrace //! [`color-backtrace`]: https://docs.rs/color-backtrace
//! [`eyre::EyreContext`]: https://docs.rs/eyre/*/eyre/trait.EyreContext.html //! [`eyre::EyreHandler`]: https://docs.rs/eyre/*/eyre/trait.EyreHandler.html
//! [`backtrace::Backtrace`]: https://docs.rs/backtrace/*/backtrace/struct.Backtrace.html //! [`backtrace::Backtrace`]: https://docs.rs/backtrace/*/backtrace/struct.Backtrace.html
//! [`tracing_error::SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html //! [`tracing_error::SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html
//! [`color-spantrace`]: https://github.com/yaahc/color-spantrace //! [`color-spantrace`]: https://github.com/yaahc/color-spantrace
//! [`Help`]: https://docs.rs/color-eyre/*/color_eyre/trait.Help.html //! [`Help`]: https://docs.rs/color-eyre/*/color_eyre/trait.Help.html
//! [`eyre::Report`]: https://docs.rs/eyre/*/eyre/struct.Report.html //! [`eyre::Report`]: https://docs.rs/eyre/*/eyre/struct.Report.html
//! [`eyre::Result`]: https://docs.rs/eyre/*/eyre/type.Result.html //! [`eyre::Result`]: https://docs.rs/eyre/*/eyre/type.Result.html
//! [`Context`]: https://docs.rs/color-eyre/*/color_eyre/struct.Context.html //! [`Handler`]: https://docs.rs/color-eyre/*/color_eyre/struct.Handler.html
//! [`examples/usage.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/usage.rs //! [`examples/usage.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/usage.rs
//! [`examples/custom_filter.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/custom_filter.rs //! [`examples/custom_filter.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/custom_filter.rs
//! [`examples/custom_section.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/custom_section.rs //! [`examples/custom_section.rs`]: https://github.com/yaahc/color-eyre/blob/master/examples/custom_section.rs
@ -279,13 +227,14 @@
unused_parens, unused_parens,
while_true while_true
)] )]
use crate::writers::HeaderWriter;
use ansi_term::Color::*; use ansi_term::Color::*;
use backtrace::Backtrace; use backtrace::Backtrace;
pub use color_backtrace::BacktracePrinter; pub use color_backtrace::BacktracePrinter;
use eyre::*; use eyre::*;
use indenter::{indented, Format}; use indenter::{indented, Format};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use section::Order; use section::help::HelpInfo;
pub use section::{help::Help, Section, SectionExt}; pub use section::{help::Help, Section, SectionExt};
#[cfg(feature = "capture-spantrace")] #[cfg(feature = "capture-spantrace")]
use std::error::Error; use std::error::Error;
@ -296,12 +245,15 @@ use std::{
}; };
#[cfg(feature = "capture-spantrace")] #[cfg(feature = "capture-spantrace")]
use tracing_error::{ExtractSpanTrace, SpanTrace, SpanTraceStatus}; use tracing_error::{ExtractSpanTrace, SpanTrace, SpanTraceStatus};
#[doc(hidden)]
pub use Handler as Context;
pub mod section; pub mod section;
mod writers;
static CONFIG: OnceCell<BacktracePrinter> = OnceCell::new(); static CONFIG: OnceCell<BacktracePrinter> = OnceCell::new();
/// A custom context type for [`eyre::Report`] which provides colorful error /// A custom handler type for [`eyre::Report`] which provides colorful error
/// reports and [`tracing-error`] support. /// reports and [`tracing-error`] support.
/// ///
/// This type is not intended to be used directly, prefer using it via the /// This type is not intended to be used directly, prefer using it via the
@ -312,17 +264,19 @@ static CONFIG: OnceCell<BacktracePrinter> = OnceCell::new();
/// [`color_eyre::Report`]: type.Report.html /// [`color_eyre::Report`]: type.Report.html
/// [`color_eyre::Result`]: type.Result.html /// [`color_eyre::Result`]: type.Result.html
#[derive(Debug)] #[derive(Debug)]
pub struct Context { pub struct Handler {
backtrace: Option<Backtrace>, backtrace: Option<Backtrace>,
#[cfg(feature = "capture-spantrace")] #[cfg(feature = "capture-spantrace")]
span_trace: Option<SpanTrace>, span_trace: Option<SpanTrace>,
sections: Vec<Section>, sections: Vec<HelpInfo>,
} }
#[derive(Debug)] #[derive(Debug)]
struct InstallError; struct InstallError;
#[cfg(feature = "capture-spantrace")]
struct FormattedSpanTrace<'a>(&'a SpanTrace);
impl Context { impl Handler {
/// Return a reference to the captured `Backtrace` type /// Return a reference to the captured `Backtrace` type
/// ///
/// # Examples /// # Examples
@ -336,7 +290,7 @@ impl Context {
/// std::env::set_var("RUST_BACKTRACE", "1"); /// std::env::set_var("RUST_BACKTRACE", "1");
/// ///
/// let report: Report = eyre!("an error occurred"); /// let report: Report = eyre!("an error occurred");
/// assert!(report.context().backtrace().is_some()); /// assert!(report.handler().backtrace().is_some());
/// ``` /// ```
/// ///
/// Alternatively, if you don't want backtraces to be printed on panic, you can use /// Alternatively, if you don't want backtraces to be printed on panic, you can use
@ -349,7 +303,7 @@ impl Context {
/// std::env::set_var("RUST_LIB_BACKTRACE", "1"); /// std::env::set_var("RUST_LIB_BACKTRACE", "1");
/// ///
/// let report: Report = eyre!("an error occurred"); /// let report: Report = eyre!("an error occurred");
/// assert!(report.context().backtrace().is_some()); /// assert!(report.handler().backtrace().is_some());
/// ``` /// ```
/// ///
/// And if you don't want backtraces to be captured but you still want panics to print /// And if you don't want backtraces to be captured but you still want panics to print
@ -363,7 +317,7 @@ impl Context {
/// std::env::set_var("RUST_LIB_BACKTRACE", "0"); /// std::env::set_var("RUST_LIB_BACKTRACE", "0");
/// ///
/// let report: Report = eyre!("an error occurred"); /// let report: Report = eyre!("an error occurred");
/// assert!(report.context().backtrace().is_none()); /// assert!(report.handler().backtrace().is_none());
/// ``` /// ```
/// ///
pub fn backtrace(&self) -> Option<&Backtrace> { pub fn backtrace(&self) -> Option<&Backtrace> {
@ -381,7 +335,7 @@ impl Context {
/// use eyre::eyre; /// use eyre::eyre;
/// ///
/// let report: Report = eyre!("an error occurred"); /// let report: Report = eyre!("an error occurred");
/// assert!(report.context().span_trace().is_some()); /// assert!(report.handler().span_trace().is_some());
/// ``` /// ```
/// ///
/// However, `SpanTrace` is not captured if one of the source errors already captured a /// However, `SpanTrace` is not captured if one of the source errors already captured a
@ -409,7 +363,7 @@ impl Context {
/// let error: TracedError<SourceError> = error.in_current_span(); /// let error: TracedError<SourceError> = error.in_current_span();
/// ///
/// let report: Report = error.into(); /// let report: Report = error.into();
/// assert!(report.context().span_trace().is_none()); /// assert!(report.handler().span_trace().is_none());
/// ``` /// ```
/// ///
/// [`tracing_error::TracedError`]: https://docs.rs/tracing-error/0.1.2/tracing_error/struct.TracedError.html /// [`tracing_error::TracedError`]: https://docs.rs/tracing-error/0.1.2/tracing_error/struct.TracedError.html
@ -420,7 +374,7 @@ impl Context {
} }
} }
impl EyreContext for Context { impl EyreHandler for Handler {
#[allow(unused_variables)] #[allow(unused_variables)]
fn default(error: &(dyn std::error::Error + 'static)) -> Self { fn default(error: &(dyn std::error::Error + 'static)) -> Self {
let backtrace = if backtrace_enabled() { let backtrace = if backtrace_enabled() {
@ -463,26 +417,32 @@ impl EyreContext for Context {
let mut buf = String::new(); let mut buf = String::new();
for (n, error) in errors { for (n, error) in errors {
writeln!(f)?;
buf.clear(); buf.clear();
write!(&mut buf, "{}", error).unwrap(); write!(&mut buf, "{}", error).unwrap();
writeln!(f)?;
write!(indented(f).ind(n), "{}", Red.paint(&buf))?; write!(indented(f).ind(n), "{}", Red.paint(&buf))?;
} }
let separated = &mut HeaderWriter {
inner: &mut *f,
header: &"\n\n",
started: false,
};
for section in self for section in self
.sections .sections
.iter() .iter()
.filter(|s| matches!(s.order, Order::AfterErrMsgs)) .filter(|s| matches!(s, HelpInfo::Error(_)))
{ {
write!(f, "\n\n{:?}", section)?; write!(separated.ready(), "{}", section)?;
} }
for section in self for section in self
.sections .sections
.iter() .iter()
.filter(|s| matches!(s.order, Order::BeforeSpanTrace)) .filter(|s| matches!(s, HelpInfo::Custom(_)))
{ {
write!(f, "\n\n{:?}", section)?; write!(separated.ready(), "{}", section)?;
} }
#[cfg(feature = "capture-spantrace")] #[cfg(feature = "capture-spantrace")]
@ -493,33 +453,23 @@ impl EyreContext for Context {
.or_else(|| get_deepest_spantrace(error)) .or_else(|| get_deepest_spantrace(error))
.expect("SpanTrace capture failed"); .expect("SpanTrace capture failed");
match span_trace.status() { write!(&mut separated.ready(), "{}", FormattedSpanTrace(span_trace))?;
SpanTraceStatus::CAPTURED => {
write!(f, "\n\n")?;
write!(indented(f).with_format(Format::Uniform { indentation: " " }), "{}", color_spantrace::colorize(span_trace))?
},
SpanTraceStatus::UNSUPPORTED => write!(f, "\n\nWarning: SpanTrace capture is Unsupported.\nEnsure that you've setup an error layer and the versions match")?,
_ => (),
}
} }
if let Some(backtrace) = self.backtrace.as_ref() { if let Some(backtrace) = self.backtrace.as_ref() {
write!(f, "\n\n")?; let bt_str = installed_printer()
let bt_str = CONFIG
.get_or_init(default_printer)
.format_trace_to_string(&backtrace) .format_trace_to_string(&backtrace)
.unwrap(); .unwrap();
write!( write!(
indented(f).with_format(Format::Uniform { indentation: " " }), indented(&mut separated.ready()).with_format(Format::Uniform { indentation: " " }),
"{}", "{}",
bt_str bt_str
)?; )?;
} else if self } else if self
.sections .sections
.iter() .iter()
.any(|s| matches!(s.order, Order::AfterBackTrace)) .any(|s| !matches!(s, HelpInfo::Custom(_) | HelpInfo::Error(_)))
{ {
writeln!(f)?; writeln!(f)?;
} }
@ -527,9 +477,26 @@ impl EyreContext for Context {
for section in self for section in self
.sections .sections
.iter() .iter()
.filter(|s| matches!(s.order, Order::AfterBackTrace)) .filter(|s| !matches!(s, HelpInfo::Custom(_) | HelpInfo::Error(_)))
{ {
write!(f, "\n{:?}", section)?; write!(f, "\n{}", section)?;
}
Ok(())
}
}
#[cfg(feature = "capture-spantrace")]
impl fmt::Display for FormattedSpanTrace<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use std::fmt::Write;
match self.0.status() {
SpanTraceStatus::CAPTURED => {
write!(indented(f).with_format(Format::Uniform { indentation: " " }), "{}", color_spantrace::colorize(self.0))?;
},
SpanTraceStatus::UNSUPPORTED => write!(f, "Warning: SpanTrace capture is Unsupported.\nEnsure that you've setup an error layer and the versions match")?,
_ => (),
} }
Ok(()) Ok(())
@ -573,7 +540,7 @@ fn get_deepest_spantrace<'a>(error: &'a (dyn Error + 'static)) -> Option<&'a Spa
.next() .next()
} }
/// Override the global BacktracePrinter used by `color_eyre::Context` when printing captured /// Override the global BacktracePrinter used by `color_eyre::Handler` when printing captured
/// backtraces. /// backtraces.
/// ///
/// # Examples /// # Examples
@ -606,7 +573,16 @@ fn get_deepest_spantrace<'a>(error: &'a (dyn Error + 'static)) -> Option<&'a Spa
/// ``` /// ```
pub fn install(printer: BacktracePrinter) -> Result<(), impl std::error::Error> { pub fn install(printer: BacktracePrinter) -> Result<(), impl std::error::Error> {
let printer = add_eyre_filters(printer); let printer = add_eyre_filters(printer);
CONFIG.set(printer).map_err(|_| InstallError)
if CONFIG.set(printer).is_err() {
return Err(InstallError);
}
Ok(())
}
fn installed_printer() -> &'static color_backtrace::BacktracePrinter {
CONFIG.get_or_init(default_printer)
} }
fn default_printer() -> BacktracePrinter { fn default_printer() -> BacktracePrinter {
@ -616,7 +592,7 @@ fn default_printer() -> BacktracePrinter {
fn add_eyre_filters(printer: BacktracePrinter) -> BacktracePrinter { fn add_eyre_filters(printer: BacktracePrinter) -> BacktracePrinter {
printer.add_frame_filter(Box::new(|frames| { printer.add_frame_filter(Box::new(|frames| {
let filters = &[ let filters = &[
"<color_eyre::Context as eyre::EyreContext>::default", "<color_eyre::Handler as eyre::EyreHandler>::default",
"eyre::", "eyre::",
"color_eyre::", "color_eyre::",
]; ];
@ -635,7 +611,7 @@ fn add_eyre_filters(printer: BacktracePrinter) -> BacktracePrinter {
})) }))
} }
/// A type alias for `eyre::Report<color_eyre::Context>` /// A type alias for `eyre::Report<color_eyre::Handler>`
/// ///
/// # Example /// # Example
/// ///
@ -648,7 +624,7 @@ fn add_eyre_filters(printer: BacktracePrinter) -> BacktracePrinter {
/// # Ok(Config) /// # Ok(Config)
/// } /// }
/// ``` /// ```
pub type Report = eyre::Report<Context>; pub type Report = eyre::Report<Handler>;
/// A type alias for `Result<T, color_eyre::Report>` /// A type alias for `Result<T, color_eyre::Report>`
/// ///

View File

@ -1,6 +1,8 @@
//! Provides an extension trait for attaching `Section`s to error reports. //! Provides an extension trait for attaching `Section`s to error reports.
use crate::{section, Report, Result, Section}; use crate::{Report, Result};
use ansi_term::Color::*; use ansi_term::Color::*;
use indenter::indented;
use std::fmt::Write;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
/// A helper trait for attaching help text to errors to be displayed after the chain of errors /// A helper trait for attaching help text to errors to be displayed after the chain of errors
@ -34,9 +36,9 @@ pub trait Help<T>: private::Sealed {
/// .section("Please report bugs to https://real.url/bugs")?; /// .section("Please report bugs to https://real.url/bugs")?;
/// # Ok::<_, Report>(()) /// # Ok::<_, Report>(())
/// ``` /// ```
fn section<C>(self, section: C) -> Result<T> fn section<D>(self, section: D) -> Result<T>
where where
C: Into<Section>; D: Display + Send + Sync + 'static;
/// Add a Section to an error report, to be displayed after the chain of errors. The closure to /// Add a Section to an error report, to be displayed after the chain of errors. The closure to
/// create the Section is lazily evaluated only in the case of an error. /// create the Section is lazily evaluated only in the case of an error.
@ -53,11 +55,7 @@ pub trait Help<T>: private::Sealed {
/// let output = if !output.status.success() { /// let output = if !output.status.success() {
/// let stderr = String::from_utf8_lossy(&output.stderr); /// let stderr = String::from_utf8_lossy(&output.stderr);
/// Err(eyre!("cmd exited with non-zero status code")) /// Err(eyre!("cmd exited with non-zero status code"))
/// .with_section(move || { /// .with_section(move || stderr.trim().to_string().header("Stderr:"))?
/// "Stderr:"
/// .skip_if(|| stderr.is_empty())
/// .body(stderr.trim().to_string())
/// })?
/// } else { /// } else {
/// String::from_utf8_lossy(&output.stdout) /// String::from_utf8_lossy(&output.stdout)
/// }; /// };
@ -65,10 +63,10 @@ pub trait Help<T>: private::Sealed {
/// println!("{}", output); /// println!("{}", output);
/// # Ok::<_, Report>(()) /// # Ok::<_, Report>(())
/// ``` /// ```
fn with_section<C, F>(self, section: F) -> Result<T> fn with_section<D, F>(self, section: F) -> Result<T>
where where
C: Into<Section>, D: Display + Send + Sync + 'static,
F: FnOnce() -> C; F: FnOnce() -> D;
/// Add an error section to an error report, to be displayed after the primary error message /// Add an error section to an error report, to be displayed after the primary error message
/// section. /// section.
@ -142,9 +140,9 @@ pub trait Help<T>: private::Sealed {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
fn note<C>(self, context: C) -> Result<T> fn note<D>(self, note: D) -> Result<T>
where where
C: Display + Send + Sync + 'static; D: Display + Send + Sync + 'static;
/// Add a Note to an error report, to be displayed after the chain of errors. The closure to /// Add a Note to an error report, to be displayed after the chain of errors. The closure to
/// create the Note is lazily evaluated only in the case of an error. /// create the Note is lazily evaluated only in the case of an error.
@ -174,156 +172,142 @@ pub trait Help<T>: private::Sealed {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
fn with_note<C, F>(self, f: F) -> Result<T> fn with_note<D, F>(self, f: F) -> Result<T>
where where
C: Display + Send + Sync + 'static, D: Display + Send + Sync + 'static,
F: FnOnce() -> C; F: FnOnce() -> D;
/// Add a Warning to an error report, to be displayed after the chain of errors. /// Add a Warning to an error report, to be displayed after the chain of errors.
fn warning<C>(self, context: C) -> Result<T> fn warning<D>(self, warning: D) -> Result<T>
where where
C: Display + Send + Sync + 'static; D: Display + Send + Sync + 'static;
/// Add a Warning to an error report, to be displayed after the chain of errors. The closure to /// Add a Warning to an error report, to be displayed after the chain of errors. The closure to
/// create the Warning is lazily evaluated only in the case of an error. /// create the Warning is lazily evaluated only in the case of an error.
fn with_warning<C, F>(self, f: F) -> Result<T> fn with_warning<D, F>(self, f: F) -> Result<T>
where where
C: Display + Send + Sync + 'static, D: Display + Send + Sync + 'static,
F: FnOnce() -> C; F: FnOnce() -> D;
/// Add a Suggestion to an error report, to be displayed after the chain of errors. /// Add a Suggestion to an error report, to be displayed after the chain of errors.
fn suggestion<C>(self, context: C) -> Result<T> fn suggestion<D>(self, suggestion: D) -> Result<T>
where where
C: Display + Send + Sync + 'static; D: Display + Send + Sync + 'static;
/// Add a Suggestion to an error report, to be displayed after the chain of errors. The closure /// Add a Suggestion to an error report, to be displayed after the chain of errors. The closure
/// to create the Suggestion is lazily evaluated only in the case of an error. /// to create the Suggestion is lazily evaluated only in the case of an error.
fn with_suggestion<C, F>(self, f: F) -> Result<T> fn with_suggestion<D, F>(self, f: F) -> Result<T>
where where
C: Display + Send + Sync + 'static, D: Display + Send + Sync + 'static,
F: FnOnce() -> C; F: FnOnce() -> D;
} }
impl<T, E> Help<T> for std::result::Result<T, E> impl<T, E> Help<T> for std::result::Result<T, E>
where where
E: Into<Report>, E: Into<Report>,
{ {
fn note<C>(self, context: C) -> Result<T> fn note<D>(self, note: D) -> Result<T>
where where
C: Display + Send + Sync + 'static, D: Display + Send + Sync + 'static,
{ {
self.map_err(|e| { self.map_err(|e| {
let mut e = e.into(); let mut e = e.into();
e.context_mut().sections.push( e.handler_mut()
Section::from(HelpInfo::Note(Box::new(context))) .sections
.order(section::Order::AfterBackTrace), .push(HelpInfo::Note(Box::new(note)));
);
e e
}) })
} }
fn with_note<C, F>(self, context: F) -> Result<T> fn with_note<D, F>(self, note: F) -> Result<T>
where where
C: Display + Send + Sync + 'static, D: Display + Send + Sync + 'static,
F: FnOnce() -> C, F: FnOnce() -> D,
{ {
self.map_err(|e| { self.map_err(|e| {
let mut e = e.into(); let mut e = e.into();
e.context_mut().sections.push( e.handler_mut()
Section::from(HelpInfo::Note(Box::new(context()))) .sections
.order(section::Order::AfterBackTrace), .push(HelpInfo::Note(Box::new(note())));
);
e e
}) })
} }
fn warning<C>(self, context: C) -> Result<T> fn warning<D>(self, warning: D) -> Result<T>
where where
C: Display + Send + Sync + 'static, D: Display + Send + Sync + 'static,
{ {
self.map_err(|e| { self.map_err(|e| {
let mut e = e.into(); let mut e = e.into();
e.context_mut().sections.push( e.handler_mut()
Section::from(HelpInfo::Warning(Box::new(context))) .sections
.order(section::Order::AfterBackTrace), .push(HelpInfo::Warning(Box::new(warning)));
);
e e
}) })
} }
fn with_warning<C, F>(self, context: F) -> Result<T> fn with_warning<D, F>(self, warning: F) -> Result<T>
where where
C: Display + Send + Sync + 'static, D: Display + Send + Sync + 'static,
F: FnOnce() -> C, F: FnOnce() -> D,
{ {
self.map_err(|e| { self.map_err(|e| {
let mut e = e.into(); let mut e = e.into();
e.context_mut().sections.push( e.handler_mut()
Section::from(HelpInfo::Warning(Box::new(context()))) .sections
.order(section::Order::AfterBackTrace), .push(HelpInfo::Warning(Box::new(warning())));
);
e e
}) })
} }
fn suggestion<C>(self, context: C) -> Result<T> fn suggestion<D>(self, suggestion: D) -> Result<T>
where where
C: Display + Send + Sync + 'static, D: Display + Send + Sync + 'static,
{ {
self.map_err(|e| { self.map_err(|e| {
let mut e = e.into(); let mut e = e.into();
e.context_mut().sections.push( e.handler_mut()
Section::from(HelpInfo::Suggestion(Box::new(context))) .sections
.order(section::Order::AfterBackTrace), .push(HelpInfo::Suggestion(Box::new(suggestion)));
);
e e
}) })
} }
fn with_suggestion<C, F>(self, context: F) -> Result<T> fn with_suggestion<D, F>(self, suggestion: F) -> Result<T>
where where
C: Display + Send + Sync + 'static, D: Display + Send + Sync + 'static,
F: FnOnce() -> C, F: FnOnce() -> D,
{ {
self.map_err(|e| { self.map_err(|e| {
let mut e = e.into(); let mut e = e.into();
e.context_mut().sections.push( e.handler_mut()
Section::from(HelpInfo::Suggestion(Box::new(context()))) .sections
.order(section::Order::AfterBackTrace), .push(HelpInfo::Suggestion(Box::new(suggestion())));
);
e e
}) })
} }
fn with_section<C, F>(self, section: F) -> Result<T> fn with_section<D, F>(self, section: F) -> Result<T>
where where
C: Into<Section>, D: Display + Send + Sync + 'static,
F: FnOnce() -> C, F: FnOnce() -> D,
{ {
self.map_err(|e| { self.map_err(|e| {
let mut e = e.into(); let mut e = e.into();
let section = section().into(); let section = Box::new(section());
e.handler_mut().sections.push(HelpInfo::Custom(section));
if !matches!(section.order, section::Order::SkipEntirely) {
e.context_mut().sections.push(section);
}
e e
}) })
} }
fn section<C>(self, section: C) -> Result<T> fn section<D>(self, section: D) -> Result<T>
where where
C: Into<Section>, D: Display + Send + Sync + 'static,
{ {
self.map_err(|e| { self.map_err(|e| {
let mut e = e.into(); let mut e = e.into();
let section = section.into(); let section = Box::new(section);
e.handler_mut().sections.push(HelpInfo::Custom(section));
if !matches!(section.order, section::Order::SkipEntirely) {
e.context_mut().sections.push(section);
}
e e
}) })
} }
@ -334,12 +318,9 @@ where
{ {
self.map_err(|e| { self.map_err(|e| {
let mut e = e.into(); let mut e = e.into();
let section = Section { let error = error.into();
inner: section::SectionKind::Error(Box::new(error)),
order: section::Order::AfterErrMsgs,
};
e.context_mut().sections.push(section); e.handler_mut().sections.push(HelpInfo::Error(error));
e e
}) })
} }
@ -351,18 +332,17 @@ where
{ {
self.map_err(|e| { self.map_err(|e| {
let mut e = e.into(); let mut e = e.into();
let section = Section { let error = error().into();
inner: section::SectionKind::Error(Box::new(error())),
order: section::Order::AfterErrMsgs,
};
e.context_mut().sections.push(section); e.handler_mut().sections.push(HelpInfo::Error(error));
e e
}) })
} }
} }
pub(crate) enum HelpInfo { pub(crate) enum HelpInfo {
Error(Box<dyn std::error::Error + Send + Sync + 'static>),
Custom(Box<dyn Display + Send + Sync + 'static>),
Note(Box<dyn Display + Send + Sync + 'static>), Note(Box<dyn Display + Send + Sync + 'static>),
Warning(Box<dyn Display + Send + Sync + 'static>), Warning(Box<dyn Display + Send + Sync + 'static>),
Suggestion(Box<dyn Display + Send + Sync + 'static>), Suggestion(Box<dyn Display + Send + Sync + 'static>),
@ -371,9 +351,28 @@ pub(crate) enum HelpInfo {
impl Display for HelpInfo { impl Display for HelpInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Note(context) => write!(f, "{}: {}", Cyan.paint("Note"), context), HelpInfo::Note(n) => write!(f, "{}: {}", Cyan.paint("Note"), n),
Self::Warning(context) => write!(f, "{}: {}", Yellow.paint("Warning"), context), HelpInfo::Warning(w) => write!(f, "{}: {}", Yellow.paint("Warning"), w),
Self::Suggestion(context) => write!(f, "{}: {}", Cyan.paint("Suggestion"), context), HelpInfo::Suggestion(s) => write!(f, "{}: {}", Cyan.paint("Suggestion"), s),
HelpInfo::Custom(c) => write!(f, "{}", c),
HelpInfo::Error(e) => {
// a lot here
let errors = std::iter::successors(
Some(e.as_ref() as &(dyn std::error::Error + 'static)),
|e| e.source(),
);
write!(f, "Error:")?;
let mut buf = String::new();
for (n, e) in errors.enumerate() {
writeln!(f)?;
buf.clear();
write!(&mut buf, "{}", e).unwrap();
write!(indented(f).ind(n), "{}", Red.paint(&buf))?;
}
Ok(())
}
} }
} }
} }
@ -381,18 +380,23 @@ impl Display for HelpInfo {
impl fmt::Debug for HelpInfo { impl fmt::Debug for HelpInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Note(context) => f HelpInfo::Note(note) => f
.debug_tuple("Note") .debug_tuple("Note")
.field(&format_args!("{}", context)) .field(&format_args!("{}", note))
.finish(), .finish(),
Self::Warning(context) => f HelpInfo::Warning(warning) => f
.debug_tuple("Warning") .debug_tuple("Warning")
.field(&format_args!("{}", context)) .field(&format_args!("{}", warning))
.finish(), .finish(),
Self::Suggestion(context) => f HelpInfo::Suggestion(suggestion) => f
.debug_tuple("Suggestion") .debug_tuple("Suggestion")
.field(&format_args!("{}", context)) .field(&format_args!("{}", suggestion))
.finish(), .finish(),
HelpInfo::Custom(custom) => f
.debug_tuple("CustomSection")
.field(&format_args!("{}", custom))
.finish(),
HelpInfo::Error(error) => f.debug_tuple("Error").field(error).finish(),
} }
} }
} }

View File

@ -1,33 +1,18 @@
//! Helpers for adding custom sections to error reports //! Helpers for adding custom sections to error reports
use ansi_term::Color::*;
use indenter::indented;
use std::fmt::{self, Display, Write}; use std::fmt::{self, Display, Write};
pub mod help; pub mod help;
#[non_exhaustive] /// An indenteted section with a header for an error report
#[derive(Debug)]
pub(crate) enum Order {
AfterErrMsgs,
BeforeSpanTrace,
AfterBackTrace,
SkipEntirely,
}
/// A custom section for an error report.
/// ///
/// # Details /// # Details
/// ///
/// Sections consist of two parts, a header: and an optional body. The header can contain any /// This helper provides two functions to help with constructing nicely formatted
/// number of lines and has no indentation applied to it by default. The body can contain any /// error reports. First, it handles indentation of every line of the body for
/// number of lines and is always written after the header with indentation inserted before /// you, and makes sure it is consistent with the rest of color-eyre's output.
/// every line. /// Second, it omits outputting the header if the body itself is empty,
/// /// preventing unnecessary pollution of the report for sections with dynamic
/// # Construction /// content.
///
/// Sections are meant to be constructed via `Into<Section>`, which is implemented for all types
/// that implement `Display`. The constructed `Section` then takes ownership of the `Display` type
/// and boxes it internally for use later when printing the report.
/// ///
/// # Examples /// # Examples
/// ///
@ -51,41 +36,27 @@ pub(crate) enum Order {
/// if !output.status.success() { /// if !output.status.success() {
/// let stderr = String::from_utf8_lossy(&output.stderr); /// let stderr = String::from_utf8_lossy(&output.stderr);
/// Err(eyre!("cmd exited with non-zero status code")) /// Err(eyre!("cmd exited with non-zero status code"))
/// .with_section(move || { /// .with_section(move || stdout.trim().to_string().header("Stdout:"))
/// "Stdout:" /// .with_section(move || stderr.trim().to_string().header("Stderr:"))
/// .skip_if(|| stdout.is_empty())
/// .body(stdout.trim().to_string())
/// })
/// .with_section(move || {
/// "Stderr:"
/// .skip_if(|| stderr.is_empty())
/// .body(stderr.trim().to_string())
/// })
/// } else { /// } else {
/// Ok(stdout.into()) /// Ok(stdout.into())
/// } /// }
/// } /// }
/// } /// }
/// ``` /// ```
pub struct Section { #[allow(missing_debug_implementations)]
pub(crate) inner: SectionKind, pub struct Section<H, B> {
pub(crate) order: Order, header: H,
body: B,
} }
pub(crate) enum SectionKind { /// Extension trait for constructing sections with commonly used formats
Header(Box<dyn Display + Send + Sync + 'static>), pub trait SectionExt: Sized {
WithBody( /// Add a header to a `Section` and indent the body
Box<dyn Display + Send + Sync + 'static>,
Box<dyn Display + Send + Sync + 'static>,
),
Error(Box<dyn std::error::Error + Send + Sync + 'static>),
}
/// Extension trait for customizing the content of a `Section`
pub trait SectionExt {
/// Add a body to a `Section`
/// ///
/// Bodies are always indented to the same level as error messages and spans. /// Bodies are always indented to the same level as error messages and spans.
/// The header is not printed if the display impl of the body produces no
/// output.
/// ///
/// # Examples /// # Examples
/// ///
@ -101,147 +72,52 @@ pub trait SectionExt {
/// let just_header = "header"; /// let just_header = "header";
/// let just_body = "body\nbody"; /// let just_body = "body\nbody";
/// let report2 = Err::<(), Report>(eyre!("an error occurred")) /// let report2 = Err::<(), Report>(eyre!("an error occurred"))
/// .section(just_header.body(just_body)) /// .section(just_body.header(just_header))
/// .unwrap_err(); /// .unwrap_err();
/// ///
/// assert_eq!(format!("{:?}", report), format!("{:?}", report2)) /// assert_eq!(format!("{:?}", report), format!("{:?}", report2))
/// ``` /// ```
fn body<C>(self, body: C) -> Section fn header<C>(self, header: C) -> Section<C, Self>
where where
C: Display + Send + Sync + 'static; C: Display + Send + Sync + 'static;
/// Skip a section based on some condition. For example, skip a section if the body is empty.
///
/// The skipped section is not stored in the report and is instead immediately dropped.
///
/// # Examples
///
/// ```rust
/// use eyre::eyre;
/// use color_eyre::{SectionExt, Report, Help};
///
/// fn add_body(report: Report, body: String) -> Result<(), Report> {
/// Err(report)
/// .with_section(|| "ExtraInfo:".skip_if(|| body.is_empty()).body(body))
/// }
///
/// let report = eyre!("an error occurred");
/// let before = format!("{:?}", report);
/// let body = String::new();
/// let report = add_body(report, body).unwrap_err();
/// let after = format!("{:?}", report);
/// assert_eq!(before, after);
///
/// let report = eyre!("an error occurred");
/// let before = format!("{:?}", report);
/// let body = String::from("Some actual text here");
/// let report = add_body(report, body).unwrap_err();
/// let after = format!("{:?}", report);
/// assert_ne!(before, after);
/// ```
fn skip_if<F>(self, condition: F) -> Section
where
F: FnOnce() -> bool;
}
impl Section {
pub(crate) fn order(mut self, order: Order) -> Self {
self.order = order;
self
}
} }
impl<T> SectionExt for T impl<T> SectionExt for T
where where
Section: From<T>, T: Display + Send + Sync + 'static,
{ {
fn body<C>(self, body: C) -> Section fn header<C>(self, header: C) -> Section<C, Self>
where where
C: Display + Send + Sync + 'static, C: Display + Send + Sync + 'static,
{ {
let section = Section::from(self); Section { body: self, header }
let header = match section.inner {
SectionKind::Header(header) => header,
SectionKind::WithBody(header, _body) => header,
SectionKind::Error(_) => unreachable!("bodies cannot be added to Error sections"),
};
let inner = SectionKind::WithBody(header, Box::new(body));
Section {
inner,
order: section.order,
}
}
fn skip_if<F>(self, condition: F) -> Section
where
F: FnOnce() -> bool,
{
let mut section = Section::from(self);
section.order = if condition() {
Order::SkipEntirely
} else {
section.order
};
section
} }
} }
impl<T> From<T> for Section impl<H, B> fmt::Display for Section<H, B>
where where
T: Display + Send + Sync + 'static, H: Display + Send + Sync + 'static,
B: Display + Send + Sync + 'static,
{ {
fn from(header: T) -> Self {
let inner = SectionKind::Header(Box::new(header));
Self {
inner,
order: Order::BeforeSpanTrace,
}
}
}
impl fmt::Debug for Section {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.inner) let mut headered = crate::writers::HeaderWriter {
} inner: f,
} header: &self.header,
started: false,
};
impl fmt::Display for SectionKind { let mut headered = crate::writers::HeaderWriter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { inner: headered.ready(),
match self { header: &"\n",
SectionKind::Header(header) => write!(f, "{}", header)?, started: false,
SectionKind::WithBody(header, body) => { };
write!(f, "{}", header)?;
writeln!(f)?;
write!(
indenter::indented(f)
.with_format(indenter::Format::Uniform { indentation: " " }),
"{}",
body
)?;
}
SectionKind::Error(error) => {
// a lot here
let errors = std::iter::successors(
Some(error.as_ref() as &(dyn std::error::Error + 'static)),
|e| e.source(),
);
write!(f, "Error:")?; let mut headered = headered.ready();
let mut buf = String::new();
for (n, error) in errors.enumerate() { let mut indented = indenter::indented(&mut headered)
writeln!(f)?; .with_format(indenter::Format::Uniform { indentation: " " });
buf.clear();
write!(&mut buf, "{}", error).unwrap(); write!(&mut indented, "{}", self.body)?;
write!(indented(f).ind(n), "{}", Red.paint(&buf))?;
}
}
}
Ok(()) Ok(())
} }

32
src/writers.rs Normal file
View File

@ -0,0 +1,32 @@
use std::fmt::{self, Display};
pub(crate) struct HeaderWriter<'a, H, W> {
pub(crate) inner: W,
pub(crate) header: &'a H,
pub(crate) started: bool,
}
pub(crate) struct ReadyHeaderWriter<'a, 'b, H, W>(&'b mut HeaderWriter<'a, H, W>);
impl<'a, H, W> HeaderWriter<'a, H, W> {
pub(crate) fn ready(&mut self) -> ReadyHeaderWriter<'a, '_, H, W> {
self.started = false;
ReadyHeaderWriter(self)
}
}
impl<H, W> fmt::Write for ReadyHeaderWriter<'_, '_, H, W>
where
H: Display,
W: fmt::Write,
{
fn write_str(&mut self, s: &str) -> fmt::Result {
if !self.0.started && !s.is_empty() {
self.0.inner.write_fmt(format_args!("{}", self.0.header))?;
self.0.started = true;
}
self.0.inner.write_str(s)
}
}