artichoke/
backtrace.rs

1//! Format Ruby `Exception` backtraces.
2
3use std::error;
4use std::io;
5
6use termcolor::{ColorSpec, WriteColor};
7
8use crate::prelude::*;
9
10/// Format an `Exception` backtrace into an [`io::Write`] suitable for
11/// displaying in a Ruby REPL.
12///
13/// This backtrace has the same style and formatting as one returned from the
14/// `irb` command in MRI.
15///
16/// # Examples
17///
18/// Executing the following Ruby code:
19///
20/// ```ruby
21/// def fail; raise RuntimeError, "bang!"; end
22/// fail
23/// ```
24///
25/// Results in this stack trace:
26///
27/// ```txt
28/// Traceback (most recent call last):
29///     2: from (airb):2
30///     1: from (airb):1:in fail
31/// RuntimeError (bang!)
32/// ```
33///
34/// # Errors
35///
36/// If writing into the provided `out` writer fails, an error is returned.
37pub fn format_repl_trace_into<W, E>(mut error: W, interp: &mut Artichoke, exc: &E) -> Result<(), Box<dyn error::Error>>
38where
39    W: io::Write + WriteColor,
40    E: RubyException,
41{
42    // reset colors
43    error.reset()?;
44
45    // Format backtrace if present
46    if let Some(backtrace) = exc.vm_backtrace(interp) {
47        error.set_color(ColorSpec::new().set_bold(true))?;
48        write!(error, "Traceback")?;
49        error.reset()?;
50        writeln!(error, " (most recent call last):")?;
51        for (num, frame) in backtrace.into_iter().enumerate().rev() {
52            write!(error, "\t{}: from ", num + 1)?;
53            error.write_all(frame.as_slice())?;
54            writeln!(error)?;
55        }
56    }
57
58    // Format exception class and message
59    error.set_color(ColorSpec::new().set_bold(true))?;
60    write!(error, "{} (", exc.name())?;
61    error.set_color(ColorSpec::new().set_bold(true).set_underline(true))?;
62    error.write_all(&exc.message())?;
63    error.set_color(ColorSpec::new().set_bold(true))?;
64    writeln!(error, ")")?;
65
66    // reset colors
67    error.reset()?;
68
69    Ok(())
70}
71
72/// Format an `Exception` backtrace into an [`io::Write`] suitable for
73/// displaying in a Ruby CLI.
74///
75/// This backtrace has the same style and formatting as one returned from the
76/// `ruby` command in MRI.
77///
78/// # Examples
79///
80/// Executing the following Ruby code:
81///
82/// ```ruby
83/// def fail; raise RuntimeError, "bang!"; end
84/// fail
85/// ```
86///
87/// Results in this stack trace:
88///
89/// ```txt
90/// Traceback (most recent call last):
91///     2: from -e:1
92/// -e:1:in fail: bang! (RuntimeError)
93/// ```
94///
95/// # Errors
96///
97/// If writing into the provided `out` writer fails, an error is returned.
98pub fn format_cli_trace_into<W, E>(mut error: W, interp: &mut Artichoke, exc: &E) -> Result<(), Box<dyn error::Error>>
99where
100    W: io::Write + WriteColor,
101    E: RubyException,
102{
103    // reset colors
104    error.reset()?;
105
106    let mut top = None;
107
108    // Format backtrace if present
109    if let Some(backtrace) = exc.vm_backtrace(interp) {
110        error.set_color(ColorSpec::new().set_bold(true))?;
111        write!(error, "Traceback")?;
112        error.reset()?;
113        writeln!(error, " (most recent call last):")?;
114        let mut iter = backtrace.into_iter().enumerate();
115        top = iter.next();
116        for (num, frame) in iter.rev() {
117            write!(error, "\t{}: from ", num + 1)?;
118            error.write_all(frame.as_slice())?;
119            writeln!(error)?;
120        }
121    }
122
123    if let Some((_, frame)) = top {
124        error.write_all(frame.as_slice())?;
125        write!(error, ": ")?;
126    }
127
128    // Format exception class and message
129    error.set_color(ColorSpec::new().set_bold(true))?;
130    error.write_all(&exc.message())?;
131    error.set_color(ColorSpec::new().set_bold(true))?;
132    write!(error, " (")?;
133    error.set_color(ColorSpec::new().set_bold(true).set_underline(true))?;
134    write!(error, "{}", exc.name())?;
135    error.set_color(ColorSpec::new().set_bold(true))?;
136    writeln!(error, ")")?;
137
138    // reset colors
139    error.reset()?;
140
141    Ok(())
142}