mirror of
https://github.com/eyre-rs/eyre.git
synced 2025-09-30 06:21:52 +00:00
Simplify sections features (#22)
* Simplify sections features * cleanup docs a bit * update docs and switch context to handler
This commit is contained in:
parent
fe284a81b1
commit
5b6507d455
@ -4,7 +4,7 @@ version = "0.3.2"
|
||||
authors = ["Jane Lusby <jlusby@yaah.dev>"]
|
||||
edition = "2018"
|
||||
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"
|
||||
documentation = "https://docs.rs/color-eyre"
|
||||
readme = "README.md"
|
||||
@ -16,7 +16,7 @@ default = ["capture-spantrace"]
|
||||
capture-spantrace = ["tracing-error", "color-spantrace"]
|
||||
|
||||
[dependencies]
|
||||
eyre = "0.4.2"
|
||||
eyre = "0.4.3"
|
||||
tracing-error = { version = "0.1.2", optional = true }
|
||||
color-backtrace = "0.4.0"
|
||||
backtrace = "0.3.48"
|
||||
|
126
README.md
126
README.md
@ -11,8 +11,8 @@
|
||||
[docs-badge]: https://img.shields.io/badge/docs-latest-blue.svg
|
||||
[docs-url]: https://docs.rs/color-eyre
|
||||
|
||||
A custom context for the [`eyre`] crate for colorful error reports with suggestions, custom
|
||||
sections, [`tracing-error`] support, and backtraces on stable.
|
||||
An error report handler for panics and the [`eyre`] crate for colorful, consistent, and well
|
||||
formatted error reports for all kinds of errors.
|
||||
|
||||
## TLDR
|
||||
|
||||
@ -82,53 +82,7 @@ opt-level = 3
|
||||
### Multiple report format verbosity levels
|
||||
|
||||
`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
|
||||
[`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")
|
||||
}
|
||||
```
|
||||
and `Backtrace`, minimal, short, and full. Take the below screenshots of the output produced by [`examples/usage.rs`]:
|
||||
|
||||
---
|
||||
|
||||
@ -154,9 +108,10 @@ error originated from, assuming it can find them on the disk.
|
||||
|
||||
### Custom `Section`s for error reports via [`Help`] trait
|
||||
|
||||
The `section` module provides helpers for adding extra sections to error reports. Sections are
|
||||
disinct from error messages and are displayed independently from the chain of errors. Take this
|
||||
example of adding sections to contain `stderr` and `stdout` from a failed command, taken from
|
||||
The `section` module provides helpers for adding extra sections to error
|
||||
reports. Sections are disinct from error messages and are displayed
|
||||
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`]:
|
||||
|
||||
```rust
|
||||
@ -179,16 +134,8 @@ impl Output for Command {
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
Err(eyre!("cmd exited with non-zero status code"))
|
||||
.with_section(move || {
|
||||
"Stdout:"
|
||||
.skip_if(|| stdout.is_empty())
|
||||
.body(stdout.trim().to_string())
|
||||
})
|
||||
.with_section(move || {
|
||||
"Stderr:"
|
||||
.skip_if(|| stderr.is_empty())
|
||||
.body(stderr.trim().to_string())
|
||||
})
|
||||
.with_section(move || stdout.trim().to_string().header("Stdout:"))
|
||||
.with_section(move || stderr.trim().to_string().header("Stderr:"))
|
||||
} else {
|
||||
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
|
||||
the failure and attaches two sections, one for `stdout` and one for `stderr`. Each section
|
||||
includes a short header and a body that contains the actual output. Additionally these sections
|
||||
use `skip_if` to tell the report not to include them if there was no output, preventing empty
|
||||
sections from polluting the end report.
|
||||
Here we have an function that, if the command exits unsuccessfully, creates a
|
||||
report indicating the failure and attaches two sections, one for `stdout` and
|
||||
one for `stderr`.
|
||||
|
||||
Running `cargo run --example custom_section` shows us how these sections are included in the
|
||||
output:
|
||||
Running `cargo run --example custom_section` shows us how these sections are
|
||||
included in the output:
|
||||
|
||||

|
||||
|
||||
Only the `Stderr:` section actually gets included. The `cat` command fails, so stdout ends up
|
||||
being empty and is skipped in the final report. This gives us a short and concise error report
|
||||
indicating exactly what was attempted and how it failed.
|
||||
Only the `Stderr:` section actually gets included. The `cat` command fails,
|
||||
so stdout ends up being empty and is skipped in the final report. This gives
|
||||
us a short and concise error report indicating exactly what was attempted and
|
||||
how it failed.
|
||||
|
||||
### Aggregating multiple errors into one report
|
||||
|
||||
It's not uncommon for programs like batched task runners or parsers to want to
|
||||
return an error with multiple sources. The current version of the error trait
|
||||
does not support this use case very well, though there is [work being
|
||||
It's not uncommon for programs like batched task runners or parsers to want
|
||||
to return an error with multiple sources. The current version of the error
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
The pretty printing for backtraces and span traces isn't actually provided by `color-eyre`, but
|
||||
instead comes from its dependencies [`color-backtrace`] and [`color-spantrace`].
|
||||
`color-backtrace` in particular has many more features than are exported by `color-eyre`, such
|
||||
as customized color schemes, panic hooks, and custom frame filters. The custom frame filters
|
||||
are particularly 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.
|
||||
The pretty printing for backtraces and span traces isn't actually provided by
|
||||
`color-eyre`, but instead comes from its dependencies [`color-backtrace`] and
|
||||
[`color-spantrace`]. `color-backtrace` in particular has many more features
|
||||
than are exported by `color-eyre`, such as customized color schemes, panic
|
||||
hooks, and custom frame filters. The custom frame filters are particularly
|
||||
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`].
|
||||
|
||||
## Explanation
|
||||
|
||||
This crate works by defining a `Context` type which implements [`eyre::EyreContext`]
|
||||
and a pair of type aliases for setting this context type as the parameter of
|
||||
[`eyre::Report`].
|
||||
This crate works by defining a `Handler` type which implements
|
||||
[`eyre::EyreHandler`] and a pair of type aliases for setting this handler
|
||||
type as the parameter of [`eyre::Report`].
|
||||
|
||||
```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>;
|
||||
```
|
||||
|
||||
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
|
||||
[`tracing-error`]: https://docs.rs/tracing-error
|
||||
[`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
|
||||
[`tracing_error::SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html
|
||||
[`color-spantrace`]: https://github.com/yaahc/color-spantrace
|
||||
[`Help`]: https://docs.rs/color-eyre/*/color_eyre/trait.Help.html
|
||||
[`eyre::Report`]: https://docs.rs/eyre/*/eyre/struct.Report.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/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
|
||||
|
@ -17,16 +17,8 @@ impl Output for Command {
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
Err(eyre!("cmd exited with non-zero status code"))
|
||||
.with_section(move || {
|
||||
"Stdout:"
|
||||
.skip_if(|| stdout.is_empty())
|
||||
.body(stdout.trim().to_string())
|
||||
})
|
||||
.with_section(move || {
|
||||
"Stderr:"
|
||||
.skip_if(|| stderr.is_empty())
|
||||
.body(stderr.trim().to_string())
|
||||
})
|
||||
.with_section(move || stdout.trim().to_string().header("Stdout:"))
|
||||
.with_section(move || stderr.trim().to_string().header("Stderr:"))
|
||||
} else {
|
||||
Ok(stdout.into())
|
||||
}
|
||||
@ -61,7 +53,7 @@ fn install_tracing() {
|
||||
|
||||
#[instrument]
|
||||
fn read_file(path: &str) -> Result<String, Report> {
|
||||
Command::new("cat").arg("fake_file").output2()
|
||||
Command::new("cat").arg(path).output2()
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
|
@ -26,7 +26,7 @@ fn time_report_inner() {
|
||||
.suggestion("try using a file that exists next time")
|
||||
.unwrap_err();
|
||||
|
||||
let _ = println!("Error: {:?}", report);
|
||||
println!("Error: {:?}", report);
|
||||
drop(report);
|
||||
let end = std::time::Instant::now();
|
||||
|
||||
|
228
src/lib.rs
228
src/lib.rs
@ -1,5 +1,5 @@
|
||||
//! A custom context for the [`eyre`] crate for colorful error reports with suggestions, custom
|
||||
//! sections, [`tracing-error`] support, and backtraces on stable.
|
||||
//! An error report handler for panics and the [`eyre`] crate for colorful, consistent, and well
|
||||
//! formatted error reports for all kinds of errors.
|
||||
//!
|
||||
//! ## TLDR
|
||||
//!
|
||||
@ -69,53 +69,7 @@
|
||||
//! ### Multiple report format verbosity levels
|
||||
//!
|
||||
//! `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
|
||||
//! [`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")
|
||||
//! }
|
||||
//! ```
|
||||
//! and `Backtrace`, minimal, short, and full. Take the below screenshots of the output produced by [`examples/usage.rs`]:
|
||||
//!
|
||||
//! ---
|
||||
//!
|
||||
@ -141,9 +95,10 @@
|
||||
//!
|
||||
//! ### Custom `Section`s for error reports via [`Help`] trait
|
||||
//!
|
||||
//! The `section` module provides helpers for adding extra sections to error reports. Sections are
|
||||
//! disinct from error messages and are displayed independently from the chain of errors. Take this
|
||||
//! example of adding sections to contain `stderr` and `stdout` from a failed command, taken from
|
||||
//! The `section` module provides helpers for adding extra sections to error
|
||||
//! reports. Sections are disinct from error messages and are displayed
|
||||
//! 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`]:
|
||||
//!
|
||||
//! ```rust
|
||||
@ -166,16 +121,8 @@
|
||||
//! if !output.status.success() {
|
||||
//! let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
//! Err(eyre!("cmd exited with non-zero status code"))
|
||||
//! .with_section(move || {
|
||||
//! "Stdout:"
|
||||
//! .skip_if(|| stdout.is_empty())
|
||||
//! .body(stdout.trim().to_string())
|
||||
//! })
|
||||
//! .with_section(move || {
|
||||
//! "Stderr:"
|
||||
//! .skip_if(|| stderr.is_empty())
|
||||
//! .body(stderr.trim().to_string())
|
||||
//! })
|
||||
//! .with_section(move || stdout.trim().to_string().header("Stdout:"))
|
||||
//! .with_section(move || stderr.trim().to_string().header("Stderr:"))
|
||||
//! } else {
|
||||
//! Ok(stdout.into())
|
||||
//! }
|
||||
@ -185,26 +132,25 @@
|
||||
//!
|
||||
//! ---
|
||||
//!
|
||||
//! Here we have an function that, if the command exits unsuccessfully, creates a report indicating
|
||||
//! the failure and attaches two sections, one for `stdout` and one for `stderr`. Each section
|
||||
//! includes a short header and a body that contains the actual output. Additionally these sections
|
||||
//! use `skip_if` to tell the report not to include them if there was no output, preventing empty
|
||||
//! sections from polluting the end report.
|
||||
//! Here we have an function that, if the command exits unsuccessfully, creates a
|
||||
//! report indicating the failure and attaches two sections, one for `stdout` and
|
||||
//! one for `stderr`.
|
||||
//!
|
||||
//! Running `cargo run --example custom_section` shows us how these sections are included in the
|
||||
//! output:
|
||||
//! Running `cargo run --example custom_section` shows us how these sections are
|
||||
//! included in the output:
|
||||
//!
|
||||
//! 
|
||||
//!
|
||||
//! Only the `Stderr:` section actually gets included. The `cat` command fails, so stdout ends up
|
||||
//! being empty and is skipped in the final report. This gives us a short and concise error report
|
||||
//! indicating exactly what was attempted and how it failed.
|
||||
//! Only the `Stderr:` section actually gets included. The `cat` command fails,
|
||||
//! so stdout ends up being empty and is skipped in the final report. This gives
|
||||
//! us a short and concise error report indicating exactly what was attempted and
|
||||
//! how it failed.
|
||||
//!
|
||||
//! ### Aggregating multiple errors into one report
|
||||
//!
|
||||
//! It's not uncommon for programs like batched task runners or parsers to want to
|
||||
//! return an error with multiple sources. The current version of the error trait
|
||||
//! does not support this use case very well, though there is [work being
|
||||
//! It's not uncommon for programs like batched task runners or parsers to want
|
||||
//! to return an error with multiple sources. The current version of the error
|
||||
//! 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.
|
||||
//!
|
||||
//! 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
|
||||
//!
|
||||
//! The pretty printing for backtraces and span traces isn't actually provided by `color-eyre`, but
|
||||
//! instead comes from its dependencies [`color-backtrace`] and [`color-spantrace`].
|
||||
//! `color-backtrace` in particular has many more features than are exported by `color-eyre`, such
|
||||
//! as customized color schemes, panic hooks, and custom frame filters. The custom frame filters
|
||||
//! are particularly 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.
|
||||
//! The pretty printing for backtraces and span traces isn't actually provided by
|
||||
//! `color-eyre`, but instead comes from its dependencies [`color-backtrace`] and
|
||||
//! [`color-spantrace`]. `color-backtrace` in particular has many more features
|
||||
//! than are exported by `color-eyre`, such as customized color schemes, panic
|
||||
//! hooks, and custom frame filters. The custom frame filters are particularly
|
||||
//! 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`].
|
||||
//!
|
||||
//! ## Explanation
|
||||
//!
|
||||
//! This crate works by defining a `Context` type which implements [`eyre::EyreContext`]
|
||||
//! and a pair of type aliases for setting this context type as the parameter of
|
||||
//! [`eyre::Report`].
|
||||
//! This crate works by defining a `Handler` type which implements
|
||||
//! [`eyre::EyreHandler`] and a pair of type aliases for setting this handler
|
||||
//! type as the parameter of [`eyre::Report`].
|
||||
//!
|
||||
//! ```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>;
|
||||
//! ```
|
||||
//!
|
||||
//! 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
|
||||
//! [`tracing-error`]: https://docs.rs/tracing-error
|
||||
//! [`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
|
||||
//! [`tracing_error::SpanTrace`]: https://docs.rs/tracing-error/*/tracing_error/struct.SpanTrace.html
|
||||
//! [`color-spantrace`]: https://github.com/yaahc/color-spantrace
|
||||
//! [`Help`]: https://docs.rs/color-eyre/*/color_eyre/trait.Help.html
|
||||
//! [`eyre::Report`]: https://docs.rs/eyre/*/eyre/struct.Report.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/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
|
||||
@ -279,13 +227,14 @@
|
||||
unused_parens,
|
||||
while_true
|
||||
)]
|
||||
use crate::writers::HeaderWriter;
|
||||
use ansi_term::Color::*;
|
||||
use backtrace::Backtrace;
|
||||
pub use color_backtrace::BacktracePrinter;
|
||||
use eyre::*;
|
||||
use indenter::{indented, Format};
|
||||
use once_cell::sync::OnceCell;
|
||||
use section::Order;
|
||||
use section::help::HelpInfo;
|
||||
pub use section::{help::Help, Section, SectionExt};
|
||||
#[cfg(feature = "capture-spantrace")]
|
||||
use std::error::Error;
|
||||
@ -296,12 +245,15 @@ use std::{
|
||||
};
|
||||
#[cfg(feature = "capture-spantrace")]
|
||||
use tracing_error::{ExtractSpanTrace, SpanTrace, SpanTraceStatus};
|
||||
#[doc(hidden)]
|
||||
pub use Handler as Context;
|
||||
|
||||
pub mod section;
|
||||
mod writers;
|
||||
|
||||
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.
|
||||
///
|
||||
/// 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::Result`]: type.Result.html
|
||||
#[derive(Debug)]
|
||||
pub struct Context {
|
||||
pub struct Handler {
|
||||
backtrace: Option<Backtrace>,
|
||||
#[cfg(feature = "capture-spantrace")]
|
||||
span_trace: Option<SpanTrace>,
|
||||
sections: Vec<Section>,
|
||||
sections: Vec<HelpInfo>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InstallError;
|
||||
#[cfg(feature = "capture-spantrace")]
|
||||
struct FormattedSpanTrace<'a>(&'a SpanTrace);
|
||||
|
||||
impl Context {
|
||||
impl Handler {
|
||||
/// Return a reference to the captured `Backtrace` type
|
||||
///
|
||||
/// # Examples
|
||||
@ -336,7 +290,7 @@ impl Context {
|
||||
/// std::env::set_var("RUST_BACKTRACE", "1");
|
||||
///
|
||||
/// 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
|
||||
@ -349,7 +303,7 @@ impl Context {
|
||||
/// std::env::set_var("RUST_LIB_BACKTRACE", "1");
|
||||
///
|
||||
/// 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
|
||||
@ -363,7 +317,7 @@ impl Context {
|
||||
/// std::env::set_var("RUST_LIB_BACKTRACE", "0");
|
||||
///
|
||||
/// 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> {
|
||||
@ -381,7 +335,7 @@ impl Context {
|
||||
/// use eyre::eyre;
|
||||
///
|
||||
/// 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
|
||||
@ -409,7 +363,7 @@ impl Context {
|
||||
/// let error: TracedError<SourceError> = error.in_current_span();
|
||||
///
|
||||
/// 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
|
||||
@ -420,7 +374,7 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
impl EyreContext for Context {
|
||||
impl EyreHandler for Handler {
|
||||
#[allow(unused_variables)]
|
||||
fn default(error: &(dyn std::error::Error + 'static)) -> Self {
|
||||
let backtrace = if backtrace_enabled() {
|
||||
@ -463,26 +417,32 @@ impl EyreContext for Context {
|
||||
|
||||
let mut buf = String::new();
|
||||
for (n, error) in errors {
|
||||
writeln!(f)?;
|
||||
buf.clear();
|
||||
write!(&mut buf, "{}", error).unwrap();
|
||||
writeln!(f)?;
|
||||
write!(indented(f).ind(n), "{}", Red.paint(&buf))?;
|
||||
}
|
||||
|
||||
let separated = &mut HeaderWriter {
|
||||
inner: &mut *f,
|
||||
header: &"\n\n",
|
||||
started: false,
|
||||
};
|
||||
|
||||
for section in self
|
||||
.sections
|
||||
.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
|
||||
.sections
|
||||
.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")]
|
||||
@ -493,33 +453,23 @@ impl EyreContext for Context {
|
||||
.or_else(|| get_deepest_spantrace(error))
|
||||
.expect("SpanTrace capture failed");
|
||||
|
||||
match span_trace.status() {
|
||||
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")?,
|
||||
_ => (),
|
||||
}
|
||||
write!(&mut separated.ready(), "{}", FormattedSpanTrace(span_trace))?;
|
||||
}
|
||||
|
||||
if let Some(backtrace) = self.backtrace.as_ref() {
|
||||
write!(f, "\n\n")?;
|
||||
|
||||
let bt_str = CONFIG
|
||||
.get_or_init(default_printer)
|
||||
let bt_str = installed_printer()
|
||||
.format_trace_to_string(&backtrace)
|
||||
.unwrap();
|
||||
|
||||
write!(
|
||||
indented(f).with_format(Format::Uniform { indentation: " " }),
|
||||
indented(&mut separated.ready()).with_format(Format::Uniform { indentation: " " }),
|
||||
"{}",
|
||||
bt_str
|
||||
)?;
|
||||
} else if self
|
||||
.sections
|
||||
.iter()
|
||||
.any(|s| matches!(s.order, Order::AfterBackTrace))
|
||||
.any(|s| !matches!(s, HelpInfo::Custom(_) | HelpInfo::Error(_)))
|
||||
{
|
||||
writeln!(f)?;
|
||||
}
|
||||
@ -527,9 +477,26 @@ impl EyreContext for Context {
|
||||
for section in self
|
||||
.sections
|
||||
.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(())
|
||||
@ -573,7 +540,7 @@ fn get_deepest_spantrace<'a>(error: &'a (dyn Error + 'static)) -> Option<&'a Spa
|
||||
.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.
|
||||
///
|
||||
/// # 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> {
|
||||
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 {
|
||||
@ -616,7 +592,7 @@ fn default_printer() -> BacktracePrinter {
|
||||
fn add_eyre_filters(printer: BacktracePrinter) -> BacktracePrinter {
|
||||
printer.add_frame_filter(Box::new(|frames| {
|
||||
let filters = &[
|
||||
"<color_eyre::Context as eyre::EyreContext>::default",
|
||||
"<color_eyre::Handler as eyre::EyreHandler>::default",
|
||||
"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
|
||||
///
|
||||
@ -648,7 +624,7 @@ fn add_eyre_filters(printer: BacktracePrinter) -> BacktracePrinter {
|
||||
/// # Ok(Config)
|
||||
/// }
|
||||
/// ```
|
||||
pub type Report = eyre::Report<Context>;
|
||||
pub type Report = eyre::Report<Handler>;
|
||||
|
||||
/// A type alias for `Result<T, color_eyre::Report>`
|
||||
///
|
||||
|
@ -1,6 +1,8 @@
|
||||
//! 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 indenter::indented;
|
||||
use std::fmt::Write;
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
/// 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")?;
|
||||
/// # Ok::<_, Report>(())
|
||||
/// ```
|
||||
fn section<C>(self, section: C) -> Result<T>
|
||||
fn section<D>(self, section: D) -> Result<T>
|
||||
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
|
||||
/// 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 stderr = String::from_utf8_lossy(&output.stderr);
|
||||
/// Err(eyre!("cmd exited with non-zero status code"))
|
||||
/// .with_section(move || {
|
||||
/// "Stderr:"
|
||||
/// .skip_if(|| stderr.is_empty())
|
||||
/// .body(stderr.trim().to_string())
|
||||
/// })?
|
||||
/// .with_section(move || stderr.trim().to_string().header("Stderr:"))?
|
||||
/// } else {
|
||||
/// String::from_utf8_lossy(&output.stdout)
|
||||
/// };
|
||||
@ -65,10 +63,10 @@ pub trait Help<T>: private::Sealed {
|
||||
/// println!("{}", output);
|
||||
/// # Ok::<_, Report>(())
|
||||
/// ```
|
||||
fn with_section<C, F>(self, section: F) -> Result<T>
|
||||
fn with_section<D, F>(self, section: F) -> Result<T>
|
||||
where
|
||||
C: Into<Section>,
|
||||
F: FnOnce() -> C;
|
||||
D: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> D;
|
||||
|
||||
/// Add an error section to an error report, to be displayed after the primary error message
|
||||
/// section.
|
||||
@ -142,9 +140,9 @@ pub trait Help<T>: private::Sealed {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn note<C>(self, context: C) -> Result<T>
|
||||
fn note<D>(self, note: D) -> Result<T>
|
||||
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
|
||||
/// create the Note is lazily evaluated only in the case of an error.
|
||||
@ -174,156 +172,142 @@ pub trait Help<T>: private::Sealed {
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
fn with_note<C, F>(self, f: F) -> Result<T>
|
||||
fn with_note<D, F>(self, f: F) -> Result<T>
|
||||
where
|
||||
C: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> C;
|
||||
D: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> D;
|
||||
|
||||
/// 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
|
||||
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
|
||||
/// 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
|
||||
C: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> C;
|
||||
D: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> D;
|
||||
|
||||
/// 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
|
||||
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
|
||||
/// 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
|
||||
C: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> C;
|
||||
D: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> D;
|
||||
}
|
||||
|
||||
impl<T, E> Help<T> for std::result::Result<T, E>
|
||||
where
|
||||
E: Into<Report>,
|
||||
{
|
||||
fn note<C>(self, context: C) -> Result<T>
|
||||
fn note<D>(self, note: D) -> Result<T>
|
||||
where
|
||||
C: Display + Send + Sync + 'static,
|
||||
D: Display + Send + Sync + 'static,
|
||||
{
|
||||
self.map_err(|e| {
|
||||
let mut e = e.into();
|
||||
e.context_mut().sections.push(
|
||||
Section::from(HelpInfo::Note(Box::new(context)))
|
||||
.order(section::Order::AfterBackTrace),
|
||||
);
|
||||
e.handler_mut()
|
||||
.sections
|
||||
.push(HelpInfo::Note(Box::new(note)));
|
||||
e
|
||||
})
|
||||
}
|
||||
|
||||
fn with_note<C, F>(self, context: F) -> Result<T>
|
||||
fn with_note<D, F>(self, note: F) -> Result<T>
|
||||
where
|
||||
C: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> C,
|
||||
D: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> D,
|
||||
{
|
||||
self.map_err(|e| {
|
||||
let mut e = e.into();
|
||||
e.context_mut().sections.push(
|
||||
Section::from(HelpInfo::Note(Box::new(context())))
|
||||
.order(section::Order::AfterBackTrace),
|
||||
);
|
||||
e.handler_mut()
|
||||
.sections
|
||||
.push(HelpInfo::Note(Box::new(note())));
|
||||
e
|
||||
})
|
||||
}
|
||||
|
||||
fn warning<C>(self, context: C) -> Result<T>
|
||||
fn warning<D>(self, warning: D) -> Result<T>
|
||||
where
|
||||
C: Display + Send + Sync + 'static,
|
||||
D: Display + Send + Sync + 'static,
|
||||
{
|
||||
self.map_err(|e| {
|
||||
let mut e = e.into();
|
||||
e.context_mut().sections.push(
|
||||
Section::from(HelpInfo::Warning(Box::new(context)))
|
||||
.order(section::Order::AfterBackTrace),
|
||||
);
|
||||
e.handler_mut()
|
||||
.sections
|
||||
.push(HelpInfo::Warning(Box::new(warning)));
|
||||
e
|
||||
})
|
||||
}
|
||||
|
||||
fn with_warning<C, F>(self, context: F) -> Result<T>
|
||||
fn with_warning<D, F>(self, warning: F) -> Result<T>
|
||||
where
|
||||
C: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> C,
|
||||
D: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> D,
|
||||
{
|
||||
self.map_err(|e| {
|
||||
let mut e = e.into();
|
||||
e.context_mut().sections.push(
|
||||
Section::from(HelpInfo::Warning(Box::new(context())))
|
||||
.order(section::Order::AfterBackTrace),
|
||||
);
|
||||
e.handler_mut()
|
||||
.sections
|
||||
.push(HelpInfo::Warning(Box::new(warning())));
|
||||
e
|
||||
})
|
||||
}
|
||||
|
||||
fn suggestion<C>(self, context: C) -> Result<T>
|
||||
fn suggestion<D>(self, suggestion: D) -> Result<T>
|
||||
where
|
||||
C: Display + Send + Sync + 'static,
|
||||
D: Display + Send + Sync + 'static,
|
||||
{
|
||||
self.map_err(|e| {
|
||||
let mut e = e.into();
|
||||
e.context_mut().sections.push(
|
||||
Section::from(HelpInfo::Suggestion(Box::new(context)))
|
||||
.order(section::Order::AfterBackTrace),
|
||||
);
|
||||
e.handler_mut()
|
||||
.sections
|
||||
.push(HelpInfo::Suggestion(Box::new(suggestion)));
|
||||
e
|
||||
})
|
||||
}
|
||||
|
||||
fn with_suggestion<C, F>(self, context: F) -> Result<T>
|
||||
fn with_suggestion<D, F>(self, suggestion: F) -> Result<T>
|
||||
where
|
||||
C: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> C,
|
||||
D: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> D,
|
||||
{
|
||||
self.map_err(|e| {
|
||||
let mut e = e.into();
|
||||
e.context_mut().sections.push(
|
||||
Section::from(HelpInfo::Suggestion(Box::new(context())))
|
||||
.order(section::Order::AfterBackTrace),
|
||||
);
|
||||
e.handler_mut()
|
||||
.sections
|
||||
.push(HelpInfo::Suggestion(Box::new(suggestion())));
|
||||
e
|
||||
})
|
||||
}
|
||||
|
||||
fn with_section<C, F>(self, section: F) -> Result<T>
|
||||
fn with_section<D, F>(self, section: F) -> Result<T>
|
||||
where
|
||||
C: Into<Section>,
|
||||
F: FnOnce() -> C,
|
||||
D: Display + Send + Sync + 'static,
|
||||
F: FnOnce() -> D,
|
||||
{
|
||||
self.map_err(|e| {
|
||||
let mut e = e.into();
|
||||
let section = section().into();
|
||||
|
||||
if !matches!(section.order, section::Order::SkipEntirely) {
|
||||
e.context_mut().sections.push(section);
|
||||
}
|
||||
|
||||
let section = Box::new(section());
|
||||
e.handler_mut().sections.push(HelpInfo::Custom(section));
|
||||
e
|
||||
})
|
||||
}
|
||||
|
||||
fn section<C>(self, section: C) -> Result<T>
|
||||
fn section<D>(self, section: D) -> Result<T>
|
||||
where
|
||||
C: Into<Section>,
|
||||
D: Display + Send + Sync + 'static,
|
||||
{
|
||||
self.map_err(|e| {
|
||||
let mut e = e.into();
|
||||
let section = section.into();
|
||||
|
||||
if !matches!(section.order, section::Order::SkipEntirely) {
|
||||
e.context_mut().sections.push(section);
|
||||
}
|
||||
|
||||
let section = Box::new(section);
|
||||
e.handler_mut().sections.push(HelpInfo::Custom(section));
|
||||
e
|
||||
})
|
||||
}
|
||||
@ -334,12 +318,9 @@ where
|
||||
{
|
||||
self.map_err(|e| {
|
||||
let mut e = e.into();
|
||||
let section = Section {
|
||||
inner: section::SectionKind::Error(Box::new(error)),
|
||||
order: section::Order::AfterErrMsgs,
|
||||
};
|
||||
let error = error.into();
|
||||
|
||||
e.context_mut().sections.push(section);
|
||||
e.handler_mut().sections.push(HelpInfo::Error(error));
|
||||
e
|
||||
})
|
||||
}
|
||||
@ -351,18 +332,17 @@ where
|
||||
{
|
||||
self.map_err(|e| {
|
||||
let mut e = e.into();
|
||||
let section = Section {
|
||||
inner: section::SectionKind::Error(Box::new(error())),
|
||||
order: section::Order::AfterErrMsgs,
|
||||
};
|
||||
let error = error().into();
|
||||
|
||||
e.context_mut().sections.push(section);
|
||||
e.handler_mut().sections.push(HelpInfo::Error(error));
|
||||
e
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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>),
|
||||
Warning(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 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Note(context) => write!(f, "{}: {}", Cyan.paint("Note"), context),
|
||||
Self::Warning(context) => write!(f, "{}: {}", Yellow.paint("Warning"), context),
|
||||
Self::Suggestion(context) => write!(f, "{}: {}", Cyan.paint("Suggestion"), context),
|
||||
HelpInfo::Note(n) => write!(f, "{}: {}", Cyan.paint("Note"), n),
|
||||
HelpInfo::Warning(w) => write!(f, "{}: {}", Yellow.paint("Warning"), w),
|
||||
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 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Note(context) => f
|
||||
HelpInfo::Note(note) => f
|
||||
.debug_tuple("Note")
|
||||
.field(&format_args!("{}", context))
|
||||
.field(&format_args!("{}", note))
|
||||
.finish(),
|
||||
Self::Warning(context) => f
|
||||
HelpInfo::Warning(warning) => f
|
||||
.debug_tuple("Warning")
|
||||
.field(&format_args!("{}", context))
|
||||
.field(&format_args!("{}", warning))
|
||||
.finish(),
|
||||
Self::Suggestion(context) => f
|
||||
HelpInfo::Suggestion(suggestion) => f
|
||||
.debug_tuple("Suggestion")
|
||||
.field(&format_args!("{}", context))
|
||||
.field(&format_args!("{}", suggestion))
|
||||
.finish(),
|
||||
HelpInfo::Custom(custom) => f
|
||||
.debug_tuple("CustomSection")
|
||||
.field(&format_args!("{}", custom))
|
||||
.finish(),
|
||||
HelpInfo::Error(error) => f.debug_tuple("Error").field(error).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,18 @@
|
||||
//! Helpers for adding custom sections to error reports
|
||||
use ansi_term::Color::*;
|
||||
use indenter::indented;
|
||||
use std::fmt::{self, Display, Write};
|
||||
|
||||
pub mod help;
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Order {
|
||||
AfterErrMsgs,
|
||||
BeforeSpanTrace,
|
||||
AfterBackTrace,
|
||||
SkipEntirely,
|
||||
}
|
||||
|
||||
/// A custom section for an error report.
|
||||
/// An indenteted section with a header for an error report
|
||||
///
|
||||
/// # Details
|
||||
///
|
||||
/// Sections consist of two parts, a header: and an optional body. The header can contain any
|
||||
/// number of lines and has no indentation applied to it by default. The body can contain any
|
||||
/// number of lines and is always written after the header with indentation inserted before
|
||||
/// every line.
|
||||
///
|
||||
/// # Construction
|
||||
///
|
||||
/// 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.
|
||||
/// This helper provides two functions to help with constructing nicely formatted
|
||||
/// error reports. First, it handles indentation of every line of the body for
|
||||
/// you, and makes sure it is consistent with the rest of color-eyre's output.
|
||||
/// Second, it omits outputting the header if the body itself is empty,
|
||||
/// preventing unnecessary pollution of the report for sections with dynamic
|
||||
/// content.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@ -51,41 +36,27 @@ pub(crate) enum Order {
|
||||
/// if !output.status.success() {
|
||||
/// let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
/// Err(eyre!("cmd exited with non-zero status code"))
|
||||
/// .with_section(move || {
|
||||
/// "Stdout:"
|
||||
/// .skip_if(|| stdout.is_empty())
|
||||
/// .body(stdout.trim().to_string())
|
||||
/// })
|
||||
/// .with_section(move || {
|
||||
/// "Stderr:"
|
||||
/// .skip_if(|| stderr.is_empty())
|
||||
/// .body(stderr.trim().to_string())
|
||||
/// })
|
||||
/// .with_section(move || stdout.trim().to_string().header("Stdout:"))
|
||||
/// .with_section(move || stderr.trim().to_string().header("Stderr:"))
|
||||
/// } else {
|
||||
/// Ok(stdout.into())
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Section {
|
||||
pub(crate) inner: SectionKind,
|
||||
pub(crate) order: Order,
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Section<H, B> {
|
||||
header: H,
|
||||
body: B,
|
||||
}
|
||||
|
||||
pub(crate) enum SectionKind {
|
||||
Header(Box<dyn Display + Send + Sync + 'static>),
|
||||
WithBody(
|
||||
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`
|
||||
/// Extension trait for constructing sections with commonly used formats
|
||||
pub trait SectionExt: Sized {
|
||||
/// Add a header to a `Section` and indent the body
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
@ -101,147 +72,52 @@ pub trait SectionExt {
|
||||
/// let just_header = "header";
|
||||
/// let just_body = "body\nbody";
|
||||
/// let report2 = Err::<(), Report>(eyre!("an error occurred"))
|
||||
/// .section(just_header.body(just_body))
|
||||
/// .section(just_body.header(just_header))
|
||||
/// .unwrap_err();
|
||||
///
|
||||
/// assert_eq!(format!("{:?}", report), format!("{:?}", report2))
|
||||
/// ```
|
||||
fn body<C>(self, body: C) -> Section
|
||||
fn header<C>(self, header: C) -> Section<C, Self>
|
||||
where
|
||||
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
|
||||
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
|
||||
C: Display + Send + Sync + 'static,
|
||||
{
|
||||
let section = Section::from(self);
|
||||
|
||||
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
|
||||
Section { body: self, header }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Section
|
||||
impl<H, B> fmt::Display for Section<H, B>
|
||||
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 {
|
||||
write!(f, "{}", self.inner)
|
||||
}
|
||||
}
|
||||
let mut headered = crate::writers::HeaderWriter {
|
||||
inner: f,
|
||||
header: &self.header,
|
||||
started: false,
|
||||
};
|
||||
|
||||
impl fmt::Display for SectionKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
SectionKind::Header(header) => write!(f, "{}", header)?,
|
||||
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(),
|
||||
);
|
||||
let mut headered = crate::writers::HeaderWriter {
|
||||
inner: headered.ready(),
|
||||
header: &"\n",
|
||||
started: false,
|
||||
};
|
||||
|
||||
write!(f, "Error:")?;
|
||||
let mut buf = String::new();
|
||||
for (n, error) in errors.enumerate() {
|
||||
writeln!(f)?;
|
||||
buf.clear();
|
||||
write!(&mut buf, "{}", error).unwrap();
|
||||
write!(indented(f).ind(n), "{}", Red.paint(&buf))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut headered = headered.ready();
|
||||
|
||||
let mut indented = indenter::indented(&mut headered)
|
||||
.with_format(indenter::Format::Uniform { indentation: " " });
|
||||
|
||||
write!(&mut indented, "{}", self.body)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
32
src/writers.rs
Normal file
32
src/writers.rs
Normal 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)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user