Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support backtrace #78

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.idea/
/target
Cargo.lock
18 changes: 18 additions & 0 deletions examples/test_io.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use ariadne::{CausedByFrame, Report, ReportKind, Source};
use std::fs::File;
use std::ops::Range;

fn main() {
let error = match File::open("file/that/must/not/exist.rs") {
Ok(_) => panic!("Oops! This should not happen."),
Err(e) => e,
};
Report::<Range<usize>>::build(ReportKind::Error, (), 34)
.with_message("File not found")
.with_help("Please check the path and try again")
.push_backtrace(CausedByFrame::io_error(error, "not-exist.rs"))
.push_backtrace(CausedByFrame::new("std/fs/file.rs").with_object("std::fs::File").with_position(123, 45))
.finish()
.print(Source::from(include_str!("sample.tao")))
.unwrap();
}
88 changes: 88 additions & 0 deletions src/caused_by.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use std::path::{Path};

/// Configuration for display backtraces
pub struct CausedByConfig {
/// Show the backtrace with number labels
pub show_label_numbers: bool,
/// Number labels start at this index, `index0` vs `index1`
pub label_index_start: usize,
}

/// A backtrace collection
pub struct CausedBy {
pub(crate) frames: Vec<CausedByFrame>,
}

/// A backtrace frame
#[derive(Debug)]
pub struct CausedByFrame {
/// File path buf
pub file: String,
/// Function name, or module path, e.g. `std::io::read_line`
pub object: Option<String>,
/// Line Column
pub position: Option<(usize, usize)>,
}

impl Default for CausedByConfig {
fn default() -> Self {
Self {
show_label_numbers: true,
label_index_start: 1,
}
}
}

impl Default for CausedBy {
fn default() -> Self {
Self { frames: Vec::new() }
}
}

impl CausedBy {
/// Add a frame to the backtrace
pub fn push_frame(&mut self, frame: CausedByFrame) {
self.frames.push(frame);
}
/// Clear all of the frames
pub fn clear(&mut self) {
self.frames.clear();
}
}

impl CausedByFrame {
/// Create a new backtrace frame
pub fn new(file: impl Into<String>) -> Self {
let path = file.into();
debug_assert!(
path.lines().count() == 1,
"File path must be in a single line"
);
Self {
file: path,
object: None,
position: None,
}
}
/// Add an io error to the backtrace
pub fn io_error(error: std::io::Error, path: impl AsRef<Path>) -> Self {
let path = path.as_ref();
let path = path.to_string_lossy().to_string();
Self::new(path).with_object(error.to_string())
}
/// Name to display for the object
pub fn with_object(mut self, object: impl Into<String>) -> Self {
let name = object.into();
debug_assert!(
name.lines().count() == 1,
"Object name must be in a single line"
);
self.object = Some(name);
self
}
/// Where the object is located
pub fn with_position(mut self, line: usize, column: usize) -> Self {
self.position = Some((line, column));
self
}
}
12 changes: 11 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ mod source;
mod display;
mod draw;
mod write;
mod caused_by;

pub use crate::{
caused_by::{CausedBy, CausedByFrame, CausedByConfig},
source::{Line, Source, Cache, FileCache, FnCache, sources},
draw::{Fmt, ColorGenerator},
};
Expand Down Expand Up @@ -141,6 +143,7 @@ pub struct Report<'a, S: Span = Range<usize>> {
help: Option<String>,
location: (<S::SourceId as ToOwned>::Owned, usize),
labels: Vec<Label<S>>,
backtrace: CausedBy,
config: Config,
}

Expand All @@ -155,6 +158,7 @@ impl<S: Span> Report<'_, S> {
help: None,
location: (src_id.into(), offset),
labels: Vec::new(),
backtrace: CausedBy::default(),
config: Config::default(),
}
}
Expand Down Expand Up @@ -220,6 +224,7 @@ pub struct ReportBuilder<'a, S: Span> {
help: Option<String>,
location: (<S::SourceId as ToOwned>::Owned, usize),
labels: Vec<Label<S>>,
backtrace: CausedBy,
config: Config,
}

Expand Down Expand Up @@ -273,7 +278,11 @@ impl<'a, S: Span> ReportBuilder<'a, S> {
let config = &self.config; // This would not be necessary in Rust 2021 edition
self.labels.extend(labels.into_iter().map(|mut label| { label.color = config.filter_color(label.color); label }));
}

/// add a new backtrace frame to the report
pub fn push_backtrace(mut self, trace: CausedByFrame) -> Self {
self.backtrace.push_frame(trace);
self
}
/// Add a label to the report.
pub fn with_label(mut self, label: Label<S>) -> Self {
self.add_label(label);
Expand Down Expand Up @@ -302,6 +311,7 @@ impl<'a, S: Span> ReportBuilder<'a, S> {
help: self.help,
location: self.location,
labels: self.labels,
backtrace: self.backtrace,
config: self.config,
}
}
Expand Down
47 changes: 47 additions & 0 deletions src/write.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::borrow::Borrow;
use std::io;
use std::ops::Range;
use crate::CausedByConfig;

use super::draw::{self, StreamAwareFmt, StreamType};
use super::{Cache, CharSet, Label, LabelAttach, Report, ReportKind, Show, Span, Write};
Expand Down Expand Up @@ -777,6 +778,52 @@ impl<S: Span> Report<'_, S> {
}
}
}
if !self.backtrace.frames.is_empty() {
let indent = if self.config.compact { " " } else { " " };
write!(w, "\n")?;
write!(w, "{}\n", "Caused by:".fg(self.config.note_color(), s))?;
// TODO: make this configurable
let mut config = CausedByConfig::default();
config.label_index_start = 0;
config.show_label_numbers = true;
if config.show_label_numbers {
let max = config.label_index_start + self.backtrace.frames.len();
let max_width = max.to_string().len();
// 0: object
// at path/to/file.rs:123:45
for (index, frame) in self.backtrace.frames.iter().enumerate() {
match &frame.object {
Some(s) => {
writeln!(w, "{indent}{:width$}: {object}", index + config.label_index_start, width = max_width, object = s)?;
write!(w, "{indent}{:width$} at {path}", "", width = max_width, path = &frame.file)?;
}
None => {
write!(w, "{:width$}: {path}", index + config.label_index_start, width = max_width, path = &frame.file)?;
}
}
match &frame.position {
Some((line, col)) => writeln!(w, "({}:{})", line, col)?,
None => writeln!(w)?
}
}
} else {
for frame in &self.backtrace.frames {
match &frame.object {
Some(s) => {
writeln!(w, "{indent}{}", s)?;
write!(w, "{indent}{indent}at {}", &frame.file)?;
}
None => {
write!(w, "{}", &frame.file)?;
}
}
match &frame.position {
Some((line, col)) => writeln!(w, "({}:{})", line, col)?,
None => writeln!(w)?
}
}
}
}
Ok(())
}
}
Expand Down