| //! Types and function used to emit pretty diagnostics for `bindgen`. |
| //! |
| //! The entry point of this module is the [`Diagnostic`] type. |
| |
| use std::fmt::Write; |
| use std::io::{self, BufRead, BufReader}; |
| use std::{borrow::Cow, fs::File}; |
| |
| use annotate_snippets::{Renderer, Snippet}; |
| |
| pub(crate) use annotate_snippets::Level; |
| |
| /// A `bindgen` diagnostic. |
| #[derive(Default)] |
| pub(crate) struct Diagnostic<'a> { |
| title: Option<(Cow<'a, str>, Level)>, |
| slices: Vec<Slice<'a>>, |
| footer: Vec<(Cow<'a, str>, Level)>, |
| } |
| |
| impl<'a> Diagnostic<'a> { |
| /// Add a title to the diagnostic and set its type. |
| pub(crate) fn with_title( |
| &mut self, |
| title: impl Into<Cow<'a, str>>, |
| level: Level, |
| ) -> &mut Self { |
| self.title = Some((title.into(), level)); |
| self |
| } |
| |
| /// Add a slice of source code to the diagnostic. |
| pub(crate) fn add_slice(&mut self, slice: Slice<'a>) -> &mut Self { |
| self.slices.push(slice); |
| self |
| } |
| |
| /// Add a footer annotation to the diagnostic. This annotation will have its own type. |
| pub(crate) fn add_annotation( |
| &mut self, |
| msg: impl Into<Cow<'a, str>>, |
| level: Level, |
| ) -> &mut Self { |
| self.footer.push((msg.into(), level)); |
| self |
| } |
| |
| /// Print this diagnostic. |
| /// |
| /// The diagnostic is printed using `cargo:warning` if `bindgen` is being invoked by a build |
| /// script or using `eprintln` otherwise. |
| pub(crate) fn display(&self) { |
| std::thread_local! { |
| static INVOKED_BY_BUILD_SCRIPT: bool = std::env::var_os("CARGO_CFG_TARGET_ARCH").is_some(); |
| } |
| |
| let mut footer = vec![]; |
| let mut slices = vec![]; |
| let snippet = if let Some((msg, level)) = &self.title { |
| (*level).title(msg) |
| } else { |
| return; |
| }; |
| |
| for (msg, level) in &self.footer { |
| footer.push((*level).title(msg)); |
| } |
| |
| // add additional info that this is generated by bindgen |
| // so as to not confuse with rustc warnings |
| footer.push( |
| Level::Info.title("This diagnostic was generated by bindgen."), |
| ); |
| |
| for slice in &self.slices { |
| if let Some(source) = &slice.source { |
| let mut snippet = Snippet::source(source) |
| .line_start(slice.line.unwrap_or_default()); |
| if let Some(origin) = &slice.filename { |
| snippet = snippet.origin(origin); |
| } |
| slices.push(snippet); |
| } |
| } |
| |
| let renderer = Renderer::styled(); |
| let dl = renderer.render(snippet.snippets(slices).footers(footer)); |
| |
| if INVOKED_BY_BUILD_SCRIPT.with(Clone::clone) { |
| // This is just a hack which hides the `warning:` added by cargo at the beginning of |
| // every line. This should be fine as our diagnostics already have a colorful title. |
| // FIXME (pvdrz): Could it be that this doesn't work in other languages? |
| let hide_warning = "\r \r"; |
| let string = dl.to_string(); |
| for line in string.lines() { |
| println!("cargo:warning={hide_warning}{line}"); |
| } |
| } else { |
| eprintln!("{dl}\n"); |
| } |
| } |
| } |
| |
| /// A slice of source code. |
| #[derive(Default)] |
| pub(crate) struct Slice<'a> { |
| source: Option<Cow<'a, str>>, |
| filename: Option<String>, |
| line: Option<usize>, |
| } |
| |
| impl<'a> Slice<'a> { |
| /// Set the source code. |
| pub(crate) fn with_source( |
| &mut self, |
| source: impl Into<Cow<'a, str>>, |
| ) -> &mut Self { |
| self.source = Some(source.into()); |
| self |
| } |
| |
| /// Set the file, line and column. |
| pub(crate) fn with_location( |
| &mut self, |
| mut name: String, |
| line: usize, |
| col: usize, |
| ) -> &mut Self { |
| write!(name, ":{line}:{col}").expect("Writing to a string cannot fail"); |
| self.filename = Some(name); |
| self.line = Some(line); |
| self |
| } |
| } |
| |
| pub(crate) fn get_line( |
| filename: &str, |
| line: usize, |
| ) -> io::Result<Option<String>> { |
| let file = BufReader::new(File::open(filename)?); |
| if let Some(line) = file.lines().nth(line.wrapping_sub(1)) { |
| return line.map(Some); |
| } |
| |
| Ok(None) |
| } |