use core::{
error::Error,
fmt::{self, Display},
marker::PhantomData,
};
use derive_where::derive_where;
use alloc::vec::Vec;
use crate::{
Format,
connectors::{TreeConnectors, Unicode},
many_errors::{ManyErrors, Node},
with_context::WithContext,
};
use crate::impl_ref_format;
use super::{ErrorCount, Label, NO_ERRORS};
#[derive_where(Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Tree<Conn = Unicode, const HEADER: bool = true>(PhantomData<fn() -> Conn>);
impl<Conn: fmt::Debug + Default, const HEADER: bool> fmt::Debug for Tree<Conn, HEADER> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Tree")
.field("connectors", &Conn::default())
.field("header", &HEADER)
.finish()
}
}
impl<C, GC, E, F, GF, Conn, const HEADER: bool> Format<ManyErrors<C, E, GC, F, GF>>
for Tree<Conn, HEADER>
where
E: Error + 'static,
F: Format<WithContext<C, E, F>>,
GF: Format<GC>,
Conn: TreeConnectors,
{
fn fmt(errors: &ManyErrors<C, E, GC, F, GF>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut levels = Vec::new();
draw_many::<Conn, C, GC, E, F, GF>(errors, &mut levels, HEADER, f)
}
}
impl_ref_format!(Tree<Conn, HEADER>, Conn, const HEADER: bool);
struct Pad<'a, Conn> {
levels: &'a [bool],
extra: usize,
_conn: PhantomData<fn() -> Conn>,
}
impl<Conn: TreeConnectors> Display for Pad<'_, Conn> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for &last in self.levels {
f.write_str(if last { Conn::GAP } else { Conn::VERT })?;
}
for _ in 0..self.extra {
f.write_str(Conn::GAP)?;
}
Ok(())
}
}
fn indented<Conn: TreeConnectors>(
f: &mut fmt::Formatter<'_>,
levels: &[bool],
extra: usize,
content: impl Display,
) -> fmt::Result {
let prefix = Pad::<Conn> {
levels,
extra,
_conn: PhantomData,
};
crate::indent::indented(f, prefix, content)
}
fn draw_many<Conn, C, GC, E, F, GF>(
errors: &ManyErrors<C, E, GC, F, GF>,
levels: &mut Vec<bool>,
show_header: bool,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result
where
E: Error + 'static,
F: Format<WithContext<C, E, F>>,
GF: Format<GC>,
Conn: TreeConnectors,
{
match errors {
ManyErrors::None => f.write_str(NO_ERRORS),
ManyErrors::One(node) => draw_node::<Conn, C, GC, E, F, GF>(node, levels, f),
ManyErrors::Many(nodes) => {
let pre_first = if show_header {
write!(f, "{}:", ErrorCount(nodes.len()))?;
"\n"
} else {
""
};
draw_children::<Conn, C, GC, E, F, GF>(nodes, levels, pre_first, f)
}
}
}
fn draw_children<Conn, C, GC, E, F, GF>(
nodes: &[Node<C, E, GC, F, GF>],
levels: &mut Vec<bool>,
pre_first: &str,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result
where
E: Error + 'static,
F: Format<WithContext<C, E, F>>,
GF: Format<GC>,
Conn: TreeConnectors,
{
for (i, node) in nodes.iter().enumerate() {
let is_last = i == nodes.len() - 1;
let connector = if is_last { Conn::LAST } else { Conn::BRANCH };
let sep = if i == 0 { pre_first } else { "\n" };
let pad = Pad::<Conn> {
levels,
extra: 0,
_conn: PhantomData,
};
write!(f, "{sep}{pad}{connector}")?;
levels.push(is_last);
draw_node::<Conn, C, GC, E, F, GF>(node, levels, f)?;
levels.pop();
}
Ok(())
}
fn draw_node<Conn, C, GC, E, F, GF>(
node: &Node<C, E, GC, F, GF>,
levels: &mut Vec<bool>,
f: &mut fmt::Formatter<'_>,
) -> fmt::Result
where
E: Error + 'static,
F: Format<WithContext<C, E, F>>,
GF: Format<GC>,
Conn: TreeConnectors,
{
match node {
Node::Leaf(w) => {
indented::<Conn>(f, levels, 0, w)?;
draw_error_chain::<Conn>(w.error.source(), levels, f)
}
Node::Group(w) => {
let label = Label::<_, GF>(&w.context, PhantomData);
match w.errors.as_ref() {
ManyErrors::None => {
indented::<Conn>(f, levels, 0, format_args!("{label}: {NO_ERRORS}"))
}
ManyErrors::One(inner) => {
indented::<Conn>(f, levels, 0, format_args!("{label}: "))?;
draw_node::<Conn, C, GC, E, F, GF>(inner, levels, f)
}
ManyErrors::Many(nodes) => {
indented::<Conn>(
f,
levels,
0,
format_args!("{label} ({}):", ErrorCount(nodes.len())),
)?;
draw_children::<Conn, C, GC, E, F, GF>(nodes, levels, "\n", f)
}
}
}
}
}
fn draw_error_chain<Conn: TreeConnectors>(
source: Option<&dyn Error>,
levels: &[bool],
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
let Some(first) = source else {
return Ok(());
};
for (depth, src) in crate::chain(first).enumerate() {
let pad = Pad::<Conn> {
levels,
extra: depth,
_conn: PhantomData,
};
write!(f, "\n{pad}{}", Conn::LAST)?;
indented::<Conn>(f, levels, depth + 1, src)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
Formatted, ManyErrors,
connectors::{Ascii, Unicode},
many_errors::strategy::test_helpers::{two_leaves, with_chain},
tests::Inner,
};
#[test]
fn test_tree_empty() {
let e = ManyErrors::<&str, Inner>::new();
assert_eq!(e.tree().to_string(), "no errors");
}
#[test]
fn test_tree_debug() {
assert_eq!(
format!("{:?}", Tree::<Unicode, true>::default()),
"Tree { connectors: Unicode, header: true }"
);
assert_eq!(
format!("{:?}", Tree::<Ascii, false>::default()),
"Tree { connectors: Ascii, header: false }"
);
}
#[test]
fn test_tree_empty_group() {
let mut outer = ManyErrors::<&str, Inner>::new();
outer.push_group("g", ManyErrors::new());
assert_eq!(outer.tree().to_string(), "g: no errors");
}
#[test]
fn test_tree_single_leaf() {
let mut e = ManyErrors::<&str, Inner>::new();
e.push("ctx", Inner::A);
assert_eq!(e.tree().to_string(), "ctx: InnerA");
}
#[test]
fn test_tree_two_leaves_unicode() {
let e = two_leaves();
assert_eq!(
e.tree().to_string(),
"2 errors:\n├─ a: InnerA\n└─ b: InnerB"
);
}
#[test]
fn test_tree_ascii() {
let e = two_leaves();
assert_eq!(
Formatted::<_, Tree<Ascii>>::new(&e).to_string(),
"2 errors:\n|- a: InnerA\n`- b: InnerB"
);
}
#[test]
fn test_tree_no_header() {
let e = two_leaves();
assert_eq!(
Formatted::<_, Tree<Unicode, false>>::new(&e).to_string(),
"├─ a: InnerA\n└─ b: InnerB"
);
}
#[test]
fn test_tree_with_source_chain() {
let e = with_chain();
let s = e.tree().to_string();
assert!(s.contains("├─ a: mid"), "got: {s}");
assert!(s.contains("│ └─ InnerA"), "got: {s}");
assert!(s.contains("└─ b: mid"), "got: {s}");
assert!(s.contains(" └─ InnerB"), "got: {s}");
}
#[test]
fn test_tree_nested_group() {
let mut inner = ManyErrors::<&str, Inner>::new();
inner.push("x", Inner::A);
inner.push("y", Inner::B);
let mut outer = ManyErrors::<&str, Inner>::new();
outer.push_group("region", inner);
outer.push("leaf", Inner::A);
let s = outer.tree().to_string();
assert!(s.contains("2 errors:"), "got: {s}");
assert!(s.contains("region (2 errors):"), "got: {s}");
assert!(s.contains("x: InnerA"), "got: {s}");
assert!(s.contains("y: InnerB"), "got: {s}");
assert!(s.contains("leaf: InnerA"), "got: {s}");
}
#[test]
fn test_tree_heterogeneous_group_label() {
let mut inner = ManyErrors::<&str, Inner, usize>::new();
inner.push("x", Inner::A);
let mut outer = ManyErrors::<&str, Inner, usize>::new();
outer.push_group(7, inner);
outer.push("leaf", Inner::B);
let s = outer.tree().to_string();
assert!(s.contains("7: x: InnerA"), "got: {s}");
assert!(s.contains("leaf: InnerB"), "got: {s}");
}
#[test]
fn test_tree_custom_group_format() {
struct Bracket;
impl<GC: Display> Format<GC> for Bracket {
fn fmt(label: &GC, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{label}]")
}
}
let mut inner = ManyErrors::<&str, Inner, &str, crate::with_context::Colon, Bracket>::new();
inner.push("x", Inner::A);
let mut outer = ManyErrors::<&str, Inner, &str, crate::with_context::Colon, Bracket>::new();
outer.push_group("region", inner);
assert_eq!(outer.tree().to_string(), "[region]: x: InnerA");
}
}