//! 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>, 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>, 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>, 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!("{}\n", dl); } } } /// A slice of source code. #[derive(Default)] pub(crate) struct Slice<'a> { source: Option>, filename: Option, line: Option, } impl<'a> Slice<'a> { /// Set the source code. pub(crate) fn with_source( &mut self, source: impl Into>, ) -> &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> { let file = BufReader::new(File::open(filename)?); if let Some(line) = file.lines().nth(line.wrapping_sub(1)) { return line.map(Some); } Ok(None) }