use crate::check::{Finding, Severity};
use crate::registry::Registry;
const HINT_INDENT: &str = " ▶ hint: ";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
Text,
GithubAnnotations,
}
pub fn format_github_annotations(findings: &[Finding]) -> String {
let mut out = String::new();
for f in findings {
let level = match f.severity {
Severity::Hard => "error",
Severity::Advisory => "warning",
};
out.push_str(&format!(
"::{level} file={file},line={line}::[{check}] {kind} — {claim}\n",
file = f.file.display(),
line = f.line.max(1),
check = f.check_id,
kind = f.kind.short(),
claim = collapse_newlines(&truncate(&f.claim, 200)),
));
}
out
}
fn collapse_newlines(s: &str) -> String {
s.replace(['\n', '\r'], " ").trim().to_string()
}
pub fn format_report(reg: &Registry, findings: &[Finding], explain: bool) -> String {
let mut out = String::new();
if findings.is_empty() {
out.push_str(&format!(
"drift: no findings ({} check(s) ran)\n",
reg.len()
));
return out;
}
let mut last_file: Option<&std::path::Path> = None;
let mut hard = 0usize;
let mut advisory = 0usize;
for f in findings {
match f.severity {
Severity::Hard => hard += 1,
Severity::Advisory => advisory += 1,
}
if last_file != Some(f.file.as_path()) {
out.push('\n');
out.push_str(&format!("{} {}\n", f.severity.label(), f.file.display()));
last_file = Some(f.file.as_path());
}
out.push_str(&format!(
" line {}: [{}] {}\n",
f.line,
f.check_id,
f.kind.short()
));
if explain {
let intent = reg
.checks()
.find(|c| c.id() == f.check_id)
.map(|c| c.intent())
.unwrap_or("");
out.push_str(&format!(" intent: {intent}\n"));
out.push_str(&format!(" claim : {}\n", f.claim));
} else {
out.push_str(&format!(" claim : {}\n", truncate(&f.claim, 120)));
}
if let Some(hint) = &f.fix_hint {
out.push_str(&format!("{HINT_INDENT}{hint}\n"));
}
}
out.push_str(&format!(
"\nsummary: {hard} hard, {advisory} advisory ({} check(s) ran)\n",
reg.len()
));
out
}
fn truncate(s: &str, max: usize) -> String {
if s.chars().count() <= max {
return s.to_string();
}
let cut: String = s.chars().take(max).collect();
format!("{cut}…")
}