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;
#[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> {
pub(crate) fn with_title(
&mut self,
title: impl Into<Cow<'a, str>>,
level: Level,
) -> &mut Self {
self.title = Some((title.into(), level));
self
}
pub(crate) fn add_slice(&mut self, slice: Slice<'a>) -> &mut Self {
self.slices.push(slice);
self
}
pub(crate) fn add_annotation(
&mut self,
msg: impl Into<Cow<'a, str>>,
level: Level,
) -> &mut Self {
self.footer.push((msg.into(), level));
self
}
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));
}
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) {
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");
}
}
}
#[derive(Default)]
pub(crate) struct Slice<'a> {
source: Option<Cow<'a, str>>,
filename: Option<String>,
line: Option<usize>,
}
impl<'a> Slice<'a> {
pub(crate) fn with_source(
&mut self,
source: impl Into<Cow<'a, str>>,
) -> &mut Self {
self.source = Some(source.into());
self
}
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)
}