use std::fmt;
use std::path::Path;
pub use yog_data::{Diagnostic, Severity};
use yog_sync::Mutex;
use yog_sync::atomic::{AtomicUsize, Ordering};
static ERRORS: AtomicUsize = AtomicUsize::new(0);
static REPORTED: Mutex<Vec<Diagnostic>> = Mutex::new(Vec::new());
pub fn report(d: &Diagnostic) {
{
let mut seen = REPORTED.lock();
if seen.contains(d) {
return;
}
seen.push(d.clone());
}
if d.is_error() {
ERRORS.fetch_add(1, Ordering::Relaxed);
}
let Some(path) = std::env::var_os(yog_ids::env::DIAGNOSTICS) else {
return;
};
if let Err(e) = append(Path::new(&path), d) {
eprintln!(
"warning: couldn't report a diagnostic to {}: {e}",
Path::new(&path).display()
);
}
}
pub fn failed(code: &str, message: impl fmt::Display) -> String {
let d = Diagnostic::error(code, message);
report(&d);
d.message
}
pub fn reported(d: Diagnostic) -> String {
report(&d);
d.to_string()
}
pub fn report_all<'d>(ds: impl IntoIterator<Item = &'d Diagnostic>) {
for d in ds {
report(d);
}
}
pub fn errors_reported() -> usize {
ERRORS.load(Ordering::Relaxed)
}
fn append(path: &Path, d: &Diagnostic) -> std::io::Result<()> {
let mut file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)?;
yog_data::io::write_frame(&mut file, d)
}
pub fn collected(
command: &mut std::process::Command,
) -> std::io::Result<(std::process::Output, Vec<Diagnostic>)> {
static RUNS: AtomicUsize = AtomicUsize::new(0);
let run = RUNS.fetch_add(1, Ordering::Relaxed);
let file = std::env::temp_dir().join(format!("{FEED_PREFIX}{}-{run}", std::process::id()));
let output = command.env(yog_ids::env::DIAGNOSTICS, &file).output();
let found = read_all(&file);
let _ = std::fs::remove_file(&file);
Ok((output?, found))
}
const FEED_PREFIX: &str = "yog-diagnostics-";
pub fn read_all(path: &Path) -> Vec<Diagnostic> {
let Ok(bytes) = std::fs::read(path) else {
return Vec::new();
};
let mut rest = bytes.as_slice();
let mut found = Vec::new();
while let Ok(Some(d)) = yog_data::io::read_frame::<Diagnostic>(&mut rest) {
found.push(d);
}
found
}
pub fn listed(ds: &[Diagnostic]) -> String {
let lines: Vec<String> = ds.iter().map(ToString::to_string).collect();
lines.join(LIST_SEP)
}
pub const LIST_SEP: &str = "\n ";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_failure_is_reported_once_and_said() {
let said = failed(yog_ids::build_problem::CONTENT, "no model");
assert_eq!(said, "no model");
let before = errors_reported();
failed(yog_ids::build_problem::CONTENT, "no model");
assert_eq!(errors_reported(), before);
}
}