Refactor and redesign API
This commit is contained in:
parent
cab16130f7
commit
db01cce71c
|
@ -5,8 +5,6 @@ edition = "2021"
|
|||
|
||||
[features]
|
||||
default = []
|
||||
backtrace = []
|
||||
extra-backtrace = ["backtrace"]
|
||||
clone-with-caveats = []
|
||||
arc-backtrace = []
|
||||
termination = ["dep:ansee"]
|
||||
|
|
173
src/context.rs
173
src/context.rs
|
@ -1,12 +1,13 @@
|
|||
#[cfg(feature = "backtrace")]
|
||||
use std::backtrace::BacktraceStatus;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::panic::Location;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::report::report_write;
|
||||
use crate::ReportOpts;
|
||||
use crate::{
|
||||
report::{fmt_report, DetailIndent},
|
||||
How, ReportOpts,
|
||||
};
|
||||
|
||||
/// Provides context furthering the explanation of *how* you got to an error.
|
||||
#[derive(Debug)]
|
||||
|
@ -14,12 +15,12 @@ use crate::ReportOpts;
|
|||
any(feature = "arc-backtrace", feature = "clone-with-caveats"),
|
||||
derive(Clone)
|
||||
)]
|
||||
pub struct Context {
|
||||
pub struct DetailTree {
|
||||
pub(crate) detail: Detail,
|
||||
pub(crate) extra: Vec<Detail>,
|
||||
pub(crate) extra: Vec<DetailTree>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
impl DetailTree {
|
||||
pub(crate) fn new(detail: Detail) -> Self {
|
||||
Self {
|
||||
detail,
|
||||
|
@ -31,7 +32,7 @@ impl Context {
|
|||
&self.detail
|
||||
}
|
||||
|
||||
pub fn extra(&self) -> &[Detail] {
|
||||
pub fn extra(&self) -> &[DetailTree] {
|
||||
&self.extra
|
||||
}
|
||||
|
||||
|
@ -39,32 +40,65 @@ impl Context {
|
|||
&mut self.detail
|
||||
}
|
||||
|
||||
pub fn extra_mut(&mut self) -> &mut [Detail] {
|
||||
pub fn extra_mut(&mut self) -> &mut [DetailTree] {
|
||||
&mut self.extra
|
||||
}
|
||||
|
||||
pub fn push_extra(&mut self, detail: impl IntoContext) {
|
||||
let detail = detail.into_context();
|
||||
if detail.extra.is_empty() {
|
||||
self.extra.push(detail.detail);
|
||||
} else {
|
||||
self.extra.push(Detail::Context(detail.into()));
|
||||
}
|
||||
pub fn push_extra(&mut self, detail: impl IntoDetails) {
|
||||
let detail = detail.into_details();
|
||||
self.extra.push(detail);
|
||||
}
|
||||
|
||||
pub fn pop_extra(&mut self) -> Option<Detail> {
|
||||
pub fn pop_extra(&mut self) -> Option<DetailTree> {
|
||||
self.extra.pop()
|
||||
}
|
||||
|
||||
fn is_multi_line(&self) -> bool {
|
||||
if self.extra().is_empty() {
|
||||
match self.detail {
|
||||
Detail::Str(s) => s.contains('\n'),
|
||||
Detail::String(ref s) => s.contains('\n'),
|
||||
Detail::Location(_) => false,
|
||||
Detail::Backtrace(_) => true,
|
||||
Detail::Error(_) => false,
|
||||
Detail::HowError(ref e) => e.0.context.len() > 1,
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Context {
|
||||
impl fmt::Display for DetailTree {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.detail.fmt(f)?;
|
||||
let opts = ReportOpts::default();
|
||||
for detail in &self.extra {
|
||||
let mut iter = self.extra.iter().peekable();
|
||||
while let Some(detail) = iter.next() {
|
||||
f.write_str("\n")?;
|
||||
report_write!(f, &opts.indent().next(), "- {detail}")?;
|
||||
if iter.peek().is_none() {
|
||||
fmt_report(
|
||||
f,
|
||||
&opts.indent().next(),
|
||||
DetailIndent::<false>,
|
||||
&format_args!("└╼ {detail}"),
|
||||
)?;
|
||||
} else if detail.is_multi_line() {
|
||||
fmt_report(
|
||||
f,
|
||||
&opts.indent().next(),
|
||||
DetailIndent::<true>,
|
||||
&format_args!("├╼ {detail}"),
|
||||
)?;
|
||||
} else {
|
||||
fmt_report(
|
||||
f,
|
||||
&opts.indent().next(),
|
||||
DetailIndent::<false>,
|
||||
&format_args!("├╼ {detail}"),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -80,14 +114,12 @@ pub enum Detail {
|
|||
Str(&'static str),
|
||||
String(String),
|
||||
Location(Location<'static>),
|
||||
#[cfg(feature = "backtrace")]
|
||||
Backtrace(PrivateBacktrace),
|
||||
Error(PrivateError),
|
||||
Context(Box<Context>),
|
||||
HowError(How),
|
||||
}
|
||||
|
||||
impl Detail {
|
||||
#[cfg(feature = "backtrace")]
|
||||
#[track_caller]
|
||||
pub fn backtrace() -> Self {
|
||||
use std::backtrace::BacktraceStatus::*;
|
||||
|
@ -104,7 +136,6 @@ impl Detail {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct PrivateError(pub(crate) Arc<dyn std::error::Error + Send + Sync>);
|
||||
|
||||
#[cfg(feature = "backtrace")]
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(
|
||||
any(feature = "arc-backtrace", feature = "clone-with-caveats"),
|
||||
|
@ -113,7 +144,6 @@ pub struct PrivateError(pub(crate) Arc<dyn std::error::Error + Send + Sync>);
|
|||
pub struct PrivateBacktrace(pub(crate) Backtrace);
|
||||
|
||||
// will be replaced with std::backtrace::Backtrace if and when it is Clone
|
||||
#[cfg(feature = "backtrace")]
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "arc-backtrace", derive(Clone))]
|
||||
pub(crate) enum Backtrace {
|
||||
|
@ -125,11 +155,7 @@ pub(crate) enum Backtrace {
|
|||
Other(std::backtrace::Backtrace),
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
feature = "backtrace",
|
||||
feature = "clone-with-caveats",
|
||||
not(feature = "arc-backtrace")
|
||||
))]
|
||||
#[cfg(all(feature = "clone-with-caveats", not(feature = "arc-backtrace")))]
|
||||
impl Clone for Backtrace {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
|
@ -145,15 +171,12 @@ impl fmt::Display for Detail {
|
|||
Self::Str(s) => f.write_str(s),
|
||||
Self::String(s) => f.write_str(s),
|
||||
Self::Location(l) => write!(f, "at {l}"),
|
||||
#[cfg(feature = "backtrace")]
|
||||
Self::Backtrace(PrivateBacktrace(Backtrace::Unsupported)) => f.write_str(
|
||||
"I'd like to show you a backtrace,\n but it's not supported on your platform",
|
||||
),
|
||||
#[cfg(feature = "backtrace")]
|
||||
Self::Backtrace(PrivateBacktrace(Backtrace::Disabled)) => {
|
||||
f.write_str("If you'd like a backtrace,\n try again with RUST_BACKTRACE=1")
|
||||
}
|
||||
#[cfg(feature = "backtrace")]
|
||||
Self::Backtrace(PrivateBacktrace(Backtrace::Other(bt))) => {
|
||||
f.write_str(if bt.status() == BacktraceStatus::Captured {
|
||||
"Here is the backtrace:\n"
|
||||
|
@ -163,119 +186,119 @@ impl fmt::Display for Detail {
|
|||
write!(f, "{}", bt)
|
||||
}
|
||||
Self::Error(PrivateError(e)) => e.fmt(f),
|
||||
Self::Context(c) => c.fmt(f),
|
||||
Self::HowError(e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoContext: Sized {
|
||||
fn into_context(self) -> Context;
|
||||
pub trait IntoDetails: Sized {
|
||||
fn into_details(self) -> DetailTree;
|
||||
|
||||
/// Annotates the context with the given detail.
|
||||
#[inline(always)]
|
||||
fn with(self, other: impl IntoContext) -> Context {
|
||||
self.into_context().with(other)
|
||||
fn with(self, detail: impl IntoDetails) -> DetailTree {
|
||||
self.into_details().with(detail)
|
||||
}
|
||||
|
||||
/// Annotates the context with the caller location.
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
fn with_caller(self) -> Context {
|
||||
fn with_caller(self) -> DetailTree {
|
||||
self.with(Location::caller())
|
||||
}
|
||||
|
||||
/// Annotates the context with the current backtrace.
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
fn with_backtrace(self) -> Context {
|
||||
#[cfg(feature = "backtrace")]
|
||||
return self.with(Detail::backtrace());
|
||||
#[cfg(not(feature = "backtrace"))]
|
||||
self.into_context()
|
||||
fn with_backtrace(self) -> DetailTree {
|
||||
self.with(Detail::backtrace())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoContext for Context {
|
||||
impl IntoDetails for DetailTree {
|
||||
#[inline(always)]
|
||||
fn into_context(self) -> Context {
|
||||
fn into_details(self) -> DetailTree {
|
||||
self
|
||||
}
|
||||
|
||||
/// Chains another piece of context that is a child from a hierarchical perspective.
|
||||
#[track_caller]
|
||||
#[inline]
|
||||
fn with(mut self, other: impl IntoContext) -> Self {
|
||||
fn with(mut self, other: impl IntoDetails) -> Self {
|
||||
self.push_extra(other);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoContext for String {
|
||||
impl IntoDetails for String {
|
||||
#[inline(always)]
|
||||
fn into_context(self) -> Context {
|
||||
Detail::String(self).into_context()
|
||||
fn into_details(self) -> DetailTree {
|
||||
Detail::String(self).into_details()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoContext for &'static str {
|
||||
impl IntoDetails for &'static str {
|
||||
#[inline(always)]
|
||||
fn into_context(self) -> Context {
|
||||
Detail::Str(self).into_context()
|
||||
fn into_details(self) -> DetailTree {
|
||||
Detail::Str(self).into_details()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoContext for Cow<'static, str> {
|
||||
impl IntoDetails for Cow<'static, str> {
|
||||
#[inline]
|
||||
fn into_context(self) -> Context {
|
||||
fn into_details(self) -> DetailTree {
|
||||
match self {
|
||||
Cow::Borrowed(s) => s.into_context(),
|
||||
Cow::Owned(s) => s.into_context(),
|
||||
Cow::Borrowed(s) => s.into_details(),
|
||||
Cow::Owned(s) => s.into_details(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoContext for &'a Location<'static> {
|
||||
impl<'a> IntoDetails for &'a Location<'static> {
|
||||
#[inline]
|
||||
fn into_context(self) -> Context {
|
||||
Location::into_context(*self)
|
||||
fn into_details(self) -> DetailTree {
|
||||
Location::into_details(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoContext for Location<'static> {
|
||||
impl IntoDetails for Location<'static> {
|
||||
#[inline]
|
||||
fn into_context(self) -> Context {
|
||||
Detail::Location(self).into_context()
|
||||
fn into_details(self) -> DetailTree {
|
||||
Detail::Location(self).into_details()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> IntoContext for Arc<E>
|
||||
impl<E> IntoDetails for Arc<E>
|
||||
where
|
||||
E: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn into_context(self) -> Context {
|
||||
Detail::Error(PrivateError(self)).into_context()
|
||||
fn into_details(self) -> DetailTree {
|
||||
Detail::Error(PrivateError(self)).into_details()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoContext for Arc<dyn std::error::Error + Send + Sync> {
|
||||
impl IntoDetails for Arc<dyn std::error::Error + Send + Sync> {
|
||||
#[inline]
|
||||
fn into_context(self) -> Context {
|
||||
Detail::Error(PrivateError(self)).into_context()
|
||||
fn into_details(self) -> DetailTree {
|
||||
Detail::Error(PrivateError(self)).into_details()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoContext for Detail {
|
||||
impl IntoDetails for Detail {
|
||||
#[inline(always)]
|
||||
fn into_context(self) -> Context {
|
||||
Context::new(self)
|
||||
fn into_details(self) -> DetailTree {
|
||||
DetailTree::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, F> IntoContext for F
|
||||
impl<C, F> IntoDetails for F
|
||||
where
|
||||
C: IntoContext,
|
||||
C: IntoDetails,
|
||||
F: FnOnce() -> C,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn into_context(self) -> Context {
|
||||
self().into_context()
|
||||
fn into_details(self) -> DetailTree {
|
||||
self().into_details()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,41 +1,31 @@
|
|||
use std::panic::Location;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{Detail, How, IntoContext};
|
||||
use crate::{How, IntoDetails};
|
||||
|
||||
pub trait Explain: Sized {
|
||||
type Output;
|
||||
type Output: Explain;
|
||||
|
||||
#[track_caller]
|
||||
#[must_use]
|
||||
fn context(self, context: impl IntoContext) -> Self::Output;
|
||||
fn without_explanation(self) -> Self::Output;
|
||||
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
#[must_use]
|
||||
fn frame(self, context: impl IntoContext) -> Self::Output {
|
||||
let mut context = context.into_context();
|
||||
|
||||
context.extra.reserve(if cfg!(feature = "extra-backtrace") {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
});
|
||||
context.extra.push(Detail::Location(*Location::caller()));
|
||||
#[cfg(feature = "extra-backtrace")]
|
||||
context.extra.push(Detail::backtrace());
|
||||
|
||||
self.context(context)
|
||||
}
|
||||
fn context(self, context: impl IntoDetails) -> Self::Output;
|
||||
}
|
||||
|
||||
impl Explain for How {
|
||||
type Output = Self;
|
||||
|
||||
#[inline(always)]
|
||||
fn without_explanation(self) -> Self::Output {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
fn context(mut self, context: impl IntoContext) -> Self {
|
||||
self.push_context(context.into_context());
|
||||
fn context(mut self, context: impl IntoDetails) -> Self {
|
||||
self.push_context(context.into_details());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -59,20 +49,44 @@ where
|
|||
{
|
||||
type Output = Result<T, How>;
|
||||
|
||||
#[inline(always)]
|
||||
fn without_explanation(self) -> Self::Output {
|
||||
#[cold]
|
||||
#[track_caller]
|
||||
fn err_inner<E>(e: E) -> How
|
||||
where
|
||||
E: std::error::Error + 'static,
|
||||
{
|
||||
// TODO: when specialization is stable, or at least *safe*, specialize on `E = How` and
|
||||
// `E: Send + Sync + 'static` and remove the `+ 'static` bound from E on this Explain
|
||||
// impl.
|
||||
match typeid_cast::cast(e) {
|
||||
Ok(e) => e,
|
||||
//Err(e) => How::new(Context(ContextInner::Elem(Detail::Error(Arc::new(e))))),
|
||||
Err(e) => How::new(e.to_string()),
|
||||
}
|
||||
}
|
||||
match self {
|
||||
Ok(t) => Ok(t),
|
||||
Err(e) => Err(err_inner(e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
fn context(self, context: impl IntoContext) -> Self::Output {
|
||||
fn context(self, context: impl IntoDetails) -> Self::Output {
|
||||
#[cold]
|
||||
#[track_caller]
|
||||
fn into_and_context<E, C>(e: E, c: C) -> How
|
||||
where
|
||||
E: std::error::Error + 'static,
|
||||
C: IntoContext,
|
||||
C: IntoDetails,
|
||||
{
|
||||
// TODO: when specialization is stable, or at least *safe*, specialize on `E = How` and
|
||||
// `E: Send + Sync + 'static` and remove the `+ 'static` bound from E on this Explain
|
||||
// impl.
|
||||
match typeid_cast::cast(e) {
|
||||
Ok(e) => e,
|
||||
// TODO: specialize on Send + Sync at runtime or compile time (possibly via
|
||||
// specialization)
|
||||
//Err(e) => How::new(Context(ContextInner::Elem(Detail::Error(Arc::new(e))))),
|
||||
Err(e) => How::new(e.to_string()),
|
||||
}
|
||||
|
@ -91,29 +105,46 @@ where
|
|||
{
|
||||
type Output = How;
|
||||
|
||||
#[inline]
|
||||
fn without_explanation(self) -> Self::Output {
|
||||
How::new(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
fn context(self, context: impl IntoContext) -> Self::Output {
|
||||
How::new(self).context(context)
|
||||
fn context(self, context: impl IntoDetails) -> Self::Output {
|
||||
self.without_explanation().context(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl Explain for Arc<dyn std::error::Error + Send + Sync> {
|
||||
type Output = How;
|
||||
|
||||
fn without_explanation(self) -> Self::Output {
|
||||
How::new(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
fn context(self, context: impl IntoContext) -> Self::Output {
|
||||
How::new(self).context(context)
|
||||
fn context(self, context: impl IntoDetails) -> Self::Output {
|
||||
self.without_explanation().context(context)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Explain for Option<T> {
|
||||
type Output = Result<T, How>;
|
||||
|
||||
#[inline(always)]
|
||||
fn without_explanation(self) -> Self::Output {
|
||||
match self {
|
||||
Some(t) => Ok(t),
|
||||
None => Err(How::new("called `Option::unwrap()` on a `None` value")),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[track_caller]
|
||||
fn context(self, context: impl IntoContext) -> Self::Output {
|
||||
fn context(self, context: impl IntoDetails) -> Self::Output {
|
||||
match self {
|
||||
Some(t) => Ok(t),
|
||||
None => Err(How::new(context)),
|
||||
|
|
38
src/how.rs
38
src/how.rs
|
@ -2,7 +2,7 @@ use core::panic::Location;
|
|||
|
||||
use crate::*;
|
||||
|
||||
use crate::report::report_write;
|
||||
use self::report::{fmt_report, DetailIndent};
|
||||
|
||||
/// The error type.
|
||||
///
|
||||
|
@ -13,29 +13,30 @@ use crate::report::report_write;
|
|||
any(feature = "arc-backtrace", feature = "clone-with-caveats"),
|
||||
derive(Clone)
|
||||
)]
|
||||
pub struct How(Box<HowInner>);
|
||||
pub struct How(pub(crate) Box<HowInner>);
|
||||
|
||||
struct HowInner {
|
||||
pub(crate) struct HowInner {
|
||||
location: &'static Location<'static>,
|
||||
// TODO: consider storing this vec inline (sharing the allocation with rest of the struct.
|
||||
// Probably move after `backtrace`)
|
||||
context: Vec<Context>,
|
||||
pub(crate) context: Vec<DetailTree>,
|
||||
}
|
||||
|
||||
impl How {
|
||||
#[must_use]
|
||||
#[inline(never)]
|
||||
#[track_caller]
|
||||
pub fn new(context: impl IntoContext) -> Self {
|
||||
pub fn new(details: impl IntoDetails) -> Self {
|
||||
let location = Location::caller();
|
||||
#[allow(unused_mut)]
|
||||
let mut how = Self(Box::new(HowInner {
|
||||
location,
|
||||
context: Vec::with_capacity(4),
|
||||
}))
|
||||
.frame(context);
|
||||
#[cfg(all(feature = "backtrace", not(feature = "extra-backtrace")))]
|
||||
how.top_mut().extra.push(Detail::backtrace());
|
||||
.context(details);
|
||||
if cfg!(not(feature = "extra-backtrace")) {
|
||||
how.top_mut().push_extra(Detail::backtrace());
|
||||
}
|
||||
how
|
||||
}
|
||||
|
||||
|
@ -45,7 +46,7 @@ impl How {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn top(&self) -> &Context {
|
||||
pub fn top(&self) -> &DetailTree {
|
||||
// SAFETY: we only ever push values into context, and the constructor ensures that there
|
||||
// is at least 1 value in context.
|
||||
let o = self.0.context.iter().next();
|
||||
|
@ -60,7 +61,7 @@ impl How {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bottom(&self) -> &Context {
|
||||
pub fn bottom(&self) -> &DetailTree {
|
||||
// SAFETY: we only ever push values into context, and the constructor ensures that there
|
||||
// is at least 1 value in context.
|
||||
let o = self.0.context.iter().next_back();
|
||||
|
@ -75,7 +76,7 @@ impl How {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn top_mut(&mut self) -> &mut Context {
|
||||
pub fn top_mut(&mut self) -> &mut DetailTree {
|
||||
// SAFETY: we only ever push values into context, and the constructor ensures that there
|
||||
// is at least 1 value in context.
|
||||
let o = self.0.context.iter_mut().next();
|
||||
|
@ -90,7 +91,7 @@ impl How {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn bottom_mut(&mut self) -> &mut Context {
|
||||
pub fn bottom_mut(&mut self) -> &mut DetailTree {
|
||||
// SAFETY: we only ever push values into context, and the constructor ensures that there
|
||||
// is at least 1 value in context.
|
||||
let o = self.0.context.iter_mut().next_back();
|
||||
|
@ -105,7 +106,7 @@ impl How {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub fn into_context(self) -> impl Iterator<Item = Context> {
|
||||
pub fn into_context_iter(self) -> impl Iterator<Item = DetailTree> {
|
||||
self.0.context.into_iter()
|
||||
}
|
||||
|
||||
|
@ -117,7 +118,7 @@ impl How {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn push_context(&mut self, context: Context) {
|
||||
pub(crate) fn push_context(&mut self, context: DetailTree) {
|
||||
self.0.context.push(context);
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +150,7 @@ impl std::fmt::Display for How {
|
|||
f.write_str("\n")?;
|
||||
}
|
||||
|
||||
report_write!(f, &opts.indent().next(), "{ctx}")?;
|
||||
fmt_report(f, &opts.indent().next(), DetailIndent::<false>, ctx)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -165,3 +166,10 @@ impl std::fmt::Debug for How {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoDetails for How {
|
||||
#[inline]
|
||||
fn into_details(self) -> crate::DetailTree {
|
||||
Detail::HowError(self).into_details()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#![feature(doc_auto_cfg)]
|
||||
|
||||
mod context;
|
||||
pub use context::{Context, Detail, IntoContext};
|
||||
pub use context::{Detail, DetailTree, IntoDetails};
|
||||
|
||||
mod report;
|
||||
pub use report::{Report, ReportOpts};
|
||||
|
|
145
src/report.rs
145
src/report.rs
|
@ -1,4 +1,4 @@
|
|||
use std::fmt::Write;
|
||||
use std::fmt::{self, Display, Write};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct ReportOpts {
|
||||
|
@ -31,21 +31,28 @@ impl ReportOpts {
|
|||
}
|
||||
}
|
||||
|
||||
macro_rules! report_write {
|
||||
($f:expr, $opts:expr, $msg:literal$(, $($tt:tt)+)?) => {
|
||||
<::std::fmt::Arguments<'_> as $crate::Report>::fmt(&::std::format_args!(
|
||||
$msg$(, $($tt)+)?), $f, $opts
|
||||
)
|
||||
};
|
||||
pub trait Indent {
|
||||
fn write(&self, n: usize, f: &mut impl Write) -> fmt::Result;
|
||||
}
|
||||
pub(crate) use report_write;
|
||||
|
||||
#[inline]
|
||||
fn write_indent(n: usize, f: &mut impl Write) -> std::fmt::Result {
|
||||
for _ in 0..n {
|
||||
f.write_str(" ")?;
|
||||
impl<'a, T: Indent> Indent for &'a T {
|
||||
#[inline]
|
||||
fn write(&self, n: usize, f: &mut impl Write) -> fmt::Result {
|
||||
T::write(*self, n, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DetailIndent<const LINED: bool>;
|
||||
|
||||
impl<const LINED: bool> Indent for DetailIndent<LINED> {
|
||||
#[inline]
|
||||
fn write(&self, n: usize, f: &mut impl Write) -> fmt::Result {
|
||||
for _ in 0..n {
|
||||
f.write_str(if LINED { "┆ " } else { " " })?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `Report` should format the output in a manner suitable for debugging, similar to
|
||||
|
@ -56,62 +63,64 @@ pub trait Report {
|
|||
fn fmt(&self, f: &mut impl Write, opts: &ReportOpts) -> std::fmt::Result;
|
||||
}
|
||||
|
||||
impl<T> Report for T
|
||||
where
|
||||
T: std::fmt::Display,
|
||||
{
|
||||
#[inline(never)]
|
||||
fn fmt(&self, f: &mut impl Write, opts: &ReportOpts) -> std::fmt::Result {
|
||||
use std::fmt::Error;
|
||||
struct IndentedWrite<'a, W: Write> {
|
||||
f: &'a mut W,
|
||||
n: usize,
|
||||
pub fn fmt_report(
|
||||
f: &mut impl Write,
|
||||
opts: &ReportOpts,
|
||||
indent: impl Indent,
|
||||
t: &impl Display,
|
||||
) -> std::fmt::Result {
|
||||
if opts.indentation > 0 {
|
||||
if opts.should_indent() {
|
||||
indent.write(opts.indentation, f)?;
|
||||
}
|
||||
impl<'a, W> Write for IndentedWrite<'a, W>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
#[inline]
|
||||
fn write_str(&mut self, s: &str) -> Result<(), Error> {
|
||||
// TODO: any room for optimization?
|
||||
// iterates over the lines where each str ends with the line terminator.
|
||||
// after giving this a bit of thought I think it is best to indent after any
|
||||
// trailing newline.
|
||||
let mut ss = s.split_inclusive('\n');
|
||||
if let Some(mut s) = ss.next() {
|
||||
self.f.write_str(s)?;
|
||||
for s_next in ss {
|
||||
write_indent(self.n, &mut self.f)?;
|
||||
self.f.write_str(s_next)?;
|
||||
s = s_next;
|
||||
}
|
||||
if matches!(s.chars().rev().next(), Some('\n')) {
|
||||
write_indent(self.n, &mut self.f)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write_char(&mut self, c: char) -> Result<(), Error> {
|
||||
self.f.write_char(c)?;
|
||||
if c == '\n' {
|
||||
write_indent(self.n, &mut self.f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
if opts.indentation > 0 {
|
||||
if opts.should_indent() {
|
||||
write_indent(opts.indentation, f)?;
|
||||
}
|
||||
IndentedWrite {
|
||||
f,
|
||||
n: opts.indentation,
|
||||
}
|
||||
.write_fmt(format_args!("{self}"))
|
||||
} else {
|
||||
f.write_fmt(format_args!("{self}"))
|
||||
IndentedWrite {
|
||||
f,
|
||||
n: opts.indentation,
|
||||
indent,
|
||||
}
|
||||
.write_fmt(format_args!("{t}"))
|
||||
} else {
|
||||
f.write_fmt(format_args!("{t}"))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IndentedWrite<'a, I: Indent, W: Write> {
|
||||
f: &'a mut W,
|
||||
indent: I,
|
||||
n: usize,
|
||||
}
|
||||
impl<'a, I, W> Write for IndentedWrite<'a, I, W>
|
||||
where
|
||||
I: Indent,
|
||||
W: Write,
|
||||
{
|
||||
#[inline]
|
||||
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
|
||||
// TODO: any room for optimization?
|
||||
// iterates over the lines where each str ends with the line terminator.
|
||||
// after giving this a bit of thought I think it is best to indent after any
|
||||
// trailing newline.
|
||||
let mut ss = s.split_inclusive('\n');
|
||||
if let Some(mut s) = ss.next() {
|
||||
self.f.write_str(s)?;
|
||||
for s_next in ss {
|
||||
self.indent.write(self.n, &mut self.f)?;
|
||||
self.f.write_str(s_next)?;
|
||||
s = s_next;
|
||||
}
|
||||
if matches!(s.chars().rev().next(), Some('\n')) {
|
||||
self.indent.write(self.n, &mut self.f)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write_char(&mut self, c: char) -> Result<(), fmt::Error> {
|
||||
self.f.write_char(c)?;
|
||||
if c == '\n' {
|
||||
self.indent.write(self.n, &mut self.f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue