1use std::fmt;
7use std::path::Path;
8
9pub use yog_data::{Diagnostic, Severity};
10use yog_sync::Mutex;
11use yog_sync::atomic::{AtomicUsize, Ordering};
12
13static ERRORS: AtomicUsize = AtomicUsize::new(0);
15
16static REPORTED: Mutex<Vec<Diagnostic>> = Mutex::new(Vec::new());
19
20pub fn report(d: &Diagnostic) {
23 {
24 let mut seen = REPORTED.lock();
25 if seen.contains(d) {
26 return;
27 }
28 seen.push(d.clone());
29 }
30 if d.is_error() {
31 ERRORS.fetch_add(1, Ordering::Relaxed);
32 }
33 let Some(path) = std::env::var_os(yog_ids::env::DIAGNOSTICS) else {
34 return;
35 };
36 if let Err(e) = append(Path::new(&path), d) {
37 yog_logging::warn!(
39 "couldn't report a diagnostic to {}: {e}",
40 Path::new(&path).display()
41 );
42 }
43}
44
45pub fn failed(code: &str, message: impl fmt::Display) -> String {
47 let d = Diagnostic::error(code, message);
48 report(&d);
49 d.message
50}
51
52pub fn reported(d: Diagnostic) -> String {
54 report(&d);
55 d.to_string()
56}
57
58pub(crate) fn report_all<'d>(ds: impl IntoIterator<Item = &'d Diagnostic>) {
60 for d in ds {
61 report(d);
62 }
63}
64
65pub fn errors_reported() -> usize {
67 ERRORS.load(Ordering::Relaxed)
68}
69
70fn append(path: &Path, d: &Diagnostic) -> std::io::Result<()> {
71 let mut file = std::fs::OpenOptions::new()
72 .create(true)
73 .append(true)
74 .open(path)?;
75 yog_data::io::write_frame(&mut file, d)
76}
77
78pub(crate) fn collected(
81 command: &mut std::process::Command,
82) -> std::io::Result<(std::process::Output, Vec<Diagnostic>)> {
83 static RUNS: AtomicUsize = AtomicUsize::new(0);
84 let run = RUNS.fetch_add(1, Ordering::Relaxed);
85 let file = std::env::temp_dir().join(format!("{FEED_PREFIX}{}-{run}", std::process::id()));
86 let output = command.env(yog_ids::env::DIAGNOSTICS, &file).output();
87 let found = read_all(&file);
88 crate::files::remove(&file);
90 Ok((output?, found))
91}
92
93const FEED_PREFIX: &str = "yog-diagnostics-";
95
96pub(crate) fn read_all(path: &Path) -> Vec<Diagnostic> {
98 let Ok(bytes) = std::fs::read(path) else {
99 return Vec::new();
100 };
101 let mut rest = bytes.as_slice();
102 let mut found = Vec::new();
103 while let Ok(Some(d)) = yog_data::io::read_frame::<Diagnostic>(&mut rest) {
104 found.push(d);
105 }
106 found
107}
108
109pub(crate) fn listed(ds: &[Diagnostic]) -> String {
111 let lines: Vec<String> = ds.iter().map(ToString::to_string).collect();
112 lines.join(LIST_SEP)
113}
114
115pub const LIST_SEP: &str = "\n ";
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn a_failure_is_reported_once_and_said() {
124 let said = failed(yog_ids::build_problem::CONTENT, "no model");
125 assert_eq!(said, "no model");
126 let before = errors_reported();
127 failed(yog_ids::build_problem::CONTENT, "no model");
128 assert_eq!(errors_reported(), before);
129 }
130}