Coverage Report

Created: 2025-02-07 03:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/runner/work/bare_err_tree/bare_err_tree/bare_err_tree/src/fmt_logic.rs
Line
Count
Source
1
/*
2
 * This Source Code Form is subject to the terms of the Mozilla Public
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this
4
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
 */
6
7
use core::{
8
    fmt::{self, Display, Formatter, Write},
9
    str::{self, Chars},
10
};
11
12
use crate::{AsErrTree, ErrTree, ErrTreeDisplay};
13
14
impl<E: AsErrTree, const FRONT_MAX: usize> ErrTreeDisplay<E, FRONT_MAX> {
15
1
    pub fn new(tree: E) -> Self {
16
1
        Self(tree)
17
1
    }
18
}
19
20
impl<E: AsErrTree, const FRONT_MAX: usize> Display for ErrTreeDisplay<E, FRONT_MAX> {
21
    #[track_caller]
22
4
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
23
4
        let mut res = Ok(());
24
4
        self.0.as_err_tree(&mut |tree| {
25
4
            res = fmt_tree::<FRONT_MAX, _, _>(tree, f);
26
4
        });
27
4
        res
28
4
    }
29
}
30
31
4
pub(crate) fn fmt_tree<const FRONT_MAX: usize, T, W>(tree: T, f: &mut W) -> fmt::Result
32
4
where
33
4
    T: ErrTreeFormattable,
34
4
    W: fmt::Write + ?Sized,
35
4
{
36
4
    #[cfg(not(feature = "heap_buffer"))]
37
4
    let mut front_lines = [0; FRONT_MAX];
38
4
39
4
    #[cfg(feature = "heap_buffer")]
40
4
    let mut front_lines = alloc::vec![0; FRONT_MAX].into_boxed_slice();
41
4
42
4
    #[cfg(all(not(feature = "heap_buffer"), feature = "tracing"))]
43
4
    let mut found_traces: [_; FRONT_MAX] = core::array::from_fn(|_| None);
44
4
45
4
    #[cfg(all(feature = "heap_buffer", feature = "tracing"))]
46
4
    let mut found_traces = core::iter::repeat_with(|| None)
47
4
        .take(FRONT_MAX)
48
4
        .collect::<alloc::vec::Vec<_>>()
49
4
        .into_boxed_slice();
50
4
51
4
    ErrTreeFmt::<FRONT_MAX, _> {
52
4
        tree,
53
4
        scratch_fill: 0,
54
4
        front_lines: &mut front_lines,
55
4
56
4
        #[cfg(feature = "tracing")]
57
4
        found_traces: &mut found_traces,
58
4
    }
59
4
    .fmt(f)
60
4
}
61
62
#[cfg(feature = "tracing")]
63
pub(crate) struct TraceSpan<T: Eq, CharIter> {
64
    pub identifier: T,
65
    pub target: CharIter,
66
    pub name: CharIter,
67
    pub fields: CharIter,
68
    pub location: Option<(CharIter, u32)>,
69
}
70
71
pub(crate) trait ErrTreeFormattable {
72
    fn apply_msg<W: fmt::Write>(&self, f: W) -> fmt::Result;
73
74
    type Source<'a>: ErrTreeFormattable<TraceSpanId = Self::TraceSpanId>;
75
76
    #[allow(unused)]
77
    fn sources_empty(&mut self) -> bool;
78
79
    fn apply_to_leading_sources<F>(&mut self, func: F) -> fmt::Result
80
    where
81
        F: FnMut(Self::Source<'_>) -> fmt::Result;
82
    fn apply_to_last_source<F>(&mut self, func: F) -> fmt::Result
83
    where
84
        F: FnMut(Self::Source<'_>) -> fmt::Result;
85
    #[cfg(feature = "source_line")]
86
    fn has_source_line(&self) -> bool;
87
    #[cfg(feature = "source_line")]
88
    fn apply_source_line<W: fmt::Write>(&self, f: W) -> fmt::Result;
89
90
    #[cfg(feature = "tracing")]
91
    fn trace_empty(&self) -> bool;
92
93
    type TraceSpanId: Eq;
94
    type TraceSpanIter<'a>: IntoIterator<Item = char>;
95
96
    #[cfg(feature = "tracing")]
97
    fn apply_trace<F>(&self, func: F) -> fmt::Result
98
    where
99
        F: FnMut(TraceSpan<Self::TraceSpanId, Self::TraceSpanIter<'_>>) -> fmt::Result;
100
}
101
102
impl<T> ErrTreeFormattable for &mut T
103
where
104
    T: ErrTreeFormattable,
105
{
106
0
    fn apply_msg<W: fmt::Write>(&self, f: W) -> fmt::Result {
107
0
        T::apply_msg(self, f)
108
0
    }
109
110
    type Source<'a> = T::Source<'a>;
111
0
    fn sources_empty(&mut self) -> bool {
112
0
        T::sources_empty(self)
113
0
    }
114
0
    fn apply_to_leading_sources<F>(&mut self, func: F) -> fmt::Result
115
0
    where
116
0
        F: FnMut(Self::Source<'_>) -> fmt::Result,
117
0
    {
118
0
        T::apply_to_leading_sources(self, func)
119
0
    }
120
0
    fn apply_to_last_source<F>(&mut self, func: F) -> fmt::Result
121
0
    where
122
0
        F: FnMut(Self::Source<'_>) -> fmt::Result,
123
0
    {
124
0
        T::apply_to_last_source(self, func)
125
0
    }
126
127
    #[cfg(feature = "source_line")]
128
    fn has_source_line(&self) -> bool {
129
        T::has_source_line(self)
130
    }
131
    #[cfg(feature = "source_line")]
132
    fn apply_source_line<W: fmt::Write>(&self, f: W) -> fmt::Result {
133
        T::apply_source_line(self, f)
134
    }
135
136
    #[cfg(feature = "tracing")]
137
    fn trace_empty(&self) -> bool {
138
        T::trace_empty(self)
139
    }
140
141
    type TraceSpanId = T::TraceSpanId;
142
    type TraceSpanIter<'a> = T::TraceSpanIter<'a>;
143
144
    #[cfg(feature = "tracing")]
145
    fn apply_trace<F>(&self, func: F) -> fmt::Result
146
    where
147
        F: FnMut(TraceSpan<Self::TraceSpanId, Self::TraceSpanIter<'_>>) -> fmt::Result,
148
    {
149
        T::apply_trace(self, func)
150
    }
151
}
152
153
impl ErrTreeFormattable for ErrTree<'_> {
154
11
    fn apply_msg<W: fmt::Write>(&self, mut f: W) -> fmt::Result {
155
11
        write!(f, "{}", self.inner)
156
11
    }
157
158
    type Source<'a> = ErrTree<'a>;
159
0
    fn sources_empty(&mut self) -> bool {
160
0
        self.sources.is_empty()
161
0
    }
162
11
    fn apply_to_leading_sources<F>(&mut self, mut func: F) -> fmt::Result
163
11
    where
164
11
        F: FnMut(Self::Source<'_>) -> fmt::Result,
165
11
    {
166
11
        if let Some(
initial_slice2
) = self.sources.take_stored_and_next() {
167
2
            let mut initial_iter = initial_slice.as_slice().iter().cloned();
168
2
            if let Some(mut source) = initial_iter.next() {
169
5
                for next_source in 
initial_iter.chain(self.sources.by_ref())2
{
170
5
                    let mut res = Ok(());
171
5
                    source.as_err_tree(&mut |tree| res = (func)(tree));
172
5
                    res
?0
;
173
5
                    source = next_source
174
                }
175
0
            }
176
9
        }
177
11
        Ok(())
178
11
    }
179
11
    fn apply_to_last_source<F>(&mut self, mut func: F) -> fmt::Result
180
11
    where
181
11
        F: FnMut(Self::Source<'_>) -> fmt::Result,
182
11
    {
183
11
        let _ = self.sources.by_ref().last();
184
11
        if let Some(
source2
) = self.sources.take_stored() {
185
2
            let mut res = Ok(());
186
2
            source.as_err_tree(&mut |tree| res = (func)(tree));
187
2
            res
?0
;
188
9
        }
189
11
        Ok(())
190
11
    }
191
192
    #[cfg(feature = "source_line")]
193
    fn has_source_line(&self) -> bool {
194
        self.location.is_some()
195
    }
196
197
    #[cfg(feature = "source_line")]
198
    fn apply_source_line<W: fmt::Write>(&self, mut f: W) -> fmt::Result {
199
        if let Some(loc) = self.location {
200
            write!(f, "{}", loc)?;
201
        }
202
        Ok(())
203
    }
204
205
    #[cfg(feature = "tracing")]
206
    fn trace_empty(&self) -> bool {
207
        let mut empty = true;
208
        if let Some(trace) = &self.trace {
209
            trace.with_spans(|_, _| {
210
                empty = false;
211
                true
212
            });
213
        }
214
        empty
215
    }
216
217
    #[cfg(not(feature = "tracing"))]
218
    type TraceSpanId = ();
219
220
    #[cfg(feature = "tracing")]
221
    type TraceSpanId = tracing_core::callsite::Identifier;
222
223
    type TraceSpanIter<'a> = Chars<'a>;
224
225
    #[cfg(feature = "tracing")]
226
    fn apply_trace<F>(&self, mut func: F) -> fmt::Result
227
    where
228
        F: FnMut(TraceSpan<Self::TraceSpanId, Self::TraceSpanIter<'_>>) -> fmt::Result,
229
    {
230
        if let Some(trace) = &self.trace {
231
            let mut res = Ok(());
232
            trace.with_spans(|metadata, fields| {
233
                res = (func)(TraceSpan {
234
                    identifier: metadata.callsite(),
235
                    target: metadata.target().chars(),
236
                    name: metadata.name().chars(),
237
                    fields: fields.chars(),
238
                    location: metadata
239
                        .file()
240
                        .and_then(|file| metadata.line().map(|line| (file.chars(), line))),
241
                });
242
                res.is_ok()
243
            });
244
            res
245
        } else {
246
            Ok(())
247
        }
248
    }
249
}
250
251
pub(crate) struct ErrTreeFmt<'a, const FRONT_MAX: usize, T: ErrTreeFormattable> {
252
    pub tree: T,
253
    pub scratch_fill: usize,
254
    /// Most be initialized large enough to fit 6 x (max depth) bytes
255
    pub front_lines: &'a mut [u8],
256
257
    #[cfg(feature = "tracing")]
258
    pub found_traces: &'a mut [Option<T::TraceSpanId>],
259
}
260
261
/// Workaround for lack of `const` in [`core::cmp::max`].
262
#[cfg_attr(coverage, coverage(off))]
263
const fn max_const(lhs: usize, rhs: usize) -> usize {
264
    if lhs >= rhs {
265
        lhs
266
    } else {
267
        rhs
268
    }
269
}
270
271
const CONTINUING: &str = "│   ";
272
const DANGLING: &str = "    ";
273
const MAX_CELL_LEN: usize = max_const(CONTINUING.len(), DANGLING.len());
274
275
impl<const FRONT_MAX: usize, T: ErrTreeFormattable> ErrTreeFmt<'_, FRONT_MAX, T> {
276
    /// The front lines
277
    #[inline]
278
25
    fn front_lines_str(front_lines: &[u8], scratch_fill: usize) -> &str {
279
25
        str::from_utf8(&front_lines[..scratch_fill])
280
25
            .expect("All characters are static and guaranteed to be valid UTF-8")
281
25
    }
282
283
    /// Preamble arrow connections
284
    #[inline]
285
14
    fn write_front_lines<W>(front_lines: &[u8], f: &mut W, scratch_fill: usize) -> fmt::Result
286
14
    where
287
14
        W: fmt::Write + ?Sized,
288
14
    {
289
14
        f.write_char('\n')
?0
;
290
14
        f.write_str(Self::front_lines_str(front_lines, scratch_fill))
291
14
    }
292
293
    /// Push in the correct fill characters
294
    #[inline]
295
13
    fn add_front_line(front_lines: &mut [u8], last: bool, scratch_fill: usize) {
296
13
        let chars: &str = if last { 
DANGLING2
} else {
CONTINUING11
};
297
298
13
        front_lines[scratch_fill..scratch_fill + chars.len()].copy_from_slice(chars.as_bytes());
299
13
    }
300
    #[cfg(feature = "tracing")]
301
    /// There is tracing after if the trace is nonempty
302
    fn tracing_after(&self) -> bool {
303
        !self.tree.trace_empty()
304
    }
305
306
    #[cfg(not(feature = "tracing"))]
307
11
    fn tracing_after(&self) -> bool {
308
11
        false
309
11
    }
310
311
    #[cfg(feature = "source_line")]
312
    fn source_line<W>(&mut self, f: &mut W, tracing_after: bool) -> fmt::Result
313
    where
314
        W: fmt::Write + ?Sized,
315
    {
316
        if self.tree.has_source_line() {
317
            Self::write_front_lines(self.front_lines, f, self.scratch_fill)?;
318
319
            if !tracing_after && self.tree.sources_empty() {
320
                f.write_str("╰─ ")?;
321
            } else {
322
                f.write_str("├─ ")?;
323
            }
324
            if cfg!(feature = "unix_color") {
325
                f.write_str("at \x1b[3m")?;
326
                self.tree.apply_source_line(&mut *f)?;
327
                f.write_str("\x1b[0m")?;
328
            } else {
329
                f.write_str("at ")?;
330
                self.tree.apply_source_line(f)?;
331
            }
332
        }
333
334
        Ok(())
335
    }
336
337
    /// Simple implementation of pretty formatting
338
    #[cfg(feature = "tracing")]
339
    fn tracing_field_fmt<I, W>(
340
        f: &mut W,
341
        front_lines: &[u8],
342
        fields: I,
343
        scratch_fill: usize,
344
    ) -> fmt::Result
345
    where
346
        I: IntoIterator<Item = char>,
347
        W: fmt::Write + ?Sized,
348
    {
349
        let mut depth = 0;
350
        let mut in_quote = false;
351
352
        const START_CHARS: [char; 3] = ['{', '[', '('];
353
        const END_CHARS: [char; 3] = ['}', ']', ')'];
354
        const ESC: char = '\\';
355
356
        let push_front = |f: &mut W, depth| {
357
            Self::write_front_lines(front_lines, f, scratch_fill)?;
358
            f.write_str("│    ")?;
359
            for _ in 0..depth {
360
                f.write_str("  ")?;
361
            }
362
            Ok(())
363
        };
364
365
        push_front(f, depth)?;
366
        let mut prev = '\0';
367
        for c in fields {
368
            let mut space_except = false;
369
370
            if in_quote {
371
                if prev == '"' {
372
                    in_quote = false;
373
                    if c == ' ' {
374
                        space_except = true;
375
                    }
376
                }
377
            } else {
378
                match prev {
379
                    x if START_CHARS.contains(&x) => {
380
                        depth += 1;
381
                        push_front(f, depth)?;
382
                        if c == ' ' {
383
                            space_except = true;
384
                        }
385
                    }
386
                    ',' => {
387
                        push_front(f, depth)?;
388
                        if c == ' ' {
389
                            space_except = true;
390
                        }
391
                    }
392
                    '"' => in_quote = true,
393
                    x => {
394
                        if END_CHARS.contains(&c) {
395
                            depth -= 1;
396
                            push_front(f, depth)?;
397
                        } else if c == ' ' && END_CHARS.contains(&x) {
398
                            space_except = true;
399
                            if depth == 0 {
400
                                push_front(f, depth)?;
401
                            }
402
                        }
403
                    }
404
                }
405
            }
406
407
            // Special case for escaping
408
            prev = if prev == ESC { '\0' } else { c };
409
410
            if !space_except {
411
                f.write_char(c)?;
412
            }
413
        }
414
415
        Ok(())
416
    }
417
418
    #[cfg(feature = "tracing")]
419
    fn tracing<W>(&mut self, f: &mut W) -> fmt::Result
420
    where
421
        W: fmt::Write + ?Sized,
422
    {
423
        if !self.tree.trace_empty() {
424
            Self::write_front_lines(self.front_lines, f, self.scratch_fill)?;
425
            write!(f, "│")?;
426
427
            #[cfg(all(not(feature = "heap_buffer"), feature = "tracing"))]
428
            let mut repeated: [_; FRONT_MAX] = core::array::from_fn(|_| None);
429
430
            #[cfg(all(feature = "heap_buffer", feature = "tracing"))]
431
            let mut repeated = core::iter::repeat_with(|| None)
432
                .take(FRONT_MAX)
433
                .collect::<alloc::vec::Vec<_>>()
434
                .into_boxed_slice();
435
436
            let mut repeated_idx = 0;
437
438
            self.tree.apply_trace(|trace_span| {
439
                let pos_dup = self
440
                    .found_traces
441
                    .iter()
442
                    .take_while(|x| x.is_some())
443
                    .flatten()
444
                    .position(|c| *c == trace_span.identifier);
445
446
                if let Some(pos_dup) = pos_dup {
447
                    repeated[repeated_idx] = Some(pos_dup);
448
                    repeated_idx += 1;
449
                } else {
450
                    let depth = self.found_traces.partition_point(|x| x.is_some());
451
                    if depth < self.found_traces.len() {
452
                        self.found_traces[depth] = Some(trace_span.identifier);
453
                    }
454
455
                    Self::write_front_lines(self.front_lines, f, self.scratch_fill)?;
456
                    write!(f, "├─ tracing frame {} => ", depth)?;
457
                    //depth, trace_span.target, trace_span.name
458
                    for c in trace_span.target {
459
                        f.write_char(c)?
460
                    }
461
                    f.write_str("::")?;
462
                    for c in trace_span.name {
463
                        f.write_char(c)?
464
                    }
465
466
                    let mut fields = trace_span.fields.into_iter().peekable();
467
                    if fields.peek().is_some() {
468
                        write!(f, " with")?;
469
                        Self::tracing_field_fmt(f, self.front_lines, fields, self.scratch_fill)?;
470
                    }
471
472
                    if let Some((file, line)) = trace_span.location {
473
                        Self::write_front_lines(self.front_lines, f, self.scratch_fill)?;
474
                        f.write_str("│        at ")?;
475
                        for c in file {
476
                            f.write_char(c)?
477
                        }
478
                        f.write_char(':')?;
479
                        write!(f, "{line}")?;
480
                    };
481
                };
482
483
                Ok(())
484
            })?;
485
486
            if repeated_idx > 0 {
487
                Self::write_front_lines(self.front_lines, f, self.scratch_fill)?;
488
                if self.tree.sources_empty() {
489
                    f.write_str("╰─ ")?;
490
                } else {
491
                    f.write_str("├─ ")?;
492
                }
493
494
                write!(f, "{} duplicate tracing frame(s): [", repeated_idx)?;
495
496
                for idx in 0..repeated_idx - 1 {
497
                    write!(f, "{}, ", repeated[idx].expect("Previously set as Some"))?;
498
                }
499
500
                write!(
501
                    f,
502
                    "{}]",
503
                    repeated[repeated_idx - 1].expect("Previously set as Some")
504
                )?;
505
            }
506
        }
507
        Ok(())
508
    }
509
510
    #[allow(unused_mut)]
511
11
    fn fmt<W>(mut self, f: &mut W) -> fmt::Result
512
11
    where
513
11
        W: fmt::Write + ?Sized,
514
11
    {
515
11
        self.tree.apply_msg(LeadingLineFormatter::new(
516
11
            &mut *f,
517
11
            Self::front_lines_str(self.front_lines, self.scratch_fill),
518
11
        ))
?0
;
519
520
        #[cfg_attr(
521
            not(any(feature = "source_line", feature = "tracing")),
522
            expect(unused_variables, reason = "only used to track for a tracing line")
523
        )]
524
11
        let tracing_after = self.tracing_after();
525
11
526
11
        #[cfg(feature = "source_line")]
527
11
        self.source_line(f, tracing_after)?;
528
11
529
11
        #[cfg(feature = "tracing")]
530
11
        self.tracing(f)?;
531
11
532
11
        let mut source_fmt =
533
            |front_lines: &mut [u8],
534
             scratch_fill: usize,
535
             #[cfg(feature = "tracing")] found_traces: &mut [Option<T::TraceSpanId>],
536
             source: T::Source<'_>,
537
7
             last: bool| {
538
7
                Self::write_front_lines(front_lines, f, scratch_fill)
?0
;
539
7
                f.write_char('│')
?0
;
540
7
                Self::write_front_lines(front_lines, f, scratch_fill)
?0
;
541
542
7
                if last {
543
2
                    f.write_str("╰─▶ ")
?0
;
544
                } else {
545
5
                    f.write_str("├─▶ ")
?0
;
546
                }
547
548
7
                let additional_scratch = if last {
549
2
                    DANGLING.len()
550
                } else {
551
5
                    CONTINUING.len()
552
                };
553
554
7
                ErrTreeFmt::<FRONT_MAX, _> {
555
7
                    tree: source,
556
7
                    scratch_fill: scratch_fill + additional_scratch,
557
7
                    front_lines,
558
7
559
7
                    #[cfg(feature = "tracing")]
560
7
                    found_traces,
561
7
                }
562
7
                .fmt(f)
563
7
            };
564
565
11
        if self.scratch_fill + MAX_CELL_LEN >= FRONT_MAX {
566
            // Stop printing deeper in the stack past this point
567
0
            writeln!(f, "{:.<1$}", "", MAX_CELL_LEN)?;
568
        } else {
569
            // Normal operation
570
571
11
            Self::add_front_line(self.front_lines, false, self.scratch_fill);
572
11
            self.tree.apply_to_leading_sources(|source| {
573
5
                source_fmt(
574
5
                    self.front_lines,
575
5
                    self.scratch_fill,
576
5
                    #[cfg(feature = "tracing")]
577
5
                    self.found_traces,
578
5
                    source,
579
5
                    false,
580
5
                )
581
11
            })
?0
;
582
583
11
            self.tree.apply_to_last_source(|source| {
584
2
                Self::add_front_line(self.front_lines, true, self.scratch_fill);
585
2
                source_fmt(
586
2
                    self.front_lines,
587
2
                    self.scratch_fill,
588
2
                    #[cfg(feature = "tracing")]
589
2
                    self.found_traces,
590
2
                    source,
591
2
                    true,
592
2
                )
593
11
            })
?0
;
594
        };
595
596
11
        Ok(())
597
11
    }
598
}
599
600
/// Injects the newline leader
601
struct LeadingLineFormatter<'a, F> {
602
    formatter: F,
603
    leading: &'a str,
604
}
605
606
impl<'a, F> LeadingLineFormatter<'a, F> {
607
11
    pub fn new(formatter: F, leading: &'a str) -> Self {
608
11
        Self { formatter, leading }
609
11
    }
610
}
611
612
impl<F: Write> Write for LeadingLineFormatter<'_, F> {
613
14
    fn write_str(&mut self, s: &str) -> fmt::Result {
614
203
        if s.chars().all(|c| c != '\n'
)14
{
615
12
            self.formatter.write_str(s)
?0
616
        } else {
617
44
            for c in 
s.chars()2
{
618
44
                self.write_char(c)
?0
;
619
            }
620
        }
621
14
        Ok(())
622
14
    }
623
624
44
    fn write_char(&mut self, c: char) -> fmt::Result {
625
44
        self.formatter.write_char(c)
?0
;
626
627
44
        if c == '\n' {
628
3
            self.formatter.write_str(self.leading)
?0
;
629
3
            self.formatter.write_str("│ ")
?0
;
630
41
        }
631
632
44
        Ok(())
633
44
    }
634
}