1use {
5 crate::{AppError, Inner},
6 core::fmt,
7 itertools::{Itertools, Position as ItertoolsPos},
8 std::vec,
9};
10
11pub struct PrettyDisplay<'a, D = ()> {
13 root: &'a Inner<D>,
15
16 ignore_err: Option<fn(&D) -> bool>,
19}
20
21impl<D> fmt::Debug for PrettyDisplay<'_, D>
22where
23 D: fmt::Debug + 'static,
24{
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 match f.alternate() {
27 true => f
28 .debug_struct("PrettyDisplay")
29 .field_with("root", |f| write!(f, "{:#?}", self.root))
30 .field("ignore_err", &self.ignore_err.as_ref().map(|_| ()))
31 .finish(),
32 false => write!(f, "{self}"),
33 }
34 }
35}
36
37#[derive(PartialEq, Clone, Copy, Debug)]
38enum Column {
39 Line,
40 Empty,
41}
42
43impl Column {
44 const fn as_str(self) -> &'static str {
46 match self {
47 Self::Line => "│ ",
48 Self::Empty => " ",
49 }
50 }
51}
52
53impl<'a, D> PrettyDisplay<'a, D> {
54 pub(crate) fn new(root: &'a Inner<D>) -> Self {
56 Self { root, ignore_err: None }
57 }
58
59 #[must_use]
61 pub fn with_ignore_err(self, ignore_err: fn(&D) -> bool) -> Self {
62 Self {
63 ignore_err: Some(ignore_err),
64 ..self
65 }
66 }
67
68 #[expect(clippy::unused_self, reason = "We might use it in the future")]
70 fn fmt_msg(&self, f: &mut fmt::Formatter<'_>, msg: &str, columns: &[Column]) -> fmt::Result {
71 for (msg_pos, msg) in msg.split_inclusive('\n').with_position() {
72 if matches!(msg_pos, ItertoolsPos::Middle | ItertoolsPos::Last) {
74 for c in columns {
75 f.pad(c.as_str())?;
76 }
77 }
78
79 write!(f, "{msg}")?;
80 }
81
82 Ok(())
83 }
84
85 fn fmt_single(&self, f: &mut fmt::Formatter<'_>, err: &Inner<D>, columns: &mut Vec<Column>) -> fmt::Result {
87 let (msg, source) = match err {
89 Inner::Single { msg, source, .. } => (msg, source),
90 Inner::Multiple(errs) => return self.fmt_multiple(f, errs, columns),
91 };
92
93 self.fmt_msg(f, msg, columns)?;
95
96 if let Some(mut cur_source) = source.as_ref() {
98 let starting_columns = columns.len();
99 loop {
100 f.pad("\n")?;
102 for c in &*columns {
103 f.pad(c.as_str())?;
104 }
105 f.pad("└─")?;
106 columns.push(Column::Empty);
107
108 match &*cur_source.inner {
110 Inner::Single { msg, source, .. } => {
111 self.fmt_msg(f, msg, columns)?;
112
113 cur_source = match source {
115 Some(source) => source,
116 _ => break,
117 };
118 },
119 Inner::Multiple(errs) => {
120 self.fmt_multiple(f, errs, columns)?;
121 break;
122 },
123 }
124 }
125 let _: vec::Drain<'_, _> = columns.drain(starting_columns..);
126 }
127
128 Ok(())
129 }
130
131 fn fmt_multiple(&self, f: &mut fmt::Formatter<'_>, errs: &[AppError<D>], columns: &mut Vec<Column>) -> fmt::Result {
133 write!(f, "Multiple errors:")?;
136
137 let mut ignored_errs = 0;
139 for (pos, err) in errs.iter().with_position() {
140 if let Some(ignore_err) = self.ignore_err &&
142 self::should_ignore(&err.inner, ignore_err)
143 {
144 ignored_errs += 1;
145 continue;
146 }
147
148 f.pad("\n")?;
149 for c in &*columns {
150 f.pad(c.as_str())?;
151 }
152
153 match ignored_errs == 0 && matches!(pos, ItertoolsPos::Last | ItertoolsPos::Only) {
156 true => {
157 f.pad("└─")?;
158 columns.push(Column::Empty);
159 },
160 false => {
161 f.pad("├─")?;
162 columns.push(Column::Line);
163 },
164 }
165
166 self.fmt_single(f, &err.inner, columns)?;
167 let _: Option<_> = columns.pop();
168 }
169
170 if ignored_errs != 0 {
171 f.pad("\n")?;
172 for c in &*columns {
173 f.pad(c.as_str())?;
174 }
175 f.pad("└─")?;
176 write!(f, "({ignored_errs} ignored errors)")?;
177 }
178
179 Ok(())
180 }
181}
182
183impl<D> fmt::Display for PrettyDisplay<'_, D> {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 let mut columns = vec![];
186 self.fmt_single(f, self.root, &mut columns)?;
187 assert_eq!(columns.len(), 0, "There should be no columns after formatting");
188
189 Ok(())
190 }
191}
192
193fn should_ignore<D>(err: &Inner<D>, ignore_err: fn(&D) -> bool) -> bool {
195 match err {
196 Inner::Single { source, data, .. } =>
199 ignore_err(data) ||
200 source
201 .as_ref()
202 .is_some_and(|source| self::should_ignore(&source.inner, ignore_err)),
203
204 Inner::Multiple(errs) => errs.iter().all(|err| self::should_ignore(&err.inner, ignore_err)),
206 }
207}