use camino::Utf8Path;
use super::invoke::is_progress;
use super::messages::{cargo_message, normalize_separators};
use crate::HashSet;
use crate::discover::Plan;
pub(super) const DIAGNOSTIC_LIMIT: usize = 5;
pub(super) fn complaints(stderr: &str) -> String {
const PROGRESS: [&str; 11] = [
"Compiling",
"Building",
"Checking",
"Downloading",
"Downloaded",
"Updating",
"Locking",
"Adding",
"Finished",
"Fresh",
"Running",
];
let mut kept = String::new();
let segments = stderr
.lines()
.flat_map(|line| line.split('\r').filter(move |segment| !segment.is_empty() || line.is_empty()));
for line in segments {
let trimmed = line.trim_start();
if is_progress(line) {
continue;
}
if PROGRESS.iter().any(|verb| {
trimmed
.strip_prefix(verb)
.is_some_and(|rest| rest.starts_with(' ') || rest.is_empty())
}) {
continue;
}
if trimmed.is_empty() && kept.is_empty() {
continue;
}
kept.push_str(line);
kept.push('\n');
}
if kept.trim().is_empty() {
return "cargo said nothing on stderr either.".to_owned();
}
kept
}
pub(super) struct Diagnostic {
pub(super) manifest: Option<String>,
pub(super) rendered: String,
}
pub(super) fn diagnostics(stdout: &str) -> Vec<Diagnostic> {
let mut rendered = Vec::new();
for line in stdout.lines() {
let Some(message) = cargo_message(line) else {
continue;
};
if message.reason != "compiler-message" {
continue;
}
let manifest = message.manifest_path.as_deref().map(normalize_separators);
let Some(diagnostic) = message.message else {
continue;
};
if diagnostic.level != "error" {
continue;
}
if let Some(text) = diagnostic.rendered {
rendered.push(Diagnostic {
manifest,
rendered: text.into_owned(),
});
}
}
rendered
}
pub(super) fn prioritize(found: &mut [Diagnostic], manifests: &HashSet<String>) {
found.sort_by_key(|diagnostic| !diagnostic.manifest.as_ref().is_some_and(|path| manifests.contains(path)));
}
pub(super) fn manifests_of(plan: &Plan, root: &Utf8Path, packages: &[String]) -> HashSet<String> {
packages
.iter()
.filter_map(|package| plan.directory_of(package))
.map(|directory| {
let absolute = if directory.as_str().is_empty() {
root.to_owned()
} else {
root.join(directory)
};
normalize_separators(absolute.join("Cargo.toml").as_str())
})
.collect()
}
pub(super) fn leading(found: &[Diagnostic], limit: usize) -> String {
let shown: String = found.iter().take(limit).map(|diagnostic| diagnostic.rendered.as_str()).collect();
let omitted = found.len().saturating_sub(limit);
if omitted == 0 {
return shown;
}
format!("{shown}\n(and {} not shown)\n", crate::report::quantity(omitted, "further error"))
}