From 119768dbfab6e1baec4504b9f6b1dab610438c59 Mon Sep 17 00:00:00 2001 From: Aster Date: Fri, 2 Jun 2023 11:52:01 +0800 Subject: [PATCH 1/2] Define the backtrace frames --- src/caused_by.rs | 74 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 2 files changed, 75 insertions(+) create mode 100644 src/caused_by.rs diff --git a/src/caused_by.rs b/src/caused_by.rs new file mode 100644 index 0000000..64197ec --- /dev/null +++ b/src/caused_by.rs @@ -0,0 +1,74 @@ +use std::path::{Path, PathBuf}; + +pub struct BacktraceConfig { + /// 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, +} + +pub struct BackTrace { + frames: Vec, +} + +pub struct BackTraceFrame { + /// File path buf + pub file: String, + /// Function name, or module path, e.g. `std::io::read_line` + pub object: Option, + /// Line Column + pub position: Option<(usize, usize)>, +} + +impl Default for BacktraceConfig { + fn default() -> Self { + Self { + show_label_numbers: true, + label_index_start: 1, + } + } +} + +impl Default for BackTrace { + fn default() -> Self { + Self { frames: Vec::new() } + } +} + +impl BackTrace { + /// Add a frame to the backtrace + pub fn with_frame(&mut self, frame: BackTraceFrame) { + self.frames.push(frame); + } + /// Add a io error to the backtrace + pub fn with_io_error(&mut self, error: std::io::Error, path: impl AsRef) { + let path = path.as_ref(); + let path = path.to_str().unwrap_or_else(|| path.to_string_lossy().as_ref()); + self.frames.push(BackTraceFrame::new(path).with_object(error.to_string())); + } +} + +impl BackTraceFrame { + /// Create a new backtrace frame + pub fn new(file: impl Into) -> 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, + } + } + /// Name to display for the object + pub fn with_object(mut self, object: impl Into) -> 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, position: (usize, usize)) -> Self { + self.position = Some(position); + self + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index d2a92dd..923d3cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ mod source; mod display; mod draw; mod write; +mod caused_by; pub use crate::{ source::{Line, Source, Cache, FileCache, FnCache, sources}, From e9f49a6032fd27bcad612bdfb6db4e104a1af764 Mon Sep 17 00:00:00 2001 From: Aster Date: Fri, 2 Jun 2023 12:32:24 +0800 Subject: [PATCH 2/2] Add io test case --- .gitignore | 1 + examples/test_io.rs | 18 +++++++++++++++ src/caused_by.rs | 54 ++++++++++++++++++++++++++++----------------- src/lib.rs | 11 ++++++++- src/write.rs | 47 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 21 deletions(-) create mode 100644 examples/test_io.rs diff --git a/.gitignore b/.gitignore index 869df07..18d09e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +.idea/ /target Cargo.lock \ No newline at end of file diff --git a/examples/test_io.rs b/examples/test_io.rs new file mode 100644 index 0000000..2c1fe66 --- /dev/null +++ b/examples/test_io.rs @@ -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::>::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(); +} diff --git a/src/caused_by.rs b/src/caused_by.rs index 64197ec..df5cba9 100644 --- a/src/caused_by.rs +++ b/src/caused_by.rs @@ -1,17 +1,21 @@ -use std::path::{Path, PathBuf}; +use std::path::{Path}; -pub struct BacktraceConfig { +/// 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, } -pub struct BackTrace { - frames: Vec, +/// A backtrace collection +pub struct CausedBy { + pub(crate) frames: Vec, } -pub struct BackTraceFrame { +/// 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` @@ -20,7 +24,7 @@ pub struct BackTraceFrame { pub position: Option<(usize, usize)>, } -impl Default for BacktraceConfig { +impl Default for CausedByConfig { fn default() -> Self { Self { show_label_numbers: true, @@ -29,46 +33,56 @@ impl Default for BacktraceConfig { } } -impl Default for BackTrace { +impl Default for CausedBy { fn default() -> Self { Self { frames: Vec::new() } } } -impl BackTrace { +impl CausedBy { /// Add a frame to the backtrace - pub fn with_frame(&mut self, frame: BackTraceFrame) { + pub fn push_frame(&mut self, frame: CausedByFrame) { self.frames.push(frame); } - /// Add a io error to the backtrace - pub fn with_io_error(&mut self, error: std::io::Error, path: impl AsRef) { - let path = path.as_ref(); - let path = path.to_str().unwrap_or_else(|| path.to_string_lossy().as_ref()); - self.frames.push(BackTraceFrame::new(path).with_object(error.to_string())); + /// Clear all of the frames + pub fn clear(&mut self) { + self.frames.clear(); } } -impl BackTraceFrame { +impl CausedByFrame { /// Create a new backtrace frame pub fn new(file: impl Into) -> Self { let path = file.into(); - debug_assert!(path.lines().count() == 1, "File path must be in a single line"); + 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) -> 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) -> Self { let name = object.into(); - debug_assert!(name.lines().count() == 1, "Object name must be in a single line"); + 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, position: (usize, usize)) -> Self { - self.position = Some(position); + pub fn with_position(mut self, line: usize, column: usize) -> Self { + self.position = Some((line, column)); self } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 923d3cc..2eee5db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ mod write; mod caused_by; pub use crate::{ + caused_by::{CausedBy, CausedByFrame, CausedByConfig}, source::{Line, Source, Cache, FileCache, FnCache, sources}, draw::{Fmt, ColorGenerator}, }; @@ -142,6 +143,7 @@ pub struct Report<'a, S: Span = Range> { help: Option, location: (::Owned, usize), labels: Vec>, + backtrace: CausedBy, config: Config, } @@ -156,6 +158,7 @@ impl Report<'_, S> { help: None, location: (src_id.into(), offset), labels: Vec::new(), + backtrace: CausedBy::default(), config: Config::default(), } } @@ -221,6 +224,7 @@ pub struct ReportBuilder<'a, S: Span> { help: Option, location: (::Owned, usize), labels: Vec>, + backtrace: CausedBy, config: Config, } @@ -274,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) -> Self { self.add_label(label); @@ -303,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, } } diff --git a/src/write.rs b/src/write.rs index 70d0383..72f488b 100644 --- a/src/write.rs +++ b/src/write.rs @@ -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}; @@ -777,6 +778,52 @@ impl 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(()) } }